MQ大牛成长课–从0到1手写分布式消息队列中间件(完结)

276 阅读2分钟

手写一个分布式消息队列中间件(MQ)是一个复杂的系统工程,涉及网络通信、存储引擎、分布式协议、高可用性等多个核心模块。以下是实现一个基础版分布式消息队列的完整思路和代码框架,帮助你从0到1构建一个轻量级MQ。

MQ大牛成长课–从0到1手写分布式消息队列中间件_优课it


一、核心设计目标

  1. 基础功能:消息的发布、存储、消费、ACK确认
  2. 扩展性:支持水平扩展(Broker集群)
  3. 高可用:消息持久化、副本机制
  4. 高性能:低延迟、高吞吐量

二、架构设计

1. 单机版MQ核心模块

text

复制

+---------------------+
|      Producer       |  --> 发送消息
+---------------------+
          |
          | (TCP/HTTP)
          v
+---------------------+
|        Broker       |
| +-----------------+ |
| |  Storage Engine | |  --> 消息存储(内存+磁盘)
| +-----------------+ |
| +-----------------+ |
| |  Request Handler| |  --> 处理生产/消费请求
| +-----------------+ |
+---------------------+
          |
          | (TCP/HTTP)
          v
+---------------------+
|      Consumer       |  --> 消费消息
+---------------------+

2. 分布式扩展

  • Broker集群:多个Broker节点组成集群
  • 服务发现:ZooKeeper/Etcd 管理节点注册与发现
  • 分区机制:Topic分片存储在不同Broker
  • 副本机制:主从复制保证高可用

三、核心实现步骤

1. 存储引擎设计

  • 内存队列:快速读写(使用 ConcurrentHashMap + PriorityQueue
  • 磁盘持久化:顺序写日志(类似Kafka的Segment设计)
  • 索引文件:加速消息查找

java

复制

// 存储引擎核心逻辑(简化版)
public class StorageEngine {
    // 内存中的消息队列(按Topic分区)
    private Map<String, Queue<Message>> topicQueues = new ConcurrentHashMap<>();
    
    // 持久化到磁盘(使用文件存储)
    public void appendToDisk(Message message) {
        try (FileChannel channel = new FileOutputStream("message.log", true).getChannel()) {
            ByteBuffer buffer = ByteBuffer.wrap(message.serialize());
            channel.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 从磁盘加载消息(启动时恢复)
    public void loadFromDisk() {
        // 解析文件并重建内存队列
    }
}

2. 网络通信层

  • 协议设计:自定义二进制协议或使用HTTP/Protobuf
  • 高性能IO:使用Netty实现异步非阻塞通信

java

复制

// Netty服务端核心代码(简化)
public class BrokerServer {
    public void start(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         protected void initChannel(SocketChannel ch) {
                             ch.pipeline().addLast(new MessageDecoder());  // 解码器
                             ch.pipeline().addLast(new MessageHandler());  // 业务处理
                         }
                     });
            ChannelFuture future = bootstrap.bind(port).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

3. Producer/Consumer客户端

  • Producer:发送消息到指定Topic
  • Consumer:订阅Topic并拉取消息,支持ACK机制

java

复制

// Producer发送消息示例
public class Producer {
    public void send(String topic, String message) {
        try (Socket socket = new Socket("localhost", 9090)) {
            OutputStream out = socket.getOutputStream();
            out.write(serializeMessage(topic, message));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// Consumer消费消息示例
public class Consumer {
    public void poll(String topic) {
        // 发送订阅请求到Broker
        // 长轮询或事件驱动模式获取消息
    }
}

4. 分布式集群实现

  • 服务注册与发现:使用ZooKeeper管理Broker节点
  • 分区路由:基于一致性哈希或Range策略分配Topic分区
  • 副本同步:Leader-Follower副本同步(类似Kafka)

java

复制

// ZooKeeper服务注册示例
public class ServiceRegistry {
    private CuratorFramework client;
    
    public void register(String brokerId, String address) {
        try {
            client.create()
                 .creatingParentsIfNeeded()
                 .withMode(CreateMode.EPHEMERAL)
                 .forPath("/brokers/" + brokerId, address.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}