Redis在秒杀功能的实践

#Redis在资源秒杀场景中的使用git

业务概述

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

业务流程

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

Redis节点说明

  • 通用redis:用于SSO作统一登陆、以及非秒杀功能使用。
  • 缓存redis:用于存储购买者热身数据,抢购这查询信息的缓存。
  • 核心redis:负责资源库存剩余数量,秒杀秒杀资源抢占等核心业务实现,须要关闭redis的lru策略,程序控制内存中key的淘汰

Redis使用详情

  • 缓存redis-数据热身 流程①② (牛奶供给降级策略)github

    • 关键伪代码redis

      cacheRedis.setex(key,EXPIRE_TIME_7D,info);
      复制代码
    • 秒杀qps峰值在1w左右,可是超过60%的qps请求的是查询列表方法,因此须要增长可购买秒杀资源缓存。spring

    • 关键伪代码数据库

      生成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-秒杀资源秒杀 流程③bash

    • 每一个秒杀资源拥有本身的队列,完成多队列,低队列长度的秒杀。服务器

    • 关键伪代码架构

      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)

cache redis

  • 核心redis

cache redis

Redis使用总结

  • 使用一主一从,rdb为备份策略的redis架构,QPS在8W如下是没有任何问题的(第一期秒杀资源秒杀,在没有作redis多库负载切分,以及没有优化使用的状况下到了5W的QPS,没有出现超时连接,或者获取不到链接池资源的状况,也和没有使用事务以及采用的低复杂度命令实现有关
  • 像列表页缓存,切勿为了减小redis的开销,将数据库每一列放到redis中,在redis中查询汇总,例如:每一个秒杀资源都放在redis中,秒杀资源页须要10次redis连接才能完成一次列表页的组装。这样作会将服务器的qps成几何倍数的扩大到与redis的qps中形成系统获取不到redis链接资源
  • 若是redis只用做缓存数据,且追求极限性能,master能够关闭内存快照和日志记录,有slave节点完成。
相关文章
相关标签/搜索