我是靠谱客的博主 俊逸犀牛,这篇文章主要介绍SpringBoot(19)学习之使用RabbitMQ实现高并发接口优化使用RabbitMQ改写秒杀功能,现在分享给大家,希望可以做个参考。

使用RabbitMQ改写秒杀功能

实现思路

思路:减少数据库访问

具体的实现流程就是

复制代码
1
2
3
4
5
1.系统初始化,把商品库存数量加载到Redis 2.收到请求,Redis预减库存,库存不足,直接返回,否则3 3.请求入队,立即返回排队中 4.请求出队,生成订单,减少库存 5.客户端轮询,是否秒杀成功

其中4和5是同时并发处理的。

具体实现

系统初始化,把商品库存数量加载到Redis

如何在初始化的时候就将库存数据存入缓存中

通过实现InitializingBean接口中的一个方法:afterPropertiesSet()

系统初始化会首先调用该函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** * 系统初始化会调用该函数 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { List<GoodsVo> goodsVoList = goodsService.listGoodsVo(); if (goodsVoList == null){ return; } for (GoodsVo goodsVo:goodsVoList){ //预先把商品库存加载到redis中 redisService.set(GoodsKey.getSeckillGoodsStock,""+goodsVo.getId(),goodsVo.getStockCount()); localOverMap.put(goodsVo.getId(),false); } }

收到请求,Redis预减库存,库存不足,直接返回,否则请求入队,立即返回排队中

首先需要一个RabbitMQ的队列

使用Direct交换机模式

复制代码
1
2
3
4
5
6
7
8
/** * Direct 交换机模式 */ //队列 @Bean public Queue secKill_QUEUE() { return new Queue(SECKILL_QUEUE,true); }

队列消息的发送

复制代码
1
2
3
4
5
public void sendSecKillMessage(SecKillMessage secKillMessage) { String msg = RedisService.Bean2String(secKillMessage); logger.info("send SecKill message: " + msg); amqpTemplate.convertAndSend(MQConfig.SECKILL_QUEUE, msg); }

秒杀的实现

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//预先减库存 long stock = redisService.decr(GoodsKey.getSeckillGoodsStock,""+goodsId); if (stock < 0){ localOverMap.put(goodsId,true); return Result.error(CodeMsg.SECKILL_OVER); } //判断是否已经秒杀到了 SecKillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(),goodsId); if (order != null){ return Result.error( CodeMsg.SECKILL_REPEATE); } //压入RabbitMQ队列 SecKillMessage secKillMessage = new SecKillMessage(); secKillMessage.setUser(user); secKillMessage.setGoodsId(goodsId); mqSender.sendSecKillMessage(secKillMessage); return Result.success(0); //排队中

请求出队,生成订单,减少库存

其实就是RabbitMQ的队列出队去处理相关的业务

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RabbitListener(queues = MQConfig.SECKILL_QUEUE) public void receive(String message){ logger.info("receive message" + message); SecKillMessage secKillMessage = RedisService.String2Bean(message,SecKillMessage.class); SecKillUser user = secKillMessage.getUser(); long goodsId = secKillMessage.getGoodsId(); //判断库存 GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); int stock = goods.getStockCount(); if (stock <= 0){ return; } //判断是否已经秒杀到了 SecKillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(),goodsId); if (order != null){ return; } //减库存 下订单 写入秒杀订单 //订单的详细信息 OrderInfo orderInfo = secKillService.secKill(user, goods); }

客户端轮询,是否秒杀成功

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//秒杀的结果 /** * orderId:秒杀成功 * -1: 秒杀失败 * 0:排队中 * @param model * @param user * @param goodsId * @return */ @RequestMapping(value = "/result",method = RequestMethod.GET) @ResponseBody public Result<Long> miaoshaResult(Model model, SecKillUser user, @RequestParam("goodsId") long goodsId){ model.addAttribute("user",user); if (user == null){ return Result.error(CodeMsg.SESSION_ERROR); } long result = secKillService.getSecKillResult(user.getId(),goodsId); return Result.success(result); }

secKillService.getSecKillResult():

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//获取结果 /** * orderId :成功 * -1:失败 * 0: 排队中 * @param userId * @param goodsId * @return */ public long getSecKillResult(Long userId, long goodsId) { SecKillOrder order = orderService.getOrderByUserIdGoodsId(userId,goodsId); if (order != null){ return order.getOrderId(); }else { boolean isOver = getGoodsOver(goodsId); if (isOver){ return -1; }else { return 0; } } }

这里涉及到了redis的访问,就是redis中有商品的数量,通过该参数判断卖没卖完,当一次性来了多于商品数目的请求的时候,redis预减库存,减为负数,其实在这个时候在来商品购买请求的时候就不需要在访问redis了。因为商品已经卖完了,这个时候就做一个标记,先判断内存这个标记,如果库存已经小于0了,就不再访问redis,这样就减少了redis的访问次数。

没有订单有两种情况,卖完了失败,和排队中,

在上面的秒杀那做个标记。这个商品是否秒杀完了。存入redis中。

之后去判断是否存在这个key就知道是哪种情况,这样

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//事务,原子性操作 @Transactional public OrderInfo secKill(SecKillUser user, GoodsVo goods) { //减库存 下订单 写入秒杀订单 必须是同时完成的 boolean success = goodsService.reduceStock(goods); //减库存成功了才进行下订单 if (success) { return orderService.createOrder(user, goods); }else{ //说明商品秒杀完了。做一个标记 setGoodsOver(goods.getId()); return null; } } //获取结果 /** * orderId :成功 * -1:失败 * 0: 排队中 * @param userId * @param goodsId * @return */ public long getSecKillResult(Long userId, long goodsId) { SecKillOrder order = orderService.getOrderByUserIdGoodsId(userId,goodsId); if (order != null){ return order.getOrderId(); }else { boolean isOver = getGoodsOver(goodsId); if (isOver){ return -1; }else { return 0; } } } public void setGoodsOver(Long goodsId) { redisService.set(SecKillKey.isGoodsOver,""+goodsId,true); } public boolean getGoodsOver(Long goodsId) { return redisService.exists(SecKillKey.isGoodsOver,""+goodsId); } }

相对应的前端的修改

原来的detail页面中秒杀事件函数:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function doMiaosha(){ $.ajax({ url:"/miaosha/do_miaosha", type:"POST", data:{ goodsId:$("#goodsId").val(), }, success:function(data){ if(data.code == 0){ window.location.href="/order_detail.htm?orderId="+data.data.id; }else{ layer.msg(data.msg); } }, error:function(){ layer.msg("客户端请求有误"); } }); }

秒杀到商品就直接返回,现在后端改为消息队列,所以需要增加函数进行判断,必要时需要轮询:

复制代码
1
2
3
4
5
if(data.code == 0){ window.location.href="/order_detail.htm?orderId="+data.data.id; }else{ layer.msg(data.msg); }

所以将其改为:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//其他的部分省略 ... if(data.code == 0){ //window.location.href="/order_detail.htm?orderId="+data.data.id; //秒杀到商品的时候,这个时候不是直接返回成功,后端是进入消息队列,所以前端是轮询结果,显示排队中 getMiaoshaResult($("#goodsId").val()); }else{ layer.msg(data.msg); } ... function getMiaoshaResult(goodsId) { g_showLoading(); $.ajax({ url:"/miaosha/result", type:"GET", data:{ goodsId:$("#goodsId").val(), }, success:function(data){ if(data.code == 0){ var result = data.data; //失败--- -1 if(result <= 0){ layer.msg("对不起,秒杀失败!"); } //排队等待,轮询--- 0 else if(result == 0){//继续轮询 setTimeout(function () { getMiaoshaResult(goodsId); },50); } //成功---- 1 else { layer.msg("恭喜你,秒杀成功,查看订单?",{btn:["确定","取消"]}, function () { window.location.href="/order_detail.htm?orderId="+result; }, function () { layer.closeAll(); } ); } }else{ layer.msg(data.msg); } }, error:function(){ layer.msg("客户端请求有误"); } }); }

压测

测试环境 1g + 4核 + 50000个请求

这里写图片描述

最后

以上就是俊逸犀牛最近收集整理的关于SpringBoot(19)学习之使用RabbitMQ实现高并发接口优化使用RabbitMQ改写秒杀功能的全部内容,更多相关SpringBoot(19)学习之使用RabbitMQ实现高并发接口优化使用RabbitMQ改写秒杀功能内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部