Redis+activeMQ实现秒杀demo
GitEE地址:https://gitee.com/jminzhou/study-project
1. 思路
- 先将要被秒杀的商品存到redis中,可以使用
aspectJ
,可以编写一个service去实现,使用商户管理去调用借口,也可以使用监听器的方式,反正最主要的思路就是把信息存到redis中。 - 将当前登录的用户信息也存到redis中,因为商品是直接下单成功的(不会判断用户的金额是否足够,所以只需要一个用户的唯一标识即可)。
- 将订单信息存到mq中,然后使用mq去转存redis中,判断库存是否足够。
2. 实现(只有一少部分核心demo代码)
2.1 aop
将商品信息存到redis中
/**
* @date 2020/10/28 14:27
*/
@Aspect
@Component
public class MyOrderAspectJ {
@Autowired
private RedisUtil redisUtil;
@Autowired
private ProductService productService;
/**
* 设置商品getOne切点
*/
@Pointcut("execution(* com.redis.seckill.controller.ProductController.get One(..))")
public void productInfo(){}
/**
* 开启秒杀之前将商品信息存到redis中。
*/
@AfterReturning(pointcut = "productInfo()")
public void saveProduct(JoinPoint joinPoint){
// 获取拦截方法的参数列表,这里的参数只有一个商品id
Object[] args = joinPoint.getArgs();
// 得到商品id
Integer productId = (Integer) args[0];
// 判断是否有该商品的key
if (!redisUtil.hasKey("productInfo"+productId)){
// 将商品信息存到redis中。
redisUtil.set("productInfo"+productId,
JSON.toJSON(productService.getOne(new QueryWrapper<Product>().eq("product_id",productId))));
}
}
}
2.2 MQ
发送者
/**
* 发送者
* @date 2020/10/29 9:08
*/
@Service
@Log4j2
public class Sender {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 如果用户抢到商品就发送到队列中去
* 信息包含用户id和商品id
* @param userId
* @param productId
*/
public void sendDirectQueue(Integer userId,Integer productId,Integer quantity){
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>秒杀请求发送,商品id:"+productId+"用户id"+userId);
try {
Map<String,Integer> map = new HashMap<>();
map.put("userId",userId);
map.put("productId",productId);
map.put("quantity",quantity);
jmsMessagingTemplate.convertAndSend(ActiveMQConfig.QUEUE_NAME,map);
}catch (Exception e){
e.printStackTrace();
log.error("【队列发送异常】{}",e.getMessage());
}
}
}
接收者
我这里再进行秒杀完毕之后直接存到数据库了。我也不知道对不对😭。
我觉得也可以先存到redis中,等到服务器的峰值没有那么大的时候,再转存到数据库中。
/**
* 消息接收者
* @date 2020/10/29 9:15
*/
@Log4j2
@Service
public class Receiver {
@Autowired
private RedisUtil redisUtil;
@Autowired
private MyOrderService myOrderService;
@Autowired
private ProductService productService;
@JmsListener(destination = ActiveMQConfig.QUEUE_NAME)
public void receive(MapMessage mapMessage){
try {
// 根据接收的map中的productId得到商品的id
String productId = mapMessage.getString("productId");
String userId = mapMessage.getString("userId");
String quantityStr = mapMessage.getString("quantity");
// 从redis中读取该商品的信息
String jsonProduct = JSONObject.toJSONString(redisUtil.get("productInfo" + productId));
Product product = JSONObject.parseObject(jsonProduct, Product.class);
// 判断想要购买的数量是否大于商品剩余的数量
Integer quantity = Integer.valueOf(quantityStr);
int num = product.getProductResidue()-quantity;
if (num < 0){
log.error("【商品库存不足】");
throw new RuntimeException("【商品库存不足】");
}
// 剩下肯定就是数量小于库存了
// 更新redis中商品信息
product.setProductResidue(product.getProductResidue()-quantity);
redisUtil.set("productInfo" + productId, JSON.toJSON(product));
// 也可以先写入到redis中,等服务器峰值过去再写入到数据库中。
// 生成订单存到数据库中
MyOrder myOrder = new MyOrder();
myOrder.setProductOrder(Integer.valueOf(productId));
myOrder.setUserOrder(Integer.valueOf(userId));
myOrder.setProductQuantity(quantity);
myOrder.setOrderCount(product.getProductPrice().multiply(new BigDecimal(quantity)));
myOrderService.save(myOrder);
// 更新商品表
productService.updateById(product);
}catch (Exception e){
log.error("【队列接收异常】{}",e.getMessage());
}
}
}
2.3 秒杀service
@Transactional
@Service
@Log4j2
public class MyOrderServiceImpl extends ServiceImpl<MyOrderMapper, MyOrder> implements MyOrderService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private Sender sender;
@Override
public boolean seckill(Integer userId, Integer productId, Integer quantity) {
boolean result = true;
// 获取redis中商品的库存容量
String jsonProduct = JSONObject.toJSONString(redisUtil.get("productInfo" + productId));
Product product = JSONObject.parseObject(jsonProduct, Product.class);
if (product.getProductResidue()<=0){
result = false;
log.error("【秒杀下单service】,库存不足");
}
sender.sendDirectQueue(userId,productId,quantity);
return result;
}
}
3. 食用方法
- 先访问http://localhost:8080/product/getOne/{商品id}端口,将想进行秒杀的商品存到redis中。
- 虚假的登录(其实就是将自己的用户信息存到redis中):http://localhost:8080/account/login ,这里直接没传参数,在
AccountController
内部直接给定了用户id的值为1。 - 执行秒杀:http://localhost:8080/order/create ,这里也是没传参数,
MyOrderController
,给定了值。
强烈建议先clone在食用。
gitEE:https://gitee.com/jminzhou/study-project