用户首先输入红包金额和红包个数, 然后天生当前红包唯一标识, 并利用二倍均值算法天生随机金额的红包, 然后将天生的红包存入缓存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 = 10000public 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缓存中,等待用户抢红包
//随机天生全局唯一的红包idredId = getRedId();//首先天生红包金额List<Integer> moneyList = getRedPackage(totalMoney,totalPeopleCount);//放入redisredisClient.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来实现红包的存储和抢红包的操作, 基于二倍均值算法来实现红包金额的随即天生, 在整体功能上还有很多不完善的地方, 可以基于整体框架进行扩展开拓, 实现更加完全的算法