Spring-WebSocket快速实现WebSocket双向通信
Spring-WebSocket快速实现WebSocket双向通信
标签:#后端# #JAVA# #SpringBoot# #通信# #物联网#
在物联网开发和业务系统开发中,与第三方设备的对接以及实时数据传输是常见的需求。虽然MQTT是目前流行的解决方案,但WebSocket依然是一个值得考虑的选择。WebSocket能够提供稳定且低延迟的双向通信,适用于多种实时数据交互场景。
最近在开发一个工单的消息推送通知功能。这个功能需要将工单的实时状态更新及时推送给相关人员,确保相关操作人能够快速响应。纠结了好久的SEE最后出局,我选择了WebSocket来实现这一功能。WebSocket的双向通信机制能够很好地满足工单通知的实时性要求,确保消息能够即时送达,从而提升整个工单处理流程的效率和用户体验。
一、WebSocket简介
1.1 WebSocket是什么
WebSocket 是一种网络通信协议,它提供了一种在单个 TCP 连接上进行全双工通信的方式。与传统的 HTTP 协议相比,WebSocket 允许服务器和客户端之间进行实时、双向的数据交互,而不需要频繁地重新建立连接。一旦 WebSocket 连接建立,客户端和服务器就可以随时发送消息,这使得它非常适合需要实时通信的应用场景,如在线游戏、聊天应用、实时数据推送等。
1.2 WebSocket与Socket
1.2.1 Socket
Socket:Socket 是一种更底层的通信接口,它允许不同主机上的进程之间进行网络通信。Socket 可以基于 TCP 或 UDP 协议,它提供了面向连接(TCP)或无连接(UDP)的通信方式。Socket 的使用相对复杂,需要手动管理连接、数据传输和错误处理。
1.2.2 WebSocket
WebSocket:WebSocket 是一种基于 TCP 的应用层协议,它在 HTTP 协议的基础上进行了扩展。WebSocket 的握手过程通过 HTTP 协议完成,一旦握手成功,就会升级为 WebSocket 协议进行通信。WebSocket 简化了实时通信的实现,提供了更高级的抽象,使得开发者可以更方便地实现客户端和服务器之间的双向通信。
1.2.3 联系
WebSocket 是基于 Socket 的一种应用层协议。Socket 是一种网络编程接口,用于在不同设备之间建立通信连接。WebSocket 协议在 TCP/IP 协议栈的传输层之上运行,它利用了 Socket 提供的底层通信能力来实现客户端和服务器之间的双向通信。
1.3 通信模式
WebSocket 的通信模式是全双工的,这意味着客户端和服务器可以同时发送和接收消息,而不需要像传统的 HTTP 请求/响应模式那样等待对方的响应。这种通信模式使得 WebSocket 非常适合需要实时交互的应用场景,例如:
- 实时聊天应用:用户之间的消息可以即时传递,而不需要轮询服务器。
- 在线游戏:游戏中的玩家动作和状态可以实时同步。
- 股票交易平台:实时推送股票价格和交易信息。
1.4 适用场景
WebSocket 适用于以下场景:
- 实时聊天应用:如微信、QQ 等即时通讯工具。
- 在线游戏:需要实时同步游戏状态和玩家动作。
- 金融交易平台:实时推送股票价格、交易信息等。
- 物联网设备监控:实时监控设备状态,接收设备数据。
- 协同办公工具:如在线文档编辑、项目管理工具等,需要实时同步数据。
1.5 WebSocket相比MQTT的优点和缺点
1.5.1 WebSocket
-
优点:
- 低延迟:实时双向通信,适合需要快速响应的场景。
- 浏览器支持:原生支持,无需额外库,开发简单。
- 数据灵活:支持文本和二进制数据,格式自定义。
-
缺点:
- 无订阅机制:需要自定义协议实现消息分类。
- 连接管理复杂:需手动处理心跳和重连。
- 带宽占用高:适合客户端数量较少的场景。
1.5.2 MQTT
-
优点:
- 发布/订阅模型:支持一对多、多对多通信,适合设备间松耦合。
- 轻量级:报文头小,适合带宽受限环境。
- 可靠性高:支持QoS,确保消息可靠传输。
- 自动化管理:自动心跳检测和遗嘱消息。
-
缺点:
- 依赖代理:需要中心代理(Broker),增加管理成本。
- 延迟稍高:消息需经代理转发。
- 客户端复杂:需要客户端库支持。
二、SpringBoot引入WebSocket
2.1 快速开始
2.1.1 Maven引入WebSocket依赖
在 Spring Boot 项目中,可以通过添加以下依赖来引入 WebSocket 支持:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.1.2 配置类
@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator{
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
2.1.3 Endpoint
在 WebSocket 的上下文中,Endpoint 是指一个 WebSocket 连接的端点。它代表了 WebSocket 服务器端或客户端的一个连接点,用于处理 WebSocket 消息的发送和接收。在 2.1.2 中,配置的 ServerEndpointExporter
,就是将使用了 @ServerEndpoint
注解的类注册为 WebSocket Endpoint 的关键组件。
2.1.4 简历EndPoint端点
当有了配置类和Endpoint后,就当有了配置类和Endpoint后,下一步就是通过Endpoint
提供WebSocket的实际连接。
具体来说,需要定义一个Endpoint类,这个类将处理客户端的连接请求,并实现消息的接收和发送。提供一个Endpoint
端点来提供WebSocket的实际连接。这个Endpoint
类需要被@ServerEndpoint注解修饰。
// ServerEndpoint支持Restfule风格传参
@ServerEndpoint("/websocket/{username}")
public class MyWebSocketEndpoint {
}
这样,一个Endpoint端点被启用,客户端可以通过地址连接到该端点。此时可以通过WebSocket客户端连接。
但是当实际连接时候会发现出现秒连接秒断开的情况
22:53:35 已连接到 ws://localhost:8080/message/1
22:53:35 已断开连接 ws://localhost:8080/message/1
这是因为,假设当前系统是电话系统,目前的操作我们只是为号码 ws://localhost:8080/message/1 开通了线路,却没有安排任何接线员。
当客户端拨号后,交换机立刻完成物理接通,却因为无人值守,只能听到空白音,随后被强制挂断。
要让这条线路真正可用,需要在 MyWebSocketEndpoint 中实现以下三个核心方法,相当于为交换机配置完整的接线流程:
-
onOpen
——接机- 客户端振铃一响,接线员立即摘机并发送标准问候,标记会话为“已建立”。
- 这一步完成逻辑链路的确认,保证后续数据可以双向流通。
-
onMessage
—— 处理- 客户端讲话时,接线员实时记录并解析内容,按需查询或转发,随后将结果即时回传。
- 整个过程无缓冲延迟,形成真正的全双工对话。
-
onClose
—— 挂机
会话结束时,接线员发送结束语并主动释放线路,回收资源,确保下一次呼叫可立即接入。
只有当“接机—处理—挂机”三步全部就位,号码才不会只响一声就断;客户端与服务端才能通过这条线路完成完整、稳定的通话。
⚠️注意
这里的
接机-处理-挂机
流程并不是 TCP 握手那样的一次性“三步完成”。WebSocket 建立的是长连接,因此,状态机中的“接机-处理-挂机”可以长期停留在“处理”阶段,客户端只需保持连接,即可随时接收服务端主动推送的数据
为了完成接机-处理-挂机
需要引入@OnOpen
、@OnMessage
、OnClose
注解分别实现三个流程。在实现这些流程之前,首先需要明确一个操作对象,就像在Servlet中操作对象是HttpServletRequest和HttpServletResponse一样,在WebSocket中,操作对象是Session。Session对象用于管理WebSocket连接的生命周期,包括接收和发送消息、管理连接状态等。
2.2 Session及核心注解
2.2.1 Session会话
Session 是 WebSocket 中的关键概念,指一条已建立的 WebSocket 连接在存活期间所对应的、持续存在的双向通信会话。
Session 有以下几个生命周期:
-
OnOpen
:连接建立成功时触发,可在此完成身份校验、加入会话组、初始化资源并向客户端推送欢迎消息; -
OnMessage
:客户端发送文本或二进制帧到达时触发,用于解析报文、执行业务逻辑并通过同一 Session 回写结果; -
OnError
:当底层 TCP 异常、协议握手失败或业务代码抛出未捕获异常时触发,用于记录日志、回滚事务并安全关闭资源; -
OnClose
:连接无论由客户端、服务端主动断开或因网络超时被动关闭时触发,用于释放内存、移除会话映射、清理定时器并更新在线状态。
作为服务端,实际上就是围绕这几个生命周期所触发的回调方法进行操作:在 OnOpen
中完成“连接准入”与初始化,在 OnMessage
中持续处理“业务对话”,在 OnError
中兜底“异常恢复”,在 OnClose
中彻底“资源回收”。
⚠️注意
Spring-WebSocket并不会对Session进行统一管理,所以一般需要开发人员在
@OnOpen
生命周期时将Session和Session标识自行管理。这里不可避免的就会出现多线程之间的管理和通信问题,所以存储Session的容器需要保证线程安全,或者直接采用外部中间件如
Redis
来存储// ServerEndpoint支持Restfule风格传参 @ServerEndpoint("/websocket/{username}") public class MyWebSocketEndpoint { // 创建一个线程安全的 Map,用于保存所有已登录用户的 Session private static final Map<String, Session> ONLINE = new ConcurrentHashMap<>(); }
2.2.2 @OnOpen
触发时机:客户端与服务端完成 WebSocket 握手,连接正式建立。
典型职责:
- 将当前
Session
保存到全局ConcurrentMap
或 Redis,方便后续按用户 ID 定向推送。 - 通过 HTTP Session、JWT、URL 参数等方式完成身份校验,失败则立即
session.close()
。 - 记录用户上线日志、更新在线计数、向同一房间/群组广播“某某加入”。
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) throws IOException {
// 尝试从握手阶段放入的 UserProperties 中取出 userId
String userId = (String) session.getUserProperties().get("userId");
// 如果解析不到 userId,说明身份校验失败
if (userId == null) {
// 主动关闭连接,并给出关闭原因
session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "非法身份"));
// 提前结束方法
return;
}
// 将当前 Session 按 userId 保存到全局 Map 中
ONLINE.put(userId, session);
// 向客户端发送欢迎消息
session.getBasicRemote().sendText("欢迎 " + userId);
}
2.2.3 @OnMessage
触发时机:客户端每发来一个文本、二进制或 Pong 帧。
典型职责:
- 解析 JSON 消息体,得到业务指令与负载。
- 调用 Service 层完成数据库读写、AI 推理、第三方接口调用等耗时任务;耗时任务可丢入线程池或使用 Reactor 异步返回。
- 把结果按同一
Session
回写,或按房间/用户列表群发。 - 捕获业务异常,通过
@OnError
统一处理。
@OnMessage
public void onMessage(String json, Session session) {
MessageDTO dto = JSON.parseObject(json, MessageDTO.class);
String userId = (String) session.getUserProperties().get("userId");
// 异步执行业务
Mono<String> resultMono = chatService.reply(userId, dto.getContent());
resultMono.subscribe(reply -> {
try {
if (session.isOpen()) {
session.getBasicRemote().sendText(reply);
}
} catch (IOException e) {
log.error("发送消息失败", e);
}
});
}
2.2.4 @OnError
触发时机:
- 协议级异常(如帧格式错误、UTF-8 非法)
- I/O 异常(网络闪断、半开连接)
- 业务代码抛出的未捕获 RuntimeException
典型职责:
- 记录错误日志,带上
session.getId()
与用户信息,方便排查。 - 回滚数据库事务、释放分布式锁。
- 若异常可恢复,可尝试重连;不可恢复则主动关闭连接,避免资源泄漏。
@OnError
public void onError(Session session, Throwable throwable) {
String userId = (String) session.getUserProperties().get("userId");
log.error("用户 {} 连接异常: {}", userId, throwable.getMessage(), throwable);
// 清理资源
ONLINE.remove(userId);
}
2.2.5 @OnClose
触发时机:
- 客户端主动发送 Close 帧
- 服务端调用
session.close()
- 网络故障、心跳超时、应用重启
典型职责:
- 从全局
Map
/Redis 移除Session
记录。 - 更新在线人数、向房间广播“某某离开”。
- 关闭与 Session 绑定的定时任务、线程池任务、数据库连接。
- 如使用分布式会话存储,发送用户离线事件到 MQ,供其他微服务订阅。
@OnClose
public void onClose(Session session) {
String userId = (String) session.getUserProperties().get("userId");
ONLINE.remove(userId);
broadcast(userId + " 已离线");
}
private void broadcast(String msg) {
ONLINE.values().parallelStream()
.filter(Session::isOpen)
.forEach(s -> {
2.3 关于Endpoint和SpringIOC不统一管理的问题
2.3.1 产生该问题的原因
使用WebSocket进行通信时,避免不了和IOC容器中的其他Service打交道,当使用@Autowired注入Spring中已经注册的容器时会发现会注入为null。
这是因为在Spring Boot 项目中,WebSocket 的 @ServerEndpoint
由 JSR-356(Java-WebSocket 规范)容器 实例化,而不是由 Spring IOC 容器 实例化。因此:
- Endpoint 实例不受 Spring 托管,生命周期由 WebSocket 容器控制。
- 在 Endpoint 内部使用
@Autowired
、@Resource
直接注入 Spring Bean 会失效(注入为null
)。 - 业务层、工具类、缓存、DAO 等大量 Bean 都注册在 Spring IOC 中,WebSocket 端点无法直接使用,导致代码重复或耦合。
2.3.2 解决方案1:静态注入
思路:用一个普通的 Spring 单例 Bean 把依赖注入后,再转存到静态字段。
优点
- 实现简单:代码量少,容易理解和实现。
- 侵入性低:不需要对现有的 Spring 容器或 WebSocket 配置进行大量修改。
缺点
- 不支持
@Autowired
:需要手动设置静态字段,增加了代码的复杂性。 - 线程安全问题:静态字段可能会带来线程安全问题,需要谨慎处理。
- 扩展性差:如果需要注入多个 Bean,代码会变得复杂且难以维护。
优点
- 实现简单,侵入性低。
缺点
- 不支持
@Autowired
,需要手动设置静态字段。 - 静态字段的使用可能会带来线程安全问题,需要谨慎处理。
// 专门用于“桥接”的 Holder
@Component
public class SpringHolder {
// 单例 Bean 被 Spring 注入
@Autowired
public void setJwtTokenUtil(JwtTokenUtil jwtTokenUtil) {
ChatEndpoint.jwtTokenUtil = jwtTokenUtil;
}
}
@ServerEndpoint("/chat")
public class ChatEndpoint {
// 静态字段供端点内部使用
public static JwtTokenUtil jwtTokenUtil;
@OnMessage
public void onMessage(String msg, Session session) {
// 直接使用
String userId = jwtTokenUtil.parseUser(msg);
}
}
2.3.3 解决方案2:ApplicationContextAware
思路:封装一个 SpringUtils.getBean()
,内部利用 ApplicationContextAware
。
优点
- 不依赖额外类:不需要额外定义类来实现,侵入性较低。
- 灵活性高:可以在任何地方通过
SpringUtils.getBean()
获取 Bean,适用于多种场景。
缺点
- 手动获取 Bean:需要手动调用
SpringUtils.getBean()
方法来获取 Bean,使用起来不够方便。 - 多实例问题:如果项目中存在多个同类型的 Bean,需要额外指定 Bean 的名称来获取,增加了复杂性。
- 性能开销:每次调用
getBean()
方法都会进行查找,可能会带来一定的性能开销。
优点
- 不需要额外的类来实现,侵入性较低。
缺点
- 需要手动调用
SpringUtils.getBean()
方法来获取 Bean,使用起来不够方便。 - 如果项目中存在多个同类型的 Bean,需要额外指定 Bean 的名称来获取,增加了复杂性。
// 工具类
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return ctx.getBean(clazz);
}
}
@ServerEndpoint("/chat")
public class ChatEndpoint {
@OnMessage
public void onMessage(String msg) {
JwtTokenUtil util = SpringUtils.getBean(JwtTokenUtil.class);
util.parseUser(msg);
}
}
2.3.4 解决方案3:构造器注入 + SpringConfigurator
思路:把依赖声明成 final 字段,用构造器注入,同样依赖 SpringConfigurator。
优点
- 支持
@Autowired
:字段为final
,不可变,更符合现代编程规范。 - 依赖明确:通过构造器注入,依赖关系明确,易于理解和维护。
- 线程安全:
final
字段保证了线程安全性。
缺点
- 需要额外定义
SpringConfigurator
- 多实例问题:如果项目中存在多个同类型的 Bean,需要额外指定 Bean 的名称来注入,增加了复杂性。
@ServerEndpoint(value = "/chat", configurator = SpringConfigurator.class)
public class ChatEndpoint {
private final JwtTokenUtil jwtTokenUtil;
// 构造器注入由 Spring 完成
@Autowired
public ChatEndpoint(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@OnMessage
public void onMessage(String msg) {
jwtTokenUtil.parseUser(msg);
}
}
2.3.5 解决方案4:方法参数注入(Spring 5.3+)
思路:Spring 5.3 以后支持在 @ServerEndpoint
的方法参数里直接注入 Spring Bean,前提是使用 SpringConfigurator。
优点
- 支持
@Autowired
:使用起来非常方便,侵入性最低。 - 代码简洁:不需要额外定义类或字段,代码更加简洁。
- 线程安全:每次调用方法时都会注入新的 Bean,保证了线程安全性。
缺点
- 仅支持 Spring 5.3 及以上版本。
- 多实例问题:如果项目中存在多个同类型的 Bean,需要额外指定 Bean 的名称来注入。
@ServerEndpoint(value = "/chat", configurator = SpringConfigurator.class)
public class ChatEndpoint {
@OnMessage
public void onMessage(String msg,
// 直接注入 Spring Bean
@Autowired JwtTokenUtil jwtTokenUtil) {
jwtTokenUtil.parseUser(msg);
}
}
2.3.6 解决方案5:SpringConfigurator + 直接 @Autowired
思路:让 WebSocket 端点本身由 Spring 容器创建,这样所有注入都生效。
优点
- 支持
@Autowired
:字段为非final
,灵活性更高。 - 依赖明确:通过字段注入,依赖关系明确,易于理解和维护。
- 线程安全:Spring 容器管理的 Bean 通常是线程安全的。
缺点
- 需要额外定义
SpringConfigurator
- 多实例问题:如果项目中存在多个同类型的 Bean,需要额外指定 Bean 的名称来注入,增加了复杂性。
// 1. 定义 Configurator,把实例化交给 Spring
public class SpringConfigurator extends ServerEndpointConfig.Configurator {
@Override
public <T> T getEndpointInstance(Class<T> clazz) {
return SpringContextHolder.getApplicationContext().getBean(clazz);
}
}
// 2. 在端点上声明
@ServerEndpoint(value = "/chat", configurator = SpringConfigurator.class)
public class ChatEndpoint {
// 现在可以正常注入
@Autowired
private JwtTokenUtil jwtTokenUtil;
@OnMessage
public void onMessage(String msg) {
jwtTokenUtil.parseUser(msg);
}
}
2.3.6 五种解决方案的选择
方案 | 需要额外类 | 是否支持 @Autowired |
侵入性 | 优点 | 缺点 |
---|---|---|---|---|---|
静态持有法 | Holder 类 | ❌ | 低 | 实现简单,侵入性低 | 不支持 @Autowired ,线程安全问题,扩展性差 |
ApplicationContextAware | 无 | ❌ | 中 | 不依赖额外类,灵活性高 | 手动获取 Bean,多实例问题,性能开销 |
构造器注入 | Configurator | ✅ | 低 | 支持 @Autowired ,依赖明确,线程安全 |
需要额外定义 SpringConfigurator ,多实例问题 |
方法参数注入 | Configurator 5.3+ | ✅ | 最低 | 支持 @Autowired ,代码简洁,线程安全 |
仅支持 Spring 5.3+,多实例问题 |
SpringConfigurator + @Autowired | Configurator | ✅ | 低 | 支持 @Autowired ,依赖明确,线程安全 |
需要额外定义 SpringConfigurator ,多实例问题 |
- 静态持有法:适用于对技术栈要求不高,且希望实现方式最简单的场景。但需要注意线程安全和扩展性问题。
- ApplicationContextAware:适用于已经大量使用
ApplicationContextAware
的项目,且对侵入性要求不高。但需要手动获取 Bean,可能会带来性能开销。 - 构造器注入:适用于对字段的不可变性有要求,且希望支持
@Autowired
的场景。但需要额外定义SpringConfigurator
,增加了代码量。 - 方法参数注入:适用于使用 Spring 5.3 及以上版本,且希望侵入性最低的场景。但需要注意多实例问题。
- SpringConfigurator + @Autowired:适用于对字段的可变性有要求,且希望支持
@Autowired
的场景。但需要额外定义SpringConfigurator
.
三、利用组合技术丰富WebSocket服务端
3.1 心跳机制与连接管理。
3.1.1 心跳机制
在WebSocket通信中,心跳机制是维持连接有效性和稳定性的关键手段。它通过客户端与服务端之间定期传输心跳消息,主要解决以下问题:
- 检测连接状态:心跳消息使服务端能够实时检测客户端是否在线,反之亦然。如果在规定时间内未收到心跳响应,即可判定连接已断开。
- 保持连接活跃:在某些网络环境下,如防火墙或代理服务器可能会因连接空闲而关闭连接。心跳消息可防止这种情况发生,确保连接始终处于活跃状态。
- 及时发现故障:心跳机制能够快速发现网络故障或服务端故障,从而及时触发故障处理机制,降低对业务的影响。
@Configuration
public class WebSocketConfig {
@Autowired
private SimpMessagingTemplate template;
@Scheduled(fixedRate = 10000) // 每10秒发送一次心跳
public void sendHeartbeat() {
template.convertAndSend("/topic/heartbeat", "heartbeat");
}
}
服务端发送心跳消息后,客户端需要监听服务端发送的心跳消息,并在收到后发送响应。服务端根据客户端发送的心跳响应,以确认客户端仍然在线。
@ServerEndpoint("/websocket")
public class WebSocketEndpoint {
@OnMessage
public void onMessage(String message, Session session) {
if ("pong".equals(message)) {
// 客户端响应心跳,更新连接状态
updateConnectionStatus(session, true);
}
}
private void updateConnectionStatus(Session session, boolean isConnected) {
// 更新会话状态逻辑
}
}
3.1.2 自动重连
客户端可以通过监听WebSocket的onclose
事件来实现自动重连。当连接关闭时,设置一个定时器,间隔一定时间后重新建立连接。服务端要支持客户端的重连请求可以通过配置HandshakeInterceptor
来设置WebSocket会话的空闲超时时间,从而控制自动断开连接的时长。
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
@Bean
public HandshakeInterceptor handshakeInterceptor() {
return new HandshakeInterceptor() {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 设置最大空闲时间
if (request instanceof ServerHttpRequest) {
((ServerHttpRequest) request).getWebSocketSession().setMaxIdleTimeout(600000); // 设置10分钟无活动后断开
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
// 后处理逻辑
}
};
}
}
3.2 参数传递
3.2.1 Param传递
3.2.2 Restful风格传递
3.2.3 Header传递
四、分布式调优 WebSocket
4.1 分布式下的会话管理
4.1.1 分布式下和单体出现问题的不同
- 单体架构会话管理问题
- 分布式架构会话管理问题
4.1.2 多服务会话共享
- 会话共享的必要性
- 实现多服务会话共享的策略
4.1.3 集成 Redis 增强会话管理
- Redis 在会话管理中的作用
- 使用 Redis 实现分布式会话管理的架构设计
- Redis 高可用性配置