分布式缓存
1.缓存分类
1.1 前端缓存:页面和浏览器缓存,App缓存
- 页面缓存属于客户端缓存一种,第一次访问时,页面缓存将浏览器渲染的页面存储在本地。用于数据更新比较少的数据,大部分浏览器自身会实现缓存功能,对于页面中的视频图片,浏览器都会进行缓存,App内各级缓存更加复杂。
- HTML5支持本地储存,包括localStorage,sessionStorge。localStorage没有时间限制,sessionStorge与session的有效期内相关。支持离线缓存,Application Cache 技术 实现应用离线的缓存,Application Cache基于mainfest文件实现缓存机制,浏览器通过这个文件上的清单解析资源。
1.2 网络传输缓存
大多数业务通过HTTP实现,工作在TCP协议之上,多次握手后,浏览器和服务器建立TCP连接,然后进行数据传输,传输过程中涉及多层缓存。
- CDN(Content Delivery Network)内容分发网络
实现关键包括内容存储和内容分发:内容存储,对数据的缓存功能。内容分发,CDN节点支持的负载均衡。前端请求经过DNS后会被指向网络中最近的CDN节点,该节点从真正的应用服务器获取资源返回给前端,同时将静态信息缓存,在新的请求过来后,可以只请求CDN节点数据,同时CDN节点也可以和服务器之间同步更新数据 - 负载均衡缓存
负载均衡服务器实现请求路由的负载均衡功能。
也可以实现部分数据的缓存,比如配置信息等很少修改的数据。
用户请求在达到应用服务器前,会先访问Nginx负载均衡器,有缓存信息直接返回给用户,没有缓存信息,Nginx会从应用服务器获取信息
1.3 服务器缓存
是缓存的重点,是业务平时最多的缓存
- 本地缓存:应用内缓存,Guava实现的各级缓存,或者Java语言中使用各类Map结构实现的数据存储。随着服务重启后失效,作用时间短,应用比较灵活
- 外部缓存: redis,memchaed等NoSQL存储的分布式缓存,在系统设计中对整体性能提升最大的缓存。外部缓存使用不当导致缓存穿透,雪崩等业务问题
1.4 数据库缓存
Mybatis为每个SqlSession创建LocalCache ,其可以实现查询请求的缓存,如果查询语句命中了缓存,返回给用户,否则查询数据库,并且写入LocalCache,返回给用户。
数据库执行查询语句时,M有SQL保存一个KV形式的缓存在内存中,Key是查询语句,Value是结果集,如果缓存被命中,会直接返回给客户端,否则通过数据库引擎进行查询,并且吧结果缓存起来,当数据有修改时,需要删除缓存
2.缓存问题
2.1 缓存穿透
- 不合理的缓存失效策略:导致大量缓存在同一时刻发生穿透,请求直接到持久层
- 外部用户的恶意攻击:用户利用不存在的key,来构造大量不存在的数据请求数据,海量请求全部穿过缓存,到数据库
2.1.1 解决方法
- 设置合理缓存失效策略
- 通过缓存空数据方式避免:针对数据库不存在数据,查询为空时,添加一个null值到缓存中
- 布隆过滤器:Bitmap
2.2 缓存击穿
前端请求大量访问某个热点key,而他在某个时刻失效
缓存穿透的特殊场景
2.3 缓存雪崩
大量缓存数据在同一时刻失效,请求全部转发到数据库,导致崩溃。
- 缓存服务不稳定,redis宕机
- 缓存失效导致,整体数据存储链路,服务调用链路,最终导致微服务整体对外服务出现问题
2.3.1 解决
- 明确缓存集群的容量峰值
- 做好缓存集群的高可用:通过RedisCluster,Proxy等不同的缓存集群,实现redis高可用
3. 缓存稳定性
- 缓存应用:防止海量业务请求击垮数据库,保护正常服务运行
- 考虑缓存的稳定性:缓存数据,缓存容器(缓存服务器本身的稳定性)
- 缓存命中率:落到缓存上的请求占整体请求的占比,一般缓存命中率要求到90%以上,大促等场景99%以上。
在应用缓存时,要对缓存服务压测,明确缓存最大水位,当前系统容量超过阈值,通过其他高可用手段进行调整,如服务限流,请求降级,使用消息队列等
4.数据不一致问题
处理少数配置信息类缓存,比如黑白名单,页面展示配置,大部分缓存作为前端请求和持久化层的中间层,承担前端的海量请求。
最理想的情况:缓冲和数据库同时更新成功,由于缓存和数据库分开的,会出现缓存更新失败或数据库更新失败情况
- 场景:某次更新将A价格从1000更新为1200,但是数据库更新成功,缓存失败,用户查看时,看到的是1000,下单是1200
4.1 更新缓存方式
通过更新缓存和更新数据库俩个操作来实现最终一致性
请求先从缓存查,没有命中从数据库查,查询成功后,返回结果同时更新缓存。
- 先数据库再缓存:在写操作时,先更新数据库,成功后,更新缓存会失败
- 先删除缓存,再更新数据库: A线程更新操作,将缓存删除,B线程查询直接查数据库,A线程再更新数据库为新的值
- 先更新数据库,再删除缓存:经典缓存+数据库读写模式,Cache Aside方案。读的时候,先读缓存,没有再读数据库,取出来数据放入缓存,并返回响应。更新时,先更新数据库,成功后再删除缓存。
考虑该情况:A删除缓存,更新主库。B读取缓存,没有命中,从从库查询并设置缓存。主库从库同步。
存在删除缓存失败,多次重试解决 - 更好的方案:更新时删除,等下次查询时再填充缓存
4.2 同步删除
在更新数据库的同时,立即删除缓存,确保缓存中的数据与数据库保持一致。
删除而不是更新缓存,删除数据比更新数据更轻量级,出问题概率更小
4.3 多级缓存
通过事务性消息队列加监听方式失效对应缓存
较难保证数据一致性,通常用于一致性不敏感业务。
针对性的设计:
- 通过给数据添加版本号,或通过时间戳+业务主键方式,控制缓存数据版本实现最终一致性
- Binlog异步更新缓存
4.4 延时双删
在更新数据库后,先删除缓存,然后延迟一段时间再次删除缓存,以确保缓存中的数据最终一致。
4.5 分布式锁
4.6 消息队列
4.7 基于缓存更新策略
通过设置缓存的过期时间和更新策略,确保缓存中的数据最终一致。
更新数据库。设置缓存过期时间。读取数据时,如果缓存失效,则从数据库加载并更新缓存。
5.缓存失效策略
删除访问频率不高的缓存,为新缓存腾空间
5.1 页面置换算法
页面的调度算法,操作系统中文件读取会先分配一定的页面空间Page,查询空间是否有该页面的缓存,有直接拿到,否则查询页面空间没有满,就缓存页面,满了就删除部分页面
- FIFO 先进先出
- LRU 最近最少使用
- LFU 最不经常使用
5.2 内存淘汰策略
当redis节点分配的内存使用到达最大值以后,redis启动内存淘汰策略
- noeviction 默认,对于写请求拒绝服务,直接返回错误,保证数据不丢失
- allkeys-lru,对所有key使用LRU算法进行缓存淘汰
- volatile-lru 对设置了过期时间的key 使用LRU算法淘汰
- allkeys-random ,对所有key随机淘汰数据
- volatile-random,对设置了过期时间的key随机淘汰数据
- volatile-ttl,对设置了过期时间的key,根据过期时间进行淘汰,越早过期的越优先被淘汰
5.3 缓存过期策略
内存淘汰是缓存服务层面的操作
过期策略是具体缓存数据何时失效,缓存key过期了该如何处理
- 定时过期:为每个设置过期时间的key都创建定时器,到期就会清除,但需要耗费大量CPU去处理过期数据,影响缓存服务性能
- 惰性过期:只有当访问一个key时,才判断是否过期,并且进行删除,极端情况出现大量过期key无法被删除
- 定时过期: 添加一个即将过期的缓存字典,每隔一段时间,会扫描一定数量的key,并清除其中过期的key
5.4 实现LRU缓存
重写LinkedHashMap,在原生的removeEldestEntry中默认返回false
6.集群
redis集群方案:Cluster,twemproxy,哨兵,Codis
更新缓存,放置那台机器,获取从那台获取
- 数据同步方案下,所有节点之间数据一样的,不同节点互为副本,但会造成服务器资源浪费,更新缓存时,考虑不同节点的一致性
- 数据不同步方案:每个缓存节点储存数据不同,在缓存读写使用一定的策略进行分发,如果需要冗余数据,通过缓存集群主从同步实现
6.1 哈希取模路由
通过对hash/节点数量得到服务器下标
适合对固定数量缓存集群进行路由,但是对横向扩展不好
6.2 一致性哈希
在负载均衡策略下,可以应用一致性哈希,减少节点拓展时数据失效或者迁移的情况。
一致性哈希是特殊的哈希算法。在使用一致性哈希算法后,哈希表槽位数的改变平均只需要对K/n个关键字重新映射,其中K是关键字的数量,n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位几乎需要对所有关键字重新映射。
6.3 Hash环
获取所有服务器节点hash值,然后获取key的hash,与节点的hash进行对比,找出顺时针最近的节点进行存储和读取。
设有四台服务器,A:100 B:200 C:300 D:400
key哈希操作200,260.500 分别放在B C A服务器
D服务下线,原先路由到D的数据顺时针迁移到A
6.3.1 缓存雪崩
10台服务器组成哈希环,顺序5台宕机,请求都转移到一台服务上,被打爆后继续转移
- 添加虚拟节点,对服务器节点进行哈希操作,在整个哈希环上均匀添加诺干个节点,a1 a2属于A b1 b2 属于B 哈希时平均各个节点数据
6.4 一致性哈希算法
TreeMap基于红黑树实现,元素默认按照Keys的自然排序排列,对外开发tailMap(K formKey)方法,可以返回formKey顺序的下一个节点
7.Redis主从复制
任何节点都可以成为主节点,通过Slaveof命令开启复制,主从复制可以作为数据备份,从节点可以拓展主节点的读请求支持能力,实现读写分离
7.1 Mysql故障转移机制
同步方式有半同步,全同步,关于GTID的复制方式
MySQL缺少一个选举决策的节点,一般人工预选主流程
当主节点发生故障宕机,需要运维工程师从从节点服务器列表中选择一个晋升为主节点,需要更新上游客户端配置
自动故障转移:自动实现Failover
7.2 Redis Sentinel 哨兵
是一个独立运行的过程,假如主节点宕机,实现功能
- 不定期监控Redis服务运行状态
- Redis节点宕机,通知上游客户端进行调整
- Master节点不可用时,可以选择一个Slave节点
作为新的Master机器,并且更新集群中的数据同步关系
7.3 Redis Sentine 方案
包含多个Sentinel节点,以及多个数据节点的分布式架构
Sentinel节点之间相互监控,当发现某个Redis数据节点不可达时,Sentinel会对这个节点做下线处理
如果是Master节点,会通过投票选择是否下线Master节点,完成故障发现和故障转移
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!