此处到应用Nginx的负载机制采用:正常情况采用一致性哈希,如果某个请求类型访问量突破了一定的阀值,则自动降级为轮询机制。另外对于一些秒杀活动之类的热点我们是可以提前知道的,可以把相关数据预先推送到应用Nginx并将负载均衡机制降级为轮询。
分布式缓存方案
另外可以考虑建立实时热点发现系统来发现热点,如下图所示:
实时热点发现方案
1)接入Nginx将请求转发给应用Nginx;
2)应用Nginx首先读取本地缓存;如果命中直接返回,不命中会读取分布式缓存、回源到Tomcat进行处理;
3)应用Nginx会将请求上报给实时热点发现系统,如使用UDP直接上报请求、或者将请求写到本地kafka、或者使用flume订阅本地nginx日志;上报给实时热点发现系统后,它将进行统计热点(可以考虑storm实时计算);
4)根据设置的阀值将热点数据推送到应用Nginx本地缓存。
因为做了本地缓存,因此对于数据一致性需要我们去考虑,即何时失效或更新缓存:
1)如果可以订阅数据变更消息,那么可以订阅变更消息进行缓存更新;
2)如果无法订阅消息或者订阅消息成本比较高,并且对短暂的数据一致性要求不严格(比如在商品详情页看到的库存,可以短暂的不一致,只要保证下单时一致即可),那么可以设置合理的过期时间,过期后再查询新的数据;
3)如果是秒杀之类的,可以订阅活动开启消息,将相关数据提前推送到前端应用,并将负载均衡机制降级为轮询;
4)建立实时热点发现系统来对热点进行统一推送和更新。
应对缓存大热点:数据复制模式
在Facebook有一招,就是通过多个key_index(key:xxx#N) 来解决数据的热点读问题。解决方案是所有热点key发布到所有web服务器;每个服务器的key有对应别名,可以通过client端的算法路由到某台服务器;做删除动作时,删除所有的别名key。可简单总结为一个通用的group内一致模型。把缓存集群划分为若干分组(group),在同组内,所有的缓存服务器,都发布热点key的数据。
对于大量读操作而言,通过client端路由策略,随意返回一台机器即可;而写操作,有一种解法是通过定时任务来写入;Facebook采取的是删除所有别名key的策略。如何保障这一个批量操作都成功?
(1)容忍部分失败导致的数据版本问题
(2)只要有写操作,则通过定时任务刷新缓存;如果涉及3台服务器,则都操作成功代表该任务表的这条记录成功完成使命,否则会重试。
▌13:缓存失效的连接风暴
引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置1分钟,5分钟,并发很高可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。那如何解决这些问题呢?
其中的一个简单方案就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
如果缓存集中在一段时间内失效,DB的压力凸显。这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。
上述是缓存使用过程中经常遇到的并发穿透、并发失效问题。一般情况下,我们解决这些问题的方法是,引入空值、锁和随机缓存过期时间的机制。
▌14:缓存预热
提前把数据读入到缓存的做法就是数据预热处理。数据预热处理要注意一些细节问题:
(1)是否有监控机制确保预热数据都写成功了!笔者曾经遇到部分数据成功而影响高峰期业务的案例;
(2)数据预热配备回滚方案,遇到紧急回滚时便于操作。对于新建cache server集群,也可以通过数据预热模式来做一番手脚。如下图所示,先从冷集群中获取key,如果获取不到,则从热集群中获取。同时把获取到的key put到冷集群。如下图
数据预热
(3)预热数据量的考量,要做好容量评估。在容量允许的范围内预热全量,否则预热访问量高的。
(4)预热过程中需要注意是否会因为批量数据库操作或慢sql等引发数据库性能问题。