多级缓存

多级缓存

  • 基本概念
  • 如何缓存数据
  • 分布式缓存与应用负载均衡
  • 热点数据与更新缓存
  • 缓存崩溃与快速修复

基本概念

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预热缓存数据
      也就是若整个缓存集群坏了、那只能慢慢重建、

你可能感兴趣的:(缓存,无事闲翻书)