文档章节

Java秒杀系统实战系列~秒杀逻辑优化之RabbitMQ接口限流二

稳杰
 稳杰
发布于 2019/08/23 16:59
字数 1621
阅读 11
收藏 0

摘要:

本篇博文是“Java秒杀系统实战系列文章”的第十八篇,我们将继续秒杀系统的优化之路。在本篇文章中我们将基于RabbitMQ异步通信、FIFO(先进先出)、接口限流的特性,在执行秒杀核心的处理逻辑之前架上一层“限流”的处理逻辑,从而让瞬时产生的,犹如波涛汹涌、潮水般的请求流量变得井井有条、有序性地到达后端的秒杀接口!

内容

接着上一篇章的讲解,我们需要在后端 接收前端高并发产生多线程请求时,及时高效地转移巨大的用户请求之MQ中间件中,为后端秒杀接口赢得足够的、规范化的处理!在这一过程,前端和后端的交互是异步的,因此,在前后端处理逻辑层面跟前面篇章的处理方式将有所不同。

(1)首先,在Controller层,需要提供响应前端秒杀请求的方法,该方法不直接处理秒杀的核心业务逻辑,而是将其转移至MQ中间件中,并立即返回success的状态信息给回到前端,其代码如下所示:

@Autowired
private RabbitSenderService rabbitSenderService;

//商品秒杀核心业务逻辑-mq限流
@RequestMapping(value = prefix+"/execute/mq",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public BaseResponse executeMq(@RequestBody @Validated KillDto dto, BindingResult result, HttpSession session){
    if (result.hasErrors() || dto.getKillId()<=0){
        return new BaseResponse(StatusCode.InvalidParams);
    }
    Object uId=session.getAttribute("uid");
    if (uId==null){
        return new BaseResponse(StatusCode.UserNotLogin);
    }
    Integer userId= (Integer)uId ;

    BaseResponse response=new BaseResponse(StatusCode.Success);
    Map<String,Object> dataMap= Maps.newHashMap();
    try {
        dataMap.put("killId",dto.getKillId());
        dataMap.put("userId",userId);
        response.setData(dataMap);

        dto.setUserId(userId);
        rabbitSenderService.sendKillExecuteMqMsg(dto);
    }catch (Exception e){
        response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
    }
    return response;
}

(2)前端info.jsp再提交秒杀请求并接收到后端的返回信息后,便立即跳转至相应的页面,即秒杀结果查看页(准备查看相应的秒杀结果的),该页面是通过响应后端Controller器方法进行跳转的,其页面的js代码如下所示:

function executeKillMq() {
    $.ajax({
        type: "POST",
        url: "${ctx}/kill/execute/mq",
        contentType: "application/json;charset=utf-8",
        data: JSON.stringify(getJsonData()),
        dataType: "json",
        success: function(res){
            if (res.code==0) {
			  //立即跳转至“秒杀结果查看页”
                window.location.href="${ctx}/kill/execute/mq/to/result?killId="+$("#killId").val()
            }else{
                window.location.href="${ctx}/kill/execute/fail"
            }
        },
        error: function (message) {
            alert("提交数据失败!");
            return;
        }
    });
}

其中,Controller对应的跳转页面的方法代码如下所示:

//商品秒杀核心业务逻辑-mq限流-立马跳转至抢购结果页
@RequestMapping(value = prefix+"/execute/mq/to/result",method = RequestMethod.GET)
public String executeToResult(@RequestParam Integer killId,HttpSession session,ModelMap modelMap){
    Object uId=session.getAttribute("uid");
    if (uId!=null){
        Integer userId= (Integer)uId ;

        modelMap.put("killId",killId);
        modelMap.put("userId",userId);
    }
    return "executeMqResult";
}

其中executeMqResult.jsp主要用于查看当前用户对于当前商品的秒杀结果,页面代码比较简单,在这里就不贴出来了;下面只贴出其发起查询秒杀结果的js请求代码,如下所示:

<script type="text/javascript">
  $(function () {
      //等待一定的时间再查询显示结果-给后端赢得足够的时间
      setTimeout(showResult,5000);
  });

  function showResult() {
      var killId=$("#killId").val();
      var userId=$("#userId").val();

      $.ajax({
          type: "GET",
          url: "${ctx}/kill/execute/mq/result?killId="+killId+"&userId="+userId,
          success: function(res){
              if (res.code==0) {
                  $("#executeResult").html(res.data.executeResult);
                  $("#waitResult").html("");
              }else{
                  $("#executeResult").html(res.msg);
              }
          },
          error: function (message) {
              alert("提交数据失败!");
              return;
          }
      });
  }
</script>

其对应的Controller的请求方法如下所示:

//商品秒杀核心业务逻辑-mq限流-在抢购结果页中发起抢购结果的查询
@RequestMapping(value = prefix+"/execute/mq/result",method = RequestMethod.GET)
@ResponseBody
public BaseResponse executeResult(@RequestParam Integer killId,@RequestParam Integer userId){
    BaseResponse response=new BaseResponse(StatusCode.Success);
    try {
        Map<String,Object> resMap=killService.checkUserKillResult(killId,userId);
        response.setData(resMap);
    }catch (Exception e){
        response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
    }
    return response;
}

(3)其中,killService.checkUserKillResult(killId,userId);方法的功能主要是根据killId和userId在item_kill_success表查询用户的秒杀结果,其源代码如下所示:

//检查用户的秒杀结果
@Override
public Map<String,Object> checkUserKillResult(Integer killId, Integer userId) throws Exception {
    Map<String,Object> dataMap= Maps.newHashMap();
    KillSuccessUserInfo info=itemKillSuccessMapper.selectByKillIdUserId(killId,userId);
    if (info!=null){
        dataMap.put("executeResult",String.format(env.getProperty("notice.kill.item.success.content"),info.getItemName()));
        dataMap.put("info",info);
    }else{
        throw new Exception(env.getProperty("notice.kill.item.fail.content"));
    }
    return dataMap;
}

而itemKillSuccessMapper.selectByKillIdUserId(killId,userId);对应的动态Sql的写法如下所示:

<!--根据秒杀成功后killId+userId的订单编码查询-->
<select id="selectByKillIdUserId" resultType="com.debug.kill.model.dto.KillSuccessUserInfo">
  SELECT
  a.*,
  b.user_name,
  b.phone,
  b.email,
  c.name AS itemName
  FROM item_kill_success AS a
  LEFT JOIN user b ON b.id = a.user_id
  LEFT JOIN item c ON c.id = a.item_id
  WHERE a.kill_id=#{killId} AND a.user_id=#{userId}
  AND b.is_active = 1
</select>

至此,关于RabbitMQ的接口限流篇章我们也就介绍完毕了,下面给大家展示一下整体的效果:

(1)首先当然是抢购页啦!为了区别之前的“抢购”,我们加上了一个新按钮,“抢购-MQ异步”:

(2)点击“抢购-MQ异步”按钮,前端将立即跳转至“抢购结果等待页”,如下图所示:

等待一定的时间之后发起查询“秒杀结果”的请求,最终即可在页面显示秒杀的结果,如下图所示:

(4)当然,Debug还提供了一个用于JMeter压测的请求方法,代码在这里就不贴出来,可以点击文末提供的链接前往查看!不过,值得一贴的是Debug亲自压测过后的效果图,如下图所示:

至此,关于秒杀系统的优化(还有之前介绍过的分布式唯一ID、业务服务模块异步解耦、用户认证、邮件通知等也是其中的优化项)之路我们就暂时到这里了。值得一提的是,各位小伙伴会发现我们做的这些优化大部分是“开发层面”的,而事实上,在“运维层面”也是大有文章可做的,比如我们可以

(1)使用中间件的集群提供服务的高可用,比如Redis集群、ZooKeeper集群、RabbitMQ集群等等

(2)Nginx集群、实现负载均衡,并从服务器的层面实现初步限流

(3)数据库Mysql做主备部署,实现读写分离,即一个Master,多个Slave,其中Master充当写角色、Slave充当读角色,提供数据库层面的操作效率。当然,还有很多很多,各位小伙伴有啥好的建议或者方案都可以拿出来提一提,或者加入技术群讨论讨论都是OK的!

© 著作权归作者所有

稳杰
粉丝 4
博文 18
码字总数 31856
作品 0
广州
高级程序员
私信 提问
加载中

评论(0)

SpringBoot整合RabbitMQ之典型应用场景实战二

实战前言 RabbitMQ 作为目前应用相当广泛的消息中间件,在企业级应用、微服务应用中充当着重要的角色。特别是在一些典型的应用场景以及业务模块中具有重要的作用,比如业务服务模块解耦、异步...

小红牛
2018/11/21
0
0
爬虫架构 | 消息队列应用场景及ActiveMQ、RabbitMQ、RocketMQ、Kafka对比

前言:在之前的业务中,使用了Kafka和RabbitMQ两种消息队列,这篇文章来做一个总结。 消息队列中间件是分布式系统中重要的组件,主要实现异步消息,应用解耦,流量削峰及消息通讯等功能。 下...

小怪聊职场
2018/04/26
0
0
基于 Spring Boot 的电商秒杀系统 - jseckill

一个基于 Spring Boot 2.1 的电商秒杀系统。 让程序员能够轻松进阶 Java 高并发架构,很适合中小型互联网项目的电商秒杀,抢票等场景。采用最新的 Spring Boot, MyBatis 版本。 架构图 秒杀步...

刘少明Frank
2019/03/11
9.4K
10
高并发架构系列:Kafka、RocketMQ、RabbitMQ的优劣势比较

在高并发业务场景下,典型的阿里双11秒杀等业务,消息队列中间件在流量削峰、解耦上有不可替代的作用。 我之前介绍了【MQ消息队列的12点核心原理总结】,【如何从0到1设计一个MQ消息队列】,...

mikechen优知
2019/01/09
0
0
Rabbitmq---消息队列

一 . MQ:message queue   消息队列的作用:   1 通信解耦   2 高峰限流 原理分析: 一开始,认证系统是强耦合的,A系统传递认证系统消息接收计算结果的过程中   1 传给认证系统   2 认...

Ala6
2018/11/14
523
2

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周三乱弹 —— 提高不了工作效率和脸有关系

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 @薛定谔的兄弟 :分享洛神有语创建的歌单「我喜欢的音乐」: 1 《夏令时记录(piano.ver)》- ゆめこ 手机党少年们想听歌,请使劲儿戳(这里) ...

小小编辑
今天
67
2
List的一波操作

public static void main(String[] args) { List<Entity> list = new ArrayList<>(); list.add(new Entity(1)); list.add(new Entity(2)); list.add(new Entity(3)); ......

那个猩猩很亮
今天
75
0
Spring基础

主要用于service层; 轻量级java开发框架; 各层 web层:struts,spring-MVC service层:spring dao层:hibernate,mybatis , jdbcTemplate --> spring-data Spring核心:控制反转IOC 切面编...

七宝1
今天
30
0
解决overflow+border-radius+transform圆角问题

网上还有其他版本,但是对我来说都不好使,下面是我在Chrome上的代码。overflow:hidden依然是不能正常使用,换成unset就可以,读者如果有更好的解决方案,请留言,谢谢。 <figure> <img...

hi懒喵
今天
53
0
《C语言》—— 数组

书籍使我变成了一个幸福的人,使我的生活变成轻松而舒适的诗。——高尔基 本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues。 https://github.com/midou-tech/articles 点关注,不迷路!...

龙跃十二
今天
84
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部