​

首先不了解WebSocket的可以先看看这篇文章,以及传统的WebSocket方案是怎么做的,https://www.cnblogs.com/jeremylai7/p/16875115.html 这是用MQ解决的版本,那么这种方案存在什么问题呢。

第一:增加MQ,可能造成消息挤压、消息顺序的问题

第二:增加MQ,则还需要保证MQ的可用性

第三:每个socket服务都需要去消费消息,增加每个服务的压力(做无用功)

那么,基于以上问题,还有没有解决方案呢?

当然有!!!

首先我们理解一个逻辑,为什么WebSocket不能直接做集群,socket是一个长链接,当我们要给socket用户发送消息的时候,我们不知道用户是连接到哪一个服务上面的,这样就无法直接发送消息了

那么,我们能不能给每一个socket服务器增加一个标识,然后在用户连接的时候将用户与socket服务器的关系绑定起来,然后在使用的时候再去判断用户存在哪,再给指定的服务器发送消息不就解决问题了吗?

那么,我们来结合springcloud来完成这个工作,根据这个理论,其他方式也可以实现

首先,来看websocket服务,启动的时候主要注意的问题

@SpringBootApplication
public class WsApplication implements CommandLineRunner { public static void main(String[] args) {
//动态服务名
System.setProperty("SpringApplicationName", "WS-" + IdUtil.simpleUUID());
SpringApplication.run(WsApplication.class, args);
} @Override
public void run(String... args) {
System.out.println("项目启动完毕");
} }

需要注意的是动态服务名这里,每个服务的名字都是不一样的,这样就保证了每个服务的一个唯一性

spring:
application:
#随机名字,做ws集群使用
name: ${SpringApplicationName}
# name: ws
redis:
host: 127.0.0.1
cloud:
nacos:
server-addr: 127.0.0.1
config:
file-extension: yaml server:
port: 9090

这里用到了nacos与redis,使用的地方待会会有,其中SpringApplicationName是在启动的时候传入的

接下来看WebSocket链接时候需要注意的点

@Component
@ServerEndpoint("/ws/{userId}")
public class WebSocket { /**
* 存放用户信息
*/
private static final ConcurrentHashMap<Long, WebSocket> WEB_SOCKET_MAP = new ConcurrentHashMap<>(16);
/**
* session
*/
private Session session; private Long userId; private String applicationName = System.getProperty("SpringApplicationName"); private StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class); /**
* 静态常量
*/
private static final String SOCKET_USER_SPRING_APPLICATION_NAME = "ws:socket:user:spring:application:name"; /**
* 当有新的WebSocket连接完成时
*
* @param session
* @param userId
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") Long userId) {
System.out.println("new connection");
System.out.println(userId);
this.session = session;
//根据token获取用户信息
this.userId = userId;
WEB_SOCKET_MAP.put(this.userId, this);
this.stringRedisTemplate.opsForHash().put(SOCKET_USER_SPRING_APPLICATION_NAME, userId + "", applicationName);
}
}

其中在链接的时候,将用户ID与socket服务的关系保存进了redis,这样我们在使用的时候就可以根据这个关系,找到对应的socket服务从而实现自己的业务逻辑

然后我们定义一个发送消息的接口

@RestController
@RequestMapping("push")
public class PushController { @PostMapping("{userId}")
public void pushMessage(@PathVariable Long userId, @RequestBody JSONObject message) {
WebSocket.sendMessage(userId, message);
}
}

再单独封装一个接口,供使用方使用feign

@FeignClient(name = "pushFeign", configuration = DynamicRoutingConfig.class)
public interface PushFeign { /**
* 推送消息
*
* @param serviceName 服务名
* @param userId 用户
* @param message 消息体
*/
@PostMapping(value = "//{serviceName}/push/{userId}")
void pushMessage(@PathVariable String serviceName, @PathVariable Long userId, @RequestBody JSONObject message);
}

再来个Service

@Service
public class PushService { @Resource
private PushFeign pushFeign; @Resource
private StringRedisTemplate stringRedisTemplate; /**
* 静态常量
*/
private static final String SOCKET_USER_SPRING_APPLICATION_NAME = "ws:socket:user:spring:application:name"; /**
* 发送消息
*
* @param userId
* @param message
*/
public void pushMessage(Long userId, JSONObject message) {
Object serviceName = this.stringRedisTemplate.opsForHash().get(SOCKET_USER_SPRING_APPLICATION_NAME, userId + "");
if (serviceName != null) {
this.pushFeign.pushMessage(serviceName.toString(), userId, message);
}
}
}

还有个feign的配置文件,将链接重写DynamicRoutingConfig

public class DynamicRoutingConfig {
@Bean
public RequestInterceptor cloudContextInterceptor() {
return template -> {
String url = template.url();
if (url.startsWith("//")) {
url = "http:" + url;
if (url.contains("?")) {
url = url.substring(0, url.indexOf("?"));
}
template.target(url);
template.uri("");
}
};
}
}

那么在使用的时候,我们可以直接调用PushService.pushMessage方法就可以直接给对应的用户发送消息了

那么可能又有人想问了,每个服务都不一样,那网关这些该怎么做,项目源码已经放在了码云上面, https://gitee.com/liupan1230/spring-cloud-websocket-cluster  大家可以参考,同时也有发送方调用示例

​

WebSocket集群解决方案,不用MQ的更多相关文章

  1. Websocket集群解决方案

    最近在项目中在做一个消息推送的功能,比如客户下单之后通知给给对应的客户发送系统通知,这种消息推送需要使用到全双工的websocket推送消息. 所谓的全双工表示客户端和服务端都能向对方发送消息.不使用 ...

  2. Terrocotta - 基于JVM的Java应用集群解决方案

    前言 越来越多的企业关键应用都必须采用集群技术,实现负载均衡(Load Balancing).容错(Fault Tolerance)和灾难恢复(Failover).以达到系统可用性(High Avai ...

  3. zookeeper、solrcloud、rediscluster集群解决方案

        集群解决方案 课程目标 目标1:说出什么是集群以及与分布式的区别 目标2:能够搭建Zookeeper集群 目标3:能够搭建SolrCloud集群 目标4:能够搭建RedisCluster集群 ...

  4. 高可用性、负载均衡的mysql集群解决方案

    高可用性.负载均衡的mysql集群解决方案 一.mysql的市场占有率 二.mysql为什么受到如此的欢迎 三.mysql数据库系统的优缺点 四.网络服务器的需求 五.什么是mysql的集群 六.什么 ...

  5. t-io 集群解决方案以及源码解析

    t-io 集群解决方案以及源码解析 0x01 概要说明 本博客是基于老谭t-io showcase中的tio-websocket-showcase 示例来实现集群.看showcase 入门还是挺容易的 ...

  6. springBoot+websocket集群系列知识

    WebSocket简介和spring boot集成简单消息代理 Spring Boot 集成 websocket,使用RabbitMQ做为消息代理 Spring Websocket实现向指定的用户发送 ...

  7. redis集群搭建 不用ruby

    redis 从5开始 可以直接用redis-cli命令创建集群了,不用那么麻烦 安装ruby环境 redis配置文件需要修改的地方 port 7000 cluster-enabled yes clus ...

  8. spring websocket集群问题的简单记录

    目录 前言 解决方案 代码示例 前言 最近公司里遇到一个问题,在集群中一些websocket的消息丢失了. 产生问题的原理很简单,发送消息的服务和接收者连接的服务不是同一个服务. 解决方案 用中间件( ...

  9. 关于websocket集群中不同服务器的用户间通讯问题

    最近将应用部署到集群时遇到一个问题,即用户命中不同的服务器导致的用户间无法进行websocket通讯,在网上搜索到类似问题但都没有具体解决方案. 于是用redis的订阅发布功能解决了该问题,具体流程如 ...

  10. Redis 集群解决方案 Codis

    (来源:开源中国社区 http://www.oschina.net/p/codis) Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生 ...

随机推荐

  1. new关键字的作用

    new关键字会进行如下的操作: 1. 创建一个空的简单JavaScript对象(即`{}`): 2. 为步骤1新创建的对象添加属性`__proto__`,将该属性链接至构造函数的原型对象 : 3. 将 ...

  2. Day15-static、抽象类、接口、内部类

    static.抽象类.接口.内部类 一.static关键字详解 1.静态的变量/方法 package Demo02; //static public class Student { private s ...

  3. Ubuntu22.04 KubeSphere 安装K8S集群

    Ubuntu22.04 KubeSphere 安装K8S集群_Ri0n的博客-CSDN博客 一.系统环境系统:Ubuntu 22.04集群IP分布hostname 角色 IP地址master mast ...

  4. AX2012 使用HTML自定义popup内样式

    在Class Box下新增方法如下: public client static DialogButton yesNoHTML( str _text, DialogButton _defaultButt ...

  5. Erlang Mnesia数据库迁移方法

    本文参考https://blog.csdn.net/yangzm/article/details/51686249 需求 因为一些原因,需要把一个Mnesia节点的数据库搬迁到另一个节点,然后弃用原来 ...

  6. module ‘pip‘ has no attribute ‘pep425tags‘的解决方案

    可行方案: E:\pyth\Anaconda\envs>python -m pip debug --verboseWARNING: This command is only meant for ...

  7. 关于PLC的脉冲输出(S7-300)

    1.   关于脉冲输出 脉冲输出的方法有很多: 如果要产生占空比为50%的脉冲信号: ①     用S7-300PLC的时钟存储器 右键点击PLC,选中时钟存储器,默认存储字节为0. 各时钟存储器的周 ...

  8. Tomcat源码部署

    1.下载源码 https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.54/src/apache-tomcat-9.0.54-src.zip 2.解压 3.在解压后 ...

  9. 1022 Digital Library (30分)

    本题题意很好读,看上去也不难写 写完运行才发现输出title只有一个单词... 后来把cin >> t换成了getline(cin, t) 还有一个坑点: Line #1: the 7-d ...

  10. iframe页面加载完成为什么还是获取不到里面的dom

    iframe页面加载完成为什么还是获取不到里面的dom? 因为Iframe是跨域,跨域的情况下是无法获取到iframe里面的DOM的,即使iframe加载完成,也无法获取到里面的DOM. 有什么方法获 ...