ZeroMQ(也称为 ØMQ、ZMQ)是一个高性能异步消息库,专为分布式/并发应用设计。它提供了类似 Socket 的 API,但支持更灵活的消息模式(如 Pub/Sub、Req/Rep、Pipeline 等),无需依赖中央消息代理(Brokerless)。
ZeroMQ 核心特性
| 特性 | 说明 |
|---|---|
| 多协议支持 | TCP、IPC(进程间)、inproc(线程间)、PGM(多播)等。 |
| 多种通信模式 | 支持 Pub/Sub、Req/Rep、Push/Pull、Router/Dealer 等模式。 |
| 高性能 | 异步 I/O,零拷贝技术,适用于高吞吐低延迟场景。 |
| 无中心化 | 无需单独的消息服务器(如 RabbitMQ/Kafka),节点可直接通信。 |
| 多语言绑定 | C/C++、Python、Java、Go、Rust 等。 |
常用消息模式
(1) 请求-应答(Req/Rep)
-
场景:客户端-服务器交互(如 RPC)。
-
代码示例:
// Server (Rep)
zmq::socket_t responder(context, zmq::socket_type::rep);
responder.bind("tcp://*:5555");
zmq::message_t request;
responder.recv(request); // 阻塞接收
responder.send(zmq::message_t("World"), zmq::send_flags::none);
// Client (Req)
zmq::socket_t requester(context, zmq::socket_type::req);
requester.connect("tcp://localhost:5555");
requester.send(zmq::message_t("Hello"), zmq::send_flags::none);
zmq::message_t reply;
requester.recv(reply);
(2) 发布-订阅(Pub/Sub)
-
场景:广播消息(如股票行情推送)。
-
代码示例:
// Publisher
zmq::socket_t publisher(context, zmq::socket_type::pub);
publisher.bind("tcp://*:5556");
publisher.send(zmq::buffer("TopicA: Data"), zmq::send_flags::none);
// Subscriber (需订阅主题)
zmq::socket_t subscriber(context, zmq::socket_type::sub);
subscriber.connect("tcp://localhost:5556");
subscriber.set(zmq::sockopt::subscribe, "TopicA");
zmq::message_t topic_msg;
subscriber.recv(topic_msg);
(3) 流水线(Push/Pull)
-
场景:任务分发(生产者-消费者)。
-
代码示例:
// Producer (Push)
zmq::socket_t sender(context, zmq::socket_type::push);
sender.bind("tcp://*:5557");
sender.send(zmq::buffer("Task1"), zmq::send_flags::none);
// Consumer (Pull)
zmq::socket_t receiver(context, zmq::socket_type::pull);
receiver.connect("tcp://localhost:5557");
zmq::message_t task;
receiver.recv(task);
(4) 路由-代理(Router/Dealer)
-
场景:负载均衡或复杂路由。
-
特点:支持异步双向通信。
实战:多线程安全聊天程序
功能需求
-
每个客户端可以发送消息到服务器。
-
服务器广播消息给所有客户端。
服务端代码
#include <zmq.hpp>
#include <iostream>
#include <thread>
void server_thread() {
zmq::context_t context(1);
zmq::socket_t publisher(context, zmq::socket_type::pub);
publisher.bind("tcp://*:5555");
zmq::socket_t collector(context, zmq::socket_type::pull);
collector.bind("tcp://*:5556");
while (true) {
zmq::message_t msg;
collector.recv(msg); // 接收客户端消息
std::cout << "Server received: " << msg.to_string() << std::endl;
publisher.send(zmq::buffer("Broadcast: " + msg.to_string()), zmq::send_flags::none); // 广播
}
}
int main() {
std::thread server(server_thread);
server.join();
return 0;
}
客户端代码
#include <zmq.hpp>
#include <iostream>
#include <thread>
void client_thread(int id) {
zmq::context_t context(1);
// 发送消息到服务器
zmq::socket_t sender(context, zmq::socket_type::push);
sender.connect("tcp://localhost:5556");
// 接收广播
zmq::socket_t receiver(context, zmq::socket_type::sub);
receiver.connect("tcp://localhost:5555");
receiver.set(zmq::sockopt::subscribe, "");
sender.send(zmq::buffer("Hello from client " + std::to_string(id)), zmq::send_flags::none);
while (true) {
zmq::message_t broadcast;
receiver.recv(broadcast);
std::cout << "Client " << id << " received: " << broadcast.to_string() << std::endl;
}
}
int main() {
std::thread c1(client_thread, 1);
std::thread c2(client_thread, 2);
c1.join();
c2.join();
return 0;
}
总结
ZeroMQ 常见问题
Q1: 如何保证消息不丢失?
-
使用 持久化队列(如结合 Redis/Kafka)或 重试机制。
-
对于关键数据,启用
ZMQ_SNDHWM/ZMQ_RCVHWM调整缓冲区大小。
Q2: 如何处理慢消费者?
-
使用
ROUTER套接字动态路由。 -
设置
ZMQ_CONFLATE=1只保留最新消息。
Q3: 如何实现跨语言通信?
-
统一消息格式(如 JSON/Protobuf)。
-
示例(Python + C++):
# Python Publisher
import zmq
context = zmq.Context()
pub = context.socket(zmq.PUB)
pub.bind("tcp://*:5555")
pub.send_json({"data": "Hello from Python"})
性能优化建议
-
批量发送:使用
zmq::send_multipart()发送多部分消息。 -
避免拷贝:复用
zmq::message_t缓冲区。 -
线程隔离:每个线程使用独立的 Socket。
消费者-消费者通信
在 ZeroMQ 中实现 消费者-消费者通信(Worker-Worker Communication)通常需要引入 中间代理(Broker) 或 对等网络(Peer-to-Peer) 模型。
1:通过代理(Broker)实现 Worker-Worker 通信
适用于需要集中式管理的场景(如任务结果汇总、动态负载均衡)。
生产者 (PUSH) → 任务队列 (Broker) (ROUTER/DEALER) ← 消费者群 (DEALER)
↑
└── 消费者间通信 (PUB/SUB)
代码实现
Broker(代理)
#include <zmq.hpp>
#include <iostream>
int main() {
zmq::context_t context(1);
// 接收生产者任务 (PULL)
zmq::socket_t task_receiver(context, zmq::socket_type::pull);
task_receiver.bind("tcp://*:5555");
// 分发任务给消费者 (ROUTER)
zmq::socket_t task_dispatcher(context, zmq::socket_type::router);
task_dispatcher.bind("tcp://*:5556");
// 接收消费者间的消息 (SUB)
zmq::socket_t worker_pub_sub(context, zmq::socket_type::sub);
worker_pub_sub.bind("tcp://*:5557");
worker_pub_sub.set(zmq::sockopt::subscribe, "");
zmq::proxy(task_receiver, task_dispatcher, nullptr);
}
消费者(Worker)
#include <zmq.hpp>
#include <iostream>
void worker(int id) {
zmq::context_t context(1);
// 连接Broker获取任务 (DEALER)
zmq::socket_t task_socket(context, zmq::socket_type::dealer);
task_socket.connect("tcp://localhost:5556");
// 消费者间通信 (PUB)
zmq::socket_t worker_pub(context, zmq::socket_type::pub);
worker_pub.connect("tcp://localhost:5557");
// 订阅其他消费者消息 (SUB)
zmq::socket_t worker_sub(context, zmq::socket_type::sub);
worker_sub.connect("tcp://localhost:5557");
worker_sub.set(zmq::sockopt::subscribe, "");
while (true) {
// 接收任务
zmq::message_t task;
task_socket.recv(task);
std::cout << "Worker " << id << " received task: " << task.to_string() << std::endl;
// 模拟处理任务
std::string result = "Result from worker " + std::to_string(id);
// 将结果发送给其他消费者
worker_pub.send(zmq::buffer(result), zmq::send_flags::none);
// 接收其他消费者的消息
zmq::message_t peer_msg;
if (worker_sub.recv(peer_msg, zmq::recv_flags::dontwait)) {
std::cout << "Worker " << id << " got peer message: " << peer_msg.to_string() << std::endl;
}
}
}
int main() {
std::thread w1(worker, 1);
std::thread w2(worker, 2);
w1.join();
w2.join();
return 0;
}
关键点
-
Broker 使用
zmq::proxy自动转发消息。 -
消费者通过
PUB-SUB相互广播消息。 -
DEALER-ROUTER 保证任务分发的负载均衡。
2:对等网络(Peer-to-Peer)直连
适用于去中心化场景(如分布式计算节点间直接通信)。
消费者1 (DEALER) ↔ 消费者2 (DEALER) 消费者2 (DEALER) ↔ 消费者3 (DEALER)
代码实现
#include <zmq.hpp>
#include <iostream>
#include <vector>
void peer_worker(int id, const std::vector<std::string>& peer_addresses) {
zmq::context_t context(1);
// 创建DEALER套接字用于对等通信
zmq::socket_t peer_socket(context, zmq::socket_type::dealer);
peer_socket.set(zmq::sockopt::routing_id, std::to_string(id));
peer_socket.bind("tcp://*:" + std::to_string(6000 + id)); // 每个Worker监听独立端口
// 连接其他Peers
for (const auto& addr : peer_addresses) {
if (addr != std::to_string(id)) {
peer_socket.connect("tcp://localhost:" + std::to_string(6000 + std::stoi(addr)));
}
}
// 发送初始消息
std::string hello_msg = "Hello from worker " + std::to_string(id);
peer_socket.send(zmq::buffer(hello_msg), zmq::send_flags::none);
while (true) {
zmq::message_t msg;
peer_socket.recv(msg);
std::cout << "Worker " << id << " received: " << msg.to_string() << std::endl;
// 模拟处理并回复
std::string reply = "Reply from " + std::to_string(id);
peer_socket.send(zmq::buffer(reply), zmq::send_flags::none);
}
}
int main() {
std::vector<std::string> workers = {"1", "2", "3"}; // Worker IDs
std::vector<std::thread> threads;
for (const auto& id : workers) {
threads.emplace_back(peer_worker, std::stoi(id), workers);
}
for (auto& t : threads) t.join();
return 0;
}
关键点
-
每个 Worker 绑定唯一端口(如
6000 + ID)。 -
使用
DEALER套接字实现双向异步通信。 -
路由ID (
routing_id) 用于标识消息来源。
总结
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Broker 中介 | 集中管理,易于扩展 | 单点故障风险 | 需要结果汇总或复杂路由 |
| P2P 直连 | 去中心化,低延迟 | 连接管理复杂 | 高吞吐量分布式计算 |
进阶优化
-
心跳检测:在 P2P 模式中添加
PING-PONG机制检测节点存活。 -
序列化:使用 Protocol Buffers 或 JSON 格式化消息。
-
错误处理:捕获
zmq::error_t处理网络异常。
如果需要更精细的控制,可以结合 ZeroMQ 的多部分消息(Multipart Messages)或 自定义路由逻辑。

1343

被折叠的 条评论
为什么被折叠?



