这个疯狂的想法
如果我们仅使用 MariaDB 来构建一个消息队列会怎样?不是 Kafka,不是 RabbitMQ,不是 Redis Streams。只是 MariaDB,它的存储引擎和 binlog 复制。
这是一个思想实验,一个概念验证。目标不是取代成熟的消息方案,而是展示 MariaDB / MySQL 架构的灵活性,并探索鲜为人知的模式。
MMQ 架构
MMQ(MariaDB Message Queue,MariaDB 消息队列)依赖三个原生组件:
- Blackhole 引擎:一个接受 INSERT 但不存储任何内容的存储引擎。数据"消失"了——但它会被记录在 binlog 中。
- Binlog 复制:MariaDB 的原生复制机制,将事件从一台服务器传播到另一台。
- InnoDB 表 + 触发器:用于消息消费和追踪。
生产者(Publisher)
生产者服务器有一个 Blackhole 表作为入口:
CREATE TABLE message_queue (
msg_id BIGINT NOT NULL,
topic VARCHAR(255) NOT NULL,
payload JSON NOT NULL,
created_at DATETIME(6) DEFAULT NOW(6)
) ENGINE=Blackhole;
当应用程序发布消息时:
INSERT INTO message_queue (msg_id, topic, payload)
VALUES (
NEXT VALUE FOR msg_sequence,
'order.created',
'{"order_id": 12345, "customer": "acme", "total": 99.99}'
);
Blackhole 引擎不写入任何数据到磁盘。但 INSERT 会被记录在服务器的 binlog 中。这就是 Blackhole 的魔力:它参与 binlog 而不消耗存储。
用于标识符的序列
MariaDB 支持序列(自版本 10.3 起),提供无需 AUTO_INCREMENT 锁定成本的唯一标识符:
CREATE SEQUENCE msg_sequence
START WITH 1
INCREMENT BY 1
CACHE 1000;
CACHE 1000 在内存中预分配 1,000 个值,减少磁盘访问和锁。
中间代理(Broker/Relay)
中间代理是一台配置为生产者从库的 MariaDB 服务器。它接收 binlog 事件并进行复制。这就是消息分发机制。
对于扇出(一条消息发送给多个消费者),你可以拥有同一主库的多个从库——每个从库独立接收所有消息的副本。
生产者 (Blackhole) → binlog → 代理 1 (从库)
→ 代理 2 (从库)
→ 代理 3 (从库)
消费者
每个消费者有一个 InnoDB 表存储接收到的消息以及消费追踪机制:
CREATE TABLE consumed_messages (
msg_id BIGINT PRIMARY KEY,
topic VARCHAR(255),
payload JSON,
created_at DATETIME(6),
consumed_at DATETIME(6) DEFAULT NULL,
consumer_id VARCHAR(100) DEFAULT NULL
) ENGINE=InnoDB;
一个触发器将复制的 INSERT 转换为可用的行:
CREATE TRIGGER trg_message_arrived
BEFORE INSERT ON message_queue
FOR EACH ROW
BEGIN
INSERT INTO consumed_messages (msg_id, topic, payload, created_at)
VALUES (NEW.msg_id, NEW.topic, NEW.payload, NEW.created_at);
END;
消费通过原子查询完成:
UPDATE consumed_messages
SET consumed_at = NOW(6),
consumer_id = 'worker-01'
WHERE consumed_at IS NULL
AND topic = 'order.created'
ORDER BY msg_id ASC
LIMIT 1;
LIMIT 1 与原子 UPDATE 结合,确保只有一个消费者处理每条消息(没有重复消费)。
JSON 消息
MariaDB 的原生 JSON 格式(自 10.2 起)允许用丰富的载荷来结构化消息:
INSERT INTO message_queue (msg_id, topic, payload) VALUES (
NEXT VALUE FOR msg_sequence,
'user.profile.updated',
JSON_OBJECT(
'user_id', 42,
'changes', JSON_ARRAY(
JSON_OBJECT('field', 'email', 'old', 'old@mail.com', 'new', 'new@mail.com'),
JSON_OBJECT('field', 'name', 'old', 'John', 'new', 'Jonathan')
),
'timestamp', NOW(6)
)
);
局限性(而且有很多)
让我们明确:MMQ 是一个概念,不是生产就绪的解决方案。
没有可靠的投递保证。 如果复制中断,消息会丢失(或延迟)。没有原生重试机制。
没有分区。 所有消息流经单个 binlog。不像 Kafka 那样有按主题的分发。
没有重放。 一旦消费,消息无法轻松重放(除非你保留生产者上的 binlog)。
复制延迟。 复制延迟在发布和消息可用之间增加了延时。对异步场景可以接受,对实时场景则不行。
没有分布式确认。 生产者不知道消费者是否处理了消息。
为什么它仍然有趣
尽管有这些局限性,这种模式展示了重要的概念:
-
binlog 作为事件流。 MariaDB / MySQL 的 binlog 是一个有序的、持久的、可复制的事件流。概念上接近 Kafka 日志。
-
Blackhole 引擎作为适配器。 Blackhole 允许"发布"而不存储,使用 binlog 作为传输通道。
-
复制作为分发机制。 多从库复制提供原生的扇出分发,无需额外配置。
-
数据库作为多功能基础设施。 如果你的生产环境中已经有 MariaDB,你就已经拥有简单消息传递的基础设施。
对于简单的使用场景——服务间的内部通知、事件审计、站点间的事件复制——MMQ 可能足够用了,无需添加额外的基础设施组件。
结论
MariaDB 作为消息队列:一个疯狂的想法,一个有趣的概念验证,以及 Blackhole 引擎 + binlog 复制灵活性的展示。不要在生产环境中将其用于关键消息传递。但记住这个概念——有时候最好的架构就是利用你已经拥有的东西。
本文最初发表于 Medium。
评论 (0)
暂无评论。
发表评论