前言

秒杀架构到后期,我们采用了消息队列的形式实现抢购逻辑,那么之前抛出过这样一个问题:消息队列异步处理完每个用户请求后,如何通知给相应用户秒杀成功?

场景映射

首先,我们举一个生活中比较常见的例子:我们去银行办理业务,一般会选择相关业务打印一个排号纸,然后就可以坐在小板凳上玩着手机,等待被小喇叭报号。当小喇叭喊到你所持有的号码,就可以拿着排号纸去柜台办理自己的业务。

这里,假设当我们取排号纸的时候,银行根据时间段内的排队情况,比较人性化的提示用户:排队人数较多,您是否继续等待?否的话我们可以换个时间段再来办理。

由此我们把生活场景映射到真实的秒杀业务逻辑中来:

  • 我们可以把柜台比喻成商品下单处理逻辑单元
  • 拿到排号纸说明你进入相应商品处理队列
  • 拿到排号纸的请求直接返回前台,提示用户抢购进行中
  • 排号纸进入队列后,等待商品业务处理逻辑
  • 小喇叭叫到自己的排号相当于服务端通知用户秒杀成功,这时候可以进行支付逻辑
  • 那些拿不到票号的同学,相当于队列已满直接返回秒杀失败

解决方案

通过上面的场景,我们很容易能够想到一种方案就是服务端通知,那么如何做到服务端异步通知的呢?下面,主角开始登场了,就是我们的Websocket。

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。

特点:

  • 异步、事件触发
  • 可以发送文本,图片等流文件
  • 数据格式比较轻量,性能开销小,通信高效
  • 使用ws或者wss协议的客户端socket,能够实现真正意义上的推送功能

缺点:

  • 部分浏览器不支持,浏览器支持的程度与方式有区别,需要各种兼容写法。

集成案例

由于我们的秒杀架构项目案例中使用了SpringBoot,因此集成webSocket也是相对比较简单的。

首先pom.xml引入以下依赖:

<!-- webSocket 秒杀通知-->
<dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>

WebSocketConfig 配置:

/*** WebSocket配置* 创建者  爪哇笔记* 创建时间 2018年5月29日*/
@Configuration
public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } 

WebSocketServer 配置:

@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer { private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class); //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //接收userId private String userId=""; /** * 连接建立成功调用的方法*/ @OnOpen public void onOpen(Session session,@PathParam("userId") String userId) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 log.info("有新窗口开始监听:"+userId+",当前在线人数为" + getOnlineCount()); this.userId=userId; try { sendMessage("连接成功"); } catch (IOException e) { log.error("websocket IO异常"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息*/ @OnMessage public void onMessage(String message, Session session) { log.info("收到来自窗口"+userId+"的信息:"+message); //群发消息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); error.printStackTrace(); } /** * 实现服务器主动推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群发自定义消息 * */ public static void sendInfo(String message,@PathParam("userId") String userId){ log.info("推送消息到窗口"+userId+",推送内容:"+message); for (WebSocketServer item : webSocketSet) { try { //这里可以设定只推送给这个userId的,为null则全部推送 if(userId==null) { item.sendMessage(message); }else if(item.userId.equals(userId)){ item.sendMessage(message); } } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }

KafkaConsumer 消费配置,通知用户是否秒杀成功:

/*** 消费者 spring-kafka 2.0 + 依赖JDK8* @author 科帮网 By https://blog.52itstyle.com*/
@Component
public class KafkaConsumer { @Autowired private ISeckillService seckillService; private static RedisUtil redisUtil = new RedisUtil(); /** * 监听seckill主题,有消息就读取 * @param message */ @KafkaListener(topics = {"seckill"}) public void receiveMessage(String message){ //收到通道的消息之后执行秒杀操作 String[] array = message.split(";"); if(redisUtil.getValue(array[0])!=null){//control层已经判断了,其实这里不需要再判断了 Result result = seckillService.startSeckil(Long.parseLong(array[0]), Long.parseLong(array[1])); if(result.equals(Result.ok())){ WebSocketServer.sendInfo(array[0].toString(), "秒杀成功");//推送给前台 }else{ WebSocketServer.sendInfo(array[0].toString(), "秒杀失败");//推送给前台 redisUtil.cacheValue(array[0], "ok");//秒杀结束 } }else{ WebSocketServer.sendInfo(array[0].toString(), "秒杀失败");//推送给前台 } } }

webSocket.js 前台通知逻辑:

$(function(){socket.init();
});
var basePath = "ws://localhost:8080/seckill/"; socket = { webSocket : "", init : function() { //userId:自行追加 if ('WebSocket' in window) { webSocket = new WebSocket(basePath+'websocket/1'); } else if ('MozWebSocket' in window) { webSocket = new MozWebSocket(basePath+"websocket/1"); } else { webSocket = new SockJS(basePath+"sockjs/websocket"); } webSocket.onerror = function(event) { alert("websockt连接发生错误,请刷新页面重试!") }; webSocket.onopen = function(event) { }; webSocket.onmessage = function(event) { var message = event.data; alert(message)//判断秒杀是否成功、自行处理逻辑 }; } }

客户端API

客户端与服务器通信

  • send() 向远程服务器发送数据
  • close() 关闭该websocket链接

监听函数 

  • onopen 当网络连接建立时触发该事件
  • onerror 当网络发生错误时触发该事件
  • onclose 当websocket被关闭时触发该事件
  • onmessage 当websocket接收到服务器发来的消息的时触发的事件,也是通信中最重要的一个监听事件。msg.data

readyState属性

这个属性可以返回websocket所处的状态。

  • CONNECTING(0) websocket正尝试与服务器建立连接
  • OPEN(1) websocket与服务器已经建立连接
  • CLOSING(2) websocket正在关闭与服务器的连接
  • CLOSED(3) websocket已经关闭了与服务器的连接

开源方案

goeasy

GoEasy实时Web推送,支持后台推送和前台推送两种:后台推送可以选择Java SDK、 Restful API支持所有开发语言;前台推送:JS推送。无论选择哪种方式推送代码都十分简单(10分钟可搞定)。由于它支持websocket 和polling两种连接方式所以兼顾大多数主流浏览器,低版本的IE浏览器也是支持的。

地址:http://goeasy.io/

Pushlets

Pushlets 是通过长连接方式实现“推”消息的。推送模式分为:Poll(轮询)、Pull(拉)。

地址:http://www.pushlets.com/

Pushlet

Pushlet 是一个开源的 Comet 框架,Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。

地址:https://github.com/wjw465150/Pushlet

总结

其实前面有提过,尽管WebSocket有诸多优点,但是,如果服务端维护很多长连接也是挺耗费资源的,服务器集群以及览器或者客户端兼容性问题,也会带来了一些不确定性因素。大体了解了一下各大厂的做法,大多数都还是基于轮询的方式实现的,比如:腾讯PC端微信扫码登录、京东商城支付成功通知等等。

有些小伙伴可能会问了,轮询岂不是会更耗费资源?其实在我看来,有些轮询是不可能穿透到后端数据库查询服务的,比如秒杀,一个缓存标记位就可以判定是否秒杀成功。相对于WS的长连接以及其不确定因素,在秒杀场景下,轮询还是相对比较合适的。

思考

最后,思考一个问题:100件商品,假如有一万人进行抢购,该如何设置队列长度?

秒杀案例:https://gitee.com/52itstyle/spring-boot-seckill

参考

https://blog.52itstyle.com/archives/736/

https://www.xoriant.com/blog/mobility/websocket-web-stateful-now.html

最后说一件重要的事,只怕有同学错过。云计算现在白菜价了,2018年各大云计算厂商竞相降价,云服务器低至300元/年。现在不管是学习linux系统,运行长时间工作的爬虫,上线个人App或小程序,搭建网站做个人站长,都是时候上云服务了,错过这一波可能你就掉队。这里整理了一份云计算优惠活动产品列表,点击拿走不谢!


本文转自:http://blog.51cto.com/itstyle/2146899

转载于:https://www.cnblogs.com/jebysun/p/9522105.html

从构建分布式秒杀系统聊聊WebSocket推送通知 1相关推荐

  1. 从构建分布式秒杀系统聊聊WebSocket推送通知

    前言 秒杀架构到后期,我们采用了消息队列的形式实现抢购逻辑,那么之前抛出过这样一个问题:消息队列异步处理完每个用户请求后,如何通知给相应用户秒杀成功? 场景映射 首先,我们举一个生活中比较常见的例子: ...

  2. 从构建分布式秒杀系统聊聊分布式锁

    从构建分布式秒杀系统聊聊分布式锁 1.案例介绍 在尝试了解分布式锁之前,大家可以想象一下,什么场景下会使用分布式锁? 单机应用架构中,秒杀案例使用ReentrantLcok或者synchronized ...

  3. 从构建分布式秒杀系统聊聊限流特技

    前言 俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的.两周前秒杀案例初步成型,分享到了中国最大的同 性友网站-码云.同时也收到了不少小伙伴的建议和投诉.我从不认为分布式.集 ...

  4. 从构建分布式秒杀系统聊聊线程池

    前言 从0到1构建分布式秒杀系统案例的代码已经全部上传至码云,文章也被分发到各个平台.其中也收到了不少小伙伴喜欢和反馈,有网友如是说: 说实话,能用上的不多,中小企业都不可能用到,大型企业也不是一个人 ...

  5. 从构建分布式秒杀系统聊聊验证码

    2019独角兽企业重金招聘Python工程师标准>>> 前言 为了拦截大部分请求,秒杀案例前端引入了验证码.淘宝上很多人吐槽,等输入完秒杀活动结束了,对,结束了...... 当然了, ...

  6. disruptor框架为什么不流行_从构建分布式秒杀系统聊聊Disruptor高性能队列

    前言 秒杀架构持续优化中,基于自身认知不足之处在所难免,也请大家指正,共同进步.文章标题来自码友的建议,希望可以把阻塞队列ArrayBlockingQueue这个队列替换成Disruptor,由于之前 ...

  7. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景 ...

  8. 锤子Smartisan系统杀后台,推送通知,闲鱼收不到消息或者延迟问题解决

    为了确保APP最大限度收到消息: 1.设置->应用管理->应用程序管理->你的APP->电量与优化->关闭"后台智能运行控制".开启"允许被 ...

  9. android自定义push通知_20个海外Web和App推送通知服务工具

    在App和网站中使用推送通知有不同的原因,并且在提高流量和与客户互动方面有很多好处.推送通知是一种交互式可点击消息,可将访问者直接引导至你的网站.它们可以帮助你以指数方式增加流量和参与率.因此,营销人 ...

最新文章

  1. 百度网络推广总结大家在做页面标题优化时需注意的事项!
  2. 常用类回顾之(String类)
  3. docker停止容器后配置_Docker 自学笔记 - april_aaa
  4. 卡耐基梅隆大学CMU Brandon Amos博士论文《可微优化机器学习建模》
  5. markdown html图片,Markdown语法对应的HTML标签实现
  6. ReactNative从零到完整项目-Flexbox使用
  7. [Maven实战-许晓斌]-[第二章]-2.5 Eclipse上面安装Maven插件 m2eclipse
  8. uniapp 跳转外部链接
  9. 服务器软硬件安装和配置,Windows Server 2016-系统安装软硬件要求
  10. 中兴echat_在追随用户的路上创新不止 — 中兴高达mini eChat解决方案
  11. HTML5 CSS3 专题 :诱人的实例 3D旋转木马效果相冊
  12. 标签ul与ol的区别及使用方法
  13. 中国十大可行性研究报告公司
  14. python秒数转化为时间用户jianpang_Python写了个域名注册查询脚本,支持自定义数字,字母,拼音组合以及域名长度....
  15. 用nero将rmvb格式影片刻录成vcd光盘
  16. 使用Python制作证件照
  17. 计算机软件理论考博复试,考博复试时的自我介绍
  18. 电力系统静态稳定性仿真Matlab编程 用simulink搭建搭建单机无穷大系统,对其静态稳定性进行仿真分析
  19. vue自定义打印模版
  20. Linux 新手学习任务

热门文章

  1. OpenHarmony 开发者大会2023 活动开始报名啦!抢票中ing
  2. 为什么学不好编程?——好文章一网打尽
  3. 配置IIS应用程序池
  4. country code table
  5. 【网络安全】beef-xss实操以及xss修复方案
  6. 将文本数据转换成arff文件
  7. [MySQL数据库]-基础多表练习题---员工工资
  8. 代理PW6276,高效同步升压转换芯片
  9. datagrip mysql 驱动_Datagrip2020下载MySQL驱动失败的问题
  10. 矩阵乘以一个系数和行列式乘以一个系数