目录
工作中遇到即时聊天的需求,由于服务是集群部署,需要实现session共享,之前写过使用Redis队列实现的博客,供大家参考
通过Redis发布者/订阅者模式实现websocket的session共享_mlwsmqq的博客-CSDN博客_redis websocket由于项目是集群部署,需要实现对websocket的session共享,可websocket的session无法序列化,不能存放到Redis当中,因此我们可以把websocket的session存放在服务器的map上,通过Redis的广播把消息发送到指定的频道上,每个服务器节点都订阅该频道,从而消息一经发布都能收到再从map中获取session完成消息的推送1、引入所需依赖<dependency> <groupId>org.springframewor...https://blog.csdn.net/mlwsmqq/article/details/121736412 本文使用阿里开源的RocketMq实现session共享,闲话不说,干货奉上
1、引入所需依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、RocketMq配置
rocketmq:
name-server: 192.168.xx.xx:xxxx
producer:
group: producer-xxxx
send-message-timeout: 6000
3、websocket配置文件
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4、发送消息
本文只展示推送消息的实现代码,代码贴的不全,主要是思路,大家理解了就能够举一反三
public void sendMessage(String userId, String message) {
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put(RocketMqConstant.KEY_USER_ID,userId);
jsonObject.put(RocketMqConstant.KEY_MESSAGE,message);
Message<JSONObject> mes = MessageBuilder.withPayload(jsonObject).build();
rocketMQTemplate.syncSend(RocketMqConstant.DESTINATION_SHARE_SESSION_SEND_MESSAGE,mes);
} catch (Exception e) {
log.error("WebsocketServer-sendMessage-exception:",e);
}
}
5、监听消息
注意消费模式是广播消费:messageModel = MessageModel.BROADCASTING,这样消息就会被所有服务上的消费者消费,就能判断session是否存在从而推送消息
package com.zzw.redops.listener.share.sendMessage;
import com.alibaba.fastjson.JSONObject;
import com.zzw.redops.constant.RocketMqConstant;
import com.zzw.redops.utils.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
/**
* @Auther:admin
* @Date:2022/11/2 16:41
* session共享:推送消息监听
*/
@Slf4j
@Component
@RocketMQMessageListener(topic = RocketMqConstant.TOPIC_REDOPS_BACKEND,
consumerGroup = RocketMqConstant.CONSUMER_SHARE_SESSION_SEND_MESSAGE,
selectorExpression = RocketMqConstant.TAG_SHARE_SESSION_SEND_MESSAGE,
messageModel = MessageModel.BROADCASTING,
consumeThreadMax = 128)
public class SendMessageListener implements RocketMQListener<MessageExt> {
@Resource
private WebSocketServer webSocketServer;
@Override
public void onMessage(MessageExt messageExt) {
try {
JSONObject parseObject = JSONObject.parseObject(new String(messageExt.getBody(), StandardCharsets.UTF_8));
String userId = parseObject.getString(RocketMqConstant.KEY_USER_ID);
log.info("share-session:监听到推送消息:{}",userId);
webSocketServer.sendToFront(userId,parseObject.getString(RocketMqConstant.KEY_MESSAGE));
}catch (Exception e){
log.error("SendMessageListener exception:",e);
}
}
}
public void sendToFront(String userId, String message){
try {
if (StringUtils.isBlank(userId)) {
log.info("sendToFront-用户ID为空:message:{} userId:{}",message,userId);
return;
}
// 根据用户ID获取session
List<WebSocketSession> list = getWebSocketSession(userId);
if (CollectionUtil.isNotEmpty(list)) {
for (WebSocketSession item : list) {
sendMessage(item, message, userId);
}
}else {
log.info("不推送消息:userId:{} session:{}",userId,list);
}
} catch (Exception e) {
log.error("WebsocketServer-sendToFront-exception:",e);
}
}
public static void sendMessage(WebSocketSession wssession, String message, String userId){
if (Objects.isNull(wssession)) {
log.info("向前端推送消息时session为空:message:{} session:{}",message,wssession);
return;
}
try {
synchronized (wssession) {
wssession.getSession().getBasicRemote().sendText(message);
log.info("推送消息成功:用户ID:{}",userId);
}
} catch (Exception e) {
log.error("sendText exception:",e);
}
}
// 根据用户ID获取session
public List<WebSocketSession> getWebSocketSession(String userId) {
try {
if (StringUtils.isBlank(userId)) {
return Collections.emptyList();
}
List<WebSocketSession> result = new ArrayList<>();
for (String key : webSocketMap.keySet()) {
if (userId.equals(key)) {
result.add(webSocketMap.get(key));
}
}
// 如果一个用户ID对应多个会话,取最新的会话返回
if (result.size() > 1) {
// 按照创建时间倒序排序
List<WebSocketSession> collect = result.parallelStream().sorted(Comparator.comparing(WebSocketSession::getCreateTime).reversed()).limit(1).collect(Collectors.toList());
log.info("会话重复时返回最新会话:{}", collect);
return collect;
}
return result;
} catch (Exception e) {
log.error("WebsocketServer-getWebSocketSession-exception:",e);
}
return Collections.emptyList();
}
6、效果展示
线上和本地分别启动一台服务,从RocketMq控制台可看出消费组已经有两个实例注册上了
在作战室中发送消息看下打印日志
线上日志:
本地日志:
从日志可看出:发送消息的用户session信息是保存在线上服务的,线上能获取到session就向前端推送消息,本地获取不到就不推送,这样就达到想要的效果了
欢迎大家指正错误,转载请注明出处!!!