基于redis实现抢红包功能(包括余额退回处理)
本文将讲述使用redis实现抢红包功能,采用发红包时将红包拆好存储,解决红包金额平衡问题(两种算法)、解决超发现象、将数据通过消息队列传递给另一个服务写入数据库,现阶段不考虑redis宕机的情况。
--新增余额处理。
框架为:springboot2.x,环境搭建、maven配置略。
一个简单的前端页面模拟并发量:
两个功能:一个发红包和一个抢红包
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script type="text/javascript">
function aaa(){var redId=1;var num=document.getElementById("userNum").value;for (var i = 1; i<=num;i++){$.post("http://localhost:8081/rushToBuy/redPaper",{"userId":i,"redId":redId},function (result) {});}
}
function bbb(){var redId=1;var amount=document.getElementById("amount").value*100;var num=document.getElementById("num").value;if (amount<num){alert("每个红包最少一分钱")return;}$.post("http://localhost:8081/rushToBuy/sendRedPaperLine",{"redId":redId,"amount":amount,"num":num},function (result) {});
}
</script>抢红包 <button id="but1" onclick="aaa()">启动</button> |抢红包人数:<input id="userNum"><br>
发红包 <button id="but2" onclick="bbb()">发送</button> |红包金额:<input id="amount"> 红包个数:<input id="num">
</body>
</html>
一、我们先来实现redis功能
实现发红包时将红包拆分存储到redis
使用的算法1:线性切割法。
中心思想:将总金额想象成一条那么长的线段,需要分割成num份,随机num-1次,将每次的随机值映射到该线段上。这样的好处是将随机交给程序,缺点是有小概率造成某个人分配过多(抢红包嘛,只要不是太离谱就可以接受)。
代码思路:获取(0,max)的随机数(防止有人抢到红包但金额为0),使用Treeset进行排序去重(去重是为了防止随机到相同大小,会导致一个人抢到红包但金额为0),然后循环将两个区间内的差值作为红包金额存入redis。
/*算法1 分段切割法 红包算法*/public void sendRedPaperLine(int redId,int amount,int num){Random random=new Random();int m=0,n=amount;Set<Integer> sets = new TreeSet<>();//(m,n)区间for (int i =0;i<(num-1);i++){int randInt = random.nextInt(n-m-1)+(m+1); //将区间控制在(0,max) ,不能出现为0和最大的情况sets.add(randInt);}while (sets.size()<(num-1)){int randInt = random.nextInt(n-m-1)+(m+1);sets.add(randInt);}int cur=0; //做为当前set循环中的上一参数for (Integer i:sets){redisTemplate.opsForList().leftPush("redId:"+redId,(double)(i-cur)/100);cur=i;}redisTemplate.opsForList().leftPush("redId:"+redId,(double)(amount-cur)/100);}
因为算法1使用的redis中的list,所以取走一个少一个,不会存在多人拿到同一个的情况,所以可以忽略超发问题。
那么不需要考虑超发问题,抢红包时的代码就非常简单。
public void RushRedPaper(int redId, int userId) {Double amount = (Double) redisTemplate.opsForList().leftPop("redId:"+redId);if (amount!=null){RedPaperUserInfo userInfo = new RedPaperUserInfo();userInfo.setRedId(redId);userInfo.setCreateTime(LocalDateTime.now());userInfo.setUserId(userId);userInfo.setRushAmount(Double.valueOf(amount));redisTemplate.opsForHash().put("redInfo:"+redId,"user:"+userId,userInfo);System.out.println("用户id:"+userId+"抢到了"+Double.valueOf(amount)+"元");}}
测试一下:100元红包,20个,100人抢。
输出结果:让我们恭喜一个4号倒霉蛋。
用户id:6抢到了2.29元
用户id:4抢到了0.09元
用户id:3抢到了13.56元
用户id:2抢到了7.86元
用户id:1抢到了5.18元
用户id:5抢到了5.36元
用户id:11抢到了2.68元
用户id:7抢到了11.6元
用户id:9抢到了1.16元
用户id:8抢到了6.48元
用户id:10抢到了1.82元
用户id:12抢到了0.47元
用户id:13抢到了5.72元
用户id:16抢到了3.72元
用户id:17抢到了17.56元
用户id:14抢到了5.89元
用户id:18抢到了5.55元
用户id:15抢到了1.18元
用户id:20抢到了0.93元
用户id:19抢到了0.9元
算法2.两倍均值法
中心思想:剩余红包金额为M,剩余人数为N,每次抢到的金额 = 随机区间 (0, M/N *2)
代码实现:抢红包代码不变,只改变发红包时的代码,需要注意的是最后一个人要把剩余的所有金额拿走。
/*算法2 二倍均值法 红包算法*/public void sendRedPaperTwo(int redId,int amount,int num){Random random=new Random();//剩余红包金额为M,剩余人数为N,每次抢到的金额 = 随机区间 (0, M/N *2)for (;num>1;num--){int randInt = random.nextInt(amount/num*2-1)+1; //将区间控制在(0, M/N *2) ,不能出现为0和最大的情况amount -= randInt;redisTemplate.opsForList().leftPush("redId:"+redId,(double)randInt/100);}//最后一个将剩余所有金额拿走redisTemplate.opsForList().leftPush("redId:"+redId,(double)amount/100);}
测试一下:100元红包,20个,100人抢。
输出结果:
用户id:1抢到了1.18元
用户id:2抢到了15.22元
用户id:3抢到了0.02元
用户id:4抢到了2.7元
用户id:7抢到了5.79元
用户id:6抢到了3.02元
用户id:5抢到了12.11元
用户id:8抢到了2.53元
用户id:9抢到了5.79元
用户id:10抢到了9.82元
用户id:11抢到了9.08元
用户id:12抢到了2.2元
用户id:13抢到了9.67元
用户id:14抢到了0.45元
用户id:15抢到了6.45元
用户id:16抢到了2.42元
用户id:17抢到了1.31元
用户id:19抢到了5.1元
用户id:18抢到了2.44元
用户id:20抢到了2.7元
二、通过消息队列异步实现持久化
使用的消息队列为activeMQ,搭建略。
在抢红包的方法中进行修改:
@Autowiredprivate JmsMessagingTemplate jmsMessagingTemplate;@Value("${activemq.name}")private String name;
@Overridepublic void RushRedPaper(int redId, int userId) {Double amount = (Double) redisTemplate.opsForList().leftPop("redId:"+redId);if (amount!=null){RedPaperUserInfo userInfo = new RedPaperUserInfo();userInfo.setRedId(redId);DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String localdataString = LocalDateTime.now().format(dtf);userInfo.setCreateTime(localdataString);userInfo.setUserId(userId);userInfo.setRushAmount(Double.valueOf(amount));redisTemplate.opsForHash().put("redInfo:"+redId,"user:"+userId,userInfo);jmsMessagingTemplate.convertAndSend(name,JSONObject.fromObject(userInfo).toString());System.out.println("用户id:"+userId+"抢到了"+Double.valueOf(amount)+"元");}}
避免包的信任问题,改由json字符串传递。
将消息发送到消息队列后,由监听器异步监听接收消息,写入到mysql进行持久化操作。
@Component
public class RedPaperListener {@Autowiredprivate RedPaperUserInfoDao redPaperUserInfoDao;@Async@JmsListener(destination = "${activemq.name}")public void getRedInfo(Message message){if (message instanceof TextMessage) {TextMessage textMessage = (TextMessage) message;try {String s = textMessage.getText();RedPaperUserInfo redPaperUserInfo=(RedPaperUserInfo) JSONObject.toBean(JSONObject.fromObject(s), RedPaperUserInfo.class);redPaperUserInfoDao.insert(redPaperUserInfo);} catch (Exception e) {e.printStackTrace();}}}
}
三、红包余额退回
本来没写这块内容,后来发现这个余额退回也不是那么直白,毕竟设置了过期时间的key一失效便拿不到红包的信息,在网上找了一些解决方案,比如quartz框架,但这个框架暂时还没学习,后面可能会有补充。于是通过逻辑去解决这个问题。
过期退回思路:在拆红包时向redis存两条数据,一条队列存小红包的信息,一条字符串存该红包的过期时间。当红包过期触发监听事件,读取队列里红包的信息,使用完删除(既然使用了队列,只需要把里面的内容都取走即可)。避免监听不及时,在领取红包的内容也增加了判断。
代码实现:
先配置redis,打开监听。放开这一条notify-keyspace-events Ex
重启redis,在项目里增加配置。
@Configuration
public class RedisListenerConfig {@BeanRedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);return container;}
}
修改之前的发红包逻辑,以二倍均值法为例。
仅仅增加了一句代码:redisTemplate.opsForValue().set("copyredId:"+redId,"0",30, TimeUnit.SECONDS);
public void sendRedPaperTwo(int redId,int amount,int num){//每次发红包将两条数据放入redis中,一条存数据,一条存过期时间redisTemplate.opsForValue().set("copyredId:"+redId,"0",30, TimeUnit.SECONDS);Random random=new Random();//剩余红包金额为M,剩余人数为N,每次抢到的金额 = 随机区间 (0, M/N *2)for (;num>1;num--){int randInt = random.nextInt(amount/num*2-1)+1; //将区间控制在(0, M/N *2) ,不能出现为0和最大的情况amount -= randInt;redisTemplate.opsForList().leftPush("redId:"+redId,(double)randInt/100);}//最后一个将剩余所有金额拿走redisTemplate.opsForList().leftPush("redId:"+redId,(double)amount/100);}
修改抢红包代码。增加了红包过期判断,当用户点击红包,如果过期则去返还这个红包余额(为了防止监听器来不及处理)。
public void RushRedPaper(int redId, int userId) {Object copyred= redisTemplate.opsForValue().get("copyredId:"+redId);//为空则已过期,当用户再次点击时清空红包if (copyred==null){Double stockMoney = 0.0;while (true){Double obj = (Double)redisTemplate.opsForList().leftPop("redId:"+redId);if (obj==null){System.out.println("该红包已过期");break;}stockMoney+=obj;}if (Math.abs(stockMoney) > 0.000001){System.out.println("还有"+stockMoney+"元未领取,返回给用户");}return;}Double amount = (Double) redisTemplate.opsForList().leftPop("redId:"+redId);if (amount!=null){RedPaperUserInfo userInfo = new RedPaperUserInfo();userInfo.setRedId(redId);DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String localdataString = LocalDateTime.now().format(dtf);userInfo.setCreateTime(localdataString);userInfo.setUserId(userId);userInfo.setRushAmount(Double.valueOf(amount));redisTemplate.opsForHash().put("redInfo:"+redId,"user:"+userId,userInfo);jmsMessagingTemplate.convertAndSend(name,JSONObject.fromObject(userInfo).toString());System.out.println("用户id:"+userId+"抢到了"+Double.valueOf(amount)+"元");}}
最后设置监听器,用来通知红包过期,返还红包余额。
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {@Autowiredprivate RedisTemplate redisTemplate;public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}/*** 针对redis数据失效事件,进行数据处理* @param message* @param pattern*/@Async@Overridepublic void onMessage(Message message, byte[] pattern) {//监听失效key,将余额返回给用户String expiredCopyKey = message.toString();String expiredKey=expiredCopyKey.substring(4);Double stockMoney=0.0;while (true){Double obj = (Double) redisTemplate.opsForList().leftPop(expiredKey);if (obj==null){break;}stockMoney+=obj;}//有余额if (Math.abs(stockMoney)>0.000001){System.out.println("还有"+stockMoney+"元未领取,返回给用户");}}
}
基于redis实现抢红包功能(包括余额退回处理)相关推荐
- redis实现抢红包功能
转自:http://blog.csdn.net/hengyunabc/article/details/19433779/ 抢红包的需求分析 抢红包的场景有点像秒杀,但是要比秒杀简单点. 因为秒杀通常要 ...
- java基于Redis实现排行榜功能-附源码
java基于Redis Zset实现排行榜功能 前言 做之前要思考的问题? Zset怎么存储需要的多个字段? 话不多说先上效果图 数据存储格式 代码 源码下载 闲暇之余,整理了一下之前利用Redis ...
- 【Redis学习02】基于session和基于redis实现登录功能
文章目录 前言 1. 基于session实现短信登录功能 1.1 发送短信验证码 1.2 短信验证登录 1.3 登录校验功能 2. 集群的Session问题 3. 基于redis实现共享session ...
- java抢红包_Java 用Redis 实现抢红包功能
红包功能描述 红包功能现在已经很普遍了,主要功能发红包和抢红包. 一发一抢或一发多抢,主要难点还是一发多择抢高并发场景. 红包功能分析 首先我们假设会员体系和金额账号体系都已经存在,我们本功能只是一个 ...
- java发红包redis_基于Redis实现类似微信抢红包
一个简单的基于Redis实现抢红包功能,分为两个步骤: 1.拆分红包 /** * 拆红包 1.红包金额要被全部拆分完 2.红包金额不能差的太离谱 * * @param total * @param c ...
- 同程旅行王晓波:同程凤凰缓存系统在基于 Redis 方面的设计与实践(上篇)
王晓波 同程旅行机票事业群 CTO 读完需要 12 分钟 速读仅需 4 分钟 本章和大家分享一下同程凤凰缓存系统在基于 Redis 方面的设计与实践.在本章中除了会列举我们工作过程中遇到各种问题和误区 ...
- 案例:同程凤凰缓存系统基于Redis的设计与实践。
本文和大家分享一下同程凤凰缓存系统在基于Redis方面的设计与实践.在本文中除了会列举我们工作过程中遇到各种问题和误区外,还会给出我们相应的解决办法,希望能够抛砖引玉为大家带来一定的启示. 同程凤凰缓 ...
- 【一图流思维导图】Redis设计与实现 包括( 数据类型-数据结构) 及应用场景(登录次数校验,在线人数统计,分布式session,redis分页,判断重复注册,社交领域共同喜好,排行榜 ,topN)
参照 Redis详解(一)------ redis的简介与安装 Redis详解(二)------ redis的配置文件介绍 Redis详解(三)------ redis的六大数据类型详细用法 Redi ...
- 基于Redis实现微信抢红包功能
使用Redis和Lua的原子性实现抢红包功能 安装Lua(可选) 编写lua脚本 lua脚本学习可以参考 [https://www.runoob.com/lua/lua-basic-syntax.ht ...
最新文章
- JSP第二次作业_6小题
- matlab版本的cnn代码,Deep Learning学习 之 CNN代码解析(MATLAB)
- 一些与oracle相关的关于查询锁的信息
- 你会因为什么原因而离职
- 『数据库』数据库编程(概念性的东西,应用一般,甚至有点过时,用来考试)
- Dex文件格式扫描器:特征API的检测和扫描-小工具一枚(转载)
- 用FlyMcu和USB转TTL给stm32中烧录程序(stm32C8/C6)
- java基于SSM的宠物医院信息管理系统
- 我从零开始学黑莓开发的过程
- 在计算机硬件系统中 cache是,CPU中Cache是什么
- java基础[多态基础,多态数组,多态参数方法]
- (附源码)springboot学生宿舍管理系统 毕业设计 211955
- Python监控股价并通过微信提醒
- 案例分析: 全球顶尖的物流公司Schenker采用SAPERION
- c语言打出的王字图形图形,C语言编程宝典(王大刚) 3 关键字和标识符
- js base64解码JWT失败:VM273:1 Uncaught DOMException: Failed to execute 'atob' on 'Window': The string to
- Vue 6. 列表渲染
- zigbee3.0 BDB 介绍(一)
- HTMLCSS 高级表格 合并单元格
- 备份介质介绍之一:磁带
热门文章
- PS学习笔记------运用脚本及自动化批量处理
- Hbase2.4.1集群安装:HMaster自动挂掉问题终于解决了
- 中国科学家或揭开生物第六感之谜
- 汇编语言里 eax, ebx, ecx, edx, esi, edi, ebp, esp,eip
- Oracle官文,明确20c不能用于生产环境
- 2021年彬州范公中学高考成绩查询,彬州市拟奖励81人名单公布!快看有你认识的吗?...
- habor-deploy docker https 私有仓库搭建
- DQN_Continuous_Action
- python自学软件-学习python用什么软件
- imx6ull 14*14 平台使用mfgtools下载代码出现UTP Waiting for device to appear错误