redis高并发问题
Redlock原理和存在的问题
Redlock 基于以下假设:
- 有多个(一般建议是 5 个)彼此独立的 Redis 实例(不是主从复制,也不是集群模式),它们之间没有数据同步。
- 客户端可以与所有 Redis 实例通信。
获取锁的步骤如下:
假设我们有 5 个 Redis 实例:R1、R2、R3、R4、R5。
- 客户端获取当前时间(毫秒级)。
- 依次尝试在 5 个 Redis 实例上设置同一个 key,使用 SET resource_name my_random_value NX PX ttl。
- 客户端在每个 Redis 实例上设置锁时都会设置一个过期时间(例如 10 秒)。
- 如果客户端成功在多数实例(至少 3 个,也就是半数以上)上获取到锁,并且耗时小于锁的过期时间(10 秒),则认为获取锁成功。
- 如果获取锁失败(少于 3 个成功或超时),客户端会在所有实例上尝试释放锁(通过比对随机值)。
- 解锁时,也要确保只能删除自己设置的锁(通过 value 校验)。
存在的问题
- 每个redis实例分别有从节点的情况,在主节点上完锁,在同步到从节点之前挂了,此时没有锁的从节点成为了主节点。此时会出现不同线程加了多把锁。
- 在redis配置一秒持久化一次时,redis刚好在这一秒宕机丢失数据,恢复后也可能会出现多把锁
分布式锁优化
- 加锁范围优化。尽量缩小锁的粒度和作用范围,使用细粒度锁或分段锁。
- 热点数据在每次查询延期,做到冷热数据分离。
缓存击穿、穿透、雪崩
- 缓存击穿。数据过期的时候一下子涌入大量的请求,在缓存中不存在,
- 加互斥锁,只允许一个线程去更新缓存数据
- 异步预热缓存
- 缓存穿透。请求的数据根本不存在于缓存和数据库中,每次请求都会打到数据库,导致数据库压力剧增。
- 缓存空对象。对不存在的数据也缓存一个空值,设置较短过期时间。查同一个不存在的商品进行空值延期
- 布隆过滤器。
- 参数校验。请求前先校验 userId 是否合理,比如正整数、存在合法范围
- 缓存雪崩。大量缓存同时失效(或者缓存层支撑不住或者宕机),请求直接打到数据库,造成数据库瞬间崩溃或响应变慢。
- 过期时间随机化。避免大量 key 同时过期(比如加上随机 1~5 分钟)
- 设置热点数据永远不过期。
- 缓存预热或提前加载。系统启动时或定时任务提前加载热门缓存
突发大量缓存重建
使用分布式锁和dcl(双重检查),只允许一个线程去更新缓存数据。好处是全局一把锁并且一把锁只锁一个对应需要重建的数据。
优化:确定得出数据的时间可以使用trylock避免大量的锁逻辑
缓存与数据库双写不一致
更新缓存的线程在查到数据以后卡顿或者cpu调度被其他线程先更新了数据库,这时候更新缓存的线程把之前读到的旧值写入缓存。
- 延迟双删
- 高一致性。对更新缓存的操作加分布式锁,优化可以使用分布式读写锁(因为写数据库要更新缓存,读数据库也要更新缓存(在缓存没对应数据的情况))
出现redis扛不住的流量
- 保证缓存层的高可用
- 接口限流
- Java的进程内缓存框架EhCache、Guava Cache,使用发布订阅让其他服务更新缓存。
CAP 冲突,指的是分布式系统中无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三个特性,最多只能同时满足其中两个。
多级缓存不要考虑绝对一致了。