总的方针还是将数据前移。

以电商系统为例,最容易产生热Key的无疑是商品详情接口(静态页面orJSON数据)。从整体来看,可以采用客户端缓存,CDN缓存,甚至限流来减少服务端的并发压力。而作为服务端,可以采用下列方案来解决高并发下的热Key问题。

数据库

高并发状况下,MySQL就指望不上了。A厂曾在源代码上做了修改,使TPS从150提高到8万5,其他公司恐怕没有这个技术实力。甚至说公司特别有钱,给MySQL装了SSD硬盘,也是很多企业学不来的。

缓存

作为比较流行的设计方案来说,应对高并发还是围绕缓存做文章,以Redis为例。

1、一主多从,读写分离。将并发读分散到N台从服务器中,提升吞吐量。

2、复制热Key到其他主Redis中。以Redis集群3主为例,假如热Key的并发超过了A服务的能力,而我们又不能无限地去增加从服务器的数量,那么采用热Key+随机值的方式,将热Key复制到B、C服务上,例如spu_111_a、spu_111_b、spu_111_c。

但这种做法的问题是:

A、Redis集群采用16384个哈希槽来给Key分片,即CRC16(key)%16384,怎么就能保证spu_111_a、spu_111_b、spu_111_c必然分片到ABC三个服务器中。

B、负载代码要自己写。客户端传来spu_111,我们要自己去维护一个spu_111:spu_111_a,spu_111_b,spu_111_c的键值对,还要实现负载均衡去访问。

综上,一般还是做到主从复制,读写分离就可以了。也可以对Redis集群进行扩容,主服务器越多,Key就越散列。

但是服务器数量毕竟不能无限增加,否则集群中的心跳信息就增大了,官方也建议不要超过1000台。

3、多级缓存。上面只是针对高并发的流行设计,面对热Key的超高瞬时并发,人家就是压你一台Redis,把从服务器的性能压满了,造成服务器中其他Key服务延时或无法服务。这时应采用多级缓存,将热Key提出来专门处理。

A、热Key专有Redis集群,这个集群只存热Key。

B、本地缓存。例如使用Guava的Cache,或者Nginx+LUA,将热Key的数据留在本地,不再访问Redis。

两个方案各有利弊。A方案管理起来方便,只需操作一台主Redis即可,而B方案的管理就要跟着服务实例的数量走了。当然,采用A方案,如果遇上10个热Key,每个10万并发,好巧不巧,10个热Key全在一台Redis上,那整个集群就出现几台Redis忙死,几台Redis闲死的状况了,这个时候扩容再分片也不现实。而B方案扩容就相对方便一些。

热Key的发现

1、预热。秒杀商品肯定是热Key,提前缓存。

2、客户端收集。一般开发中,都会把Jedis这类客户端封装成一个工具类,这里面就可以对访问的Key进行统计,例如每访问一个Key,就通过MQ发给统计程序进行计数。

3、使用Redis的hotkeys命令。个人比较推荐此方案。要注意的是,hotekeys是基于Redis的LFU淘汰策略,要配置成allkeys-lfu或者volatile-lfu。并且如果Key的数量较多,统计起来也是会变慢的。

4、还有其他方式,例如proxy层收集,抓包分析等,不再详述。

热Key的缓存与失效

不论是采取热Key的Redis集群,还是本地缓存,在发现热Key后总要有个处理方式。

1、MQ。订阅一个hotkeys的Topic,一旦出现热Key,或热Key退火,接收到消息后将其缓存下来或删除。

2、Zookeeper。watch一个/hotkeys的节点,子节点是热Key列表,一旦发生变更,则触发watch机制,拉取新的热Key列表进行处理。

条条大路通罗马,做工程,面对的情况不同——需求、团队、硬件资源等,解决方案并无唯一解,具体问题具体分析就好。