# cache **Repository Path**: luyue_zhang/cache ## Basic Information - **Project Name**: cache - **Description**: 锁、缓存策略、分布式锁 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-08 - **Last Updated**: 2025-06-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 概览 ## 锁 sync.Mutex 竞争锁的两种模式: - 正常模式:和新来的(未释放资源,占着processor)一起抢锁,大概率失败(保证效率) - 饥饿模式:肯定能拿到锁(退出饥饿模式:队列中只剩下一个goroutine或者等待时间小于1ms ## channel ### chansend ### chanrecv ## 接口设计 ``` type Cache interface { Set(ctx context.Context, key string, value any, expiration time.Duration) error Get(ctx context.Context, key string) (any, error) Delete(ctx context.Context, key string) error } ``` ## 源码 gitee: https://gitee.com/luyue_zhang/cache/tree/main/cache ## 本地缓存相关问题 ### 过期时间如何控制? **策略一:每个key开一个goroutine盯着** 缺点:goroutine数量随着key增大;大部分goroutine大多数时间被阻塞,每个都盯着浪费资源; **策略二:定时轮询**:goroutinue定时轮询所有key 注意事项:定时轮询间隔时间过短,消耗资源;key数量超大时,需要限制,比如限制轮询key的个数或者轮询时长 问题:定时轮询不能访问到所有key,所以有过期key没处理,不能单独定时轮询。可搭配Get时判断一起使用。 **策略三:Get时判断**(懒惰删除):取数据时再判断是否已过期 ### 缓存满了怎么办? - 控制缓存使用:限制缓存占用内存量、限制缓存键值对 - 使用lru算法淘汰缓存 ## 缓存模式 - cache-aside - read-through - write-though - write-back - refresh-ahead ## 缓存异常 - 穿透:大量不存在的key访问redis不存在,打到db上 - 击穿:key不在缓存中,但在db中 一般情况下,击穿不会有问题,访问一次db就能回写redis;但攻击者短时间大量访问这个key,可能会压垮数据库 - 雪崩:大量key同时过期 ### 如何解决缓存异常? 所有问题的落脚点:大量请求达到db上 #### singleflight 大量goroutine同时访问一个key时,singleflight会让其他的原地等待,只有一个访问db #### 缓存穿透解决方案 存在第三方比如bloomFilter,先确认db上是否hasKey,存在再请求db #### 缓存击穿解决方案 1. 使用singleflight 2. 缓存没有,不查db,直接返回默认值(等待一段时间db数据就同步至缓存) 或 回查db时添加限流器 #### 缓存雪崩解决方案 过期时间添加随机数偏移量 ## 缓存一致性 解决不了,只能说在什么时间精度上容忍 | 问题根源 | 尝试解决方案 | | ----------------------------------------------------------- | ------------ | | 并发更新 | 分布式锁 | | 部分失败:无论是先更新DB,还是cache都可能出现部分失败的可能 | 分布式事务 | ### 缓存一致性的可能方案 一致性哈希负载均衡算法+singleFlight,确保某个key对应的请求必然打到一台机器上。 在这种方案下,可能出现的一致性问题的场景:扩容、缩容和应用重启时;比如扩容时,原先打到C节点上的请求可能全部打到C1节点上,原先用的C的缓存,现在用的C1请求db的数据。 解决方案:扩容、缩容时禁用变动节点C上的缓存 ### 分布式锁 分布式锁就是不同实例在网络上抢一把锁,普通锁就是不同线程间在本地竞争锁 使用redis setnx能够排他的设置一个键值对,适合做分布式锁 #### 锁需要设置过期时间,原因 不设置过期时间,容易死锁。比如持有锁的实例宕机就死锁了 ##### 过期时间应该设置多久? 过短容易过期,过长浪费资源。而且理论上,极限情况下,无论锁时间设置多长,都可能任务没有执行完就过期。 所以在业务推测一个合理的过期时间外,我们还要有续约机制。 #### 锁需要保证唯一性,原因 比如:当释放锁时,要释放的是自己锁住的,不能把别人锁着的释放掉 场景举例:任务task1加锁后执行,锁过期释放;task2竞争得到锁,task2加锁;task1执行完成后,过来想释放锁需要比对这把锁是不是自己配置的,不能把task2的加锁释放掉 #### 源码 gitee: https://gitee.com/luyue_zhang/cache/tree/main/lock