Redis+Lua实现高并发下抢红包
一. 实现业务功能点
- 将所有红包全部存储到Redis ( 红包池 )
- 用户抢了多少红包, 记录红包被抢的详情信息;
- 用户只能抢一次红包, 不能重复抢红包
二. 抢红包流程示意
三. 代码部分
-
完整代码已上传至github 点击获取
-
代码运行环境
redis + lua + jdk1.8 + maven
ps: 从Redis 2.6 版本开始,内嵌支持 Lua 环境。通过使用EVAL或EVALSHA命令可以使用 Lua 解释器来执行脚本 -
实现代码
红包池初始化
public class GenRedPack {
/**
* 多线程模拟红包池初始化
*/
public static void genRedPack(){
final JedisUtils jedis = new JedisUtils(Basic.ip, Basic.port, Basic.auth);
// 发枪器
final CountDownLatch latch = new CountDownLatch(Basic.threadCount);
// 清空所有redis缓存
jedis.flushall();
// 每个线程需要初始化多少红包
int per = Basic.redPackCount/Basic.threadCount;
for (int i = 0; i < Basic.threadCount; i++) {
final int page = i;
new Thread( () -> {
latch.countDown();
try {
latch.await();
} catch (InterruptedException e) {
e.getStackTrace();
}
JSONObject object = new JSONObject();
for (int j = page * per; j < (page+1) * per; j++){
object.put("id", "rid_" + j);// 红包id
object.put("money", j);
jedis.lpush(Basic.redPackPoolKey, object.toJSONString());
}
}).start();
}
}
}
模拟并发抢红包
public class GetRedPack {
/**
* 多线程模拟用户抢红包
*/
public static void getRedPack(){
// 发枪器
final CountDownLatch latch = new CountDownLatch(Basic.threadCount);
for (int i = 0; i < Basic.threadCount; i++) {
new Thread(()->{
latch.countDown();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
final JedisUtils jedis = new JedisUtils(Basic.ip, Basic.port, Basic.auth);
while (true){
String userId = UUID.randomUUID().toString();
Object eval = jedis.eval(Basic.getRedPackScript, 4, Basic.redPackPoolKey, Basic.redPackDetailListKey, Basic.userIdRecordKey, userId);
if (eval!=null){
System.out.println("用户id:" + userId + " 抢红包详情为" + eval);
}else{
if (jedis.llen(Basic.redPackPoolKey)==0){
break;
}
}
}
}).start();
}
}
}
常量定义
public interface Basic {
String ip = "192.168.168.14";
int port = 6379;
String auth = "123456";
int redPackCount = 1000;
int threadCount = 20;
String redPackPoolKey = "redPackPoolKey"; //LIST类型来模拟红包池子
String redPackDetailListKey = "redPackDetailListKey";//LIST类型,记录所有用户抢红包的详情
String userIdRecordKey = "userIdRecordKey";//记录已经抢过红包的用户ID,防止重复抢
/*
* KEYS[1]:redPackPool //模拟红包池,用来从红包池抢红包
* KEYS[2]:redPackDetailList://记录所有用户抢红包的详情
* KEYS[3]:userIdRecord//记录所有已经抢过红包的用户ID
* KEYS[4]:userId //模拟抢红包的用户ID
*/
String getRedPackScript =
// 判断是否用户是否已抢过红包 如果已抢过直接返回nil
"if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then \n" +
"\t\treturn nil\n" +
"else\n" +
//从红包池取出一个红包
"\tlocal redPack = redis.call('rpop', KEYS[1]);\n" +
//判断红包是否为空
"\tif redPack then \n" +
"\t\tlocal x = cjson.decode(redPack);\n" +
"\t\tx['userId'] = KEYS[4];\n" +
"\t\tlocal re = cjson.encode(x);\n" +
// 记录用户已抢过userIdRecordKey
"\t\tredis.call('hset', KEYS[3], KEYS[4], '1');\n" +
// 保存用户抢红包详情
"\t\tredis.call('lpush', KEYS[2],re);\n" +
"\t\treturn re;\n" +
"\tend\n" +
"end\t\n" +
"return nil";
}
测试类
public class RedPackTest {
public static void main(String[] args) {
GenRedPack.genRedPack();//初始化红包
GetRedPack.getRedPack();//从红包池抢红包
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lilei.project</groupId>
<artifactId>redis-redpack</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>redis Maven Webapp</name>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastJson</artifactId>
<version>1.2.31</version>
</dependency>
</dependencies>
<build>
<finalName>redis-redpack</finalName>
</build>
</project>
四.致谢
感谢大家阅读这篇文章 如有任何问题 可在评论下留言 笔者会在第一时间回复!!