当前位置: 首页 > news >正文

从零实现分布式WebSocket组件:设计模式深度实践指南

一、为什么需要WebSocket组件?

  1. 实时通信需求
    • 传统HTTP轮询效率低,WebSocket提供全双工通信
    • 适用于即时聊天、实时数据监控、协同编辑等场景
  2. 分布式系统挑战
    • 多节点部署时需解决会话同步问题
    • 跨节点消息广播需借助中间件(Redis/RocketMQ等)
  3. 统一管理诉求
    • 会话管理、安全认证、消息路由等共性功能需抽象

二、组件核心功能全景

功能模块实现要点
会话管理按用户维度存储WebSocketSession,支持按ID/用户类型/用户ID查询
消息路由根据消息类型分发到对应处理器(WebSocketMessageListener)
消息广播支持本地/Redis/RocketMQ等多种发送策略,适配不同集群规模
安全认证握手阶段Token验证,用户信息绑定到Session
并发控制使用ConcurrentWebSocketSessionDecorator防止并发发送异常

三、组件工作全流程图解

1. 连接建立流程

前端 LoginUserHandshakeInterceptor WebSocketSession WebSocketSessionHandlerDecorator WebSocketSessionManagerImpl 发起连接?token=xxx 解析Token获取LoginUser setAttribute(LOGIN_USER) addSession(session) 存储到idSessions/userSessions 前端 LoginUserHandshakeInterceptor WebSocketSession WebSocketSessionHandlerDecorator WebSocketSessionManagerImpl

2. 消息接收处理流程

前端 JsonWebSocketMessageHandler WebSocketMessageListener 发送消息 解析消息类型(type) 忽略/返回错误 根据type查找监听器 记录错误日志 触发onMessage() 执行业务逻辑 alt [未找到该消费类型的监听器] [找到该消费类型的监听器] alt [无效消息] [有效消息] 前端 JsonWebSocketMessageHandler WebSocketMessageListener

3.消息发送流程(以RocketMQ为例)

业务代码 WebSocketMessageSender RocketMQWebSocketMessageSender RocketMQTemplate RocketMQ RocketMQWebSocketMessageConsumer AbstractWebSocketMessageSender WebSocketSession sendObject() send() 发送RocketMQ消息 发布到指定topic 订阅消费消息 doSend() 遍历会话发送 业务代码 WebSocketMessageSender RocketMQWebSocketMessageSender RocketMQTemplate RocketMQ RocketMQWebSocketMessageConsumer AbstractWebSocketMessageSender WebSocketSession

四、四步构建WebSocket核心链路

步骤一:连接建立(认证与会话管理)

相关类

WebSocketFrameworkUtils

import org.springframework.web.socket.WebSocketSession;import java.util.Map;/*** 专属于 web 包的工具类** @author dyh*/
public class WebSocketFrameworkUtils {public static final String ATTRIBUTE_LOGIN_USER = "LOGIN_USER";/*** 设置当前用户** @param loginUser 登录用户* @param attributes Session*/public static void setLoginUser(LoginUser loginUser, Map<String, Object> attributes) {attributes.put(ATTRIBUTE_LOGIN_USER, loginUser);}/*** 获取当前用户** @return 当前用户*/public static LoginUser getLoginUser(WebSocketSession session) {return (LoginUser) session.getAttributes().get(ATTRIBUTE_LOGIN_USER);}/*** 获得当前用户的编号** @return 用户编号*/public static Long getLoginUserId(WebSocketSession session) {LoginUser loginUser = getLoginUser(session);return loginUser != null ? loginUser.getId() : null;}/*** 获得当前用户的类型** @return 用户编号*/public static Integer getLoginUserType(WebSocketSession session) {LoginUser loginUser = getLoginUser(session);return loginUser != null ? loginUser.getUserType() : null;}/*** 获得当前用户的租户编号** @param session Session* @return 租户编号*/public static Long getTenantId(WebSocketSession session) {LoginUser loginUser = getLoginUser(session);return loginUser != null ? loginUser.getTenantId() : null;}}

JsonWebSocketMessage

/*** JSON 格式的 WebSocket 消息帧** @author dyh*/
@Data
public class JsonWebSocketMessage implements Serializable {/*** 消息类型** 目的:用于分发到对应的 {@link WebSocketMessageListener} 实现类*/private String type;/*** 消息内容** 要求 JSON 对象*/private String content;}

涉及核心类

设置用户属性
注册会话
LoginUserHandshakeInterceptor
WebSocketSessionHandlerDecorator
WebSocketSessionManagerImpl
WebSocketSession

实现步骤

1. 握手拦截器(认证用户)

LoginUserHandshakeInterceptor


import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;/*** 登录用户的 {@link HandshakeInterceptor} 实现类** 流程如下:* 1. 前端连接 websocket 时,会通过拼接 ?token={token} 到 ws:// 连接后,这样它可以被 {@link TokenAuthenticationFilter} 所认证通过* 2. {@link LoginUserHandshakeInterceptor} 负责把 {@link LoginUser} 添加到 {@link WebSocketSession} 中** @author dyh*/
public class LoginUserHandshakeInterceptor implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Map<String, Object> attributes) {LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();if (loginUser != null) {WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);}return true;}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Exception exception) {// do nothing}}
2. 会话装饰器(线程安全增强)

WebSocketSessionHandlerDecorator


import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;/*** {@link WebSocketHandler} 的装饰类,实现了以下功能:** 1. {@link WebSocketSession} 连接或关闭时,使用 {@link #sessionManager} 进行管理* 2. 封装 {@link WebSocketSession} 支持并发操作** @author dyh*/
public class WebSocketSessionHandlerDecorator extends WebSocketHandlerDecorator {/*** 发送时间的限制,单位:毫秒*/private static final Integer SEND_TIME_LIMIT = 1000 * 5;/*** 发送消息缓冲上线,单位:bytes*/private static final Integer BUFFER_SIZE_LIMIT = 1024 * 100;private final WebSocketSessionManager sessionManager;public WebSocketSessionHandlerDecorator(WebSocketHandler delegate,WebSocketSessionManager sessionManager) {super(delegate);this.sessionManager = sessionManager;}@Overridepublic void afterConnectionEstablished(WebSocketSession session) {// 实现 session 支持并发session = new ConcurrentWebSocketSessionDecorator(session, SEND_TIME_LIMIT, BUFFER_SIZE_LIMIT);// 添加到 WebSocketSessionManager 中sessionManager.addSession(session);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {sessionManager.removeSession(session);}}
3. 会话管理器(存储结构)

WebSocketSessionManager


import org.springframework.web.socket.WebSocketSession;import java.util.Collection;/*** {@link WebSocketSession} 管理器的接口** @author dyh*/
public interface WebSocketSessionManager {/*** 添加 Session** @param session Session*/void addSession(WebSocketSession session);/*** 移除 Session** @param session Session*/void removeSession(WebSocketSession session);/*** 获得指定编号的 Session** @param id Session 编号* @return Session*/WebSocketSession getSession(String id);/*** 获得指定用户类型的 Session 列表** @param userType 用户类型* @return Session 列表*/Collection<WebSocketSession> getSessionList(Integer userType);/*** 获得指定用户编号的 Session 列表** @param userType 用户类型* @param userId 用户编号* @return Session 列表*/Collection<WebSocketSession> getSessionList(Integer userType, Long userId);}

WebSocketSessionManagerImpl

import org.springframework.web.socket.WebSocketSession;
import cn.hutool.core.collection.CollUtil;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;/*** 默认的 {@link WebSocketSessionManager} 实现类* 负责管理WebSocket会话,包含双重存储结构:* 1. 基于会话ID的快速查找* 2. 基于用户类型+用户ID的分层存储* @author dyh*/
public class WebSocketSessionManagerImpl implements WebSocketSessionManager {/*** 会话ID与WebSocketSession的映射关系(线程安全)* Key格式:Session ID(WebSocket自动生成的唯一标识)* Value:对应的WebSocketSession对象*/private final ConcurrentMap<String, WebSocketSession> idSessions = new ConcurrentHashMap<>();/*** 用户维度存储WebSocketSession(三级嵌套线程安全结构)* Key1:用户类型(Integer,如1-管理员 2-普通用户)* Key2:用户ID(Long类型)* Value:该用户的所有WebSocket会话列表(线程安全的CopyOnWriteArrayList)*/private final ConcurrentMap<Integer, ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>>> userSessions = new ConcurrentHashMap<>();@Overridepublic void addSession(WebSocketSession session) {// 1. 加入ID索引idSessions.put(session.getId(), session);// 2. 加入用户维度索引LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);if (user == null) return; // 未认证会话不记录用户维度// 2.1 获取或创建用户类型层(双重检查锁模式)userSessions.computeIfAbsent(user.getUserType(), k -> new ConcurrentHashMap<>())// 2.2 获取或创建用户ID层.computeIfAbsent(user.getId(), k -> new CopyOnWriteArrayList<>())// 2.3 添加会话到列表(线程安全操作).add(session);}@Overridepublic void removeSession(WebSocketSession session) {// 1. 从ID索引移除idSessions.remove(session.getId());// 2. 从用户维度索引移除LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);if (user == null) return;userSessions.computeIfPresent(user.getUserType(), (userType, userMap) -> {userMap.computeIfPresent(user.getId(), (userId, sessions) -> {// 2.1 移除指定会话(根据ID精准匹配)sessions.removeIf(s -> s.getId().equals(session.getId()));// 2.2 列表为空时自动清理用户ID层return sessions.isEmpty() ? null : sessions;});// 2.3 用户ID层空时自动清理用户类型层return userMap.isEmpty() ? null : userMap;});}@Overridepublic WebSocketSession getSession(String id) {return idSessions.get(id); // O(1)时间复杂度直接查找}@Overridepublic Collection<WebSocketSession> getSessionList(Integer userType) {// 1. 获取指定用户类型的所有会话映射ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>> userMap = userSessions.get(userType);if (CollUtil.isEmpty(userMap)) return Collections.emptyList();// 2. 多租户过滤处理Long currentTenantId = TenantContextHolder.getTenantId();LinkedList<WebSocketSession> result = new LinkedList<>(); // 避免ArrayList扩容开销userMap.values().forEach(sessions -> {if (CollUtil.isEmpty(sessions)) return;// 2.1 租户隔离检查(第一个会话代表用户所属租户)Long userTenantId = WebSocketFrameworkUtils.getTenantId(sessions.get(0));if (currentTenantId != null && !currentTenantId.equals(userTenantId)) return;result.addAll(sessions);});return result;}@Overridepublic Collection<WebSocketSession> getSessionList(Integer userType, Long userId) {// 直接获取指定用户的所有会话(带租户上下文过滤)return Optional.ofNullable(userSessions.get(userType)).map(userMap -> userMap.get(userId)).filter(CollUtil::isNotEmpty).map(ArrayList::new) // 返回拷贝避免直接操作内部集合.orElseGet(ArrayList::new);}
}

设计模式应用

  • 装饰器模式:ConcurrentWebSocketSessionDecorator增强原生Session的并发能力
  • 工厂方法模式:通过拦截器创建带有用户属性的Session对象

步骤二:接收消息(路由与处理)

涉及核心类

路由调用
实现
JsonWebSocketMessageHandler
-Map listeners
+handleTextMessage()
«interface»
WebSocketMessageListener
+onMessage()
+getType()
WebSocketMessageListener具体实现类
+onMessage()
+getType()

实现步骤

1. 消息处理器(路由分发)

JsonWebSocketMessageHandler

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;/*** JSON 格式 {@link WebSocketHandler} 实现类** 基于 {@link JsonWebSocketMessage#getType()} 消息类型,调度到对应的 {@link WebSocketMessageListener} 监听器。** @author dyh*/
@Slf4j
public class JsonWebSocketMessageHandler extends TextWebSocketHandler {/*** type 与 WebSocketMessageListener 的映射*/private final Map<String, WebSocketMessageListener<Object>> listeners = new HashMap<>();@SuppressWarnings({"rawtypes", "unchecked"})public JsonWebSocketMessageHandler(List<? extends WebSocketMessageListener> listenersList) {listenersList.forEach((Consumer<WebSocketMessageListener>)listener -> listeners.put(listener.getType(), listener));}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 1.1 空消息,跳过if (message.getPayloadLength() == 0) {return;}// 1.2 ping 心跳消息,直接返回 pong 消息。if (message.getPayloadLength() == 4 && Objects.equals(message.getPayload(), "ping")) {session.sendMessage(new TextMessage("pong"));return;}// 2.1 解析消息try {JsonWebSocketMessage jsonMessage = JsonUtils.parseObject(message.getPayload(), JsonWebSocketMessage.class);if (jsonMessage == null) {log.error("[handleTextMessage][session({}) message({}) 解析为空]", session.getId(), message.getPayload());return;}if (StrUtil.isEmpty(jsonMessage.getType())) {log.error("[handleTextMessage][session({}) message({}) 类型为空]", session.getId(), message.getPayload());return;}// 2.2 获得对应的 WebSocketMessageListenerWebSocketMessageListener<Object> messageListener = listeners.get(jsonMessage.getType());if (messageListener == null) {log.error("[handleTextMessage][session({}) message({}) 监听器为空]", session.getId(), message.getPayload());return;}// 2.3 处理消息Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0);Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type);Long tenantId = WebSocketFrameworkUtils.getTenantId(session);TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));} catch (Throwable ex) {log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());}}}
2. 定义监听器接口

WebSocketMessageListener

import org.springframework.web.socket.WebSocketSession;/*** WebSocket 消息监听器接口** 目的:前端发送消息给后端后,处理对应 {@link #getType()} 类型的消息** @param <T> 泛型,消息类型 * @author dyh          */
public interface WebSocketMessageListener<T> {/*** 处理消息** @param session Session* @param message 消息*/void onMessage(WebSocketSession session, T message);/*** 获得消息类型** @see JsonWebSocketMessage#getType()* @return 消息类型*/String getType();}
3. 业务监听器(使用示例)

注意:这里提供的是使用示例,具体接收到前端消息处理逻辑请根据业务实现
OrderMessageListener

   @Componentpublic class OrderMessageListener implements WebSocketMessageListener<OrderMessage> {@Overridepublic String getType() { return "order-msg"; }@Overridepublic void onMessage(WebSocketSession session, OrderMessage message) {orderService.process(message);}}

设计模式应用

  • 策略模式:每个业务监听器对应一种消息处理策略
  • 观察者模式:将消息类型与处理逻辑解耦,方便扩展新消息类型。

设计原则

  • 依赖注入:Spring自动注入所有监听器实现
  • 开闭原则:新增消息类型只需添加Listener实现类
  • 单一职责:Handler只负责路由,Listener专注业务处理

步骤三:发送消息(策略化广播)

注意:此处只给了rocketmq消息策略,还可以自行实现rediskafkarabbitmq策略

相关类

RocketMQWebSocketMessage


import lombok.Data;/*** RocketMQ 广播 WebSocket 的消息** @author dyh*/
@Data
public class RocketMQWebSocketMessage {/*** Session 编号*/private String sessionId;/*** 用户类型*/private Integer userType;/*** 用户编号*/private Long userId;/*** 消息类型*/private String messageType;/*** 消息内容*/private String messageContent;}

涉及核心类

触发实际发送
«interface»
WebSocketMessageSender
+send(Integer userType, Long userId, String type, String content) : void
AbstractWebSocketMessageSender
-WebSocketSessionManager sessionManager
+send(...) : void
#doSend(...) : void
RocketMQWebSocketMessageSender
-RocketMQTemplate rocketMQTemplate
+doSend(...) : void
RedisWebSocketMessageSender
-RedisMQTemplate redisMQTemplate
+doSend(...) : void
RocketMQWebSocketMessageConsumer
+onMessage(RocketMQWebSocketMessage message) : void

实现步骤

1. 定义发送器接口与抽象类

WebSocketMessageSender


/*** WebSocket 消息的发送器接口** @author dyh*/
public interface WebSocketMessageSender {/*** 发送消息给指定用户** @param userType 用户类型* @param userId 用户编号* @param messageType 消息类型* @param messageContent 消息内容,JSON 格式*/void send(Integer userType, Long userId, String messageType, String messageContent);/*** 发送消息给指定用户类型** @param userType 用户类型* @param messageType 消息类型* @param messageContent 消息内容,JSON 格式*/void send(Integer userType, String messageType, String messageContent);/*** 发送消息给指定 Session** @param sessionId Session 编号* @param messageType 消息类型* @param messageContent 消息内容,JSON 格式*/void send(String sessionId, String messageType, String messageContent);default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) {send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));}default void sendObject(Integer userType, String messageType, Object messageContent) {send(userType, messageType, JsonUtils.toJsonString(messageContent));}default void sendObject(String sessionId, String messageType, Object messageContent) {send(sessionId, messageType, JsonUtils.toJsonString(messageContent));}}

AbstractWebSocketMessageSender

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;/*** WebSocketMessageSender 实现类** @author dyh*/
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractWebSocketMessageSender implements WebSocketMessageSender {private final WebSocketSessionManager sessionManager;@Overridepublic void send(Integer userType, Long userId, String messageType, String messageContent) {send(null, userType, userId, messageType, messageContent);}@Overridepublic void send(Integer userType, String messageType, String messageContent) {send(null, userType, null, messageType, messageContent);}@Overridepublic void send(String sessionId, String messageType, String messageContent) {send(sessionId, null, null, messageType, messageContent);}/*** 发送消息** @param sessionId Session 编号* @param userType 用户类型* @param userId 用户编号* @param messageType 消息类型* @param messageContent 消息内容*/public void send(String sessionId, Integer userType, Long userId, String messageType, String messageContent) {// 1. 获得 Session 列表List<WebSocketSession> sessions = Collections.emptyList();if (StrUtil.isNotEmpty(sessionId)) {WebSocketSession session = sessionManager.getSession(sessionId);if (session != null) {sessions = Collections.singletonList(session);}} else if (userType != null && userId != null) {sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType, userId);} else if (userType != null) {sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);}if (CollUtil.isEmpty(sessions)) {if (log.isDebugEnabled()) {log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",sessionId, userType, userId, messageType, messageContent);}}// 2. 执行发送doSend(sessions, messageType, messageContent);}/*** 发送消息的具体实现** @param sessions Session 列表* @param messageType 消息类型* @param messageContent 消息内容*/public void doSend(Collection<WebSocketSession> sessions, String messageType, String messageContent) {JsonWebSocketMessage message = new JsonWebSocketMessage().setType(messageType).setContent(messageContent);String payload = JsonUtils.toJsonString(message); // 关键,使用 JSON 序列化sessions.forEach(session -> {// 1. 各种校验,保证 Session 可以被发送if (session == null) {log.error("[doSend][session 为空, message({})]", message);return;}if (!session.isOpen()) {log.error("[doSend][session({}) 已关闭, message({})]", session.getId(), message);return;}// 2. 执行发送try {session.sendMessage(new TextMessage(payload));log.info("[doSend][session({}) 发送消息成功,message({})]", session.getId(), message);} catch (IOException ex) {log.error("[doSend][session({}) 发送消息失败,message({})]", session.getId(), message, ex);}});}}
2. 实现具体策略(以RocketMQ为例)

RocketMQWebSocketMessageSender

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;/*** 基于 RocketMQ 的 {@link WebSocketMessageSender} 实现类** @author dyh*/
@Slf4j
public class RocketMQWebSocketMessageSender extends AbstractWebSocketMessageSender {private final RocketMQTemplate rocketMQTemplate;private final String topic;public RocketMQWebSocketMessageSender(WebSocketSessionManager sessionManager,RocketMQTemplate rocketMQTemplate,String topic) {super(sessionManager);this.rocketMQTemplate = rocketMQTemplate;this.topic = topic;}@Overridepublic void send(Integer userType, Long userId, String messageType, String messageContent) {sendRocketMQMessage(null, userId, userType, messageType, messageContent);}@Overridepublic void send(Integer userType, String messageType, String messageContent) {sendRocketMQMessage(null, null, userType, messageType, messageContent);}@Overridepublic void send(String sessionId, String messageType, String messageContent) {sendRocketMQMessage(sessionId, null, null, messageType, messageContent);}/*** 通过 RocketMQ 广播消息** @param sessionId Session 编号* @param userId 用户编号* @param userType 用户类型* @param messageType 消息类型* @param messageContent 消息内容*/private void sendRocketMQMessage(String sessionId, Long userId, Integer userType,String messageType, String messageContent) {RocketMQWebSocketMessage mqMessage = new RocketMQWebSocketMessage().setSessionId(sessionId).setUserId(userId).setUserType(userType).setMessageType(messageType).setMessageContent(messageContent);rocketMQTemplate.syncSend(topic, mqMessage);}}
3. 消息消费者处理广播

RocketMQWebSocketMessageConsumer


import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;/*** {@link RocketMQWebSocketMessage} 广播消息的消费者,真正把消息发送出去** @author dyh*/
@RocketMQMessageListener( // 重点:添加 @RocketMQMessageListener 注解,声明消费的 topictopic = "${dyh.websocket.sender-rocketmq.topic}",consumerGroup = "${dyh.websocket.sender-rocketmq.consumer-group}",messageModel = MessageModel.BROADCASTING // 设置为广播模式,保证每个实例都能收到消息
)
@RequiredArgsConstructor
public class RocketMQWebSocketMessageConsumer implements RocketMQListener<RocketMQWebSocketMessage> {private final RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender;@Overridepublic void onMessage(RocketMQWebSocketMessage message) {rocketMQWebSocketMessageSender.send(message.getSessionId(),message.getUserType(), message.getUserId(),message.getMessageType(), message.getMessageContent());}}

设计模式应用

  • 策略模式:WebSocketMessageSender 接口统一发送策略,RedisWebSocketMessageSender 和 RocketMQWebSocketMessageSender 实现不同策略。
  • 模板方法模式:AbstractWebSocketMessageSender

设计原则

  • 开闭原则:新增发送策略只需继承AbstractWebSocketMessageSender抽象类,无需修改核心逻辑
  • 依赖倒置:高层模块依赖WebSocketMessageSender抽象,不关心具体实现

步骤四:Spring 自动装配

相关类

WebSocketProperties

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;/*** WebSocket 配置项** @author dyh*/
@ConfigurationProperties("dyh.websocket")
@Data
@Validated
public class WebSocketProperties {/*** WebSocket 的连接路径*/@NotEmpty(message = "WebSocket 的连接路径不能为空")private String path = "/ws";/*** 消息发送器的类型** 可选值:local、redis、rocketmq、kafka、rabbitmq*/@NotNull(message = "WebSocket 的消息发送者不能为空")private String senderType = "local";}

WebSocket 自动配置

DyhWebSocketAutoConfiguration

import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.server.HandshakeInterceptor;import java.util.List;/*** WebSocket 自动配置** @author dyh*/
@AutoConfiguration(before = DyhRedisMQConsumerAutoConfiguration.class) // before DyhRedisMQConsumerAutoConfiguration 的原因是,需要保证 RedisWebSocketMessageConsumer 先创建,才能创建 RedisMessageListenerContainer
@EnableWebSocket // 开启 websocket
@ConditionalOnProperty(prefix = "dyh.websocket", value = "enable", matchIfMissing = true) // 允许使用 dyh.websocket.enable=false 禁用 websocket
@EnableConfigurationProperties(WebSocketProperties.class)
public class DyhWebSocketAutoConfiguration {@Beanpublic WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor[] handshakeInterceptors,WebSocketHandler webSocketHandler,WebSocketProperties webSocketProperties) {return registry -> registry// 添加 WebSocketHandler.addHandler(webSocketHandler, webSocketProperties.getPath()).addInterceptors(handshakeInterceptors)// 允许跨域,否则前端连接会直接断开.setAllowedOriginPatterns("*");}@Beanpublic HandshakeInterceptor handshakeInterceptor() {return new LoginUserHandshakeInterceptor();}@Beanpublic WebSocketHandler webSocketHandler(WebSocketSessionManager sessionManager,List<? extends WebSocketMessageListener<?>> messageListeners) {// 1. 创建 JsonWebSocketMessageHandler 对象,处理消息JsonWebSocketMessageHandler messageHandler = new JsonWebSocketMessageHandler(messageListeners);// 2. 创建 WebSocketSessionHandlerDecorator 对象,处理连接return new WebSocketSessionHandlerDecorator(messageHandler, sessionManager);}@Beanpublic WebSocketSessionManager webSocketSessionManager() {return new WebSocketSessionManagerImpl();}@Beanpublic WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) {return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties);}// ==================== Sender 相关 ====================@Configuration@ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "local")public class LocalWebSocketMessageSenderConfiguration {@Beanpublic LocalWebSocketMessageSender localWebSocketMessageSender(WebSocketSessionManager sessionManager) {return new LocalWebSocketMessageSender(sessionManager);}}@Configuration@ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "redis")public class RedisWebSocketMessageSenderConfiguration {@Beanpublic RedisWebSocketMessageSender redisWebSocketMessageSender(WebSocketSessionManager sessionManager,RedisMQTemplate redisMQTemplate) {return new RedisWebSocketMessageSender(sessionManager, redisMQTemplate);}@Beanpublic RedisWebSocketMessageConsumer redisWebSocketMessageConsumer(RedisWebSocketMessageSender redisWebSocketMessageSender) {return new RedisWebSocketMessageConsumer(redisWebSocketMessageSender);}}@Configuration@ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "rocketmq")public class RocketMQWebSocketMessageSenderConfiguration {@Beanpublic RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender(WebSocketSessionManager sessionManager, RocketMQTemplate rocketMQTemplate,@Value("${dyh.websocket.sender-rocketmq.topic}") String topic) {return new RocketMQWebSocketMessageSender(sessionManager, rocketMQTemplate, topic);}@Beanpublic RocketMQWebSocketMessageConsumer rocketMQWebSocketMessageConsumer(RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender) {return new RocketMQWebSocketMessageConsumer(rocketMQWebSocketMessageSender);}}@Configuration@ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "rabbitmq")public class RabbitMQWebSocketMessageSenderConfiguration {@Beanpublic RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender(WebSocketSessionManager sessionManager, RabbitTemplate rabbitTemplate,TopicExchange websocketTopicExchange) {return new RabbitMQWebSocketMessageSender(sessionManager, rabbitTemplate, websocketTopicExchange);}@Beanpublic RabbitMQWebSocketMessageConsumer rabbitMQWebSocketMessageConsumer(RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender) {return new RabbitMQWebSocketMessageConsumer(rabbitMQWebSocketMessageSender);}/*** 创建 Topic Exchange*/@Beanpublic TopicExchange websocketTopicExchange(@Value("${dyh.websocket.sender-rabbitmq.exchange}") String exchange) {return new TopicExchange(exchange,true,  // durable: 是否持久化false);  // exclusive: 是否排它}}@Configuration@ConditionalOnProperty(prefix = "dyh.websocket", name = "sender-type", havingValue = "kafka")public class KafkaWebSocketMessageSenderConfiguration {@Beanpublic KafkaWebSocketMessageSender kafkaWebSocketMessageSender(WebSocketSessionManager sessionManager, KafkaTemplate<Object, Object> kafkaTemplate,@Value("${dyh.websocket.sender-kafka.topic}") String topic) {return new KafkaWebSocketMessageSender(sessionManager, kafkaTemplate, topic);}@Beanpublic KafkaWebSocketMessageConsumer kafkaWebSocketMessageConsumer(KafkaWebSocketMessageSender kafkaWebSocketMessageSender) {return new KafkaWebSocketMessageConsumer(kafkaWebSocketMessageSender);}}}

五、使用指南(含代码示例)

1. 发送消息示例

NotifyController

@RestController
public class NotifyController {@Autowiredprivate WebSocketMessageSender sender;// 发送给指定用户@PostMapping("/send")public void sendToUser(@RequestParam Long userId) {sender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,"demo-message", new DemoMessage("Hello from server!"));}
}

2. 接收消息示例

DemoMessageListener

@Component
public class DemoMessageListener implements WebSocketMessageListener<DemoMessage> {@Overridepublic String getType() {return "demo-message"; // 声明处理的消息类型}@Overridepublic void onMessage(WebSocketSession session, DemoMessage message) {// 1. 获取当前用户Long userId = WebSocketFrameworkUtils.getLoginUserId(session);// 2. 处理业务逻辑System.out.println("收到来自用户:" + userId + " 的消息:" + message.getContent());}
}

六、设计模式与开发原则实践

1. 经典设计模式

模式名称应用场景代码示例
装饰器模式增强WebSocketHandler的会话管理能力WebSocketSessionHandlerDecorator
策略模式可切换的消息发送策略(Local/Redis/RocketMQ)WebSocketMessageSender多实现
观察者模式消息类型与监听器的解耦WebSocketMessageListener+消息路由
模板方法定义消息发送流程骨架AbstractWebSocketMessageSender#send()

2. 开发原则体现

  • 单一职责原则:会话管理、消息路由、安全认证模块分离
  • 开闭原则:新增消息类型只需实现新Listener,无需修改核心逻辑
  • 依赖倒置:高层模块依赖WebSocketMessageSender抽象,不关心具体实现

七、从组件设计中学到的经验

  1. 分层架构的价值
    • 将会话管理、消息路由、网络传输分层处理,提高可维护性
    • 通过抽象接口隔离变化点(如消息发送策略)
  2. 分布式设计要点
    • 会话信息存储需考虑线程安全(ConcurrentHashMap)
    • 广播消息需借助中间件实现最终一致性
  3. 扩展性设计技巧
    • 使用Spring条件装配实现策略动态切换
    • 监听器机制方便业务功能扩展
  4. 生产环境考量
    • 心跳机制:内置Ping/Pong处理
    • 流量控制:ConcurrentWebSocketSessionDecorator限制缓冲区大小

相关文章:

从零实现分布式WebSocket组件:设计模式深度实践指南

一、为什么需要WebSocket组件&#xff1f; 实时通信需求 传统HTTP轮询效率低&#xff0c;WebSocket提供全双工通信适用于即时聊天、实时数据监控、协同编辑等场景 分布式系统挑战 多节点部署时需解决会话同步问题跨节点消息广播需借助中间件&#xff08;Redis/RocketMQ等&…...

使用 OpenCV 和 dlib 进行人脸检测

文章目录 1. 什么是 dlib2. 前期准备介绍2.1 环境准备2.2 dlib 的人脸检测器 3. 代码实现3.1 导入库3.2 加载检测器3.3 读取并调整图像大小3.4 检测人脸3.5 绘制检测框3.6 显示结果 4. 完整代码5. 优化与改进5.1 提高检测率5.2 处理 BGR 与 RGB 问题 6. 总结 人脸检测是计算机视…...

03.使用spring-ai玩转MCP

接着上篇&#xff1a;https://blog.csdn.net/sinat_15906013/article/details/147052013&#xff0c;我们介绍了&#xff0c;什么是MCP&#xff1f;使用cline插件/cherry-studio安装了Mcp Server&#xff0c;本篇我们要借助spring-ai实现MCP Client和Server。 使用spring-ai的…...

LeetCode12_整数转罗马数字

LeetCode12_整数转罗马数字 标签&#xff1a;#哈希表 #数字 #字符串Ⅰ. 题目Ⅱ. 示例 0. 个人方法&#xff1a;模拟官方题解二&#xff1a;硬编码数字 标签&#xff1a;#哈希表 #数字 #字符串 Ⅰ. 题目 七个不同的符号代表罗马数字&#xff0c;其值如下&#xff1a; 符号值I…...

展销编辑器操作难度及优势分析​

也许有人会担心&#xff0c;如此强大的展销编辑器&#xff0c;操作起来是否会很复杂?答案是否定的。展销编辑器秉持着 “简单易用” 的设计理念&#xff0c;致力于让每一位用户都能轻松上手&#xff0c;即使是没有任何技术背景的小白&#xff0c;也能在短时间内熟练掌握。​ 编…...

展销编辑器在未来的发展前景​

展销编辑器在展销行业的发展前景极为广阔&#xff0c;有望引领行业迈向更加智能化、个性化、沉浸式的新时代&#xff0c;对行业变革产生深远影响。​ 随着人工智能、虚拟现实、增强现实等技术的不断发展和融合&#xff0c;展销编辑器将实现更加智能化的功能。例如&#xff0c;借…...

央视两次采访报道爱藏评级,聚焦生肖钞市场升温,评级币成交易安全“定心丸”

CCTV央视财经频道《经济信息联播》《第一时间》两档节目分别对生肖贺岁钞进行了5分钟20秒的专题报道。长期以来&#xff0c;我国一直保持着发行生肖纪念钞和纪念币的传统&#xff0c;生肖纪念钞和纪念币在收藏市场保持着较高的热度。特别是2024年初&#xff0c;央行发行了首张贺…...

登高架设作业指的是什么?有什么安全操作规程?

登高架设作业是指在高处从事脚手架、跨越架架设或拆除的作业。具体包括以下方面&#xff1a; 脚手架作业 搭建各类脚手架&#xff0c;如落地式脚手架、悬挑式脚手架、附着式升降脚手架等&#xff0c;为建筑施工、设备安装、高处维修等作业提供安全稳定的工作平台。对脚手架进行…...

Kaamel白皮书:IoT设备安全隐私评估实践

1. IoT安全与隐私领域的现状与挑战 随着物联网技术的快速发展&#xff0c;IoT设备在全球范围内呈现爆发式增长。然而&#xff0c;IoT设备带来便捷的同时&#xff0c;也引发了严峻的安全与隐私问题。根据NSF&#xff08;美国国家科学基金会&#xff09;的研究表明&#xff0c;I…...

uniapp跨平台开发---动态控制底部切换显示

业务需求 不同用户或者应用场景,底部tab展示不同的内容,针对活动用户额外增加底部tab选项 活动用户 非活动用户 实现思路 首先在tabbar list中增加中间活动tab的路径代码,设置visible:false,然后再根据条件信息控制活动tab是否展示 pages.json {"pagePath": "…...

django admin 去掉新增 删除

在Django Admin中&#xff0c;你可以通过自定义Admin类来自定义哪些按钮显示&#xff0c;哪些不显示。如果你想隐藏“新增”和“删除”按钮&#xff0c;可以通过重写change_list_template或使用ModelAdmin的has_add_permission和has_delete_permission属性来实现。 方法1&…...

final static 中是什么final static联合使用呢

final static 联合使用详解 final 和 static 在 Java 中经常一起使用&#xff0c;主要用来定义类级别的常量。这种组合具有两者的特性&#xff1a; 基本用法 public class Constants {// 典型的 final static 常量定义public static final double PI 3.141592653589793;pub…...

【项目管理】知识点复习

项目管理-相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 项目管理知识域 知识点: (项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域) 对应:第6章-第19章 第6章 项目管理概论 4分第13章 项目资源管理 3-4分第7章 项目…...

cocos creator使用jenkins打包流程,打包webmobile

windows电脑使用 如果你的电脑作为打包机&#xff0c;一定要锁定自己的ip,如果ip动态获取&#xff0c;可能后续会导致jenkins无法访问,还需要重新配置jenkins和http-server的端口 从jenkins官网下载windows版 Thank you for downloading Windows Stable installer 1.jenkins安…...

颠覆传统微商!开源AI智能名片链动2+1模式S2B2C商城小程序:重构社交电商的“降维打击”革命

摘要&#xff1a;传统微商模式长期依赖暴力刷屏、多层分销与价格战&#xff0c;导致用户信任崩塌、行业合规风险激增&#xff0c;近三年行业淘汰率高达67%。本文创新性提出“开源AI智能名片链动21模式S2B2C商城小程序”技术-商业融合架构&#xff0c;通过AI驱动的智能内容引擎、…...

pycharm无法创建venv虚拟环境

pycharm 2022.2.2在创建新project时&#xff0c;选择Virtualenv environment时&#xff0c;提示“无法创建虚拟环境”。 1.查看 PyCharm 日志 日志文件&#xff08;路径示例&#xff1a;C:\Users\<用户名>\AppData\Local\JetBrains\PyCharm2022.1\log\idea.log&#xff…...

nextjs整合快速整合市面上各种AI进行prompt连调测试

nextjs整合快速整合市面上各种AI进行prompt连调测试。这样写法只是我用来做测试。快速对比各种AI大模理效果. 这里参数通过APIPOST进来 import { OpenAIService } from ./openai.service; import { Controller, Post, Body, Param } from nestjs/common; import { jsonrepair …...

Greenbone(绿骨)开源GVM容器docker部署和汉化介绍

文章目录 Greenbone&#xff08;绿骨&#xff09;开源GVM容器docker部署和汉化介绍前言用容器部署GVM第一步&#xff1a;安装依赖项第二步&#xff1a;安装 Docker第三步&#xff1a;使用 docker-compose编排文件&#xff0c;完成GVM服务部署第四步&#xff1a;启动Greenbone社…...

PDF嵌入隐藏的文字

所需依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>9.0.0</version><type>pom</type> </dependency>源码 /*** PDF工具*/ public class PdfUtils {/*** 在 PD…...

为什么从Word复制到PPT的格式总是乱掉?

从Word复制到PPT的格式总是乱掉&#xff0c;主要有以下原因&#xff1a; 格式兼容性问题 - 软件版本差异&#xff1a;不同版本的Office或WPS软件&#xff0c;对文档格式的支持和处理方式有所不同。如Office 2021中的新功能“动态网格对齐”&#xff0c;在粘贴到Office 2016的…...

五分钟讲清数据需求怎么梳理!

目录 一、为什么要进行数据需求梳理&#xff1f; 1.确保企业收集到真正有价值的数据 2.有助于提高数据分析的效率和质量 3.促进企业内部各部门之间的沟通与协作 二、数据需求怎么梳理&#xff1f; 1. 与业务部门深度沟通 2. 进行业务流程分析 3. 参考行业最佳实践 4. …...

03_多线程任务失败解决方案

文章目录 问题&#xff1a;多线程并发处理时,其中一个任务失败怎么办&#xff1f;1. 异常捕获2. 线程同步3. 资源清理4. 错误恢复5. 通知其他线程6. 使用并发框架 问题&#xff1a;多线程并发处理时,其中一个任务失败怎么办&#xff1f; 这是一个典型的并发编程问题&#xff0…...

MyBatis 类型处理器(TypeHandler)注册与映射机制:JsonListTypeHandler和JsonListTypeHandler注册时机

下面几种机制会让你的 List<String>/Map<String,?> 能正确读写成 JSON 数组&#xff0f;对象文本&#xff1a; MyBatis-Plus 自动注册 最新版本的 MyBatis-Plus starter 会把类路径下所有带 MappedTypes({List.class})、MappedJdbcTypes(JdbcType.VARCHAR) 这类注…...

Spark SQL开发实战:从IDEA环境搭建到UDF/UDAF自定义函数实现

利用IDEA开发Spark-SQL 1、创建子模块Spark-SQL&#xff0c;并添加依赖 <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql_2.12</artifactId> <version>3.0.0</version> </dependency> 3…...

神经网络笔记 - 神经网络

一.神经网络基础知识 1.神经网络解决了什么问题 将人类眼中的数据&#xff08;如图像、文本&#xff09;转换成计算机能理解的特征矩阵。适用于分类、回归等多种任务&#xff0c;本质上是进行特征提取与决策映射。 2.神经网络基本结构 输入层&#xff08;Input Layer&#x…...

C20-breakcontinue

一 break break的作用:用于跳出当前的循环 #include <stdio.h> int main() {//变量初始化int TallPeopleNumber;int TallMoney0;int SingelMoney;//循环体for(TallPeopleNumber1;TallPeopleNumber<1000;TallPeopleNumber){printf("请输入单笔捐款金额:\n")…...

关于IDEA的循环依赖问题

bug描述&#xff1a;&#xff08;java: 模块循环不支持注解处理。请确保将循环 [...] 中的所有模块排除在注解处理之外&#xff09; 解决方法&#xff1a;...

uniapp跳转和获取参数方式

1.小程序跳转 1.1 原生组件跳转 <navigator url"/pages/about/about?id10">跳转</navigator> 1.2 方法接口跳转 uni.navigateTo({url:/pages/about/about?id2}) 2.获取参数值 页面获取id值 onLoad(e) {console.log(e.id);}...

BP 算法探秘 :神经网络的幕后引擎

大家好&#xff0c;我是沛哥儿&#xff0c;很高兴又和大家见面了。 在人工智能的世界里&#xff0c;神经网络如同大脑一般神秘又强大&#xff0c;而其中 **BP 算法&#xff08;Backpropagation Algorithm&#xff09;**就是驱动这个 “大脑” 不断学习进化的幕后引擎。 文章目录…...

物联网相关

文章目录 1 MQTT2 MQTT FX3 EMQ X 1 MQTT MQTT是一种基于发布/订阅模式的轻量级物联网消息协议&#xff0c;全称为Message Queuing Telemetry Transport&#xff08;消息队列遥测传输&#xff09;。它具有低功耗、低带宽占用、可靠性高等特点&#xff0c;广泛应用于物联网设备…...

【Axure高保真原型】3级多选下拉列表

今天和大家分享3级多选下拉列表原型模板&#xff0c;这个模版是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;选项的数据在中继器表格里维护即可自动生成交互效果&#xff0c;具体效果可以打开下方原型地址体验或者点击下方视频观看 【原型效果含使用说明】 【Axur…...

光敏材料与智能传感技术的能源系统创新研究

一、光敏储能体系的作用机理与技术创新 1.1 分子光能转换机制 基于分子构型变化的能量存储技术展现出独特优势&#xff0c;其核心机理涉及光敏材料在光照下的可逆分子构型变化。以偶氮苯体系为例&#xff0c;在365nm紫外光激发下&#xff0c;分子发生反式到顺式的异构转变&…...

Docker 安装 kafka (bitnami/kafka:4.0)

1、拉取镜像 docker pull bitnami/kafka:4.02、创建挂载目录 mkdir -p /user/lzl/tool/docker/kafka/bitnami/bitnami_kafka_4.0/home/datamkdir -p /user/lzl/tool/docker/kafka/bitnami/bitnami_kafka_4.0/home/logs3、给挂载目录授权 chmod 777 /user/lzl/tool/docker/ka…...

NameSilo转入转出域名

一、总起 域名转入转出主要沟通方式就是靠注册邮箱收取转移授权码。 因为namesilo的界面一直在慢慢改动&#xff08;很慢很慢&#xff09;&#xff0c;所以本文和网上教程里的截图有所不同&#xff0c;以后本文可能也会与实际界面有所不同。 二、转入域名 1. 在其它域名服务商…...

Python----深度学习(基于DNN的吃鸡预测)

一、目标 如何使用 PyTorch 实现一个简单的深度神经网络&#xff08;DNN&#xff09;模型&#xff0c;并用于回归任务。该模型通过训练数据集来预测玩家在游戏中的最终排名百分比。代码通过读取数据集、数据处理、模型训练和模型评估等步骤。 二、数据集介绍 和平精英&#xf…...

DeepSeek系列(10):与其他AI工具协同

DeepSeek与绘图AI配合使用 在当今多元化的AI生态中,将不同专长的AI工具协同使用,能够实现远超单一工具的综合效果。DeepSeek作为强大的语言模型,与专业绘图AI的配合尤为默契,可以在创意构思与视觉呈现之间建立无缝桥梁。 创意-视觉协作流程 从文本到图像的完整路径 创意…...

Spark-Streaming核心编程:有状态转化操作与DStream输出

在Spark-Streaming的学习旅程中&#xff0c;有状态转化操作和DStream输出是两个关键知识点&#xff0c;今天就来深入聊聊它们。 先说说有状态转化操作&#xff0c;这里面 UpdateStateByKey 和 WindowOperations 很重要。 UpdateStateByKey 主要用于跨批次维护状态&#xff0c;就…...

Ldap高效数据同步- MirrorMode双主复制模式配置详解(上)

#作者&#xff1a;朱雷 文章目录 一、Syncrepl 复制和MirrorMode复制1.1. 什么是复制模式1.2. 什么是 syncrepl同步复制1.3. 什么是 MirrorMode 复制&#xff08;双主模式&#xff09;1.4. 双数据中心配置镜像模式架构 二、Ldap环境部署三、配置Mirror复制类型3.1. 配置节点1配…...

【刷题Day28】Python/JAVA - 02(浅)

Python 什么是 Python 的闭包&#xff1f; 闭包&#xff08;Closure&#xff09;是Python中的一种独特的函数机制。简而言之&#xff0c;闭包是指在一个内部函数中&#xff0c;引用了外部函数的变量&#xff0c;而这个外部函数已经执行完毕并返回了内部函数&#xff0c;然而内…...

纯净IP的优势:稳定性与安全性的结合

在跨境电商、数据采集、社交运营等对网络质量要求高的场景中&#xff0c;选择一个可靠的IP资源&#xff0c;是保护账号安全、提升业务效率的关键。纯净IP凭借其独特的稳定性与安全性&#xff0c;成为越来越多用户的选择。本文将带你深入了解纯净IP的价值&#xff0c;以及如何应…...

探索DeepWiki:GitHub源码阅读的变革性工具

DeepWiki 是什么 DeepWiki 是由 Cognition Labs 精心打造的一款创新工具&#xff0c;堪称 GitHub Repo 源代码的 “智慧解读器”&#xff0c;能将其转化为可对话式文档 &#xff0c;为开发者提供实时交流、即时更新文档的功能。它基于 Devin 技术&#xff0c;为每一个 GitHub …...

基于WebRTC技术,EasyRTC音视频实时通话助力全网会议的智能化转型

一、方案背景 随着数字化转型&#xff0c;企业、教育、政府等对全网会议需求激增。传统视频会议部署复杂、成本高、兼容性差&#xff0c;无法满足远程协作的多样化需求。EasyRTC实时通信功能强大&#xff0c;能为全网会议提供高效、稳定、易用的解决方案&#xff0c;支持多终端…...

设计模式全解析:23种经典设计模式及其应用

创建型模式 1. 单例模式&#xff08;Singleton Pattern&#xff09; 核心思想&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。适用场景&#xff1a;需要共享资源的场景&#xff0c;如配置管理、日志记录等。 public class Singleton {// 静态变量保存…...

Web开发-JavaEE应用依赖项Log4j日志Shiro验证FastJson数据XStream格式

知识点&#xff1a; 1、安全开发-JavaEE-第三方依赖开发安全 2、安全开发-JavaEE-数据转换&FastJson&XStream 3、安全开发-JavaEE-Shiro身份验证&Log4j日志处理 一、演示案例-WEB开发-JavaEE-第三方依赖&FastJson&XStream FastJson 一个阿里巴巴开发的J…...

小集合 VS 大集合:MySQL 去重计数性能优化

小集合 VS 大集合&#xff1a;MySQL 去重计数性能优化 前言一、场景与问题 &#x1f50e;二、通俗执行流程对比三、MySQL 执行计划解析 &#x1f4ca;四、性能瓶颈深度剖析 &#x1f50d;五、终极优化方案 &#x1f3c6;六、总结 前言 &#x1f4c8; 测试结果&#xff1a; 在…...

什么是模块化区块链?Polkadot 架构解析

原文&#xff1a;https://polkadot.com/blog/understanding-modular-blockchains/ 作者&#xff1a;Joey Prebys 编译&#xff1a;OneBlock 区块链的构建方式有很多种&#xff0c;而不同的架构选择会直接影响性能、可扩展性和开发者体验。随着行业的发展&#xff0c;单体区块…...

C++翻转数相乘 2024年信息素养大赛复赛 C++小学/初中组 算法创意实践挑战赛 真题详细解析

目录 C++翻转数相乘 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 六、 推荐资料 1、C++资料 2、Scratch资料 3、Python资料 C++翻转数相乘 2024年信息素养大赛 C++复赛真题 一、题目要求 1、编程实现 假设一个…...

Go 语言中的 `select` 语句详解

select 是 Go 语言中处理通道(Channel)操作的一个强大控制结构&#xff0c;它允许 goroutine 同时等待多个通道操作。下面我将全面详细地解释 select 语句的各个方面。 基本语法 select 语句的基本语法如下&#xff1a; select { case <-ch1:// 如果从 ch1 成功接收数据&…...

Nacos简介—4.Nacos架构和原理二

大纲 1.Nacos的定位和优势 2.Nacos的整体架构 3.Nacos的配置模型 4.Nacos内核设计之一致性协议 5.Nacos内核设计之自研Distro协议 6.Nacos内核设计之通信通道 7.Nacos内核设计之寻址机制 8.服务注册发现模块的注册中心的设计原理 9.服务注册发现模块的注册中心的服务数…...

Web服务器技术选型指南:主流方案、核心对比与策略选择

Web服务器技术选型指南&#xff1a;主流方案、核心对比与策略选择 一、主流Web服务器概览 在当今互联网架构中&#xff0c;Web服务器承担着处理HTTP请求、管理资源分配和保障服务稳定性的核心职责。根据应用场景和技术特性的不同&#xff0c;主流的Web服务器可分为以下五类&a…...