前情提要

之前我的项目里有一个合作编辑的功能,多个客户端的用户可以合作写一篇文章的不同部分,而且合作的任意作者互相还可以进行文字通讯。这种需求肯定是首选websocket了,因为服务器需要主动给客户端推送消息,维持一个长连接是最经济实惠的手段。如果一个客户端需要给另一个客户端做推送,那肯定是需要中间服务器作为中转。当两个客户端注册到同一个服务器的时候,发送方带上消息接受者的相关信息,服务器替发送方去对接受者做推送,这样实现了通讯的功能。

那么,这个设计有没有什么问题呢?

在微服务的时代,很多时候需要考虑的是部署多台机器的情况。这时候,上述的思路就不好用了。例如,当客户端A注册到了第一台机器,客户端B注册到了第二台机器,这时候如果客户端A还希望第一台机器为他完成转发的功能,显然是找不到客户端B的。这就是websocket的集群化问题。这问题也让我陷入了深深的思考,那就是产生了一个问题:微信是如何做出来的?

设计个微信?

当很久很久以前,微信还是一个小而美的通讯软件,只有通讯的功能和朋友圈功能。在这个时期,不妨思考思考微信会怎么做。消息通讯的核心仍然是websocket,只不过微信不可能只有一台服务器,微信肯定是一个集群才能正常运转,只有一台机器维持不了那么多的连接。这时候很容易想到微信应该是利用了消息中间件对消息进行了精准投递。对于不在线的用户,微信应该是将离线消息储存到了数据库,用户上线时进行查库,收到消息。

何为“精准投递”?

当客户端A想给客户端B投送消息时,服务器1需要找到客户端B注册到了哪台机器,这时可以考虑用数据库将客户端B建立连接的机器写到数据库里,通过这个数据去找到对应的机器。这时候,消息队列就产生了它应有的作用。核心在于Rabbit MQ的Topic交换机可以绑定多条队列,而且可以选择性推送到指定的队列。所以这时候就产生一个思考,每台机器进行一条队列的绑定,服务器和队列的对应关系可以使用服务器的ip地址,这样保证了唯一性,而且可以复用这个ip标志。

思路设计

当客户端A和服务器1建立连接时,需要往Redis里维持一份会话,这份会话信息保存了客户端A和服务器1建立了连接,也就是保存了服务器1的ip地址。客户端2也是如此。当客户端A想给客户端B发送消息时,先从redis里找到客户端B注册到机器的ip地址,进而确定消息投递的队列。当客户端B收到消息后,将消息推送到客户端B。

代码

那么看看代码吧:

 public static String WS_QUEUE = "wx_queue_";
public static final String WS_TOPIC_EXCHANGE = "wx_topic_exchange";
public static final String WS_BINDING_KEY = "wx_exchange_"; @Bean("WS_QUEUE")
public Queue wsQueue() {
WS_QUEUE += serverIpHost;
return new Queue(WS_QUEUE);
} //交换机
@Bean("WS_TOPIC_EXCHANGE")
public TopicExchange wsExchange() {
return new TopicExchange(WS_TOPIC_EXCHANGE);
} //绑定队列和ES交换机
@Bean
public Binding wsTopicBinding(@Qualifier("WS_QUEUE") Queue wsQueue, @Qualifier("WS_TOPIC_EXCHANGE") TopicExchange wsExchange) {
return BindingBuilder.bind(wsQueue).to(wsExchange).with(WS_BINDING_KEY + serverIpHost);
}

将本机ip写到配置文件里,在初始化队列的时候,将队列名字和该ip关联起来。不同机器这个地方是需要改配置的。也就是一台机器对应一个队列。

对于某一个通讯ws接口:

    @MessageMapping("/chat/{from}/{to}/{blogId}")
public void chat(String msg, @DestinationVariable String from, @DestinationVariable Long to, @DestinationVariable Long blogId) {
UserEntityVo userEntityVo = MyUtils.jsonToObj(redisTemplate.opsForHash().get(Const.CO_PREFIX + blogId, to.toString()), UserEntityVo.class);
if (userEntityVo != null) {
String toServerIpHost = userEntityVo.getServerIpHost();
String idStr = blogId.toString();
String toStr = to.toString();
ChatDto dto = MyUtils.transferToDto(Message.class, ChatDto.class, new Object[]{msg, from, toStr, idStr},
new Class[]{msg.getClass(), from.getClass(), toStr.getClass(), idStr.getClass()});
rabbitTemplate.convertAndSend(
RabbitConfig.WS_TOPIC_EXCHANGE,RabbitConfig.WS_BINDING_KEY + toServerIpHost,
dto);
}
}

即是从redis查出对方所在机器的ip地址,然后投递到那个队列里。


@Bean("WSMessageListener")
//processMessage作为listener
MessageListenerAdapter wSMessageListener(WSMessageHandler wSMessageHandler) {
return new MessageListenerAdapter(wSMessageHandler, "processMessage");
} //在container内将queue和listener绑定
@Bean("WSMessageListenerContainer")
SimpleMessageListenerContainer wSMessageListenerContainer(ConnectionFactory connectionFactory,
@Qualifier("WSMessageListener") MessageListenerAdapter listenerAdapter,
@Qualifier("WS_QUEUE") Queue queue) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queue.getName());
container.setMessageListener(listenerAdapter);
return container;
} public void processMessage(MessageDto msg) { String methodName = msg.getMethodName(); switch (methodName) {
...
case "chat":
Container<Message> containerV4 = msg.getData();
Message message = containerV4.getData();
String id = message.getBlogId();
String to = message.getTo();
simpMessagingTemplate.convertAndSendToUser(to, "/" + id + "/queue/chat", message);
break;
... }

这里监听到方法的机器其实就是客户端B所在的机器,其他机器不监听这个队列,是不会做消息转发的尝试的。这里直接推送给客户端B就可以了。

方案的优点

这个方案的优点就是理论上集群可以无限扩张,每台机器的代码并不需要改,只是配置文件会有一些差异。当然也可以将ip地址换成uuid,只要有唯一性就可以了。

一个分布式websocket的实现的更多相关文章

  1. 分享在Linux下使用OSGi.NET插件框架快速实现一个分布式服务集群的方法

    在这篇文章我分享了如何使用分层与模块化的方法来设计一个分布式服务集群.这个分布式服务集群是基于DynamicProxy.WCF和OSGi.NET插件框架实现的.我将从设计思路.目标和实现三方面来描述. ...

  2. Elasticsearch是一个分布式可扩展的实时搜索和分析引擎,elasticsearch安装配置及中文分词

    http://fuxiaopang.gitbooks.io/learnelasticsearch/content/  (中文) 在Elasticsearch中,文档术语一种类型(type),各种各样的 ...

  3. Flink 另外一个分布式流式和批量数据处理的开源平台

    Apache Flink是一个分布式流式和批量数据处理的开源平台. Flink的核心是一个流式数据流动引擎,它为数据流上面的分布式计算提供数据分发.通讯.容错.Flink包括几个使用 Flink引擎创 ...

  4. Dubbo[一个分布式服务框架

    http://alibaba.github.io/dubbo-doc-static/User+Guide-zh.htm#UserGuide-zh-API%E9%85%8D%E7%BD%AE http: ...

  5. JStorm 是一个分布式实时计算引擎

    alibaba/jstorm JStorm 是一个分布式实时计算引擎. JStorm 是一个类似Hadoop MapReduce的系统, 用户按照指定的接口实现一个任务,然后将这个任务递交给JStor ...

  6. Cola:一个分布式爬虫框架 - 系统架构 - Python4cn(news, jobs)

    Cola:一个分布式爬虫框架 - 系统架构 - Python4cn(news, jobs) Cola:一个分布式爬虫框架 发布时间:2013-06-17 14:58:27, 关注:+2034, 赞美: ...

  7. 日志采集框架Flume以及Flume的安装部署(一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统)

    Flume支持众多的source和sink类型,详细手册可参考官方文档,更多source和sink组件 http://flume.apache.org/FlumeUserGuide.html Flum ...

  8. Sql Server 中如果使用TransactionScope开启一个分布式事务,使用该事务两个并发的连接会互相死锁吗

    提问: 如果使用TransactionScope开启一个分布式事务,使用该事务两个并发的连接会互相死锁吗? 如果在.Net中用TransactionScope开启一个事务. 然后在该事务范围内启动两个 ...

  9. Spring Cloud 5分钟搭建教程(附上一个分布式日志系统项目作为参考) - 推荐

    http://blog.csdn.net/lc0817/article/details/53266212/ https://github.com/leoChaoGlut/log-sys 上面是我基于S ...

  10. 设计一个分布式RPC框架

    0 前言 提前先祝大家春节快乐!好了,先简单聊聊. 我从事的是大数据开发相关的工作,主要负责的是大数据计算这块的内容.最近Hive集群跑任务总是会出现Thrift连接HS2相关问题,研究了解了下内部原 ...

随机推荐

  1. CoreText学习笔记

    CoreText是Apple系统的文字渲染引擎. 我们先看一个字符的字形图,了解一下一个字形包含的部分: 它的坐标系为窗口的左下角为视图的原点(跟Mac系统一样的坐标系),而iOS系统的坐标系为窗口的 ...

  2. Wahahahehehe 随笔归档

    [连载教程类] <RISC-V MCU 原理与应用> 1. 计算机理论模型--图灵机 2. 从图灵机到计算机 3. MCU构成及其运行原理 <RISC-V MCU 开发那些事> ...

  3. 浏览器打开微信小程序

    function h5() { $wx = new Wx('appId', 'appSecret'); // \dump($wx->getAccessToken()); $url = 'http ...

  4. SSD1306 屏幕使用-Micropython

    1.I2C总线是什么? I2C:俗称集成电路总线,是一种简单.双向二线制同步串行通信总线,使用多主从架构.它只需要两根线即可在连接于总线上的器件之间传送信息. 主器件用于启动总线传送数据,并产生时钟以 ...

  5. 实用的JavaScript技巧

    1.数组去重 let arr = [...new Set([1,2,3,2,1])]; //输出:[1, 2, 3] 2.删除数组中的虚值(undefined .null.NaN.0 .'' .fal ...

  6. UVM reg model 常见问题记录

    1.仿真log中报出大量的"include_coverage not located, did you mean ***"? (1) user在构建register model或者 ...

  7. 安装使用反编译工具ILSPY

    一.ILSPY简介1.1.ILSPY介绍 ILSPY是一款开源.免费的.且适用于.NET平台反编译[C#语言编写的程序和库(.dll)内容]工具:可以集成在Visual Studio 开发工具中,能够 ...

  8. oracle之PGA相关的sql

    在上篇文章中初步介绍了关于pga的基础知识,阅读了其他很多关于pga的内容,今天总结一些关于pga的sql和其他知识. 在网上找了相关资料整理而来,可能有点乱,先码上后再整理下. https://bl ...

  9. tp项目部署到宝塔,运行nginx时无法访问首页之外的页面

    http://www.upwqy.com/details/254.html tp项目 部署到宝塔里面 运行环境nginx 直接访问首页是可以访问的.但是请求其他的接口的时候 报404 . 需要把下面这 ...

  10. Linux下获取线程ID tid的方法

    使用Linux Redhat7编写代码的时候,需要使用 gettid() 函数获取线程ID.使用 man gettid 命令查看了一下,gettid()函数的头文件是 #include<sys/ ...