文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何从构建分布式秒杀系统进行WebSocket推送通知

2023-06-05 07:04

关注

这期内容当中小编将会给大家带来有关如何从构建分布式秒杀系统进行WebSocket推送通知,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

前言

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

场景映射

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

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

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

解决方案

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

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

HTTP VS WebSocket

如何从构建分布式秒杀系统进行WebSocket推送通知

特点:

缺点:

集成案例

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

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

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

WebSocketConfig 配置:

@Configuration  public class WebSocketConfig {      @Bean      public ServerEndpointExporter serverEndpointExporter() {          return new ServerEndpointExporter();      }  }

WebSocketServer 配置:

@ServerEndpoint("/websocket/{userId}")@Componentpublic 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());    }        @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();           }        }    }        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 消费配置,通知用户是否秒杀成功:

@Componentpublic class KafkaConsumer {    @Autowired    private ISeckillService seckillService;    private static RedisUtil redisUtil = new RedisUtil();        @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

客户端与服务器通信
监听函数 
readyState属性

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

开源方案

goeasy

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

Pushlets

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

Pushlet

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

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

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

上述就是小编为大家分享的如何从构建分布式秒杀系统进行WebSocket推送通知了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯