版权
你是否因为游戏画面模糊不清、头疼不已却又不知道如何改变?锁定1月21日在线公开课,技术大佬空降直播间与您共同探讨如何借助硬件光线追踪技术,打造移动端影视级画质!
需求:用户分享红包到微信群中。
每一个用户只能领取一个红包。
比如饿了么的红包分享:
在设计之前,先了解一下redis的list的数据结构:
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个红包的池子
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
如下图:
判断用户11111是否领过红包,可以根据 hexists hb:rd:12345 11111 进行判断,如果领过返回1,否则返回0;
3、记录用户抢了多少钱:这里采用list结构进行存储。
key:hb:detailList:{orderId}
value : 用户抢到的红包列表。
抢红包流程:
保证用户抢红包整个流程的原子操作就必然要引入lua脚本,redis+Lua可以保证多条命令组合的原子性。
lua脚本:
-
/**
-
* 脚本调用方式
-
* Object object = jedisUtils.eval(LuaScript.getHbLua,//lua脚本
-
4,//参数个数
-
RedisKeys.getHbPoolKey(orderId),//对应脚本里的KEYS[1]
-
RedisKeys.getDetailListKey(orderId),//对应脚本里的KEYS[2]
-
RedisKeys.getHbRdKey(orderId),//对应脚本里的KEYS[3]
-
String.valueOf(userId));//对应脚本里的KEYS[4]
-
*
-
*
-
*/
-
public static String getHbLua =
-
//查询用户是否已抢过红包,如果用户已抢过红包,则直接返回
-
"if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then\n" +
-
//如果抢过红包 返回“1”
-
"return '1';\n" +
-
"else\n" +
-
//从红包池取出一个小红包
-
"local hb = redis.call('rpop', KEYS[1]);\n" +
-
//判断红包池的红包不为空
-
"if hb then\n" +
-
"local x = cjson.decode(hb);\n" +
-
//将红包信息与用户ID信息绑定,表示该用户已抢到红包
-
"x['userId'] = KEYS[4];\n" +
-
"local re = cjson.encode(x);\n" +
-
//记录用户已抢过 比如 hset hb:rd:{orderId} {userId} 1
-
"redis.call('hset', KEYS[3], KEYS[4], '1');\n" +
-
//将抢红包的结果详情存入hb:detailList:{orderId}
-
"redis.call('lpush', KEYS[2], re);\n" +
-
"return re;\n" +
-
"else\n" +
-
//如果红包已被抢完 返回“0”
-
"return '0';" +
-
"end\n" +
-
"end\n" +
-
"return nil";
生成红包:
-
/**
-
* 生成红包
-
* @param orderId
-
*/
-
public void genRedpack(long orderId,int redPackCount){
-
Boolean exists = jedisUtils.exists(RedisKeys.getHbPoolKey(orderId));
-
if (!exists){
-
//根据业务规则生成红包
-
int totalAmount = 2000;//总的红包金额20元 也就是2000分
-
int[] redpacks = doPartitionRedpack(totalAmount,redPackCount);
-
String[] list = new String[redpacks.length];
-
//将生成的红包push到redis中
-
for (int i = 0;i < redpacks.length; i++){
-
JSONObject object = new JSONObject();
-
object.put("hbId", i); //红包ID
-
object.put("amount", redpacks[i]); //红包金额,存的是分
-
list[i] = object.toJSONString();
-
}
-
jedisUtils.lpush(RedisKeys.getHbPoolKey(orderId),list);
-
}
-
}
-
/**
-
* 划分红包
-
* @param totalAmount 红包总额 单位:分
-
* @param redPackCount 红包数量
-
* @return
-
*/
-
private int[] doPartitionRedpack(int totalAmount,int redPackCount) {
-
Random random = new Random();
-
int randomMax= totalAmount - redPackCount;//每个人至少分1分钱,2000 - 6 = 1994元 也就是要随机分的钱。
-
//要把1994 随机分成6份,我们需要向1994 这个数字中插入5个点
-
// 比如 6 100 500 500 1600 这5个数字把1994分成了6份:6分 94分 400分 0分 1000分 394分
-
int[] posArray = new int[redPackCount-1];
-
for (int i = 0;i < posArray.length; i++){
-
int pos = random.nextInt(randomMax);
-
posArray[i] = pos;
-
}
-
Arrays.sort(posArray);//对数组进行排序
-
//生成红包
-
int[] redpacks = new int[redPackCount];
-
for (int i = 0;i <= posArray.length; i++){
-
if (i == 0){
-
redpacks[i] = posArray[i] + 1;//第一份
-
}else if(i == posArray.length){//如果循环到posArray.length,此时数组已越界1位,randomMax - 该值 + 1分钱=最后一份
-
redpacks[i] = randomMax - posArray[i-1] + 1;
-
}else {
-
redpacks[i] = posArray[i] - posArray[i-1] + 1;
-
}
-
}
-
return redpacks;
-
}
上面首先给每个红包分1分钱,然后把剩下的钱通过插入(redPackCount-1)个板子,就将剩余的钱分为redPackCount份。每份钱加上1分钱,就是每个红包的大小。分完红包后,将红包push到redis中。
抢红包:
-
/**
-
* 抢红包
-
* @param userId
-
* @param orderId
-
*/
-
public String snatchRedpack(long userId,long orderId){
-
Object object = jedisUtils.eval(LuaScript.getHbLua,4,
-
RedisKeys.getHbPoolKey(orderId),//
-
RedisKeys.getDetailListKey(orderId),//
-
RedisKeys.getHbRdKey(orderId),String.valueOf(userId));
-
return (String) object;
-
}
抢红包只需要执行一下上面的lua脚本。
测试:
运行生成红包:这里生成5个红包,orderId为111111.
-
@Test
-
public void genRedpack(){
-
JedisUtils jedisUtils = new JedisUtils("127.0.0.1", 6379, "123456");
-
RedpackService redpackService = new RedpackService(jedisUtils);
-
redpackService.genRedpack(111111,5);
-
}
运行后,redis中红包池就生成好了
运行抢红包:这里模拟了100个人,也就是100个线程。这里用了CyclicBarrier,等到所有的线程都准备好,同时开抢。
-
@Test
-
public void snatchRedpack() throws InterruptedException {
-
JedisUtils jedisUtils = new JedisUtils("118.89.196.99", 6379, "123456");
-
RedpackService redpackService = new RedpackService(jedisUtils);
-
IdWorker idWorker = new IdWorker();
-
int N = 100;
-
CyclicBarrier barrier = new CyclicBarrier(N);
-
for (int i = 0;i<N;i++){
-
new Thread(()->{
-
long userId = idWorker.nextId();
-
try {
-
System.out.println("用户"+userId+"准备抢红包");
-
barrier.await();
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
} catch (BrokenBarrierException e) {
-
e.printStackTrace();
-
}
-
String result = redpackService.snatchRedpack(userId, 111111);
-
if ("0".equals(result)){
-
System.out.println("用户" + userId + "未抢到红包,原因:红包已领完");
-
}else if ("1".equals(result)){
-
System.out.println("用户" + userId + "未抢到红包,原因:红包已领过");
-
}else{
-
System.out.println("用户" + userId + "抢到红包:" + result);
-
}
-
},"thread"+i).start();
-
}
-
Thread.sleep(Integer.MAX_VALUE);
-
}
运行结果:
-
......
-
用户1078994397959344128准备抢红包
-
用户1078994397959344129准备抢红包
-
用户1078994397959344130准备抢红包
-
用户1078994397959344131准备抢红包
-
用户1078994397896429573抢到红包:{"userId":"1078994397896429573","hbId":2,"amount":126}
-
用户1078994397888040960抢到红包:{"userId":"1078994397888040960","hbId":1,"amount":526}
-
用户1078994397896429572抢到红包:{"userId":"1078994397896429572","hbId":5,"amount":666}
-
用户1078994397950955528未抢到红包,原因:红包已领完
-
用户1078994397925789696抢到红包:{"userId":"1078994397925789696","hbId":4,"amount":490}
-
用户1078994397929984004未抢到红包,原因:红包已领完
-
用户1078994397959344128抢到红包:{"userId":"1078994397959344128","hbId":3,"amount":192}
-
用户1078994397955149824未抢到红包,原因:红包已领完
-
用户1078994397900623875未抢到红包,原因:红包已领完
-
用户1078994397929984006未抢到红包,原因:红包已领完
-
用户1078994397892235264未抢到红包,原因:红包已领完
-
.......
结果看出100个线程同时抢,只有5个人抢成功了。
查看redis里的数据:红包池子里的红包已被领完,对应红包用户信息也相应的生成。
详细源码地址:https://github.com/suzhe2018/redis-redpack
测试类:TestRedpackService
转载于:https://my.oschina.net/suzheworld/blog/2995288