RocketMQ学习笔记:秒杀+分布式锁 Redis RocketMQ SpringBoot
架构图
MySQL数据库
goods
order
secill-web模块
controller类
@RestController
public class SeckillController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 1. 用户去重
* 2. 库存的预扣减
* 3. 消息放入mq
* @param goodsId
* @param userId
* @return
*/
@GetMapping("seckill/{goodsId}/{userId}")
public String doSecKill(@PathVariable Integer goodsId, @PathVariable Integer userId) {
// uk uniqueKey = userId + goodsId
String uk = userId + "-" + goodsId;
// setIfAbsent = setnx
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(uk, "");
if (!flag) {
return "您已经参与过该商品的抢购,请参与其他商品";
}
// mq 异步处理
rocketMQTemplate.asyncSend("secKillTopic2", uk, new SendCallback(){
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功");
}
@Override
public void onException(Throwable throwable) {
System.out.println("发送失败" + throwable.getMessage());
}
});
return "恭喜,抢购成功";
}
}
seckill-service
同步mysql中数据到redis
@Component
public class DataSync {
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 希望这个方法在项目启动以后
* 并且在这个类的属性注入完毕以后执行
*
*/
@PostConstruct
public void initData () {
QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
List<Goods> goodsList = goodsMapper.selectList(queryWrapper);
if (CollectionUtils.isEmpty(goodsList)) {
return;
}
goodsList.forEach(goods -> {
stringRedisTemplate.opsForValue().set("goodsId:" + goods.getGoodsId(), goods.getTotalStocks().toString());
});
}
}
consumer监听
@Component
@RocketMQMessageListener(topic = "secKillTopic2",
consumerGroup = "seckill-consumer-group",
consumeThreadNumber = 40,
consumeMode = ConsumeMode.CONCURRENTLY)
public class SeckillListener implements RocketMQListener<MessageExt> {
@Autowired
private GoodsService goodsService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
final int ZX_TIME = 10000;
/**
* 扣减库存
* 写订单表
* redis setnx
* @param messageExt
*/
@Override
public void onMessage(MessageExt messageExt) {
String msg = new String(messageExt.getBody());
// userId + "-" + goodsId
Integer userId = Integer.parseInt(msg.split("-")[0]);
Integer goodsId = Integer.parseInt(msg.split("-")[1]);
int currentThreadTime = 0;
while (currentThreadTime < ZX_TIME) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock:" + goodsId, "", Duration.ofSeconds(30));
if (flag) {
// 拿到锁成功
try {
goodsService.realSeckill(userId, goodsId);
return;
} finally {
// 释放锁
stringRedisTemplate.delete("lock:" + goodsId);
}
} else {
// 没拿到锁 自旋
currentThreadTime += 200;
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}