Redis解决websocket在分布式场景下session共享问题
欢迎关注方志朋的博客,回复”666“获面试宝典
来源:blog.csdn.net/weixin_45089791/article/
details/118028312
在显示项目中遇到了一个问题,需要使用到websocket与小程序建立长链接。由于项目是负载均衡的,存在项目部署在多台机器上。这样就会存在一个问题,当一次请求负载到第一台服务器时,socketsession在第一台服务器线程上,第二次请求,负载到第二台服务器上,需要通过id查找当前用户的session时,是查找不到的。
可以看到,由于websocket的session并没有实现序列化接口。所以无法将session序列化到redis中。
web的中的httpsession 主要是通过下面的两个管理器实现序列化的。
org.apache.catalina.session.StandardManagerorg.apache.catalina.session.PersistentManager
StandardManager是Tomcat默认使用的,在web应用程序关闭时,对内存中的所有HttpSession对象进行持久化,把他们保存到文件系统中。默认的存储文件为
<tomcat安装目录>/work/Catalina/<主机名>/<应用程序名>/sessions.ser
PersistentManager比StandardManager更为灵活,只要某个设备提供了实现org.apache.catalina.Store
接口的驱动类,PersistentManager就可以将HttpSession对象保存到该设备。
所以spring-session-redis
解决分布场景下的session共享就是将session序列化到redis中间件中,使用filter 加装饰器模式解决分布式场景httpsession 共享问题。
解决方案
使用消息中间件解决websocket session共享问题。
使用redis的发布订阅模式解决
本文使用方式二
使用StringRedisTemplate的convertAndSend方法向指定频道发送指定消息:
this.execute((connection) -> {connection.publish(rawChannel, rawMessage);return null;}, true);
redis的命令publish channel message
添加一个监听的容器以及一个监听器适配器
@BeanRedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter){RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);// 可以添加多个 messageListener,配置不同的交换机container.addMessageListener(listenerAdapter, new PatternTopic(Constants.REDIS_CHANNEL));// 订阅最新消息频道return container;}@BeanMessageListenerAdapter listenerAdapter(RedisReceiver receiver){// 消息监听适配器return new MessageListenerAdapter(receiver, "onMessage");}
添加消息接收器
/*** 消息监听对象,接收订阅消息*/
@Component
public class RedisReceiver implements MessageListener {Logger log = LoggerFactory.getLogger(this.getClass());@Autowiredprivate WebSocketServer webSocketServer;/*** 处理接收到的订阅消息*/@Overridepublic void onMessage(Message message, byte[] pattern){String channel = new String(message.getChannel());// 订阅的频道名称String msg = "";try{msg = new String(message.getBody(), Constants.UTF8);//注意与发布消息编码一致,否则会乱码if (!StringUtils.isEmpty(msg)){if (Constants.REDIS_CHANNEL.endsWith(channel))// 最新消息{JSONObject jsonObject = JSON.parseObject(msg);webSocketServer.sendMessageByWayBillId(Long.parseLong(jsonObject.get(Constants.REDIS_MESSAGE_KEY).toString()),jsonObject.get(Constants.REDIS_MESSAGE_VALUE).toString());}else{//TODO 其他订阅的消息处理}}else{log.info("消息内容为空,不处理。");}}catch (Exception e){log.error("处理消息异常:"+e.toString());e.printStackTrace();}}
}
websocket的配置类
/*** @description: websocket的配置类*/
@Configuration
@EnableWebSocket
public class WebSocketConfiguration {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
添加websocket的服务组件
@ServerEndpoint("/websocket/{id}")
@Component
public class WebSocketServer {private static final long sessionTimeout = 600000;private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);/*** 当前在线连接数*/private static AtomicInteger onlineCount = new AtomicInteger(0);/*** 用来存放每个客户端对应的 WebSocketServer 对象*/private static ConcurrentHashMap<Long, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 接收 id*/private Long id;@Autowiredprivate StringRedisTemplate template;/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("id") Long id) {session.setMaxIdleTimeout(sessionTimeout);this.session = session;this.id = id;if (webSocketMap.containsKey(id)) {webSocketMap.remove(id);webSocketMap.put(id, this);} else {webSocketMap.put(id, this);addOnlineCount();}log.info("编号id:" + id + "连接,当前在线数为:" + getOnlineCount());try {sendMessage("连接成功!");} catch (IOException e) {log.error("编号id:" + id + ",网络异常!!!!!!");}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {if (webSocketMap.containsKey(id)) {webSocketMap.remove(id);subOnlineCount();}log.info("编号id:" + id + "退出,当前在线数为:" + getOnlineCount());}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, Session session) {log.info("编号id消息:" + id + ",报文:" + message);}/*** 发生错误时调用** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("编号id错误:" + this.id + ",原因:" + error.getMessage());error.printStackTrace();}/*** @description: 分布式 使用redis 去发布消息* @dateTime: 2021/6/17 10:31*/public void sendMessage(@NotNull String key,String message) {String newMessge= null;try {newMessge = new String(message.getBytes(Constants.UTF8), Constants.UTF8);} catch (UnsupportedEncodingException e) {e.printStackTrace();}Map<String,String> map = new HashMap<String, String>();map.put(Constants.REDIS_MESSAGE_KEY, key);map.put(Constants.REDIS_MESSAGE_VALUE, newMessge);template.convertAndSend(Constants.REDIS_CHANNEL, JSON.toJSONString(map));}/*** @description: 单机使用 外部接口通过指定的客户id向该客户推送消息。* @dateTime: 2021/6/16 17:49*/public void sendMessageByWayBillId(@NotNull Long key, String message) {WebSocketServer webSocketServer = webSocketMap.get(key);if (!StringUtils.isEmpty(webSocketServer)) {try {webSocketServer.sendMessage(message);log.info("编号id为:"+key+"发送消息:"+message);} catch (IOException e) {e.printStackTrace();log.error("编号id为:"+key+"发送消息失败");}}log.error("编号id号为:"+key+"未连接");}/*** 实现服务器主动推送*/public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}public static synchronized AtomicInteger getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount.getAndIncrement();}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount.getAndDecrement();}
}
项目结构
将该项目使用三个端口号启动三个服务
使用下面的这个网站进行演示。
http://www.easyswoole.com/wstool.html
启动两个页面网址分别是:
ws://127.0.0.1:8081/websocket/456
ws://127.0.0.1:8082/websocket/456
使用postman给http://localhost:8080/socket/456
发送请求
可以看到,我们给8080服务发送的消息,我们订阅的8081和8082 服务可以也可以使用该编号进行消息的推送。
使用8082服务发送这个消息格式{"KEY":456,"VALUE":"aaaa"} 的消息。其他的服务也会收到这个信息。
以上就是使用redis的发布订阅解决websocket 的分布式session 问题。
码云地址是:https://gitee.com/jack_whh/dcs-websocket-session
热门内容:
一款基于 Spring Boot 的现代化社区
Spring Boot 3.0 M1 发布,正式弃用 Java 8,最低要求 Java 17。。。
「冰墩墩」代码,开源了!
比 Xshell 还好用的 SSH 客户端神器,MobaXterm 太爱了!
干掉visio,这个画图神器真的绝了!!!
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡
Redis解决websocket在分布式场景下session共享问题相关推荐
- Redis 解决 WebSocket 分布式场景下 Session共享问题
点击上方"服务端思维",选择"设为星标" 回复"669"获取独家整理的精选资料集 回复"加群"加入全国服务端高端社群「后 ...
- 分布式场景下的并发安全问题,收割快手,字节,百度,美团的Offer之旅
try { lock.lock(); SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss&quo ...
- 干货 | 解决分布式场景下数据一致性问题,我有办法!
此次分享的缘由 支付重构 考虑支付重构的时候,自然想到原本属于一个本地事务中的处理,现在要跨应用了要怎么处理.拿充值订单举个栗子吧,假设:原本订单模块和账户模块是放在一起的,现在需要做服务拆分,拆分成 ...
- sqlserver检测到基于一致性的逻辑_面试官;解决分布式场景下数据一致性问题
在这一篇中主要回答目前分布式事务问题是怎么解决的?行业中有什么解决方案?这些解决方案分别有什么优缺点?别人是怎么做的?我们可以怎么来做? 支付重构 考虑支付重构的时候,自然想到原本属于一个本地事务中的 ...
- 分布式场景下数据一致性的问题——【分布式锁】 Java常用技术方案
2019独角兽企业重金招聘Python工程师标准>>> 前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解 ...
- 如何在分布式场景下生成全局唯一 ID ?
作者 l 会点代码的大叔(CodeDaShu) 在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或 ...
- session共享怎么做的(分布式如何实现session共享)?
session共享怎么做的(分布式如何实现session共享)? 问题描述:一个用户在登录成功以后会把用户信息存储在session当中,这时session所在服务器为server1,那 么用户在 s ...
- 【Redis】分布式场景下Redis高可用部署方案
文章内容 1.Redis高可用概述 2.持久化 1)RDB持久化 2)AOF持久化 3.Redis主从复制 4.Redis哨兵模式 3.Redis集群模式 1.Redis高可用概述 参考博文: 深入学 ...
- 【基于唯品会MP平台】集群环境下session共享技术方案及分布式单点登录
背景 传统的B/S架构的系统中,一般为单点部署,并不存在集群,所以也不存在session丢失的问题.那么,由于单点部署一旦宕机,无法保证系统可用性,那我们就想到把它扩展为多台服务器部署,这样既保证了系 ...
最新文章
- 阿里公共DNS正式发布:223.5.5.5 223.6.6.6
- android 漂亮的表单界面,面向ArcGIS for Android的表单界面生成器的设计与实现
- 【基础操作】线性基详解
- oracle 实现ID自增
- 图像处理与图像识别笔记(六)图像增强3
- 定义一个函数模板,要求返回两个值中的较小值。
- 用Jenkins自动化搭建测试环境-前奏
- 腾讯员工每年写3万行代码;马斯克承认利用摄像头监视车主;Chrome 89上线实时字幕 | 极客头条...
- 和华为杯_2020华为手机杯围甲联赛圆满闭幕 助力围棋文化推广
- 2021 年全国大学生电子设计竞赛实施过程说明
- 下列不属于计算机完成科学计算机特点的是,通知|1906统考模拟题计算机试卷2...
- 打开英伟达控制面板超时打不开解决办法
- Java入门 J2SE
- 常见不透明度16进制转换
- 360产品无法安装,此程序被组策略阻止
- 2013武汉住房公积金新政详解
- SQL解决过去M天内连续N天符合某条件问题
- mybatis 中between and用法
- Win10 屏幕保护突然不出现怎么办
- oracle基础|什么是数据库操作语言|什么是DDL、DML、DCL
热门文章
- Oracle发布开源的轻量级 Java 微服务框架 Helidon
- Ubuntu任务栏Tint2安装与使用
- 面向对象(类的概念,属性,方法,属性的声明,面向对象编程思维
- Python 之 杂谈(迭代器iter)、偏函数
- MSF渗透测试-CVE-2017-11882(MSOffice漏洞)
- 计算机网络面试知识总结1
- [Git/Github] ubuntu 14.0 下github 配置
- 线性代数:04 特征值与特征向量 -- 矩阵的相似对角化
- 利用BP神经网络教计算机识别语音特征信号(代码部分SSR)
- 计算机在轻工行业中的应用,计算机在不同领域中的具体应用