IM之java端侧消息接收与处理系统设计与实现文档

166 阅读9分钟

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枚举和MessageContentoneof分支,不影响旧版本解析。
  • 字段保留​:使用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 新增消息类型

  1. 修改Protobuf定义​:在MessageType枚举中添加新类型(如VIDEO_MESSAGE=5),并在MessageContent中扩展oneof分支(如VideoContent video=5;)。
  2. 生成多语言代码​:使用protoc重新生成Java/Go等语言的类。
  3. 实现处理逻辑​:在各端添加对新类型的处理(如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确认、幂等性校验、死信队列确保消息不丢失,同时支持内存水位监控与动态扩展。适用于高可靠、多类型、实时性要求的消息处理场景(如即时通讯、金融交易、物联网设备通信)。