003 ZeroMQ PUSH and POLL

本文介绍了一个基于ZMQ的简单任务分发系统,包括任务分发、工作执行和结果收集三个关键部分。通过创建任务通风器、工人和收集器组件,实现高效的任务管理和资源调度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理:taskVent分发任务,worker工作,collect收集结果
框架:
这里写图片描述

实践part1:
这里写图片描述

实践part2:
这里写图片描述

以下是代码:
code:taskvent.cpp

#include<zmq.h>
#include<iostream>
#include<thread>
#include<chrono>
using std::cout;
using std::endl;

int main()
{
    void* pContext = zmq_ctx_new();
    void* pPushSocket = zmq_socket(pContext, ZMQ_PUSH);
    zmq_bind(pPushSocket, "tcp://*:5601");

    void* pPushToCollectSocket = zmq_socket(pContext, ZMQ_PUSH);
    zmq_connect(pPushToCollectSocket, "tcp://localhost:5602");
    cout << "tell collector to ready..." << endl;
    zmq_msg_t msgToCollect;
    zmq_msg_init(&msgToCollect);
    zmq_msg_send(&msgToCollect, pPushToCollectSocket, 0);
    zmq_msg_close(&msgToCollect);

    cout << "press Enter when workers are all running" << endl;
    getchar();

    cout << "start vent 100 tasks..." << endl;
    for (int i = 0; i < 100;++i)
    {
        char buffer[32];
        memset(buffer, 0, sizeof(buffer));
        sprintf_s(buffer, "task item %d", i);
        zmq_msg_t msgToWorker;
        zmq_msg_init_size(&msgToWorker, sizeof(buffer));
        memcpy(zmq_msg_data(&msgToWorker), buffer, sizeof(buffer));
        zmq_msg_send(&msgToWorker, pPushSocket, 0);
        zmq_msg_close(&msgToWorker);
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(1200));
    cout << "task vent complete,press Enter to exit" << endl;
    getchar();

    zmq_close(pPushSocket);
    zmq_close(pPushToCollectSocket);
    zmq_ctx_destroy(pContext);
    return 0;
}

code:worker.cpp

#include<zmq.h>
#include<iostream>
#include<thread>
#include<chrono>
#include<string>
using std::cout;
using std::endl;

int main()
{
    void* pContext = zmq_ctx_new();
    void* pPullSocket = zmq_socket(pContext, ZMQ_PULL);
    zmq_connect(pPullSocket, "tcp://localhost:5601");      //用于接收任务;

    void* pPushSocket = zmq_socket(pContext, ZMQ_PUSH);
    zmq_connect(pPushSocket, "tcp://localhost:5602");      //用于发送结果;

    char buffer[32];
    memset(buffer, 0, sizeof(buffer));
    while (true)
    {
        cout << "listen..." << endl;
        zmq_msg_t msgIn;
        zmq_msg_init(&msgIn);
        zmq_msg_recv(&msgIn, pPullSocket, 0);
        cout << "work task is:" << (char*)zmq_msg_data(&msgIn);
        memcpy(buffer, zmq_msg_data(&msgIn), zmq_msg_size(&msgIn));
        zmq_msg_close(&msgIn);

        //do some work
        std::this_thread::sleep_for(std::chrono::milliseconds(1200));
        cout << "-----done" << endl;

        //report result
        zmq_msg_t msgOut;
        zmq_msg_init_size(&msgOut, sizeof(buffer));
        memcpy(zmq_msg_data(&msgOut), buffer, sizeof(buffer));
        zmq_msg_send(&msgOut, pPushSocket, 0);
        zmq_msg_close(&msgOut);
    }
    zmq_close(pPullSocket);
    zmq_close(pPushSocket);
    zmq_ctx_destroy(pContext);
    return 0;

}

code:collect.cpp

#include<zmq.h>
#include<iostream>
#include<thread>
#include<chrono>
using std::cout;
using std::endl;

int main()
{
    void* pContext = zmq_ctx_new();
    void* pPullSocket = zmq_socket(pContext, ZMQ_PULL);
    zmq_bind(pPullSocket, "tcp://*:5602");

    cout << "receiving ready signal" << endl;
    zmq_msg_t msgReady;
    zmq_msg_init(&msgReady);
    zmq_msg_recv(&msgReady, pPullSocket, 0);
    zmq_msg_close(&msgReady);
    cout << "done!" << endl;

    cout << "start collect..." << endl;
    int i;
    for (i = 0; i < 100;++i)
    {
        zmq_msg_t msgIn;
        zmq_msg_init(&msgIn);
        zmq_msg_recv(&msgIn, pPullSocket, 0);
        cout << "[" << (char*)zmq_msg_data(&msgIn) << "]---" << i << endl;
        zmq_msg_close(&msgIn);
    }
    if (i==100)
    {
        cout << "all done,press Enter to exit" << endl;
    }
    getchar();
    zmq_close(pPullSocket);
    zmq_ctx_term(pContext);
    return 0;
}

//share end.

<think>我们正在讨论ZeroMQPUSH-PULL模式。根据引用[1],PUSH-PULL模式(也称为管道模式)通过PUSH和PULL套接字实现负载均衡和任务分发。引用[2]提到,PUSH套接字会将任务均匀分发给所有已连接的PULL套接字(负载均衡),而PULL套接字会以公平排队的方式收集结果。引用[4]指出,PUSH-PULL模式是单向的,一条消息只会被一个消费者(PULL端)接收,适合多个消费者的情况。 下面我将提供一个使用ZeroMQPUSH-PULL模式的完整示例,并解释其工作原理。 ### ZeroMQ PUSH-PULL模式示例 #### 场景描述 假设有一个任务分发者(PUSH套接字)和多个任务执行者(PULL套接字)。分发者将任务发送给执行者,执行者处理任务后将结果发送给结果收集者(另一个PULL套接字)。收集者使用PULL套接字收集结果。 #### 代码结构 1. **任务分发者(Producer)**:绑定PUSH套接字,发送任务。 2. **任务执行者(Worker)**:连接分发者的PUSH套接字(使用PULL),同时连接结果收集者的PULL套接字(使用PUSH)发送结果。 3. **结果收集者(Collector)**:绑定PULL套接字,收集结果。 #### 示例代码 以下是一个简化的Java示例(基于引用[3]和[5]的代码风格): **任务分发者(Producer.java)**: ```java package zeromq.pushpull; import org.zeromq.ZMQ; public class Producer { public static void main(String[] args) throws InterruptedException { ZMQ.Context context = ZMQ.context(1); ZMQ.Socket sender = context.socket(ZMQ.PUSH); sender.bind("tcp://*:5557"); // 绑定端口,等待worker连接 System.out.println("任务分发者启动..."); // 等待worker连接(实际应用中需要同步机制) Thread.sleep(2000); for (int i = 0; i < 100; i++) { String task = "任务" + i; sender.send(task.getBytes(), 0); System.out.println("分发任务: " + task); } // 发送结束信号 sender.send("END".getBytes(), 0); sender.close(); context.term(); } } ``` **任务执行者(Worker.java)**: ```java package zeromq.pushpull; import org.zeromq.ZMQ; public class Worker { public static void main(String[] args) { ZMQ.Context context = ZMQ.context(1); // 连接任务分发者(PULL模式接收任务) ZMQ.Socket receiver = context.socket(ZMQ.PULL); receiver.connect("tcp://localhost:5557"); // 连接结果收集者(PUSH模式发送结果) ZMQ.Socket sender = context.socket(ZMQ.PUSH); sender.connect("tcp://localhost:5558"); while (true) { byte[] taskBytes = receiver.recv(0); String task = new String(taskBytes); if ("END".equals(task)) { // 将结束信号继续传递,以便其他worker也能收到 sender.send("END", 0); break; } // 处理任务(这里简单模拟) String result = "处理完成: " + task; // 发送结果 sender.send(result.getBytes(), 0); System.out.println(Thread.currentThread().getName() + " 处理: " + task); } receiver.close(); sender.close(); context.term(); } } ``` **结果收集者(Collector.java)**: ```java package zeromq.pushpull; import org.zeromq.ZMQ; public class Collector { public static void main(String[] args) { ZMQ.Context context = ZMQ.context(1); ZMQ.Socket receiver = context.socket(ZMQ.PULL); receiver.bind("tcp://*:5558"); // 绑定端口,接收worker的结果 System.out.println("结果收集者启动..."); int endCount = 0; int workerCount = 2; // 假设有2个worker while (true) { byte[] resultBytes = receiver.recv(0); String result = new String(resultBytes); if ("END".equals(result)) { endCount++; if (endCount == workerCount) { break; // 所有worker都发送了END,则退出 } } else { System.out.println("收到结果: " + result); } } receiver.close(); context.term(); } } ``` #### 工作原理 1. **任务分发**: - Producer通过PUSH套接字发送任务。ZeroMQ会自动将任务以轮询(round-robin)的方式分发给所有已连接的Worker(PULL套接字)。这就是负载均衡(引用[2]和[4])。 2. **任务处理**: - 每个Worker通过PULL套接字接收任务,处理完成后,将结果通过PUSH套接字发送给Collector(PULL套接字)。 3. **结果收集**: - Collector通过PULL套接字收集结果,同样以公平排队(fair-queuing)的方式接收来自不同Worker的结果(引用[2])。 #### 关键点 - **单向通信**:PUSH-PULL模式是单向的,数据只能从PUSH端流向PULL端(引用[4])。 - **负载均衡**:PUSH套接字会将消息均匀分发给所有已连接的PULL套接字(引用[2])。 - **同步问题**:在启动时,需要确保Worker已经连接上Producer,否则先发送的消息可能会丢失(引用[2]提到“slow joiner”问题)。通常的解决方法是等待所有Worker连接后再开始发送任务(示例中使用了`Thread.sleep(2000)`作为简单演示,实际应用中需要更健壮的同步机制)。 - **结束信号**:示例中通过发送特殊的“END”消息来通知Worker和Collector停止工作。注意,每个Worker都需要收到“END”消息,并且Collector需要收到所有Worker的“END”消息才停止。 #### 性能特点 引用[2]中提到,增加Worker数量可以显著减少总处理时间: - 1个Worker:5034ms - 2个Worker:2421ms - 4个Worker:1018ms 这是因为任务被平均分配给了多个Worker,实现了并行处理。 #### 注意事项 - **慢连接问题(Slow Joiner)**:如果Worker连接速度慢,可能会错过最初发送的一些消息。因此,在实际应用中,需要确保所有Worker都连接成功后再开始发送任务(引用[2])。 - **消息丢失**:ZeroMQ不保证消息的持久化。如果程序崩溃,未处理的消息会丢失。如果需要持久化,应使用其他机制(如持久化队列)。 - **扩展性**:可以动态增加或减少Worker数量,系统会自动调整负载(引用[1])。 ### 总结 PUSH-PULL模式适用于需要任务分发的场景,如分布式计算、数据处理流水线等。它提供了自动负载均衡和良好的扩展性,但需要注意同步和消息持久化问题。 §§相关问题§§ 1. 如何解决PUSH-PULL模式中的慢连接(Slow Joiner)问题? 2. PUSH-PULL模式与PUB-SUB模式在消息分发机制上有何不同? 3. 在分布式系统中,如何保证PUSH-PULL模式的消息可靠性? 4. PUSH-PULL模式是否支持双向通信?如果不支持,如何实现双向通信?</think>### ZeroMQ PUSH-PULL 模式详解与使用示例 #### 一、核心原理 PUSH-PULL 模式是 ZeroMQ 的**单向管道模式**,专为**流水线式任务处理**设计: 1. **单向数据流** - `PUSH` 套接字:**主动推送**数据(生产者) - `PULL` 套接字:**被动接收**数据(消费者) - 数据流向:`PUSH → PULL`(不可反向) 2. **负载均衡机制** - 当存在多个 `PULL` 消费者时,`PUSH` 会自动以**轮询方式**分配任务[^2] - 每条消息仅被**一个消费者**处理(非广播)[^4] ```mermaid graph LR PUSH-->|任务1| PULL1 PUSH-->|任务2| PULL2 PUSH-->|任务3| PULL1 PUSH-->|任务4| PULL2 ``` 3. **关键特性** - **自动负载均衡**:新加入的消费者立即参与任务分配 - **公平排队**:结果收集时按完成顺序处理[^2] - **高吞吐量**:实测性能随消费者数量线性提升[^2]: ``` 1个消费者:5034ms 2个消费者:2421ms(提速2.08倍) 4个消费者:1018ms(提速4.94倍) ``` #### 二、完整使用示例(Python实现) ```python import time import threading import zmq # 任务分发者(PUSH) def task_producer(): context = zmq.Context() sender = context.socket(zmq.PUSH) sender.bind("tcp://*:5557") # 绑定端口 # 等待消费者准备(解决slow joiner问题[^2]) print("等待消费者连接...") time.sleep(1) # 实际应用需用同步机制替代 # 发送10个任务 for i in range(10): task_data = f"Task-{i}" sender.send_string(task_data) print(f"[Producer] 已分发: {task_data}") time.sleep(0.1) # 模拟任务生成间隔 sender.close() context.term() # 任务消费者(PULL) def task_consumer(worker_id): context = zmq.Context() receiver = context.socket(zmq.PULL) receiver.connect("tcp://localhost:5557") # 连接生产者 while True: task = receiver.recv_string() if task.startswith("Task"): print(f"[Worker-{worker_id}] 处理: {task}") time.sleep(0.5) # 模拟任务处理耗时 else: break receiver.close() if __name__ == "__main__": # 启动生产者线程 producer_thread = threading.Thread(target=task_producer) producer_thread.start() # 启动3个消费者 consumers = [] for i in range(3): t = threading.Thread(target=task_consumer, args=(i,)) t.start() consumers.append(t) producer_thread.join() for t in consumers: t.join() ``` ##### 执行结果示例: ``` [Producer] 已分发: Task-0 [Worker-0] 处理: Task-0 [Producer] 已分发: Task-1 [Worker-1] 处理: Task-1 # 自动负载均衡 [Producer] 已分发: Task-2 [Worker-2] 处理: Task-2 [Producer] 已分发: Task-3 [Worker-0] 处理: Task-3 # 再次分配给Worker-0 ... ``` #### 三、关键实践技巧 1. **解决慢连接问题(Slow Joiner)** - 问题:先启动的消费者会获取更多任务[^2] - 方案:使用同步屏障(如`zmq.proxy`或等待信号) ```python # 同步示例(生产者端) ready_signal = context.socket(zmq.PUB) ready_signal.bind("inproc://ready") ready_signal.send(b"GO") # 消费者收到后开始工作 ``` 2. **多级流水线架构** 扩展为生产者→工作者→收集者的三级架构: ```mermaid graph LR Producer(PUSH) --> Workers(PULL-PUSH) Workers --> Collector(PULL) ``` - 工作者同时使用`PULL`(收任务)和`PUSH`(发结果) - 收集者用`PULL`汇总结果[^2] 3. **跨语言实现兼容性** Java 消费者示例(引用[3][5]): ```java // Java PULL消费者 ZMQ.Context context = ZMQ.context(1); ZMQ.Socket receiver = context.socket(ZMQ.PULL); receiver.connect("tcp://localhost:5557"); // 连接Python生产者 while (true) { String task = new String(receiver.recv(0)); System.out.println("Java处理: " + task); } ``` #### 四、适用场景 1. **分布式计算**:MapReduce任务分发 2. **日志处理**:多节点日志收集 3. **实时流水线**:视频转码/数据清洗流水线 4. **负载均衡服务**:HTTP请求分发 > **注意限制**:消息不持久化、无重试机制,需业务层实现容错
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值