关于Redis热点key的一些思考

关于Redis热点key的一些思考

昨天在和一个已经跳槽的同事聊天时,询问他这段时间面试时碰到的一些问题。自己也想积累一下这方面的知识。其中他说了在面试某赞公司时面试官问他关于热点Key的的解决方案。于是针对这次谈话以及上网查的一些资料后的思考进行一下总结。方便后续自己查阅。

什么是热点Key

其实对于热点Key,网上一查一大堆,这里我就引用网上的一段话。

从基于用户消费的数据远远大于生产的数据的角度来讲,我们平常使用的知乎等软件时,大多数人平常仅仅只是浏览,并不会去提问问题、发表的文章,偶尔会发表自己的文章或者看法,这就是一个典型的读多写少的情景,当然此类情景不太容易导致热点的产生。

在日常工作生活中一些突发的的事件,诸如:“双11”期间某些热门商品的降价促销,当这其中的某一件商品被数万次点击、购买时,会形成一个较大的需求量,这种情况下就会产生一个单一的Key,这样就会引起一个热点;同理,当被大量刊发、浏览的热点新闻,热点评论等也会产生热点;另外,在服务端读数据进行访问时,往往会对数据进行分片切分,此类过程中会在某一主机Server上对相应的Key进行访问,当访问超过主机Server极限时,就会导致热点Key问题的产生。

如何解决?

针对于热点Key的解决方案网上的查找出来无非就是两种

  • 服务端缓存:即将热点数据缓存至服务端的内存中
  • 备份热点Key:即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。

其实这两个解决方案前提都是知道了热点Key是什么的情况,那么如何找到热点key呢?

热点检测

  1. 凭借经验,进行预估:例如提前知道了某个活动的开启,那么就将此Key作为热点Key
  2. 客户端收集:在操作Redis之前对数据进行统计
  3. 抓包进行评估:Redis使用TCP协议与客户端进行通信,通信协议采用的是RESP,所以能进行拦截包进行解析
  4. 在proxy层,对每一个 redis 请求进行收集上报
  5. 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 Map<String,String> hotKeyMap = new ConcurrentHashMap<>();
    private static List<String> 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

参考文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容