欢迎关注方志朋的博客,回复”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共享问题相关推荐

  1. Redis 解决 WebSocket 分布式场景下 Session共享问题

    点击上方"服务端思维",选择"设为星标" 回复"669"获取独家整理的精选资料集 回复"加群"加入全国服务端高端社群「后 ...

  2. 分布式场景下的并发安全问题,收割快手,字节,百度,美团的Offer之旅

    ​ try { ​ lock.lock(); ​ SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss&quo ...

  3. 干货 | 解决分布式场景下数据一致性问题,我有办法!

    此次分享的缘由 支付重构 考虑支付重构的时候,自然想到原本属于一个本地事务中的处理,现在要跨应用了要怎么处理.拿充值订单举个栗子吧,假设:原本订单模块和账户模块是放在一起的,现在需要做服务拆分,拆分成 ...

  4. sqlserver检测到基于一致性的逻辑_面试官;解决分布式场景下数据一致性问题

    在这一篇中主要回答目前分布式事务问题是怎么解决的?行业中有什么解决方案?这些解决方案分别有什么优缺点?别人是怎么做的?我们可以怎么来做? 支付重构 考虑支付重构的时候,自然想到原本属于一个本地事务中的 ...

  5. 分布式场景下数据一致性的问题——【分布式锁】 Java常用技术方案

    2019独角兽企业重金招聘Python工程师标准>>> 前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解 ...

  6. 如何在分布式场景下生成全局唯一 ID ?

    作者 l 会点代码的大叔(CodeDaShu) 在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或 ...

  7. session共享怎么做的(分布式如何实现session共享)?

    session共享怎么做的(分布式如何实现session共享)?  问题描述:一个用户在登录成功以后会把用户信息存储在session当中,这时session所在服务器为server1,那 么用户在 s ...

  8. 【Redis】分布式场景下Redis高可用部署方案

    文章内容 1.Redis高可用概述 2.持久化 1)RDB持久化 2)AOF持久化 3.Redis主从复制 4.Redis哨兵模式 3.Redis集群模式 1.Redis高可用概述 参考博文: 深入学 ...

  9. 【基于唯品会MP平台】集群环境下session共享技术方案及分布式单点登录

    背景 传统的B/S架构的系统中,一般为单点部署,并不存在集群,所以也不存在session丢失的问题.那么,由于单点部署一旦宕机,无法保证系统可用性,那我们就想到把它扩展为多台服务器部署,这样既保证了系 ...

最新文章

  1. 阿里公共DNS正式发布:223.5.5.5 223.6.6.6
  2. android 漂亮的表单界面,面向ArcGIS for Android的表单界面生成器的设计与实现
  3. 【基础操作】线性基详解
  4. oracle 实现ID自增
  5. 图像处理与图像识别笔记(六)图像增强3
  6. 定义一个函数模板,要求返回两个值中的较小值。
  7. 用Jenkins自动化搭建测试环境-前奏
  8. 腾讯员工每年写3万行代码;马斯克承认利用摄像头监视车主;Chrome 89上线实时字幕 | 极客头条...
  9. 和华为杯_2020华为手机杯围甲联赛圆满闭幕 助力围棋文化推广
  10. 2021 年全国大学生电子设计竞赛实施过程说明
  11. 下列不属于计算机完成科学计算机特点的是,通知|1906统考模拟题计算机试卷2...
  12. 打开英伟达控制面板超时打不开解决办法
  13. Java入门 J2SE
  14. 常见不透明度16进制转换
  15. 360产品无法安装,此程序被组策略阻止
  16. 2013武汉住房公积金新政详解
  17. SQL解决过去M天内连续N天符合某条件问题
  18. mybatis 中between and用法
  19. Win10 屏幕保护突然不出现怎么办
  20. oracle基础|什么是数据库操作语言|什么是DDL、DML、DCL

热门文章

  1. Oracle发布开源的轻量级 Java 微服务框架 Helidon
  2. Ubuntu任务栏Tint2安装与使用
  3. 面向对象(类的概念,属性,方法,属性的声明,面向对象编程思维
  4. Python 之 杂谈(迭代器iter)、偏函数
  5. MSF渗透测试-CVE-2017-11882(MSOffice漏洞)
  6. 计算机网络面试知识总结1
  7. [Git/Github] ubuntu 14.0 下github 配置
  8. 线性代数:04 特征值与特征向量 -- 矩阵的相似对角化
  9. 利用BP神经网络教计算机识别语音特征信号(代码部分SSR)
  10. 计算机在轻工行业中的应用,计算机在不同领域中的具体应用