什么是Redis
- 基于key-value存储结构的NoSQL数据库
- 提供了String, Map, Set, ZSet, List等多种数据类型
- 功能丰富:支持发布订阅模式,能够为数据设置过期时间,能够对数据进行持久化,支持分布式存储和读写分离,支持创建事务
- 性能高:基于内存操作、对数据结构进行优化
- 问题:容量小,可能出现缓存击穿、缓存雪崩等问题,可能出现线程安全问题
数据类型
简述
字符串
- 最基本的数据类型
- 可以存储任意类型的数据,例如文本、数字、序列化的对象
- 支持字符串追加、获取子串、计数器等操作
哈希
- 键值对的集合,适用于存储对象的属性
列表
- 维护插入顺序
- 允许在列表两端进行元素的插入与删除,可以通过索引访问、获取、修改和删除列表中的元素,支持范围操作如获取片段、修剪等
集合
- 无序不重复的字符串元素的集合
- 可以对集合添加、删除、查找、计数
- 支持集合运算
有序集合
- 有序的字符串元素集合,每个元素关联一个分数(score),用于排序和唯一性标识
- 可以对有序集合进行添加、删除、查找、范围查询
- 支持根据分数范围进行检索和排名
缓存击穿
概念
在高并发场景下,当非常热门的缓存键过期时,大量请求无法访问该键,进而直接访问数据库,从而给数据库带来巨大压力,甚至导致数据库宕机。
发生场景:
- 键过期
- 键不存在
解决方案:
- 热点数据预加载
- 互斥锁
- 限流与熔断
热点数据预加载解决缓存击穿
在缓存键失效之前,提前异步加载热点数据到缓存中,确保缓存不会在关键时刻失效。
可以创建定时任务,每次判断是否为热点数据,如果是就读取后添加到缓存。
互斥锁解决缓存击穿
基于Redis的SETNX指令实现分布式锁。
取值时首先到缓存中判断,若缓存中存在则直接获取。
缓存中不存在时,尝试获取分布式锁。
若获取成功,再次判断缓存中是否存在数据,避免在其他客户端的请求已经触发了缓存的写入的情况下再次读取。
若缓存仍不存在,从数据库读入数据并写入缓存。
若获取分布式锁不成功,则等待一段时间后再判断。
注意事项:
- 对锁设置超时时间
- 获取锁后尽快释放锁
通过限流与熔断解决缓存击穿问题
限流:
- 限制单位时间内的请求数,超出此范围的请求被丢弃或延迟处理
- 有多种限流算法可供选择,如令牌桶算法或漏桶算法
熔断:
- 是一种故障保护机制,基于一定的条件或阈值判断是否熔断
- 有多个熔断库可供选择,如Hystrix、Resilience4j等
缓存穿透
概念:某个key在Redis与MySQL中均不存在,对于这一key的查询请求会经历Redis与MySQL的查询,最终返回空值。
解决方案:
- 缓存空值
- 布隆过滤器
缓存空值解决Redis缓存穿透
将请求的key加入缓存,设置值为空,并设置过期时间。则过期前的请求均会停留与Redis层。
缺点:
- 额外占用空间
- 会造成数据不一致
布隆过滤器解决Redis缓存穿透
布隆过滤器:
- 由位数组和一组哈希函数组成,用于快速判断某个元素是否在集合里
- 判断时,由哈希函数求出多个索引位置,分别检测每个位置的位值是否为1,全1则可能存在,不全为1则必定不存在
- 插入时,将对应的多个位置置位为1
- 删除时无法维护
- 存在误判率
布隆过滤器在这种场景下主要用于判断一个键(key)是否存在于应用内部的多个数据存储层中,包括数据库和Redis等。达到的效果是,如果一个key在数据库中根本不存在,那么就不会在数据库中进行查询,若key可能存在,则先在Redis中取值,Redis中不存在时再查表。
缓存雪崩
缓存中大量key同时过期,或Redis宕机,导致大量请求同时打入Mysql。
解决方案:
- 将key的过期时间打散
- 部署Redis高可用
- 多级缓存
多级缓存策略
Nginx缓存
JVM缓存:Caffeine等
延迟双删
延迟双删是高并发场景下一定程度上避免mysql与redis数据不一致的一种手段。
为什么要延迟双删?
设想以下场景:我们要对数据库中的数据进行修改,并将缓存中的旧数据删除。
有两种可能的方案:
- 先update,再删缓存
- 先删缓存,再update
两种方案均存在一定问题。
方案一:update后,删缓存前的这段时间,请求会直接从缓存获取旧数据。删缓存后,缓存中没有查到,再到mysql中查询,并同步到缓存。即在update后、删缓存前这段时间数据不一致。由于这段时间长度较小,故影响较小,但若缓存删除失败,则会导致长时间的数据不一致,并且由于不断请求删除缓存,导致响应变慢。
方案二:删缓存后,update前,请求在缓存中没有获取到数据,便到mysql中查询,查到了旧数据,同时同步到缓存中。update以后,缓存中仍然存放旧数据。这会导致比较严重的数据不一致。
延迟双删流程:
先删缓存,再update,update后等待一段足够写入的时间,再删一次缓存。
伪代码:
def(cache)
update(mysql)
sleep()
def(cache)
效果:类似于方案二,但是解决了update后缓存没有同步的问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了