# 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