mirror of
https://gitee.com/52itstyle/spring-boot-seckill.git
synced 2025-12-30 10:22:26 +00:00
:sparkles:高并发微信抢红包秒杀实战案例
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package com.itstyle.seckill.service;
|
||||
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
|
||||
public interface IRedPacketService {
|
||||
|
||||
|
||||
/**
|
||||
* 秒杀一
|
||||
* @param redPacketId
|
||||
* @return
|
||||
*/
|
||||
Result startSeckil(long redPacketId,int userId);
|
||||
|
||||
/**
|
||||
* 秒杀二
|
||||
* @param redPacketId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Result startTwoSeckil(long redPacketId,int userId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.itstyle.seckill.service.impl;
|
||||
|
||||
import com.itstyle.seckill.common.dynamicquery.DynamicQuery;
|
||||
import com.itstyle.seckill.common.entity.RedPacketRecord;
|
||||
import com.itstyle.seckill.common.entity.Result;
|
||||
import com.itstyle.seckill.common.redis.RedisUtil;
|
||||
import com.itstyle.seckill.distributedlock.redis.RedissLockUtil;
|
||||
import com.itstyle.seckill.service.IRedPacketService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service("redPacketService")
|
||||
public class RedPacketService implements IRedPacketService {
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private DynamicQuery dynamicQuery;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startSeckil(long redPacketId,int userId) {
|
||||
Integer money = 0;
|
||||
boolean res=false;
|
||||
try {
|
||||
/**
|
||||
* 获取锁
|
||||
*/
|
||||
res = RedissLockUtil.tryLock(redPacketId+"", TimeUnit.SECONDS, 3, 10);
|
||||
if(res){
|
||||
long restPeople = redisUtil.decr(redPacketId+"-restPeople",1);
|
||||
/**
|
||||
* 如果是最后一人
|
||||
*/
|
||||
if(restPeople==1){
|
||||
money = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
|
||||
}else{
|
||||
Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
|
||||
Random random = new Random();
|
||||
//随机范围:[1,剩余人均金额的两倍]
|
||||
money = random.nextInt((int) (restMoney / (restPeople+1) * 2 - 1)) + 1;
|
||||
}
|
||||
redisUtil.decr(redPacketId+"-money",money);
|
||||
/**
|
||||
* 异步入库
|
||||
*/
|
||||
RedPacketRecord record = new RedPacketRecord();
|
||||
record.setMoney(money);
|
||||
record.setRedPacketId(redPacketId);
|
||||
record.setUid(userId);
|
||||
record.setCreateTime(new Timestamp(System.currentTimeMillis()));
|
||||
saveRecord(record);
|
||||
/**
|
||||
* 异步入账
|
||||
*/
|
||||
}else{
|
||||
/**
|
||||
* 获取锁失败相当于抢红包失败,红包个数加一
|
||||
*/
|
||||
redisUtil.incr(redPacketId+"-num",1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
if(res){//释放锁
|
||||
RedissLockUtil.unlock(redPacketId+"");
|
||||
}
|
||||
}
|
||||
return Result.ok(money);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result startTwoSeckil(long redPacketId, int userId) {
|
||||
Integer money = 0;
|
||||
boolean res=false;
|
||||
try {
|
||||
/**
|
||||
* 获取锁 保证红包数量和计算红白金额的原子性操作
|
||||
*/
|
||||
res = RedissLockUtil.tryLock(redPacketId+"", TimeUnit.SECONDS, 3, 10);
|
||||
if(res){
|
||||
long restPeople = redisUtil.decr(redPacketId+"-num",1);
|
||||
if(restPeople>0){
|
||||
/**
|
||||
* 如果是最后一人
|
||||
*/
|
||||
if(restPeople==1){
|
||||
money = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
|
||||
}else{
|
||||
Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
|
||||
Random random = new Random();
|
||||
//随机范围:[1,剩余人均金额的两倍]
|
||||
money = random.nextInt((int) (restMoney / (restPeople+1) * 2 - 1)) + 1;
|
||||
}
|
||||
redisUtil.decr(redPacketId+"-money",money);
|
||||
/**
|
||||
* 异步入库
|
||||
*/
|
||||
RedPacketRecord record = new RedPacketRecord();
|
||||
record.setMoney(money);
|
||||
record.setRedPacketId(redPacketId);
|
||||
record.setUid(userId);
|
||||
record.setCreateTime(new Timestamp(System.currentTimeMillis()));
|
||||
saveRecord(record);
|
||||
/**
|
||||
* 异步入账
|
||||
*/
|
||||
}else{
|
||||
return Result.error("手慢了,红包派完了");
|
||||
}
|
||||
}else{
|
||||
/**
|
||||
* 获取锁失败相当于抢红包失败,红包个数加一
|
||||
*/
|
||||
redisUtil.incr(redPacketId+"-num",1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
if(res){//释放锁
|
||||
RedissLockUtil.unlock(redPacketId+"");
|
||||
}
|
||||
}
|
||||
return Result.ok(money);
|
||||
}
|
||||
|
||||
@Async
|
||||
void saveRecord(RedPacketRecord record){
|
||||
dynamicQuery.save(record);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public class RedPacketController {
|
||||
@Autowired
|
||||
private IRedPacketService redPacketService;
|
||||
|
||||
/**
|
||||
* 抢红包 拆红包 抢到基本能拆到
|
||||
* @param redPacketId
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value="抢红包一",nickname="爪哇笔记")
|
||||
@PostMapping("/start")
|
||||
public Result start(long redPacketId){
|
||||
@@ -70,7 +75,7 @@ public class RedPacketController {
|
||||
long count = redisUtil.decr(redPacketId+"-num",1);
|
||||
if(count>0){
|
||||
Result result = redPacketService.startSeckil(redPacketId,userId);
|
||||
Double amount = DoubleUtil.divide(Double.parseDouble(result.get("msg").toString()), (double) 100);
|
||||
Double amount = DoubleUtil.divide(Double.parseDouble(result.get("msg").toString()), (double) 100);
|
||||
LOGGER.info("用户{}抢红包成功,金额:{}", userId,amount);
|
||||
}else{
|
||||
LOGGER.info("用户{}抢红包失败",userId);
|
||||
@@ -88,4 +93,64 @@ public class RedPacketController {
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 抢红包 拆红包 抢到不一定能拆到
|
||||
* @param redPacketId
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value="抢红包二",nickname="爪哇笔记")
|
||||
@PostMapping("/startTwo")
|
||||
public Result startTwo(long redPacketId){
|
||||
int skillNum = 100;
|
||||
final CountDownLatch latch = new CountDownLatch(skillNum);//N个抢红包
|
||||
/**
|
||||
* 初始化红包数据,抢红包拦截
|
||||
*/
|
||||
redisUtil.cacheValue(redPacketId+"-num",10);
|
||||
/**
|
||||
* 初始化红包金额,单位为分
|
||||
*/
|
||||
redisUtil.cacheValue(redPacketId+"-money",20000);
|
||||
/**
|
||||
* 模拟100个用户抢10个红包
|
||||
*/
|
||||
for(int i=1;i<=skillNum;i++){
|
||||
int userId = i;
|
||||
Runnable task = () -> {
|
||||
/**
|
||||
* 抢红包 判断剩余金额
|
||||
*/
|
||||
Integer money = (Integer) redisUtil.getValue(redPacketId+"-money");
|
||||
if(money>0){
|
||||
/**
|
||||
* 虽然能抢到 但是不一定能拆到
|
||||
* 类似于微信的 点击红包显示抢的按钮
|
||||
*/
|
||||
Result result = redPacketService.startTwoSeckil(redPacketId,userId);
|
||||
if(result.get("code").toString().equals("500")){
|
||||
LOGGER.info("用户{}手慢了,红包派完了",userId);
|
||||
}else{
|
||||
Double amount = DoubleUtil.divide(Double.parseDouble(result.get("msg").toString()), (double) 100);
|
||||
LOGGER.info("用户{}抢红包成功,金额:{}", userId,amount);
|
||||
}
|
||||
}else{
|
||||
/**
|
||||
* 直接显示手慢了,红包派完了
|
||||
*/
|
||||
//LOGGER.info("用户{}手慢了,红包派完了",userId);
|
||||
}
|
||||
latch.countDown();
|
||||
};
|
||||
executor.execute(task);
|
||||
}
|
||||
try {
|
||||
latch.await();
|
||||
Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());
|
||||
LOGGER.info("剩余金额:{}",restMoney);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user