性能文章>看我如何把Redis使用优化到极致>

看我如何把Redis使用优化到极致原创

1年前
723439

我们有个这样的需求:每天每一个抢购商品只能买一次,并且全场抢购商品总购买次数不允许超过5次。那么,整个商品限购的流程大概如下图所示:

image.png

那么,在每次购买成功商品成功后,发送的MQ大概是这样的(假设当前这笔订单有两件抢购商品):

[{
"orderId": "2020020622000001",
"orderTime": "1581001673012",
"productId": "599055114591",
"userId": "860000000000001",
"merchantCode": "A045"
}, {
"orderId": "2020020622000001",
"orderTime": "1581001673012",
"productId": "599055114592",
"userId": "860000000000001",
"merchantCode": "A045"
}]

这条消息表示860000000000001这个用户在1581001673012这个时间点(北京时间为2020/02/06 23:07:53)在A045这个商户分别购买了商品ID为599055114591和599055114592两样商品。

那么,当消费这条信息后,更新频控的几条关键Redis命令如下(上面的需求不是重点,优化下面5条命令才是本文的重点):

命令1:hset mall:sale:freq:ctrl:860000000000001 599055114591 1(hash结构,field表示购买的商品ID,value表示购买次数)
命令2:hset mall:sale:freq:ctrl:860000000000001 599055114592 2
命令3:expire mall:sale:freq:ctrl:860000000000001 3127(设置过期时间)

命令4:set mall:total:freq:ctrl:860000000000001 3
命令5:expire mall:total:freq:ctrl:860000000000001 3127(设置过期时间)

我们首先了解一下执行一条Redis命令耗时由哪几部分组成:发送命令网络传输时间,命令在Redis服务端队列中等待的时间,命令执行的时间(Redis中的slowlog只是检测这一步骤的时间),结果返回的Redis客户端的时间。如下图所示:

image.png

上面的业务总计涉及5条Redis命令,每条命令都需要经过这些步骤,可想而知性能真的弱爆了(可能整个执行过程还不需要10ms,但还是弱爆了)。

  • 第1次优化

第一次优化非常简单,稍微有点经验就能看出来,利用hmset命令将两条hmset命令合二为一,优化后的Redis命令如下:

hmset mall:sale:freq:ctrl:860000000000001 599055114591 1 599055114592 2
expire mall:sale:freq:ctrl:860000000000001 3127

set mall:total:freq:ctrl:860000000000001 3
expire mall:total:freq:ctrl:860000000000001 3127
  • 第2次优化

第二次优化将set和expire命令合二为一,这个一般对Redis有点了解的也知道如何优化:

hmset mall:sale:freq:ctrl:860000000000001 599055114591 1 599055114592 2
expire mall:sale:freq:ctrl:860000000000001 3127
setex mall:total:freq:ctrl:860000000000001 3127 3
  • 第3次优化

第3次优化需要借助pipeline,简直就是Redis优化的一大杀器。不过,需要注意的是在RedisCluster中使用pipeline时必须满足pipeline打包的所有命令key在RedisCluster的同一个slot上。如果打包命令的key不在同一个slot上,就会报错。所以我们需要分两批打包:

-- 这两条命令的key都是一样的,肯定在同一个slot上
pipeline(
hmset mall:sale:freq:ctrl:860000000000001 599055114591 1 599055114592 2
expire mall:sale:freq:ctrl:860000000000001 3127
)

-- mall:total:freq:ctrl:860000000000001和mall:sale:freq:ctrl:860000000000001两条命令不在同一个slot上,所以需要单独执行下面这条命令
setex mall:total:freq:ctrl:860000000000001 3127 3

经过第3次的优化后,这些命令还是需要2次网络交互。较劲的我还是不甘心,想要将其优化到只需要一次网络交互即可,有没有办法?当然有!

  • 第4次优化

这次优化利用了一个高级特性:hashtag。是啥子意思呢?我们知道,RedisCluster总计有16*1024=16384个slot。那么执行一条Redis命令时,其key对应的是哪个slot呢?是利用这样一个计算公式得到的:slot = CRC16(key)%16384,示意图如下:

image.png

也就是说,默认情况下,key在哪个slot上,与key有关。那么,我们能否只让key在哪个slot上与部分key有关呢?当然可以,这就是hashtag特性。用法非常简单,假设一个key是mall:sale:freq:ctrl:860000000000001,我们只需要用{}将key中我们需要的那部分包括起来即可。例如,我们只想让其根据用户IMEI计算即可,那么key是这样的:mall:sale:freq:ctrl:{860000000000001}。只要key中有{860000000000001}这一部分,就一定落在同一个slot上。

所以,第四次优化以后的命令执行如下所示:

pipeline(
hmset mall:sale:freq:ctrl:${860000000000001} 599055114591 1 599055114592 2
expire mall:sale:freq:ctrl:${860000000000001} 3127
setex mall:total:freq:ctrl:${860000000000001} 3127 3
)

image.png

优化后,5条Redis命令压缩到3条Redis命令,并且3条Redis命令只需要发送一次,并且结果也一次就能全部返回。简直完美!!

注意事项

我们在使用hashtag特性时,一定要注意,不能把key的离散性变得非常差。以本文为例,没有利用hashtag特性之前,key是这样的:mall:sale:freq:ctrl:860000000000001,很明显这种key由于与用户相关,所以离散性非常好。而使用hashtag以后,key是这样的:mall:sale:freq:ctrl:{860000000000001},这种key还是与用户相关,所以离散性依然非常好。我们千万不要这样来使用hashtag特性,例如将key设置为:mall:{sale:freq:ctrl}:860000000000001。这样的话,无论有多少个用户多少个key,其{}中的内容完全一样都是sale:freq:ctrl,也就是说,所有的key都会落在同一个slot上,导致整个Redis集群出现严重的倾斜问题。

 

相关阅读

Redis高级玩法:如何利用SortedSet实现多维度排序

D炸天的Redis,该如何监控?

收藏:一些比较好的Redis 性能优化思路总结

Redis进阶:深入解读阿里云Redis开发规范(修订版)

分类:
标签:
请先登录,再评论

如何保证验证完成到更新数量到 redis 间的并发问题?

1年前
回复 carl-zhao:

这个,我这边的业务事实上不是电商问题,不需要严格限制。我们是广告频控,允许一部分容错数据的。

1年前回复

mall:total:freq:ctrl:860000000000001 这个key的值难道不需要 根据前面的命令执行完再重新统计吗?

1年前

加上${}就算使用了hashTag特性了?

1年前
回复 蜃:

只需要{}不需要$

1年前回复

为你推荐

D炸天的Redis,该如何监控?
本文重点讲述Redis的哪些metrics需要重要监控(篇幅有限,不能涵盖所有),以及我们如何获取这些metrics数据。从而确保对我们应用至关重要的Redis是否健康运行,以及当出现问题时能及时通知
Redis进阶:深入解读阿里云Redis开发规范(修订版)
Key命名设计:可读性、可管理性、简介性规范建议使用冒号即:进行分割拼接,因为很多Redis客户端是根据冒号分类的。比如有几个Key:apps:app:1、apps:app:2和apps:app:3。
构建企业级业务高可用的延时消息中台
业务场景剖析公司业务系统(比如:电商系统)中有大量涉及定时任务的业务场景,例如:实现买卖双方在线沟通的IM系统,为了确保接收方能够收到消息,服务端一般都会有重试策略,即服务端在消息发出的一段时间内,如
Redis client链接池配置不当引起的频繁full gc
现象笔者负责的一个RPC服务就是简单的从Redis Cluster中读取数据,然后返回给上游。理论上该服务的对象大部分都应该是朝生夕死的,但是笔者查看gc log 的时候发现 age =2 的对象还真
记一次线上请求偶尔变慢的排查
前言最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章。 Bug现场这是一个偶发的性能问题。在每天几百万比交易请求中,平均耗时大约为300ms,但总有那么100多笔会超过
空中楼阁之纸上谈兵 redis中hash的思考
思考点:------与jdk中hashmap的区别?(提示从添加元素、扩容这两个行为开始分析)为什么使用这种添加方式?为什么这样扩容?扩容的时机?具体标准是?是否还有缩容呢?若有,则时机和标准呢?hg
RedLock: 看完这篇文章后请不要有任何疑惑了
后台经常会有小伙伴咨询RedLock相关问题,笔者在此再来一篇文章剖析一下RedLock,希望此文能解决你对它所有的疑惑和误解。 为什么需要RedLock这一点很好理解,因为普通的分布式锁算法在加锁时
Redis OOM(out of memory)内存占用过高,故障分析过程。
本文正在参加「Java应用线上问题排查经验/工具分享」活动先点赞再看,养成好习惯背景全国最大的短信平台,大家都用过我们的产品。数据量比较大,最多时候一天近7亿条短信。简单介绍一下redis(我们平台用