摘要:如何解决针对于热点的解决方案网上的查找出来无非就是两种服务端缓存即将热点数据缓存至服务端的内存中备份热点即将热点随机数,随机分配至其他节点中。伪代码如下生成随机数构造备份新从数据库中取数据存放在中,以便下次能取到代码地址参考文章
关于Redis热点key的一些思考
昨天在和一个已经跳槽的同事聊天时,询问他这段时间面试时碰到的一些问题。自己也想积累一下这方面的知识。其中他说了在面试某赞公司时面试官问他关于热点Key的的解决方案。于是针对这次谈话以及上网查的一些资料后的思考进行一下总结。方便后续自己查阅。
什么是热点Key其实对于热点Key,网上一查一大堆,这里我就引用网上的一段话。
从基于用户消费的数据远远大于生产的数据的角度来讲,我们平常使用的知乎等软件时,大多数人平常仅仅只是浏览,并不会去提问问题、发表的文章,偶尔会发表自己的文章或者看法,这就是一个典型的读多写少的情景,当然此类情景不太容易导致热点的产生。
在日常工作生活中一些突发的的事件,诸如:“双11”期间某些热门商品的降价促销,当这其中的某一件商品被数万次点击、购买时,会形成一个较大的需求量,这种情况下就会产生一个单一的Key,这样就会引起一个热点;同理,当被大量刊发、浏览的热点新闻,热点评论等也会产生热点;另外,在服务端读数据进行访问时,往往会对数据进行分片切分,此类过程中会在某一主机Server上对相应的Key进行访问,当访问超过主机Server极限时,就会导致热点Key问题的产生。
如何解决?针对于热点Key的解决方案网上的查找出来无非就是两种
服务端缓存:即将热点数据缓存至服务端的内存中
备份热点Key:即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。
其实这两个解决方案前提都是知道了热点Key是什么的情况,那么如何找到热点key呢?
热点检测凭借经验,进行预估:例如提前知道了某个活动的开启,那么就将此Key作为热点Key
客户端收集:在操作Redis之前对数据进行统计
抓包进行评估:Redis使用TCP协议与客户端进行通信,通信协议采用的是RESP,所以能进行拦截包进行解析
在proxy层,对每一个 redis 请求进行收集上报
Redis自带命令查询:Redis4.0.4版本提供了redis-cli –hotkeys就能找出热点Key
如果要用Redis自带命令查询时,要注意需要先把内存逐出策略设置为allkeys-lfu或者volatile-lfu,否则会返回错误。进入Redis中使用config set maxmemory-policy allkeys-lfu即可。服务端缓存
假设我们已经统计出了一些热点Key,将这些数据缓存到了服务端,那么还有一个问题。就是如何保证Redis和服务端热点Key的数据一致性。我这里想到的解决方案是利用Redis自带的消息通知机制,对于热点Key客户端建立一个监听,当热点Key有更新操作的时候,客户端也随之更新。
主要代码如下,监听类负责接收到Redis的事件,然后筛选出热点Key进行相应的动作
public class KeyExpiredEventMessageListener implements MessageListener { @Autowired private RedisTemplate redisTemplate; @Override public void onMessage(Message message, byte[] pattern) { String key = new String(message.getChannel()); key = key.substring(key.indexOf(":")+1); String action = new String(message.getBody()); if (HotKey.containKey(key)){ String value = redisTemplate.opsForValue().get(key)+""; switch (action){ case "set": log.info("热点Key:{} 修改",key); HotKeyAction.UPDATE.action(key,value); break; case "expired": log.info("热点Key:{} 到期删除",key); HotKeyAction.REMOVE.action(key,null); break; case "del": log.info("热点Key:{} 删除",key); HotKeyAction.REMOVE.action(key,null); break; } } } }
建立一个存储热点Key的数据结构ConcurrentHashMap,并设置相应的操作方法,这里设置了假数据,在static代码块中直接设置了两个热点Key
public class HotKey { private static MaphotKeyMap = new ConcurrentHashMap<>(); private static List hotKeyList = new CopyOnWriteArrayList<>(); static { setHotKey("hu1","1"); setHotKey("hu2","2"); } public static void setHotKey(String key,String value){ hotKeyMap.put(key,value); hotKeyList.add(key); } public static void updateHotKey(String key,String value){ hotKeyMap.put(key,value); } public static String getHotValue(String key){ return hotKeyMap.get(key); } public static void removeHotKey(String key){ hotKeyMap.remove(key); } public static boolean containKey(String key){ return hotKeyList.contains(key); } }
其实用Redis的事件通知机制挺不好的,因为只要开启了事件通知,那么每个Key的变化都会发消息,这样也会平白无故的加重Redis服务器的负担。当然我只是简单的演示一下,除了这种通知方案以外还有很多种方法。
备份热点Key这个方案说起来其实也很简单,就是不要让key走到一台机器上就行,但是我们知道在Redis集群中包含了16384个哈希槽(Hash slot),集群使用公式CRC16(key) % 16384来计算Key属于哪个槽。那么同一个Key计算出来的值应该都是一样的,如何将Key分到其他机器上呢?只要再后面加上随机数就行了,这样就能保证同一个Key分布在不同机器上,在访问的时候通过Key+随机数的方式进行访问。
伪代码如下
const M = N * 2 //生成随机数 random = GenRandom(0, M) //构造备份新key bakHotKey = hotKey + “_” + random data = redis.GET(bakHotKey) if data == NULL { //从数据库中取数据 data = GetFromDB() //存放在Redis中,以便下次能取到 redis.SET(bakHotKey, expireTime + GenRandom(0,5)) }代码地址Github 参考文章
http://mysql.taobao.org/monthly/2018/09/08/
https://www.cnblogs.com/rjzheng/p/10874537.html
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/75317.html
摘要:未完,待续阿里云云数据库版兼容协议标准的提供持久化的内存数据库服务,基于高可靠双机热备架构可无缝扩展的集群架构以及读写分离架构,满足高读写性能场景及容量需弹性变配的业务需求。 摘要: Redis是开源的基于内存且可以持久化的分布式 Key – Value数据库。自2009年发布最初版本以来,Redis的热度只增不减,除了经常位居DB-Engines的最受欢迎Key-Value数据库榜首...
摘要:关于缓存热点重建原文说到在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃,并给出互斥锁和永远不过期两种候选方案。即使绕过互斥锁,也不会产生什么不好的后果,因为更新缓存是一个幂等操作。 向大家推荐这篇文章——Redis架构之防雪崩设计:网站不宕机背后的兵法 (另外推荐我去年的短文作为餐前点心——略谈服务端缓存设计) 《Redis架构之防雪崩设计》这篇文章(下...
阅读 1995·2021-11-23 10:08
阅读 2325·2021-11-22 15:25
阅读 3269·2021-11-11 16:55
阅读 763·2021-11-04 16:05
阅读 2575·2021-09-10 10:51
阅读 704·2019-08-29 15:38
阅读 1573·2019-08-29 14:11
阅读 3480·2019-08-29 12:42