前言
与一个同学聊了他最近面试,被面试官问到的问题。其中一个是,如果采用缓存来支撑海量数据的读取。简单的,叙述下方案。
那么,我们今天就探讨下,如何应用多级缓存来支撑海量数据的读操作。
多级缓存
多级缓存,即在整个系统架构中,要在不同系统层级进行数据缓存,而不是仅仅的采用中间件,来进行缓存,以此来提升访问效率,这是应用最广的方案之一。
整体流程,我们分析如下:
1)首先接入负载均衡中间件,比如Nginx(请求负载均衡),将请求负载均衡到接入层应用Nginx(业务转发),常用的负载均衡算法,是轮询或者一致性哈希。
轮询,使服务器请求更加均衡,而一致性哈希可以提升应用Nginx的缓存命中率,相对于轮询,一致性哈希会存在单机热点问题,存在两种解决方案
,一种是热点直接推送到Nginx,另一种方法是设置一个阈值,当到达阈值后,采用轮询算法
2)应用Nginx读取本地缓存,如果命中,则返回。应用Nginx本地缓存,可以提升整体系统吞吐量,降低后端的压力,尤其针对热点问题非常有效。
本地缓存可以使用Nginx Proxy Cache(磁盘/内存)、Local Redis、Lua Shared Dict
3)如果应用Nginx本地缓存没有命中,则读取分布式缓存(如Redis缓存,可以采用主从架构提升性能,吞吐量)如果分布式缓存命中,则响应返回,并更新应用层Nginx本地缓存
4)如果分布式缓存也没有命中,则到服务内部服务器中,比如Tomcat,在进行Tomcat集群时,也可以使用轮询和一致性哈希作为负载均衡算法,可以采用ribbon,作为服务端负载均衡组件
5)在Tomcat集群中,首先读取本地堆缓存,如果有,则返回响应,依次回写前面的缓存层。
6)如果所有缓存,没有命中,则需要读写DB,然后响应、回写。
应用整体,分为三部分缓存:应用层Nginx本地缓存、分布式缓存、Tomcat堆缓存,每一层缓存来解决相关的问题。
如何缓存数据
- 过期与不过期
缓存的创建,可以包括不过期缓存和具备时间时效的缓存。
1)不过期缓存一般应用在开启事务,执行SQL,提交事务,写缓存的场景。
基本上来讲,对于高频访问的数据,若缓存空间足够,则可以考虑不过期缓存,当缓存空间满了,可以采用LRU进行缓存清理
2)过期缓存,即采用懒加载,一般用于缓存其他系统的数据,常见缓存空间有限、低频热点缓存等场景 - 维护化缓存与增量缓存
对于类似商品属性数据的缓存,往往有公共的部分,更新的只是其中一部分,那么缓存的创建,需要增量更新缓存 - 大Value缓存
对于大Value的缓存,要非常警惕,以防把缓存撑爆,尤其在使用Redis时。此时,可以考虑采用多线程缓存,如Memcached - 热点缓存
可以通过多挂从缓存,客户端通过负载均衡机制,来读取从缓存的系统数据。
更新缓存与原子性
如果多个应用,同时操作一份数据很可能造成缓存数据是脏数据。
解决方案分析如下:
- 更新数据时使用版本号或者使用戳,例如redis利用其单线程原子性特点处理更新
- 使用canal订阅数据库binlog。canal作为消费者,可以订阅mysql发布者,发布的消息binlog,进行增量更新redis
- 使用分布式锁,更新之前获取相关的锁
- 将更新请求按照规则分散到队列中,每个队列单线程原子更新。
缓存崩溃与快速恢复
我们要考虑到一部分缓存实例宕机情况,那么如何解决的。当缓存数据是可丢失的情况,我们可以选择一致性哈希算法
- 取模
对于取模,如果其中一个实例故障,如果当前被摘除,则造成大量不能命中,就会造成缓存击穿,大流量导致DB出现问题。对此,我们可以采用主从复制来解决。 - 一致性哈希
- 快速恢复
出现了上述问题,如何解决,我们可以采用以下方案:
1)主从复制,做好冗余
2)考虑用户降级