多级缓存
- 基本概念
- 如何缓存数据
- 分布式缓存与应用负载均衡
- 热点数据与更新缓存
- 缓存崩溃与快速修复
基本概念
1. 什么是多级缓存
是指在整个系统架构的不同系统层级进行数据缓存、以提高访问效率
一般会使用nginx本地缓存解决热点缓存问题
使用分布式缓存减少访问回源率
使用tomcat堆缓存用于防止缓存失效/崩溃之后的冲击
如何缓存数据
1. 过期与不过期
1) 不过期缓存场景
业务 -> 开启事务 -> 执行SQL -> 提交事务 -> 写缓存
首先会写db、若成功则写缓存、会存在db写成功、但缓存写失败、但无法回滚事务的情况
还应该避免把写缓存放在事务中、尤其是写分布式缓存、因为网络抖动可能导致写缓存响应时间很慢、引起事务阻塞、若对缓存数据的一致性要求没那么高、数据量没那么大、可以考虑定期全量同步缓存
2) 过期缓存、eg. 采用懒加载、一般用于缓存其它系统的数据(如无法订阅变更消息或者成本很高)、缓存空间有限、低频热点缓存等场景
常见步骤是:读cache、无 -> 查询并缓存 -> 下次读cache ok、这种情况缓存可能存在一段时间的数据不一致、需要根据场景来决定如何设计过期时间、如库存数据可以在前端应用上缓存几秒、短时间内的不一致是可以忍受的
2. 维度化缓存
对于电商系统、一个商品可能拆分成如基础属性、图片列表、上下架、规格参数、商品介绍等
若商品变更、需要把所有的数据都更新一遍、更新成本很高、包括接口调用量和带宽、因此最好将数据进行维度化并增量更新(只变更变动部分)、尤其是上下架状态、这种只是一个状态但变更频繁的数据、维度化后可以减少server很多压力
3. 大value缓存
遇到需要大value缓存的情况、可以考虑多线程实现的缓存(eg. memcache)或者对value进行压缩、或者将value拆分成多个小value、客户端再进行查询、聚合
4. 热点缓存
对于热点缓存、若每次都从远程缓存获取、可能会因为访问量太大而导致远程缓存系统请求过多、负载过高或者带宽过高等问题、最终可能导致缓存响应慢、使客户端请求超时
1) 通过挂载更多的从缓存、客户端通过负载均衡机制读取从缓存系统数据、
2) 也可以在客户端所在的应用/代理层本地存储一份、避免访问远程缓存、即eg.库存量、在有些应用系统中也可以进行几秒钟的本地缓存、降低远程系统的压力
分布式缓存与应用负载均衡
1. 缓存分布式
一般采用分片实现、即将数据分散到多个实例或者多台服务器
算法一般采用取模和一致性hash
如果是不过期缓存、可以考虑取模机制、扩容时新建一个集群、对于可以丢失的缓存数据、可以考虑一致性hash、即使一个实例出问题、也只是丢一小部分、对于分片实现可以考虑客户端实现、或者使用如 twemproxy的中间件进行代理(分片对客户端是透明的)、如果使用redis、则可以考虑redis-cluster分布式集群方案
2. 应用负载均衡
一般采用轮询和一致性hash、
一致性hash可以根据应用请求的url或者url参数将相同的请求转发到同一个节点
轮询是将请求均匀的转发到每个服务器
轮询的优点是:请求更加均匀、使得每个服务器的负载基本均衡、
缺点是:随着nginx应用server的增加、缓存的命中率会下降、
eg.原来10台server的命中率为90%、再增加10台、可能下降到45%
这种方式不会因为热点问题导致其中一台server的负载过重
一致性hash的优点是:相同的请求都会转发到同一台server、命中率不会因为server的增加而降低
缺点是:因为相同的请求会转发到同一server、可能会造成某台机器负载过重
可以根据实际情况选择使用哪种算法
1. 负载较低时、使用一致性hash
2. 热点请求降级一致性hash为轮询、
3. 或者若请求数据有规律、可以考虑带权重的一致性hash
4. 将热点数据推送到nginx层、直接响应给用户
热点数据与更新缓存
热点数据会造成服务器压力过大、导致服务器性能、吞吐量、带宽达到极限、出现响应慢或者拒绝服务的现象、是肯定不能被允许的
1. 单机全量缓存 + 主从
所有缓存都存储在应用本机、回源之后将数据更新到主redis集群、然后通过主从模式复制到其它从redis集群、缓存的更新可以采用懒加载或者订阅消息进行同步
2. 对于分布式缓存、需要在nginx+lua应用中减小应用缓存来减少redis集群的访问冲击、
即、首先查询应用本地缓存、如果命中、则直接使用缓存、若未命中、则查询redis集群、回源tomcat、然后将数据缓存到应用本地
对于lvs+haproxy 到应用nginx的负载机制、正常情况采用一致性hash、若某个请求类型的访问量突破了一定的阈值、则自动降级为轮询机制、而对于一些秒杀活动之类的热点、可以把相关数据预先推送到应用nginx、并将负载均衡机制降级为轮询(使用接入层nginx+应用层nginx来实现)
3. 可以建立实时热点系统
eg. 使用udp直接上报请求 或者 将请求写入本地kafka 或者 使用flume订阅本地nginx日志、
上报之后、实时热点系统进行热点统计(可以考虑storm实时计算)
根据设置的阈值将热点数据推送到应用nginx做本地缓存
4. 怎么避免多个应用同时操作一份缓存时造成的脏数据问题?
1) 更新数据时使用更新时间戳或者版本对比、如 使用redis、可以使用其单线程机制进行原子化更新
2) 使用canal订阅数据库binlog
3) 将更新请求安装相应的规则分散到多个队列、然后每个队列进行单线程更新、更新时拉取最新的数据保存
4) 使用分布式锁、在更新之前获取相关的锁
缓存崩溃与快速修复
1. 取模
对于取模机制、若一个实例坏掉、摘除此实例将导致大量缓存命不中、则瞬间流量可能导致后端db/服务出现问题、可以采用主从机制来避免实例坏掉的问题
但是取模机制下增加一个节点将导致大量缓存不命中、一般是建立另一个集群、
把数据迁移到新的集群、把流量迁移过去
2. 一致性hash
对于一致性hash机制、若其中一个实例坏了、摘除此实例只影响hash环上的部分缓存、
不会导致大量缓存瞬间回源到后端db/服务、但是也会产生一些影响、
3. 那么问题出现时、如何快速恢复 ?
1) 主从机制、做好冗余、其中一部分不可用、立即将对等的补上去
2) 若因为缓存导致应用可用性已经下降、可用考虑部分用户降级、然后慢慢减少降级量
后台通过woker预热缓存数据
也就是若整个缓存集群坏了、那只能慢慢重建、