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的更多相关文章

  1. 浅谈surging服务引擎中的rabbitmq组件和容器化部署

    1.前言 上个星期完成了surging 的0.9.0.1 更新工作,此版本通过nuget下载引擎组件,下载后,无需通过代码build集成,引擎会通过Sidecar模式自动扫描装配异构组件来构建服务引擎 ...

  2. 二、消息队列之如何在C#中使用RabbitMQ

    1.什么是RabbitMQ.详见 http://www.rabbitmq.com/. 作用就是提高系统的并发性,将一些不需要及时响应客户端且占用较多资源的操作,放入队列,再由另外一个线程,去异步处理这 ...

  3. [java初探总结篇]__java初探总结

    前言 终于,java初探系列的学习,要告一阶段了,java初探系列在我的计划中是从头学java中的第一个阶段,知识主要涉及java的基础知识,所以在笔记上实在花了不少的功夫.虽然是在第一阶段上面花费了 ...

  4. [java初探10]__关于数字处理类

    前言 在我们的日常开发过程中,我们会经常性的使用到数字类型的数据,同时,也会有众多的对数字处理的需求,针对这个方面的问题,在JAVA语言中.提供解决方法的类就是数字处理类 java中的数字处理类包括: ...

  5. 二、消息队列之如何在C#中使用RabbitMQ(转载)

    二.消息队列之如何在C#中使用RabbitMQ 1.什么是RabbitMQ.详见 http://www.rabbitmq.com/. 作用就是提高系统的并发性,将一些不需要及时响应客户端且占用较多资源 ...

  6. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  7. Spring Boot中使用RabbitMQ

    很久没有写Spring Boot的内容了,正好最近在写Spring Cloud Bus的内容,因为内容会有一些相关性,所以先补一篇关于AMQP的整合. Message Broker与AMQP简介 Me ...

  8. 在SpringBoot中使用RabbitMQ

    目录 RabbitMQ简介 RabbitMQ在CentOS上安装 配置文件 实践 概述 Demo 遇到的BUG 启动异常 无法自动创建队列 RabbitMQ简介 wikipedia RabbitMQ在 ...

  9. ubuntu中安装rabbitmq服务并成功启动

    在我们使用rabbitmq时,首先要对其进行安装,而后才能对其进行使用 安装 Erlang 由于 RabbitMQ 是采用 Erlang 编写的,所以需要安装 Erlang 语言库.就像 java 需 ...

随机推荐

  1. mPaaS 小程序架构解析 | 实操演示小程序如何实现多端开发

    对于 mPaaS 小程序开发框架,想必读者们并不陌生.它源自于支付宝小程序框架,继承了易开发性.跨平台性及 Native 性能,不仅帮助开发者实现面向自有 App 投放小程序,还可快速构建打包,覆盖支 ...

  2. Spring Boot 集成 Elasticsearch 实战

    最近有读者问我能不能写下如何使用 Spring Boot 开发 Elasticsearch(以下简称 ES) 相关应用,今天就讲解下如何使用 Spring Boot 结合 ES. 可以在 ES 官方文 ...

  3. JAVA三种多线程实现方法和应用总结

    最近在做代码优化时学习和研究了下JAVA多线程的使用,看了菜鸟们的见解后做了下总结.1.JAVA多线程实现方式JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用Exe ...

  4. C#LeetCode刷题之#58-最后一个单词的长度(Length of Last Word)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3927 访问. 给定一个仅包含大小写字母和空格 ' ' 的字符串, ...

  5. 论如何实现最强大脑黑白迭代(c++附代码)

    最近看了最强大脑,对节目中的黑白迭代很感兴趣,就自己写了一个500多行的程序.燃鹅,只实现了一部分功能,还非常简陋.无奈之下,我只好从网上下载了一份代码,然后自己稍加修改就成了一份半改编的代码. 虽然 ...

  6. LeetCode 122 best-time-to-buy-and-sell-stock-ii 详解

    题目描述 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你可以尽可能地完成更多的交易(多次买卖一支股票). 注意:你不能同时参与多笔交易(你 ...

  7. C#图解教程(第四版)—01—类型,存储,变量

    3.1 如何广泛的描述C#程序 可以说C程序是一组函数和数据类型,C++程序是一组函数和类,然而C#程序是一组类型声明 3.2 类型 可以把类型想象成一个用来创建数据结构的模板,模板本身并不是数据结构 ...

  8. 编译Uboot时出错:【已解决】 /bin/bash: arm-linux-gcc: command not found dirname: missing operand Try 'dirname --help' for more information.

    编译Uboot时出错: 错误信息如下: /bin/bash: arm-linux-gcc: command not found dirname: missing operand Try 'dirnam ...

  9. 5.oracle用户管理

    一.创建用户概述:在oracle中要创建一个新的用户使用create user语句,一般是具有dba(数据库管理员)的权限才能使用.create user 用户名 identified by 密码;  ...

  10. SparkStreaming-DStream(Discretized Stream)

    DStream(Discretized Stream)离散流 ◆ 和Spark基于RDD的概念很相似,Spark Streaming使用离散流 (discretized stream)作为抽象表示,叫 ...