:sparkles:高并发微信抢红包秒杀实战案例

This commit is contained in:
小柒2012
2020-02-02 19:44:03 +08:00
parent f47200d0d3
commit 37f7b0f385
3 changed files with 227 additions and 1 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}