redis+lua实现抢红包

文章标签: 数据库 lua python

版权

IMG公开课火热来袭,游戏开发者绝不能错过的尖端课堂!

你是否因为游戏画面模糊不清、头疼不已却又不知道如何改变?锁定1月21日在线公开课,技术大佬空降直播间与您共同探讨如何借助硬件光线追踪技术,打造移动端影视级画质!

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

需求:用户分享红包到微信群中。

          每一个用户只能领取一个红包。

比如饿了么的红包分享:

614a9f05ac40304122c8289e69a01f38862.jpg

 

 

 

在设计之前,先了解一下redis的list的数据结构:

360afdc0a648d6c7c1af2deb001b32703ec.jpg

1、lpush+lpop=Stack(栈)

2、lpush+rpop=Queue(队列)

3、lpsh+ltrim=Capped Collection(有限集合)

4、lpush+brpop=Message Queue(消息队列)

我们基于lpush+rpop进行红包的设计。

如何设计?

1、当用户点击分享按钮,首先会给该订单生成若干个红包,将该红包push到redis中。

key的设计: hb:pool:{orderId}

value : n个红包的List队列,用来表示n个红包的池子

97cfbefa7eb674a34af8b70f7affd176c45.jpg

2、记录哪些用户已抢过红包,防止重复抢:从红包池中rpop(弹出)一个红包,就需要记录下来该用户领取了红包,这里用了hash结构。

key:hb:rd:{orderId}   例如订单号为12345,key为hb:rd:12345, 有4个用户id分别为 11111,11112,11113,11114

hset  hb:rd:12345  11111  1 

hset  hb:rd:12345  11112  1

hset  hb:rd:12345  11113  1

hset  hb:rd:12345  11114  1

如下图:

39a38d840ec35be545f3ad6b19de8cdb886.jpg
判断用户11111是否领过红包,可以根据 hexists hb:rd:12345 11111 进行判断,如果领过返回1,否则返回0;

 

3、记录用户抢了多少钱:这里采用list结构进行存储。

key:hb:detailList:{orderId}

value : 用户抢到的红包列表。

0223ff46fc9d7797c6955b3409eb7a2812e.jpg

 

抢红包流程:

ce2fbc8c9a3b9659bc9a20819e4070d8ff0.jpg

 

保证用户抢红包整个流程的原子操作就必然要引入lua脚本,redis+Lua可以保证多条命令组合的原子性。

lua脚本:

 
  1. /**

  2. * 脚本调用方式

  3. * Object object = jedisUtils.eval(LuaScript.getHbLua,//lua脚本

  4. 4,//参数个数

  5. RedisKeys.getHbPoolKey(orderId),//对应脚本里的KEYS[1]

  6. RedisKeys.getDetailListKey(orderId),//对应脚本里的KEYS[2]

  7. RedisKeys.getHbRdKey(orderId),//对应脚本里的KEYS[3]

  8. String.valueOf(userId));//对应脚本里的KEYS[4]

  9. *

  10. *

  11. */

  12. public static String getHbLua =

  13. //查询用户是否已抢过红包,如果用户已抢过红包,则直接返回

  14. "if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then\n" +

  15. //如果抢过红包 返回“1”

  16. "return '1';\n" +

  17. "else\n" +

  18. //从红包池取出一个小红包

  19. "local hb = redis.call('rpop', KEYS[1]);\n" +

  20. //判断红包池的红包不为空

  21. "if hb then\n" +

  22. "local x = cjson.decode(hb);\n" +

  23. //将红包信息与用户ID信息绑定,表示该用户已抢到红包 

  24. "x['userId'] = KEYS[4];\n" +

  25. "local re = cjson.encode(x);\n" +

  26. //记录用户已抢过 比如 hset hb:rd:{orderId}  {userId}  1

  27. "redis.call('hset', KEYS[3], KEYS[4], '1');\n" +

  28. //将抢红包的结果详情存入hb:detailList:{orderId}

  29. "redis.call('lpush', KEYS[2], re);\n" +

  30. "return re;\n" +

  31. "else\n" +

  32. //如果红包已被抢完 返回“0”

  33. "return '0';" +

  34. "end\n" +

  35. "end\n" +

  36. "return nil";

生成红包:

 
  1. /**

  2. * 生成红包

  3. * @param orderId

  4. */

  5. public void genRedpack(long orderId,int redPackCount){

  6. Boolean exists = jedisUtils.exists(RedisKeys.getHbPoolKey(orderId));

  7. if (!exists){

  8. //根据业务规则生成红包

  9. int totalAmount = 2000;//总的红包金额20元 也就是2000分

  10. int[] redpacks = doPartitionRedpack(totalAmount,redPackCount);

  11.  
  12. String[] list = new String[redpacks.length];

  13. //将生成的红包push到redis中

  14. for (int i = 0;i < redpacks.length; i++){

  15. JSONObject object = new JSONObject();

  16. object.put("hbId", i); //红包ID

  17. object.put("amount", redpacks[i]); //红包金额,存的是分

  18. list[i] = object.toJSONString();

  19. }

  20. jedisUtils.lpush(RedisKeys.getHbPoolKey(orderId),list);

  21. }

  22. }

  23.  
  24. /**

  25. * 划分红包

  26. * @param totalAmount 红包总额 单位:分

  27. * @param redPackCount 红包数量

  28. * @return

  29. */

  30. private int[] doPartitionRedpack(int totalAmount,int redPackCount) {

  31. Random random = new Random();

  32. int randomMax= totalAmount - redPackCount;//每个人至少分1分钱,2000 - 6 = 1994元 也就是要随机分的钱。

  33. //要把1994 随机分成6份,我们需要向1994 这个数字中插入5个点

  34. // 比如 6 100 500 500 1600 这5个数字把1994分成了6份:6分 94分 400分 0分 1000分 394分

  35. int[] posArray = new int[redPackCount-1];

  36. for (int i = 0;i < posArray.length; i++){

  37. int pos = random.nextInt(randomMax);

  38. posArray[i] = pos;

  39. }

  40. Arrays.sort(posArray);//对数组进行排序

  41. //生成红包

  42. int[] redpacks = new int[redPackCount];

  43. for (int i = 0;i <= posArray.length; i++){

  44. if (i == 0){

  45. redpacks[i] = posArray[i] + 1;//第一份

  46. }else if(i == posArray.length){//如果循环到posArray.length,此时数组已越界1位,randomMax - 该值 + 1分钱=最后一份

  47. redpacks[i] = randomMax - posArray[i-1] + 1;

  48. }else {

  49. redpacks[i] = posArray[i] - posArray[i-1] + 1;

  50. }

  51. }

  52. return redpacks;

  53. }

上面首先给每个红包分1分钱,然后把剩下的钱通过插入(redPackCount-1)个板子,就将剩余的钱分为redPackCount份。每份钱加上1分钱,就是每个红包的大小。分完红包后,将红包push到redis中。

 

抢红包:

 
  1. /**

  2. * 抢红包

  3. * @param userId

  4. * @param orderId

  5. */

  6. public String snatchRedpack(long userId,long orderId){

  7. Object object = jedisUtils.eval(LuaScript.getHbLua,4,

  8. RedisKeys.getHbPoolKey(orderId),//

  9. RedisKeys.getDetailListKey(orderId),//

  10. RedisKeys.getHbRdKey(orderId),String.valueOf(userId));

  11.  
  12. return (String) object;

  13. }

抢红包只需要执行一下上面的lua脚本。

 

测试:

运行生成红包:这里生成5个红包,orderId为111111.

 
  1. @Test

  2. public void genRedpack(){

  3. JedisUtils jedisUtils = new JedisUtils("127.0.0.1", 6379, "123456");

  4. RedpackService redpackService = new RedpackService(jedisUtils);

  5. redpackService.genRedpack(111111,5);

  6. }

运行后,redis中红包池就生成好了

3725a4b272a86e42f1fef1d3fe3cfb8e0fe.jpg

 

运行抢红包:这里模拟了100个人,也就是100个线程。这里用了CyclicBarrier,等到所有的线程都准备好,同时开抢。

 
  1. @Test

  2. public void snatchRedpack() throws InterruptedException {

  3. JedisUtils jedisUtils = new JedisUtils("118.89.196.99", 6379, "123456");

  4. RedpackService redpackService = new RedpackService(jedisUtils);

  5. IdWorker idWorker = new IdWorker();

  6. int N = 100;

  7. CyclicBarrier barrier = new CyclicBarrier(N);

  8.  
  9. for (int i = 0;i<N;i++){

  10. new Thread(()->{

  11. long userId = idWorker.nextId();

  12. try {

  13. System.out.println("用户"+userId+"准备抢红包");

  14. barrier.await();

  15. } catch (InterruptedException e) {

  16. e.printStackTrace();

  17. } catch (BrokenBarrierException e) {

  18. e.printStackTrace();

  19. }

  20. String result = redpackService.snatchRedpack(userId, 111111);

  21. if ("0".equals(result)){

  22. System.out.println("用户" + userId + "未抢到红包,原因:红包已领完");

  23. }else if ("1".equals(result)){

  24. System.out.println("用户" + userId + "未抢到红包,原因:红包已领过");

  25. }else{

  26. System.out.println("用户" + userId + "抢到红包:" + result);

  27. }

  28. },"thread"+i).start();

  29. }

  30. Thread.sleep(Integer.MAX_VALUE);

  31. }

 

运行结果:

 
  1. ......

  2. 用户1078994397959344128准备抢红包

  3. 用户1078994397959344129准备抢红包

  4. 用户1078994397959344130准备抢红包

  5. 用户1078994397959344131准备抢红包

  6. 用户1078994397896429573抢到红包:{"userId":"1078994397896429573","hbId":2,"amount":126}

  7. 用户1078994397888040960抢到红包:{"userId":"1078994397888040960","hbId":1,"amount":526}

  8. 用户1078994397896429572抢到红包:{"userId":"1078994397896429572","hbId":5,"amount":666}

  9. 用户1078994397950955528未抢到红包,原因:红包已领完

  10. 用户1078994397925789696抢到红包:{"userId":"1078994397925789696","hbId":4,"amount":490}

  11. 用户1078994397929984004未抢到红包,原因:红包已领完

  12. 用户1078994397959344128抢到红包:{"userId":"1078994397959344128","hbId":3,"amount":192}

  13. 用户1078994397955149824未抢到红包,原因:红包已领完

  14. 用户1078994397900623875未抢到红包,原因:红包已领完

  15. 用户1078994397929984006未抢到红包,原因:红包已领完

  16. 用户1078994397892235264未抢到红包,原因:红包已领完

  17. .......

结果看出100个线程同时抢,只有5个人抢成功了。

查看redis里的数据:红包池子里的红包已被领完,对应红包用户信息也相应的生成。

8cf2241b83c23e77f1294f35d288bed5adf.jpg

21a4da26dfb1ad36bd743fd64ac9ad344c7.jpg

04232d56dd05bade9cd4bba45c851d35c0e.jpg

详细源码地址:https://github.com/suzhe2018/redis-redpack

测试类:TestRedpackService

转载于:https://my.oschina.net/suzheworld/blog/2995288

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值