IM之java端侧端侧消息接收与处理系统设计与实现文档(不涉及多端交互)
一、概述
本系统旨在实现高可靠、可扩展、严格顺序的消息接收与处理能力,支持多类型消息(文本、二进制、自定义事件等),并通过Protobuf保证跨端数据一致性。核心功能包括:
- 消息类型泛化(支持动态扩展)
- 异步处理与顺序保证(按时间戳排序)
- 容错机制(超时检测、重试、死信队列)
- 内存管理(超时清理、水位监控)
二、核心设计
2.1 消息类型泛化(Protobuf定义)
通过Protobuf定义通用消息结构,支持多类型扩展,确保跨端(Java/Go/Python)数据一致性。
2.1.1 消息协议定义(message.proto)
syntax = "proto3";
package common;
// 消息类型枚举(可扩展)
enum MessageType {
TEXT_MESSAGE = 0; // 文本消息
BINARY_MESSAGE = 1; // 二进制消息
CUSTOM_EVENT = 2; // 自定义事件
HEARTBEAT = 3; // 心跳包(保活)
IMAGE_MESSAGE = 4; // 图片消息(示例扩展类型)
RESERVED_100_TO_199 = 100; // 保留字段(防止冲突)
}
// 消息内容(多类型扩展)
message MessageContent {
oneof content {
TextContent text = 1; // 文本内容
BinaryContent binary = 2; // 二进制内容
CustomEvent custom = 3; // 自定义事件
ImageContent image = 4; // 图片内容(示例扩展)
}
}
// 文本消息内容
message TextContent {
string text = 1; // 文本数据
}
// 二进制消息内容
message BinaryContent {
bytes data = 1; // 二进制数据(如图像、文件)
}
// 自定义事件内容
message CustomEvent {
string event_type = 1; // 事件类型(如"ORDER_CREATED")
map<string, string> params = 2; // 事件参数
}
// 图片消息内容(示例扩展)
message ImageContent {
string url = 1; // 图片URL
int32 width = 2; // 图片宽度(像素)
int32 height = 3; // 图片高度(像素)
}
// 完整消息结构(全局唯一、可扩展)
message Message {
string message_id = 1; // 全局唯一ID(UUID或雪花算法生成)
int64 timestamp = 2; // 消息时间戳(毫秒级,用于排序)
MessageType type = 3; // 消息类型(枚举值)
MessageContent content = 4; // 消息内容(多类型支持)
int32 retry_count = 5; // 重试次数(默认0,失败时递增)
string source = 6; // 消息来源(如"CLIENT_APP", "SERVICE_A")
string destination = 7; // 目标服务(如"PROCESSOR", "STORAGE")
reserved 100 to 199; // 保留字段(防止后续扩展冲突)
}
2.1.2 跨端一致性保障
- 多语言支持:通过
protoc编译器生成各语言类(Java/Go/Python),确保序列化/反序列化一致。 - 版本兼容:新增消息类型时,仅扩展
MessageType枚举和MessageContent的oneof分支,不影响旧版本解析。 - 字段保留:使用
reserved关键字标记保留字段号,避免后续扩展时冲突。
2.2 消息接收与处理流程
sequenceDiagram
participant Sender as 消息发送方
participant Receiver as 消息接收方
participant Storage as 持久化存储
participant Processor as 消息处理器
participant Queue as 全局优先队列
participant Output as 结果输出模块
Sender->>Receiver: 发送消息(未持久化)
Receiver->>Storage: 持久化消息(防丢失)
Storage-->>Receiver: 持久化成功响应
Receiver->>Queue: 提交消息(按时间戳排序)
Queue->>Processor: 分发消息(按类型到线程池)
Processor->>Processor: 处理消息(异步执行)
Processor->>Storage: 更新状态(处理中/完成/失败)
Processor->>Output: 完成消息(按时间戳顺序)
Output->>Sender: 发送ACK确认
2.3 核心组件设计
2.3.1 全局优先队列(按时间戳排序)
使用PriorityBlockingQueue存储未处理消息,确保最早到达的消息优先处理。
// 全局优先队列(按时间戳升序排列)
public class GlobalMessageQueue {
private final PriorityBlockingQueue<Message> queue = new PriorityBlockingQueue<>(
Comparator.comparingLong(Message::getTimestamp)
);
// 生产者:提交消息
public void offer(Message message) {
queue.offer(message);
}
// 消费者:取出最早消息
public Message take() throws InterruptedException {
return queue.take();
}
// 获取当前队列大小
public int size() {
return queue.size();
}
}
2.3.2 泛化线程池管理器
根据消息类型动态创建线程池,支持灵活扩展和资源隔离。
public class ThreadPoolManager {
// 线程池映射(消息类型 -> 线程池)
private final ConcurrentHashMap<MessageType, ExecutorService> threadPools = new ConcurrentHashMap<>();
// 获取或创建线程池(核心线程数=CPU核心数,最大线程数=2*CPU核心数)
public ExecutorService getOrCreatePool(MessageType type) {
return threadPools.computeIfAbsent(type, k ->
Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2,
r -> {
Thread t = new Thread(r);
t.setName("MessageProcessor-" + type.name());
return t;
}
)
);
}
// 关闭所有线程池
public void shutdown() {
threadPools.values().forEach(ExecutorService::shutdownNow);
}
}
2.3.3 可靠性保障模块
包含持久化存储、ACK确认、幂等性校验、死信队列四大机制。
2.3.3.1 持久化存储(生产阶段)
消息发送方在发送前先将消息持久化到本地或分布式存储(如Kafka),避免进程崩溃导致丢失。
public class MessageSender {
private final MessageRepository repository; // 持久化存储(如Kafka)
private final GlobalMessageQueue queue;
// 发送消息(先持久化,再入队)
public void sendMessage(Message message) {
// 1. 生成全局唯一ID(若未设置)
if (message.getMessageId() == null) {
message = message.toBuilder()
.setMessageId(UUID.randomUUID().toString())
.build();
}
// 2. 持久化到存储(同步写,确保成功)
repository.save(message);
// 3. 提交到全局队列
queue.offer(message);
}
}
// 持久化存储接口(示例:Kafka生产者)
public interface MessageRepository {
void save(Message message);
}
2.3.3.2 ACK确认与重试(处理阶段)
处理方完成消息后发送ACK,发送方未收到ACK则重试。
public class MessageProcessor {
private final MessageSender sender; // 反向引用发送方
private final GlobalMessageQueue queue;
// 处理消息
public void process(Message message) {
try {
// 1. 按类型分发处理(示例:文本消息直接输出)
if (message.getType() == MessageType.TEXT_MESSAGE) {
processTextMessage(message);
}
// 2. 处理成功,发送ACK
sender.sendAck(message.getMessageId());
} catch (Exception e) {
// 3. 处理失败,记录日志并重试
handleFailure(message, e);
}
}
// 发送ACK(示例:通过HTTP调用发送方接口)
private void sendAck(String messageId) {
// 调用发送方API:POST /ack/{messageId}
boolean ackSuccess = HttpUtils.post("/ack/" + messageId);
if (!ackSuccess) {
// ACK失败,触发重试
queue.offer(message); // 重新入队(避免丢失)
}
}
// 处理失败(重试或转移死信队列)
private void handleFailure(Message message, Exception e) {
message = message.toBuilder()
.setRetryCount(message.getRetryCount() + 1)
.build();
if (message.getRetryCount() > 3) { // 最大重试3次
// 转移死信队列(人工排查)
DeadLetterQueue.send(message);
} else {
// 延迟重试(指数退避:1s, 2s, 4s...)
long delay = (long) Math.pow(2, message.getRetryCount()) * 1000;
queue.offerWithDelay(message, delay); // 自定义延迟入队
}
}
}
2.3.3.3 幂等性校验
处理方通过消息ID去重,避免重复处理。
public class IdempotentChecker {
private final ConcurrentHashMap<String, Long> processedMessages = new ConcurrentHashMap<>();
// 校验消息是否已处理(时间戳防旧消息覆盖)
public boolean isProcessed(String messageId, long currentTimestamp) {
Long lastTimestamp = processedMessages.get(messageId);
return lastTimestamp != null && lastTimestamp >= currentTimestamp;
}
// 标记消息为已处理
public void markAsProcessed(String messageId, long timestamp) {
processedMessages.put(messageId, timestamp);
}
}
2.3.3.4 死信队列(DLQ)
多次重试失败的消息转移至死信队列,人工或定时任务分析。
public class DeadLetterQueue {
private final GlobalMessageQueue dlq;
public DeadLetterQueue(GlobalMessageQueue dlq) {
this.dlq = dlq;
}
// 转移消息到死信队列
public void send(Message message) {
message = message.toBuilder()
.setType(MessageType.DEAD_LETTER) // 标记为死信类型
.build();
dlq.offer(message);
}
// 消费死信队列(人工排查)
public Message pollDeadLetter() throws InterruptedException {
return dlq.take();
}
}
三、顺序保证与实时性优化
3.1 严格顺序处理
通过全局优先队列按时间戳排序,确保消息按接收顺序处理。
public class OrderedProcessor {
private final GlobalMessageQueue queue;
private final ThreadPoolManager threadPoolManager;
private final IdempotentChecker idempotentChecker;
// 处理循环
public void start() {
new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// 1. 取出最早消息
Message message = queue.take();
// 2. 幂等校验(避免重复处理)
if (idempotentChecker.isProcessed(message.getMessageId(), message.getTimestamp())) {
continue;
}
// 3. 提交到对应类型的线程池处理
ExecutorService executor = threadPoolManager.getOrCreatePool(message.getType());
executor.submit(() -> {
try {
// 4. 处理消息(按时间戳顺序)
processMessage(message);
// 5. 标记为已处理
idempotentChecker.markAsProcessed(message.getMessageId(), message.getTimestamp());
} catch (Exception e) {
// 异常处理(记录日志、重试)
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
private void processMessage(Message message) {
// 按类型处理消息(示例:文本消息输出)
if (message.getType() == MessageType.TEXT_MESSAGE) {
System.out.println("Process [" + message.getTimestamp() + "]: " + message.getContent().getText());
}
}
}
3.2 内存水位监控与清理
避免内存暴增,对超时或无效消息进行清理。
public class MemoryManager {
private final GlobalMessageQueue queue;
private final long MEMORY_THRESHOLD = 1_000_000; // 内存阈值(消息数)
private final long TIMEOUT_MS = 30_000; // 超时时间(30秒)
// 监控循环
public void start() {
new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// 1. 检查内存水位
if (queue.size() > MEMORY_THRESHOLD) {
// 2. 清理超时消息(优先清理)
cleanUpTimeoutMessages();
}
Thread.sleep(5_000); // 每5秒检查一次
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
// 清理超时消息
private void cleanUpTimeoutMessages() {
long now = System.currentTimeMillis();
List<Message> toRemove = new ArrayList<>();
for (Message message : queue) { // 遍历队列(需线程安全)
if (now - message.getTimestamp() > TIMEOUT_MS) {
toRemove.add(message);
}
}
// 移除超时消息(示例:批量删除)
queue.removeAll(toRemove);
System.out.println("Cleaned up " + toRemove.size() + " timeout messages.");
}
}
四、扩展建议
4.1 新增消息类型
- 修改Protobuf定义:在
MessageType枚举中添加新类型(如VIDEO_MESSAGE=5),并在MessageContent中扩展oneof分支(如VideoContent video=5;)。 - 生成多语言代码:使用
protoc重新生成Java/Go等语言的类。 - 实现处理逻辑:在各端添加对新类型的处理(如
VideoMessageHandler)。
4.2 性能优化
- 批量处理:对短耗时消息(如文本)启用批量处理(每次处理100条),减少线程切换开销。
- 异步IO:对耗时操作(如网络请求)使用异步IO(如Netty),避免阻塞处理线程。
- 内存池:使用
ByteBuffer内存池(如Netty的PooledByteBufAllocator)减少GC压力。
4.3 监控与告警
- 关键指标:消息延迟(生产→处理)、重试率、死信队列大小、内存使用率。
- 告警规则:重试率>10%、死信队列增长>100条/分钟、内存使用率>80%时触发告警。
五、测试验证
5.1 单元测试(消息顺序保证)
public class OrderedProcessorTest {
@Test
public void testMessageOrder() throws InterruptedException {
// 1. 准备乱序消息(时间戳:3→1→2)
Message msg1 = Message.newBuilder()
.setMessageId("1")
.setTimestamp(3)
.setType(MessageType.TEXT_MESSAGE)
.setContent(TextContent.newBuilder().setText("Msg3").build())
.build();
Message msg2 = Message.newBuilder()
.setMessageId("2")
.setTimestamp(1)
.setType(MessageType.TEXT_MESSAGE)
.setContent(TextContent.newBuilder().setText("Msg1").build())
.build();
Message msg3 = Message.newBuilder()
.setMessageId("3")
.setTimestamp(2)
.setType(MessageType.TEXT_MESSAGE)
.setContent(TextContent.newBuilder().setText("Msg2").build())
.build();
// 2. 提交消息到全局队列
GlobalMessageQueue queue = new GlobalMessageQueue();
queue.offer(msg1);
queue.offer(msg2);
queue.offer(msg3);
// 3. 启动处理器
OrderedProcessor processor = new OrderedProcessor(queue, new ThreadPoolManager(), new IdempotentChecker());
processor.start();
// 4. 等待处理完成(模拟)
Thread.sleep(1_000);
// 5. 验证输出顺序(应按时间戳1→2→3)
// (实际需捕获输出日志或结果队列验证)
assertTrue(true); // 示例断言
}
}
5.2 集成测试(可靠性)
public class ReliabilityTest {
@Test
public void testMessageNotLost() throws InterruptedException {
// 1. 启动发送方、接收方、处理器
MessageRepository storage = new KafkaRepository(); // 模拟Kafka存储
GlobalMessageQueue queue = new GlobalMessageQueue();
MessageSender sender = new MessageSender(storage, queue);
MessageProcessor processor = new MessageProcessor(sender, new ThreadPoolManager());
processor.start();
// 2. 发送消息(模拟网络中断)
Message message = Message.newBuilder()
.setMessageId("test-1")
.setTimestamp(System.currentTimeMillis())
.setType(MessageType.TEXT_MESSAGE)
.setContent(TextContent.newBuilder().setText("Test").build())
.build();
sender.sendMessage(message);
// 3. 模拟接收方重启(丢失内存中的消息)
receiver.restart();
// 4. 验证消息是否从存储恢复并处理
Thread.sleep(2_000);
assertTrue(storage.exists("test-1")); // 存储中存在消息
assertTrue(processor.isProcessed("test-1")); // 消息已处理
}
}
六、总结
本系统通过Protobuf实现消息类型泛化与跨端一致性,结合全局优先队列保证顺序,通过持久化存储、ACK确认、幂等性校验、死信队列确保消息不丢失,同时支持内存水位监控与动态扩展。适用于高可靠、多类型、实时性要求的消息处理场景(如即时通讯、金融交易、物联网设备通信)。