redis系列1---高并发下redis的实际用法

264 阅读5分钟

hotkey的处理

什么是hotkey?比如你有一个秒杀商品,每个人进系统的首页一定要加载这个,比如sku:001,从数据库查询肯定是不现实的,那如果用redis缓存可不可以?可以的,但是也会有个问题,由于redis的key是hash定位的,所以同样的key一定会被分配同一个节点,假设你有3台redis实例组成的集群,那这个key都会往其中一个上面查询,就会导致资源倾斜,假设你访问量很高,那可能会出现某一个redis实例cpu快满了并且还影响其他请求的操作,其他两个实例没啥压力的情况。这种key就是hotkey。

怎么解决?其实说起来也很简单,第一发现他,第二缓存到你的应用服务缓存里(即jvm缓存)。落地到实际场景,一般都是用京东的一个开源框架去探测,他的原理大概就是你需要部署几个计算节点集群起来,用etcd当类似配置中心和注册中心的概念,简单的来说,就是你在你的项目引入这个京东jar包客户端,在etcd配置好规则,比如定义下什么是hotkey(1s被访问了100次等等),hotkey要缓存多久等规则,你的应用服务会在操作redis的时候,每隔xx毫秒额外去把这个操作记录发给计算节点,让计算节点去分布式的计算下,有没有满足规则,他会把结果通知你的应用,缓存到jvm里。(盗图--儒猿系列)

image.png

image.png

image.png

bigkey的处理?

什么是bigkey?大key本质上是大value,这个其实并没有特别明确的指标,一般情况下,我们可以把一个string类型,超过1M的,或者list这种超过3000的这种叫大key。至于解决思路,还是一样的,1发现2解决。 发现我们可以通过crontab定时调度shell脚本,凌晨的时候通过rdbtools去解析redis的rdb文件,把符合大key的数据导出到csv文件,然后通过sql导入到mysql中。这个时候就可以通过canal去监听mysql的binlog,发送到rocketmq中,在消费端处理,比如发告警之类的。 那为啥我不直接扫描csv文件去解析,还要这么麻烦?首先这么做肯定可以,缺点就是我们要开发一个程序,定时扫描这些文件,而且是多个节点,更重要的是,如果我们需要统计每一天的大key,或者每一月的大key,就很不方便,弄到mysql就会很直观。至于需不需要去弄canal+rq去处理,那就看业务了。如果你的告警并不要求实时,定时扫描mysql也不是不行。

那发现大key了怎么处理?其实核心就一种,拆,原本一次操作的,现在分多次,比如coupon:xxxx,我们可以把这个key拆成coupon-1:xxx,coupon-2xxx。

如何高并发的操作redis库存

我们经常会有这种场景,想基于redis去扣减库存或者资源,但是如果这个key是一个热key,比如秒杀商品,假设有10W用户都同时需要扣减,由于key都相同,那大家都命中一个机器了,也就是热key问题,但是这里肯定不能走缓存了,那要怎么办?一个典型解决方案就是库存分片。如果有这种特殊场景,那我的redis就不组cluster集群,分开,比如1,2,3机器独立部署,用jedis分开连接,那即便是同一个key,也不会命中一个机器(因为节点之间没有关系)。我们可以在初始化的时候把库存均匀分散到n个节点,比如100个库存分散到3个节点,先计算下这次需要分配的100/3,每个需要分配33,但是这里有个问题,假如前两个分配成功,第三个失败,就会把压力集中到前两个,所以这里我们可以再次分桶,比如分成10,那每个桶也就是3,那流程就变成1节点3个,2节点3个,3节点3个这种循环下去,即便中间分配失败了,我们可以记录剩下的未同步库存,发到mq继续消费。扣减的时候按照节点,自增取模,那就不会出现资源倾斜问题。当然,细节上可能要注意,比如单节点不够可能需要凑节点一起,我们也可以去合并扣减,用lua去执行,假设有异常我们可以回滚这些库存。

系统运行着,redis集群崩了怎么办?

依旧是两个步骤,1发现,2解决。

如何发现?先从设计上来说,我们可以设置一个规则,比如30s内有10个redis连接错误,我们就认为是集群出问题了,是不是有点熟悉?跟hotkey差不多,首先我们可以把所有对redis的操作都通过一个类操作,比如缓存通过redisCache类,锁通过redisLock,这样我们对redis的操作就有了切面,有了这个就能把这个切面try catch,在catch里判断这个是不是hotkey,当在catch里触发了这个规则,就设置一个hotkey的缓存,比如给60s,那系统就在这个时间内知道redis挂了。

如何解决?既然能发现,那就可以走降级或者限流,限流的话,可以走全局限流和接口限流,在redis挂的时候就去走全局限流,因为几乎每个接口都会走mysql了,没挂的时候就可以针对限流,甚至不配置也行。当然,本地缓存要要有点帮助,比如我们可以用缓存框架caffeine,在redis挂的时候,缓存一些数据,这里可以设置缓存上限,用lru规则,省的把机器打爆。