WebSocket集群解决方案,不用MQ
首先不了解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的更多相关文章
- Websocket集群解决方案
最近在项目中在做一个消息推送的功能,比如客户下单之后通知给给对应的客户发送系统通知,这种消息推送需要使用到全双工的websocket推送消息. 所谓的全双工表示客户端和服务端都能向对方发送消息.不使用 ...
- Terrocotta - 基于JVM的Java应用集群解决方案
前言 越来越多的企业关键应用都必须采用集群技术,实现负载均衡(Load Balancing).容错(Fault Tolerance)和灾难恢复(Failover).以达到系统可用性(High Avai ...
- zookeeper、solrcloud、rediscluster集群解决方案
集群解决方案 课程目标 目标1:说出什么是集群以及与分布式的区别 目标2:能够搭建Zookeeper集群 目标3:能够搭建SolrCloud集群 目标4:能够搭建RedisCluster集群 ...
- 高可用性、负载均衡的mysql集群解决方案
高可用性.负载均衡的mysql集群解决方案 一.mysql的市场占有率 二.mysql为什么受到如此的欢迎 三.mysql数据库系统的优缺点 四.网络服务器的需求 五.什么是mysql的集群 六.什么 ...
- t-io 集群解决方案以及源码解析
t-io 集群解决方案以及源码解析 0x01 概要说明 本博客是基于老谭t-io showcase中的tio-websocket-showcase 示例来实现集群.看showcase 入门还是挺容易的 ...
- springBoot+websocket集群系列知识
WebSocket简介和spring boot集成简单消息代理 Spring Boot 集成 websocket,使用RabbitMQ做为消息代理 Spring Websocket实现向指定的用户发送 ...
- redis集群搭建 不用ruby
redis 从5开始 可以直接用redis-cli命令创建集群了,不用那么麻烦 安装ruby环境 redis配置文件需要修改的地方 port 7000 cluster-enabled yes clus ...
- spring websocket集群问题的简单记录
目录 前言 解决方案 代码示例 前言 最近公司里遇到一个问题,在集群中一些websocket的消息丢失了. 产生问题的原理很简单,发送消息的服务和接收者连接的服务不是同一个服务. 解决方案 用中间件( ...
- 关于websocket集群中不同服务器的用户间通讯问题
最近将应用部署到集群时遇到一个问题,即用户命中不同的服务器导致的用户间无法进行websocket通讯,在网上搜索到类似问题但都没有具体解决方案. 于是用redis的订阅发布功能解决了该问题,具体流程如 ...
- Redis 集群解决方案 Codis
(来源:开源中国社区 http://www.oschina.net/p/codis) Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生 ...
随机推荐
- vscode php 方法无法跳转问题
安装扩展 PHP IntelliSense #路径感知 PHP Intelephense #自动补全 PHP DocBlocker #在类或属性.方法上面敲/**回车自动生成参数注释等 PHP Na ...
- 通过modbus+socket+GPRS采集硬件数据
# !/usr/bin/python # -*- coding: UTF-8 -*- import sys import os TESTCASE = os.path.dirname(os.path.a ...
- P4921 [MtOI2018]情侣?给我烧了!
前言 情人节写的这道题,题目名称好符合我当时的心情. 题目链接 Luogu:P4921 解法 容斥 我们发现最后要求的结果是恰好 \(k\) 对情侣坐在一起的方案数,我们就不难想到去计算恰好 \(n- ...
- Flutter showModalBottomSheet 顶部圆角
showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRad ...
- php 解密 java des 密文
背景 对方系统java des 加密, 我们用 php 解密 模式基本介绍 ECB模式:电子密本方式,JAVA封装的DES算法的默认模式,就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节 ...
- P3366 模板最小生成树
题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz. 输入格式 第一行包含两个整数N,M,表示该图共有N个结点和M条无向边. 接下来M行每行包含三个整数 Xi,Yi,Zi,表 ...
- 导入maven包时,非常慢的解决方案
在导入maven包时,很多时候导的很慢,导致找不到包,需要将maven包下载地址替换为aliyun的地址,以下为解决方案 找到使用的maven地址,在文件内添加以下节点然后重启ide即可 <mi ...
- Matlab %壹
第一章 基本操作 MATLAB as A Calculator operators: + - * / ^ 顺序: Parenthesis () Power (^) *or/ +or- 特殊的: sqr ...
- linux 部署轻量级堡垒机Next Terminal
对比 类似的开源堡垒机有 Teleport,Jumpserver,Next Terminal等等. Teleport 安全性最好,较轻量,但是对被管理资产不透明,纳管前需要双向认证,在资产设备上需额外 ...
- scrapy.Request callback不执行
1.在scrapy.Request方法里边加上参数dont_filter=True(去重) 2.增加是不是allowed_domains范围内的url 3.yield Request 改为yield ...