玖叶教程网

前端编程开发入门

Redis在秒杀功能的实践(使用redis解决秒杀场景)

业务概述

  1. 秒杀资源:以周为时长的资源。
  2. 每个页面都会有秒杀资源,数量在1~8份,以随机形式展示给访客。
  3. 每周秒杀资源价格由数据部门计算定价,没有有一个时间点进行抢购,如:每周三10点。购买者抢购数量可以是 秒杀资源剩余资源中的任意数量。
  4. 购买者是否有抢购秒杀资源的权限,由用户接口信息,账户信息,等权限接口等决定。
  5. 购买者支付方式使用界面支付,系统生成购买者抢购支付加密信息,跳转支付页面,再支付界面后,异步回掉确定是否购买成功,如果购买失败需要及时退回秒杀资源库存,以供他人报买。

业务流程

时间轴 业务 流程节点备注 周一 生成资源数据 流程① 周三10:00前 check资源数据 流程② 周三10:00 购买者秒杀秒杀资源 流程③ 周三10:00后 购买者退款 流程④ 周日 本周资源抢购结束,生成外网展示信息 流程⑤ Redis节点说明

  • 通用redis:用于SSO做统一登录、以及非秒杀功能使用。
  • 缓存redis:用于存储购买者热身数据,抢购这查询信息的缓存。

核心redis:负责资源库存剩余数量,秒杀秒杀资源抢占等核心业务实现,需要关闭redis的lru策略,程序控制内存中key的淘汰

Redis使用详情

  • 缓存redis-数据热身 流程①② (牛奶供给降级策略)
  • 关键伪代码
cacheRedis.setex(key,EXPIRE_TIME_7D,info);

秒杀qps峰值在1w左右,但是超过60%的qps请求的是查询列表方法,所以需要增加可购买秒杀资源缓存。

  • 关键伪代码
生成rediskey, objects包括ucid、用户输入入参、分页信息等等
public static String builder(String prefix, Object... objects) {
 String input = JSONObject.toJSONString(Arrays.asList(objects));
 String output = Util.md5_16(input);
 return prefix+output;
}
cacheRedis.setex(key,EXPIRE_TIME_2S,info);

设计优点:借鉴spring-data-redis将入参通用为objects...序列化,然后将JsonString Md5压缩为16位,这里主要由于在秒杀开始时,redis数据会出现大量缓存列表数据,redis储存100w个value长度为32位,key长度为16位的数据时,需要使用个130MB内存,如果key的长度为32位时需要160MB左右的内存,所以压缩key的长度在这种场景很有必要。

  • 核心redis-秒杀资源秒杀 流程③
  • 每个秒杀资源拥有自己的队列,完成多队列,低队列长度的秒杀。
  • 关键伪代码
String key = PURCHASING_PRODUCT + productId;
Long count = coreRedis.llen(key);
判断count是否大于库存
判断count+用户欲购买秒杀资源数量(share)是否大于库存
String[] values = (uuid+uid) * share; 
if (inventory - coreRedis.lpush(key, values)) < 0) {
 coreRedis.lrem(key, share, values);
}
例如:id:1 秒杀资源有3份流量的库存, 
当llen时发现秒杀资源在redis中没有数据,
购买者20xxxxx1想买此资源3份流量,
这时lpush后发现超卖,lrem退回库存。
redis 127.0.0.1:6379> lrange XX_PRODUCT_1 0 -1
1) "jali7xz20xxxxx1"
2) "3whsh6b20xxxxx2"
3) "3whsh6b20xxxxx2"
4) "3whsh6b20xxxxx2"

设计优点:核心命令llen、lpush的时间复杂度都是O(1)、lrem时间复杂度是O(N),官方lrem给出的复杂度是O(N)但我觉得在这种使用场景下lrem的复杂度应该无极限接近于O(count),但是将补偿操作封装为原子性,且支持多次、幂等执行。曾经也想过用一些getset,setnx,pipelin、将库存缓存到队列然后pop、事务等实现秒杀。但是性能、或者鲁棒性在这种场景下都没有以上设计表现出色,而且这种方式在支付失败,或者查询到未支付的情况下立刻幂等lrem秒杀资源队列的订单,其他有资格购买的购买者可以继续购买。

Redis线上使用情况

  • 缓存redis (图片来源地址:github)
  • 核心redis

Redis使用总结

使用一主一从,rdb为备份策略的redis架构,QPS在8W以下是没有任何问题的(第一期秒杀资源秒杀,在没有做redis多库负载切分,以及没有优化使用的情况下到了5W的QPS,没有出现超时链接,或者获取不到连接池资源的情况,也和没有使用事务以及采用的低复杂度命令实现有关

像列表页缓存,切勿为了减少redis的开销,将数据库每一列放到redis中,在redis中查询汇总,例如:每个秒杀资源都放在redis中,秒杀资源页需要10次redis链接才能完成一次列表页的组装。这样做会将服务器的qps成几何倍数的扩大到与redis的qps中造成系统获取不到redis连接资源

如果redis只用作缓存数据,且追求极限性能,master可以关闭内存快照和日志记录,有slave节点完成。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言