您的位置 首页 java

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

本文是“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异步”:

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

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

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

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

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

至此,关于秒杀系统的优化(还有之前介绍过的分布式唯一ID、业务服务模块异步解耦、用户认证、邮件通知等也是其中的优化项)之路我们就暂时到这里了。

值得一提的是,各位小伙伴会发现我们做的这些优化大部分是“开发层面”的,而事实上,在“运维层面”也是大有文章可做的,比如我们可以:

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

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

(3)数据库Mysql做主备部署,实现读写分离,即一个Master,多个Slave,其中Master充当写角色、Slave充当读角色,提供数据库层面的操作效率。

当然,还有很多很多,各位小伙伴有啥好的建议或者方案都可以拿出来提一提,或者加入技术群讨论讨论都是OK的!

相关视频教程可私信咨询。

推荐阅读:

文章来源:智云一二三科技

文章标题:Java秒杀系统实战系列-秒杀逻辑优化之RabbitMQ接口限流二

文章地址:https://www.zhihuclub.com/193978.shtml

关于作者: 智云科技

热门文章

网站地图