java初探(1)之秒杀中的rabbitMQ
rabbitMQ
消息队列,通过一定的通信协议,生产者和消费者在应用程序内传递通信。
主要的作用,提高负载,减耦合。
场景描述:当点击秒杀按钮的那个时刻,有很高的并发量,客户端发出请求之后,会判断库存,如果库存大于0,就判断是否已经下单,如果没有下单,就执行秒杀逻辑,对于秒杀逻辑,分两个步骤,一是减库存,二是创建订单。
以上就是不使用rabbitMQ的场景描述。
利用消息队列,我们可以在执行秒杀逻辑之前,将用户和待秒杀的商品进行入队(rabbitMQ的生产者类),然后将秒杀逻辑放在出队上(rabbitMQ的消费者类)
在客户端页面上创建一个轮询函数,每隔一段时间,设置一个请求是否有秒杀结果,然后进行数据展示,或者跳转。
通过以上操作,我们可以将用户的请求与返回的结果通过rabbitMQ进行解耦合。从而降低网站的并发。
内存标记库存
假如后面的操作已经确认库存小于0,则设置一个标记器,直接返回错误信息。
//内存标记,第一次拦截
Boolean over = localOverMap.get(goodsId);
if(over){
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
预减库存
该操作相当于一个拦截器,当我们的秒杀请求进到秒杀控制器中,需要先从redis中减库存,然后判断库存是否充足。因此,需要先在redis中加载商品的库存数
这里利用控制器继承一个接口,来写一个系统初始化的方法,将商品的库存数放到redis中,每次从秒杀点击过来的请求,先加载该初始化方法,把库存数从数据库里拿出来,(这里可以改进为从缓存中拿值),然后判断库存数,如果库存数小于0,则不需要后续操作,直接返回错误。
/**
* 系统初始化
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception { //得到商品的所有列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
if(goodsList==null){
return;
} for (GoodsVo goods : goodsList) {
redisService.set(GoodsKey.getMiaoshaGoodsStock,""+goods.getId(),goods.getStockCount());
//对内存进行标志,若没有结束库存就是false,若有库存就是true
localOverMap.put(goods.getId(),false);//false表示有库存
} } //预减库存
Long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, "" + goodsId);
if(stock<0){
localOverMap.put(goodsId,true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
当判断,既有库存,又没有下订单,则执行入队操作,将需要传递的用户和商品名加入到队列中,传递给消费者。
//判断是否已经秒杀到了
MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order!=null){
return Result.error(CodeMsg.REPEATE_MIAOSHA);
} //入队操作
//创建需要入队的数据类,需要用户类和商品的id。
MiaoshaMessage message=new MiaoshaMessage();
message.setGoodsId(goodsId);
message.setUser(user);
//调用rabbitMQ生产者的发送方法,发送该数据类
mqSender.sendMiaoshaMessage(message);
完整MiaoshaController类
@Controller
@RequestMapping("/miaosha")
public class MiaoshaController implements InitializingBean{ @Autowired
private GoodsService goodsService; @Autowired
private OrderService orderService; @Autowired
private MiaoshaService miaoshaService; @Autowired
private RedisService redisService; @Autowired
private MQSender mqSender; private HashMap<Long,Boolean> localOverMap=new HashMap<>(); /**
* 系统初始化
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception { //得到商品的所有列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
if(goodsList==null){
return;
} for (GoodsVo goods : goodsList) {
redisService.set(GoodsKey.getMiaoshaGoodsStock,""+goods.getId(),goods.getStockCount());
//对内存进行标志,若没有结束库存就是false,若有库存就是true
localOverMap.put(goods.getId(),false);//false表示有库存
} } @RequestMapping(value = "/do_miaosha",method = RequestMethod.POST)
@ResponseBody
public Result<Integer> list(Model model, MiaoshaUser user,
@RequestParam("goodsId") long goodsId){ model.addAttribute("user",user);
if(user==null){
//如果没有获取到user值,报异常
return Result.error(CodeMsg.SESSION_ERROR);
} //内存标记,第一次拦截
Boolean over = localOverMap.get(goodsId);
if(over){
return Result.error(CodeMsg.MIAO_SHA_OVER);
} //预减库存
Long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, "" + goodsId);
if(stock<0){
localOverMap.put(goodsId,true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
} //判断是否已经秒杀到了
MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order!=null){
return Result.error(CodeMsg.REPEATE_MIAOSHA);
} //入队操作
//创建需要入队的数据类,需要用户类和商品的id。
MiaoshaMessage message=new MiaoshaMessage();
message.setGoodsId(goodsId);
message.setUser(user);
//调用rabbitMQ生产者的发送方法,发送该数据类
mqSender.sendMiaoshaMessage(message); return Result.success(0);//返回0代表排队中 // //判断库存
// GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
// Integer stock= goods.getStockCount();
//
// if(stock<=0){
// model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
// return Result.error(CodeMsg.MIAO_SHA_OVER);
// }
//
// //判断是否已经秒杀到了
// MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
//
// if(order!=null){
// return Result.error(CodeMsg.REPEATE_MIAOSHA);
// }
//
// //进行秒杀逻辑
// //减库存,下订单,写入秒杀订单
// OrderInfo orderInfo=miaoshaService.miaosha(user, goods); // return Result.success(orderInfo);
} @RequestMapping(value="/result", method=RequestMethod.GET)
@ResponseBody
public Result<Long> miaoshaResult(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId){
model.addAttribute("user",user);
if(user==null){
return Result.error(CodeMsg.SESSION_ERROR);
}
//获取秒杀的结果
long result =miaoshaService.getMiaoshaResult(user.getId(), goodsId); return Result.success(result);
} }
将队列的消息寄出去之后,需要由消费者接收数据,并执行秒杀的具体逻辑
这里的逻辑代码就很简单,继续判断库存以及是否秒杀到,然后执行秒杀逻辑
- MQReceiver类
@Service
public class MQReceiver { private static Logger log = LoggerFactory.getLogger(MQReceiver.class); @Autowired
private GoodsService goodsService; @Autowired
private OrderService orderService; @Autowired
private MiaoshaService miaoshaService; @RabbitListener(queues= MQConfig.MIAOSHA_QUEUE)
public void receive(String message){ //获取到生产者发送过来的数据
log.info("收到的消息:"+message); //转换数据格式
MiaoshaMessage mm = RedisService.stringToBean(message, MiaoshaMessage.class); MiaoshaUser user = mm.getUser();
long goodsId = mm.getGoodsId(); //数据传过来之后,仍然需要判断库存以及是否完成订单
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goods.getStockCount();
if(stock <= 0) {
return;
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return;
} //当真正秒杀成功之后,再将请求转到秒杀逻辑内
miaoshaService.miaosha(user,goods); } }
具体的秒杀逻辑是减库存,然后创建订单,这些代码在之前已经写过。如果没有减库存成功,则表示已经结束秒杀了,在之前的代码中,什么都没做,让事务进行回滚。这次在缓存中设置一个秒杀结束的标志。
@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) { // //减库存,下订单,写入秒杀订单
// goodsService.reduceStock(goods);
// return orderService.createOrder(user, goods); boolean success = goodsService.reduceStock(goods);
if (success){
return orderService.createOrder(user, goods);
}else {
setGoodsOver(goods.getId());
return null;
} } private void setGoodsOver(Long goodsId) {
redisService.set(MiaoshaKey.isGoodsOver, ""+goodsId, true);
} private boolean getGoodsOver(long goodsId) {
return redisService.exists(MiaoshaKey.isGoodsOver, ""+goodsId);
}
做到此处,秒杀的逻辑结束了,但并没有返回任何页面,只是对数据库做了相应的修改。
接下来应该在客户端进行循环提出请求,来判断是否秒杀成功,在商品的详情页面,当入队成功返回一个0时,就开始启动轮询函数,不停的向服务器发请求,看是否已经下单成功,若成功就跳转到订单详情页面。
function getMiaoshaResult(goodsId) {
g_showLoading();//显示处理等待
//发起ajax请求
$.ajax({
url:"/miaosha/result",
type:"GET",
data:{
goodsId:$("#goodsId").val()
},
success:function(data){
if (data.code==0){
var result=data.data;
if(result<0){
layer.msg("对不起,秒杀失败");
}else if(result==0){
//继续轮询
setTimeout(function () {
getMiaoshaResult(goodsId)
},50);
}
else {
layer.confirm("恭喜你,秒杀成功!查看订单?", {btn:["确定","取消"]},
function(){
window.location.href="/order_detail.htm?orderId="+result;
},
function(){
layer.closeAll();
});
}
}else {
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
每次轮询代码请求服务器时,都会将秒杀的结果返回
public long getMiaoshaResult(Long userId, long goodsId) {
//查看是否下订单成功
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
if (order!=null){
return order.getOrderId();
}
else {
boolean isover = getGoodsOver(goodsId);
if(isover){
return -1;
}
else {
return 0;
}
}
}
若订单不为空,则返回订单的id,若为空,则查看有没有库存,若没有库存则返回-1,若有库存,则说明还在排队中,返回0,
@RequestMapping(value="/result", method=RequestMethod.GET)
@ResponseBody
public Result<Long> miaoshaResult(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId){
model.addAttribute("user",user);
if(user==null){
return Result.error(CodeMsg.SESSION_ERROR);
}
//获取秒杀的结果
long result =miaoshaService.getMiaoshaResult(user.getId(), goodsId); return Result.success(result);
}
到此,rabbitMQ实现的秒杀功能就完成了。
java初探(1)之秒杀中的rabbitMQ的更多相关文章
- 浅谈surging服务引擎中的rabbitmq组件和容器化部署
1.前言 上个星期完成了surging 的0.9.0.1 更新工作,此版本通过nuget下载引擎组件,下载后,无需通过代码build集成,引擎会通过Sidecar模式自动扫描装配异构组件来构建服务引擎 ...
- 二、消息队列之如何在C#中使用RabbitMQ
1.什么是RabbitMQ.详见 http://www.rabbitmq.com/. 作用就是提高系统的并发性,将一些不需要及时响应客户端且占用较多资源的操作,放入队列,再由另外一个线程,去异步处理这 ...
- [java初探总结篇]__java初探总结
前言 终于,java初探系列的学习,要告一阶段了,java初探系列在我的计划中是从头学java中的第一个阶段,知识主要涉及java的基础知识,所以在笔记上实在花了不少的功夫.虽然是在第一阶段上面花费了 ...
- [java初探10]__关于数字处理类
前言 在我们的日常开发过程中,我们会经常性的使用到数字类型的数据,同时,也会有众多的对数字处理的需求,针对这个方面的问题,在JAVA语言中.提供解决方法的类就是数字处理类 java中的数字处理类包括: ...
- 二、消息队列之如何在C#中使用RabbitMQ(转载)
二.消息队列之如何在C#中使用RabbitMQ 1.什么是RabbitMQ.详见 http://www.rabbitmq.com/. 作用就是提高系统的并发性,将一些不需要及时响应客户端且占用较多资源 ...
- .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇
.NET 环境中使用RabbitMQ 在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...
- Spring Boot中使用RabbitMQ
很久没有写Spring Boot的内容了,正好最近在写Spring Cloud Bus的内容,因为内容会有一些相关性,所以先补一篇关于AMQP的整合. Message Broker与AMQP简介 Me ...
- 在SpringBoot中使用RabbitMQ
目录 RabbitMQ简介 RabbitMQ在CentOS上安装 配置文件 实践 概述 Demo 遇到的BUG 启动异常 无法自动创建队列 RabbitMQ简介 wikipedia RabbitMQ在 ...
- ubuntu中安装rabbitmq服务并成功启动
在我们使用rabbitmq时,首先要对其进行安装,而后才能对其进行使用 安装 Erlang 由于 RabbitMQ 是采用 Erlang 编写的,所以需要安装 Erlang 语言库.就像 java 需 ...
随机推荐
- 牛逼!Python的判断、循环和各种表达式(长文系列第2篇
流程控制是python语法很重要的一个分支,主要包括我们经常用到的判断语句.循环语句以及各种表达式,这也是上一篇文章没有介绍表达式的原因,在这篇文章中会更加系统全面的讲解这三方面的基础知识. 很多人学 ...
- Azure DevOps+Docker+Asp.NET Core 实现CI/CD(二.创建CI持续集成管道)
前言 本文主要是讲解如何使用Azure DevOps+Docker 来实现持续集成Asp.NET Core项目(当然 也可以是任意项目). 上一篇: Azure DevOps+Docker+Asp.N ...
- 在 .NetCore 项目中使用 SkyWalkingAPM 踩坑排坑日记
SkyWalking 概述 SkyWalking 是观察性分析平台和应用性能管理系统.提供分布式追踪.服务网格遥测分析.度量聚合和可视化一体化解决方案.支持Java, .Net Core, PHP, ...
- 面试这么撩准拿offer,HashMap深度学习,扰动函数、负载因子、扩容拆分,原理和实践验证,让懂了就是真的懂!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 得益于Doug Lea老爷子的操刀,让HashMap成为使用和面试最频繁的API,没 ...
- tableauRFM分析
1.数据源 2.创建相关字段 2.1 购买点会员生命期 2.2 会员最后购买时间 2.3 最后购买点生命期 3.近一个月老客户的生命期情况 排除了当天创建当天购买的情况,可以看到超市的大部分用户是两年 ...
- JavaScript 严格模式(strict mode)
概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:'严格模式'.顾名思义,这种模式使得Javascript在更严格的条件下运行. 目的 1: 消除Javascript语法的一些不合 ...
- 用python爬取B站在线用户人数
最近在自学Python爬虫,所以想练一下手,用python来爬取B站在线人数,应该可以拿来小小分析一下 设计思路 首先查看网页源代码,找到相应的html,然后利用各种工具(BeautifulSoup或 ...
- C#分布式登录——jwt
一.传统的session登录 在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这 ...
- .Net MVC5(.Net Framework 4.0+)多语言解决方案
最近项目需要做多语言,原先是2种语言(中文/英文),现在又要加一种语言,成了3种.那么原来的方式肯定不适用了,只能升级解决方案. 原来的写法,使用三目表达式,按照当前全局变量的语言类型,返回不同的语言 ...
- 通俗易懂的 Java 位操作运算讲解
所有数值都是2进制 软件开发者都知道 10 进制.16 进制.8 进制. 比如数字 10 的各位进制形式表现如下. 十进制:10 八进制:012 十六进制:0x0a 二进制:1010 原码 反码 补码 ...