首页 > 数据库 >基于Redis实现基本抢红包算法

基于Redis实现基本抢红包算法

时间:2024-04-17 10:36:36浏览次数:32  
标签:红包 Redis 抢红包 金额 算法 money redId

简介:

[key, value]的缓存数据库, Redis官方性能描述非常高, 所以面对高并发场景, 使用Redis来克服高并发压力是一个不错的手段, 本文主要基于Redis来实现基本的抢红包系统设计.

发红包模块:

1:发红包模块流程图如下:

 


 

用户首先输入红包金额和红包个数, 然后生成当前红包唯一标识, 并使用二倍均值算法生成随机金额的红包, 然后将生成的红包存入缓存Redis数据库中, Redis数据库中会保存当前剩余的红包数量和每个红包的金额, 由于Redis数据库是作为临时存储的地方, 所以发红包记录需要持久化存储在数据库中, 这里为加快系统响应, 使用异步的方式, 将红包金额纪录存储入Mysql数据库中, 以上就是发红包模块的简要系统设计.

2:随机生成红包金额

对于抢红包来说, 生成红包金额是非常关键的, 这里有许多生成随机数方法, 在本文中介绍一种使用较多的二倍均值算法来随机生成红包金额.对于抢红包来说, 如果发送一个金额为J的红包, 那么对与抢红包的N个人来说, 公平的概率是: 每个人抢到J / N 的金额的概率是相同的, 例如100元红包发给10个人,那么最公平的策略是使每个人抢到10元的概率相同, 二倍均值算法就是基于上面这个概率策略. 二倍均值算法流程如下: 首先设置红包金额为J, 抢红包人数为N, 接下来计算随机数区间上U = J / N * 2, 得到随机数区间(0,U), 从而在这个区间里生成第一个随机数金额M, 接下来继续生成第二个随机金额. 首先更新总红包金额为J-M,总抢红包人数为N-1, 然后生成第二个随机金额区间(0, (J-M) / (N-1) *2) , 从这个区间里面生成第二个随机金额M2, 继续迭代, 直到生成最后一个红包金额, 下图是二倍均值算法的流程

 

 


 

二倍均值算法案例: 红包总金额100元, 总计10个人

计算第一个随机金额区间: 100/10X2 = 20, 第一个随机金额的区间是(0,20 ),区间均值为10

假设第一个人抢到10元,剩余金额是90 元

计算第二个随机金额区间: 90/9X2 = 20, 第一个随机金额的区间是(0,20 ),区间均值为10

假设第二个人抢到10元,剩余金额是80 元 计算第三个随机金额区间: 80/8X2 = 20, 第一个随机金额的区间是(0,20 ),区间均值为10

...............

所以使用二倍均值算法能够在不论谁先抢的情况下, 都能公平保证每个人抢到平均金额的概率是相等的, 二倍均值算法生成红包金额的代码如下:

//这里输入的totalMoney单位是分,例如100元,totalMoney = 10000
public List<Integer> getRedPackage(Integer totalMoney,Integer totalPeopleCount) {
    List<Integer> moneyList = new ArrayList<>();
    //暂存剩余金额为红包的总金额
    Integer restMoney = totalMoney;
    //暂存剩余的总人数-初始化时即为指定的总人数
    Integer restPeopleCount = totalPeopleCount;    
    //随机数对象
    Random random = new Random();
    //开始循环迭代生成红包
    for (int i =0;i< totalPeopleNum-1;i++){
       //加1是为了至少抢到1分钱
       int money = random.nextInt (restMoney / restPeopleCount * 2) + 1;
       restMoney -= money;
       restPeopleCount--;
       moneyList.add(money);
    }
    //添加最后的一个红包金额
    amountList.add(restAmount);
    return amountList;
}

3: 红包存储

为了应对用户高并发的请求, 也就是需要频繁读取红包金额和数量, 所以将红包金额和数量存储在Mysql中是不行的, 所以只能借助基于内存的Redis数据库来支持高并发的读取操作.Redis中有5种基本的数据结构分别是:String, List, Set, Sorted Set, Map这五种, 红包金额数量是一个List集合, 所以使用List来存储最为合适,在发红包时, 我们先用二倍均值算法随机生成一定数量的红包金额, 然后将红包金额和红包数量存入Redis缓存中,等待用户抢红包

//随机生成全局唯一的红包id
redId = getRedId();
//首先生成红包金额
List<Integer> moneyList = getRedPackage(totalMoney,totalPeopleCount);
//放入redis
redisClient.lpush(redId, moneyList);
//redis中记录红包个数
redisClient.set(redId, moneyList.size());
//异步存储发红包记录到Mysql数据库
//将红包id返回
return redId;

抢红包模块:

1:抢红包模块流程图如下:

 


 

首先判断用户是否已经抢过红包了, 是否还有剩余的红包, 如果抢过或者剩余红包数量小于等于0, 则代表红包已经被抢完了, 直接结束用户本次抢红包流程. 如果还有剩余的红包数量, 则从Redis缓存列表中弹出一个红包金额, 然后将剩余红包数量减1, 同时异步将用户抢红包记录存入Mysql数据库, 最后将抢到的红包金额返回给用户, 结束本次抢红包流程

2:首先判断是否已经抢过红包

通过在Redis中以用户ID构建一个唯一Key来判断是否抢过红包, Key的构建规则是:业务前缀+红包id+用户id

redMoney = redisClient.get("rob" + redId + useId)
//如果不为空,则说明已经抢过了,直接返回抢过的红包金额
if (redMoney != null) {
    return redMoney
}

3:判断是否还有红包

通过在Redis中以红包id记录一个数量来判断是否还有红包, key的构建规则是:业务前缀+红包id

totalNum = redisClient.get("totalNum" + redId)
//如果为空或者小于等于0则代表没有了
if (totalNum == null || totalNum <= 0) {
    return null
}

4:弹出一个红包金额

因为我们是把红包金额存储到Redis的List列表中的, 所以直接使用列表的Pop操作就行了

money = redisClient.rpop(redId)
//如果不为空,则说明抢到了
if (money != null) {
    ....
    红包个数减1
    存储抢红包记录
    设置该用户已经抢过红包
    ....
    //返回抢到的金额
    return money
}
//没抢到
return null

5:减少红包个数

红包总数是以一个[key, value] 键值对存储在Redis中的, 所以这里使用Redis的DECR命令就行了

money = redisClient.rpop(redId)
//如果不为空,则说明抢到了
if (money != null) {
    //红包个数减1
    redisClient.decr(redId)
    ....
    存储抢红包记录
    设置该用户已经抢过红包
    ....
    //返回抢到的金额
    return money
}
//没抢到
return null

6:异步记录抢红包记录

采用异步的方式将记录存入Mysql数据库, 异步的方式可以采用消息队列或者多线程的方式来实现

money = redisClient.rpop(redId)
//如果不为空,则说明抢到了
if (money != null) {
    //红包个数减1
    redisClient.decr(redId)
    //异步存储抢红包记录
    这里可以使用mq或者多线程的方式来实现
    ....
    设置该用户已经抢过红包
    ....
    //返回抢到的金额
    return money
}
//没抢到
return null

7:设置该用户已经抢过红包

money = redisClient.rpop(redId)
//如果不为空,则说明抢到了
if (money != null) {
    //红包个数减1
    redisClient.decr(redId)
    //异步存储抢红包记录
    这里可以使用mq或者多线程的方式来实现
    //设置该用户已经抢过红包
    redisClient.set("rob" + redId + useId, money)
    //返回抢到的金额
    return money
}
//没抢到
return null

8: 整体的伪代码逻辑如下:

redMoney = redisClient.get("rob" + redId + useId)
//如果不为空,则说明已经抢过了,直接返回抢过的红包金额
if (redMoney != null) {
    return redMoney
}
totalNum = redisClient.get("totalNum" + redId)
//如果红包总数小于0, 则代表已经抢完了, 直接返回空
if (totalNum == null || totalNum <= 0) {
    return null
}
money = redisClient.rpop(redId)
//如果不为空,则说明抢到了
if (money != null) {
    //红包个数减1
    redisClient.decr(redId)
    //异步存储抢红包记录
    这里可以使用mq或者多线程的方式来实现
    //设置该用户已经抢过红包
    redisClient.set("rob" + redId + useId, money)
    //返回抢到的金额
    return money
} 
//没抢到
return null

9:分布式锁

这里涉及到了同一个用户多次高并发来抢红包的情况, 并且代码逻辑中包含了下面这种逻辑: 判断条件成立然后进行业务操作,最后设置条件. 这种业务逻辑如果不防止并发的话, 就会产生重复操作, 所以需要使用锁来限制每一个用的访问频率, 加锁的方式是使用分布式锁, 这是因为我们抢红包服务不可能只在一台服务器上部署, 同时基于Redis也能很容易的实现分布式锁, 使用Redis命令setNx命令就可以实现简单分布式锁

redMoney = redisClient.get("rob" + redId + useId)
//如果不为空,则说明已经抢过了,直接返回抢过的红包金额
if (redMoney != null) {
    return redMoney
}
totalNum = redisClient.get("totalNum" + redId)
//如果红包总数小于0, 则代表已经抢完了, 直接返回空
if (totalNum == null || totalNum <= 0) {
    return null
}
//加分布式锁
lockResut = redisClient.setNx(useId,redId,timeOut);
//加锁失败,直接返回
if(!lockResult){
    return;
}
try{
    money = redisClient.rpop(redId)
    //如果不为空,则说明抢到了
    if (money != null) {
        //红包个数减1
        redisClient.decr(redId)
        //异步存储抢红包记录
        这里可以使用mq或者多线程的方式来实现
        //设置该用户已经抢过红包
        redisClient.set("rob" + redId + useId, money)
        //返回抢到的金额
        return money
    }     
} finally {
    //删除锁
    redisClient.del(useId)
}
//没抢到
return null

总结

以上就是完整的抢红包伪代码流程, 可以基本实现发红包以及抢红包功能, 该方法基于Redis来实现红包的存储和抢红包的操作, 基于二倍均值算法来实现红包金额的随即生成, 在整体功能上还有很多不完善的地方, 可以基于整体框架进行扩展开发, 实现更加完整的算法

标签:红包,Redis,抢红包,金额,算法,money,redId
From: https://www.cnblogs.com/Jcloud/p/18139998

相关文章

  • day14_我的Java学习笔记 (常用API、Lambda、常见算法)
    1.常用API1.1Date类【案例】:计算出当前时间往后走1小时121秒之后的时间是多少。1.2SimpleDateFormat【练习】:秒杀活动1.3Calendar2.JDK8新增日期类2.1概述、LocalTime/LocalDate/LocalDateTime2.2Ins......
  • 基于yolov2深度学习网络的螺丝螺母识别算法matlab仿真
    1.算法运行效果图预览 2.算法运行软件版本matlab2022a 3.算法理论概述      在工业自动化和质量控制领域,准确且高效的螺丝螺母识别至关重要。深度学习方法,特别是基于卷积神经网络(CNN)的目标检测技术,因其卓越的特征提取能力,成为解决此类问题的有效手段。YOLOv2......
  • 新a_bogus算法还原大赏
    **新a_bogus算法还原大赏**记得加我我们的学习群哦:`529528142`1、本次新ab是继承之前旧ab的过程,新ab分为上半部分和下半部分,上半部分是之前的旧ab,下半部分我们开始讲解。![请添加图片描述](https://img-blog.csdnimg.cn/direct/726d949cf2e546618f0a38fb989ee9b0.png)```jss.ap......
  • winform车牌识别源码(纯算法)
    车牌识别,本是图像领域中,非常成熟的一个应用,也是目前无处不在的停车场自动收费设备的技术基础。前言本文将使用c#语言,winform框架开发一个车牌识别系统M=,不借助任何框架,纯算法。效果  使用工具VisualStudio2019思路打开要识别的车牌对车牌进行去雾操作接着进......
  • ResNet50算法
    ResNet(Residualnet)是残差网络的通用概念,而ResNet50是一个具体的网络结构,其由50个卷积层组成。ResNet50是指包含了50个卷积层(包括卷积层、池化层、全连接层等)的ResNet网络。ResNet50是基于ImageNet数据集上的训练所提出的一个具体网络结构。ResNet核心:在最终输出中,除了......
  • 一种算法
          #!usr/bin/envpython#-*-coding:utf-8-*-"""@author:Suyue@file:speedeeinsert.py@time:2024/04/16@desc:"""#importnumpyasnpimportpandasaspddf1=pd.read_excel('G:/尺度速度.xls')file_path......
  • redis自学(35)搭建分片集群
    分片集群结构主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:l 海量数据存储问题l 高并发写的问题使用分片集群可以解决上述问题,分片集群特征:l 集群中有多个master,每个master保存不同数据,因此能存多少取决于master节点的数量,解决了海量数据存储的......
  • 每个程序员都应该知道的 40 个算法(二)
    原文:zh.annas-archive.org/md5/8ddea683d78e7bd756401ec665273969译者:飞龙协议:CCBY-NC-SA4.0第五章:图算法有一类计算问题最好以图的术语来表示。这类问题可以使用一类称为图算法的算法来解决。例如,图算法可以用于在数据的图形表示中高效搜索值。为了高效工作,这些算法首先......
  • Redis
      关联知识点(1)关系型数据库、非关系型数据库(2)BSD协议(3)发布/订阅模式(4)Hash散列(5) Lua脚本(6)事件驱动模型参考文献(1)官网地址:https://redis.io/(2)源码地址:https://github.com/redis/redis(3)Redis在线测试:http://try.redis.io/(4)Redis命令参考:http:......
  • 密码学中的RSA算法与椭圆曲线算法
    PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、密码学、联邦学习、同态加密等隐私计算领域的技术和内容。在数字安全领域,加密算法扮演着至关重要的角色。它们确保了信息的机密性、完整性和不可否认性。RSA算法和椭圆曲线算法(ECC)是当前最广泛使用的两......