概念介绍

热点Key

产生的背景

用户消费的数据远大于生产的数据(热卖商品、热点新闻、热点评论、热门明星直播)。

对于电商网站中,我们经常可以会遇到热门商品的抢购或者秒杀场景以及事先经过广告投放等措施进行定向引流,这样就会导致某个热卖商品在短时间内涌入大量流量。

比如,双十一期间某些热门商品的降价促销,当这其中的某一件商品被数万次点击浏览或者购买时,会形成一个较大的需求量,这种情况下就会造成热点问题。

导致的问题及解决方案

热点Key产生问题的原因

请求到的分片过于集中,超过单台Server的性能极限。

在服务端读数据进行访问时,往往会对数据进行分片切分。此过程中会在某一主机Server上对相应的Key进行访问,当访问超过Server极限时,就会导致热点 Key 问题的产生。

热点Key的危害
  • 流量集中,达到物理网卡上限。
  • 请求过多,缓存分片服务被打垮。
  • DB 击穿,引起业务雪崩。
解决方式
  • 服务端缓存:即将热点数据缓存至服务端的内存中。
  • 备份热点Key:即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。

大Key

Redis使用过程中经常会有各种大key的情况, 比如单个简单的key存储的value很大。
由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间造成负面影响,导致IO网络拥塞。

解决方案

将整存整取的大对象,分拆为多个小对象。可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;

一次实战优化过程

问题简介

在电商网站的一次营销事件中,通过相关的引流操作,将大量流量在指定时间引流到了商品抢购页面,在抢购页中涉及到的几个后台接口,其中有一两个都会去查商品信息。

在商品模块对外暴露了一个对条件查询的接口,在上述的场景下就会短时间高频次的调用这个接口。

根据这个场景发现,商品数据,在活动期间会有很大的访问量,这是一个热点Key。另外由于前期错误的设置导致了这个热点Key又是一个大Key。

所以我们的优化过程就是按照如果解决掉热点Key和大Key的这两个问题进行的。之前并没有上述的概念,都是摸着石头过河,渐渐地思路才清晰起来。

解决过程

第一版

直接按条件查询数据库。

public List<ReturnObject> version1(QueryCriterionDto queryDto) {
    // 1.直接按照条件查询数据库

    // 2.将查询到的结果返回
}

第二版

将全量数据缓存到Redis中,查出数据后再进行过滤返回。

public List<ReturnObject> version2(QueryCriterionDto queryDto) {
    // 1.先尝试从缓存中查询全量数据

    // 2.如果不存在,则从数据库中把全量数据出,并缓存到Redis中

    // 3.对全量数据进行过滤筛选

    // 4.返回最终的结果
}

第三版:

仍然将全量数据缓存到Redis中,但是只缓存必要的数据,比如过滤条件,从缓存中拿出全部的数据后,进行过滤后列出需要返回的对象唯一id,再根据这一批唯一id去缓存中查单个的对象出来,最后拼成List返回。

public List<ReturnObject> version3(QueryCriterionDto queryDto) {
    // 1.先尝试从缓存中查询全量数据(这里的全量数据指的不是ReturnObject,而是另外一个只用于过滤筛选的简单对象)

    // 2.如果不存在,则从数据库中把全量数据出,转成简单对象,并缓存到Redis中

    // 3.简单对象中包含所有的过滤条件,过滤后得到一组最终的idList(每个id在缓存中对应一个ReturnObject)

    // 4.根据上面得到的idList,然后循环get取出最后的返回结果
}

第四版

将每次的获取单个对象的get操作,调整成mget这样就可以一次操作取出多个对象出来,然后再跟查询的keys比较一下size,如果少的话,再尝试从库中查一次,把少的数据补上。

public List<ReturnObject> version4(QueryCriterionDto queryDto) {
    // 1.先尝试从缓存中查询全量数据(这里的全量数据指的不是ReturnObject,而是另外一个只用于过滤筛选的简单对象)

    // 2.如果不存在,则从数据库中把全量数据出,转成简单对象,并缓存到Redis中

    // 3.简单对象中包含所有的过滤条件,过滤后得到一组最终的idList(每个id在缓存中对应一个ReturnObject)

    // 4.对上面得到的idList进行分组比如每50个keys作为一组,然后使用multiGet一次获取50个对象,从而降低redis调用次数

    // 5.针对从redis中没有取到的对象,再尝试从库中查询,进行填补

    // 6.返回最终的对象
}

参考文章:
https://cloud.tencent.com/dev...
https://blog.csdn.net/youanyy...
https://segmentfault.com/a/11...


翎野君
76 声望5 粉丝