ZeroMQ笔记
ZeroMQ笔记
zmq是一个轻量级消息传输内核。可以理解为封装后的网络库,把一些在实践中总结出来的消息通信模型封装成不同类型的套接字,以供我们使用。
通信单元是消息,业务层收到的消息一定是一个完整的包,不用考虑分包和粘包。
不同模型可以按照一定的条件组合,并且接口统一。
安装
python为例:
#安装
pip3 install pyzmq
#查看是否安装成功
import zmq
print(zmq.__version__)
print(zmq.zmq_version_info())
传输方式
- inproc:同一个进程的线程之间,bind("inproc://#1.inproc")
- ipc:本地进程之间,bind("ipc:///tmp/GameLogicServer.ipc")
- tcp:远程进程之间,bind("tcp://*:5555")
- pgm:PGM datagrams are layered directly on top of IP datagrams as defined by RFC 3208
- epgm: EPGM where PGM datagrams are encapsulated inside UDP datagrams
(Encapsulated Pragmatic General Multicast)
如果两个进程在同一主机且在同一个操作平台,可选择IPC或TCI/IP两种通讯方式都可以,但IPC效率高于TCP/IP。
采用IPC通讯,进程1直接把通讯包发给进程2。
采用TCP/IP通讯,进程1将要先把通讯包发给“LO”即本地环路接口,通过"LO"再把通讯包发给进程2。
基础的消息模式
有三种,其他模式都是在这些基础上改进的。
req-rep
pub-sub
push-pull
router-dealer基于req-rep
Request-Reply (请求响应模式)
消息双向的,同步。
两个角色:请求端、回应端,请求端相当于客户端,回应端相当于服务端。
请求端和回应端无论谁先启动,效果是相同的。
由请求端发起请求,然后等待回应端应答。
一个请求必须对应一个回应,从请求端的角度来看是发-收配对,从回应端的角度是收-发对。
Server和Client是1:N的模型。
该模式主要用于远程调用及任务分配。

#client.py
import zmq
ctx = zmq.Context()
print("Connecting to hello world server…")
s = ctx.socket(zmq.REQ)
s.connect("tcp://localhost:5555")
s.send(b"Hello")
msg =s.recv()
print(f"Recv reply [{msg}]")
# server.py
import time
import zmq
ctx = zmq.Context()
s = ctx.socket(zmq.REP)
s.bind("tcp://*:5555")
print("Listening …")
while True:
msg = s.recv()
print("Recv request:%s"%msg)
time.sleep(1)
s.send(b"World")
Publish/Subscribe(订阅-发布模式 )
消息单向。
两个角色:发布端、订阅端。
一个发布端,多个订阅端;发布端只管产生数据。
发布端发布一条消息,可被多个订阅端同时收到。
发布者不必关心订阅者的加入和离开,消息会以 1:N 的方式扩散到每个订阅者。
广播所有client,没有队列缓存,断开连接数据将永远丢失。
PUB和SUB谁bind谁connect并无严格要求(虽本质并无区别),但仍建议PUB使用bind,SUB使用connect。
从ZMQ v3.x开始,在使用连接的协议是tcp或者ipc时,过滤发生在发布端。使用epgm协议,过滤发生在订阅端。但在ZMQ v2.x版本中,所有过滤都发生在订阅端。
订阅端可以使用zmq_connect()同时连接到多个发布端。不同发布者推送的消息将交错到达。
订阅端调用zmq_send()来发送消息是会报错的,同样发布端使用zmq_recv()来接收消息也会报错。

# publish.py
import time
import zmq
from random import randrange
ctx = zmq.Context()
s = ctx.socket(zmq.PUB)
s.bind("tcp://*:5556")
print("Listening …")
while True:
a = randrange(100,103)
b = randrange(200,300)
c = randrange(300,400)
s.send_string("%i %i %i" %(a,b,c))
time.sleep(0.1)
# subscribe.py
import sys
import zmq
ctx = zmq.Context()
print("Collecting …")
s = ctx.socket(zmq.SUB)
s.connect("tcp://localhost:5556")
filter = sys.argv[1]
s.setsockopt_string(zmq.SUBSCRIBE, filter)
sum = 0
for i in range(10):
str = s.recv_string()
a,b,c = str.split()
print(a,b,c)
sum += int(b)
print("filter=%s,sum=%d"%(filter,sum))
Push/Pull(流水线模式)
消息单向
三个角色:push work pull
模型描述:
- 上游(任务发布) 负载均衡
- 工人(中间,具体工作) 中途可以加入和退出
- 下游(信号采集或者工作结果收集)

push进行数据推送,work进行数据缓存,pull进行数据竞争获取处理。
Push的任何一个消息,始终只会有一个Pull端收到消息。
也叫管线模式。
主要用于多任务并行。
router-dealer模式(信封机制)
DEALER是对REQ的包装,本质就是在发送数据前发送一段bytes数据。
ROUTER是对REP的包装,是在接收数据前先接收第一段bytes数据。
消息双向,异步。
用于消息分发。

代码参考:zeromq中两个dealer 通过一个router进行通信
option设置
用来设置socket的控制参数。
事件监控
zmq_socket_monitor(3) - 0MQ Api
用于定位问题的。
需要另开一个线程。
组合模式
- PUB - SUB
- REQ - REP
- REQ - ROUTER
- DEALER - REP
- DEALER - ROUTER
- DEALER - DEALER
- ROUTER - ROUTER
- PUSH - PULL
- PAIR - PAIR
以上为合法的组合
消息帧
zmq_msg_send(&message, socket, ZMQ_SNDMORE ); //发第1帧
zmq_msg_send(&message, socket, ZMQ_SNDMORE ); //发第2帧
zmq_msg_send(&message, socket, ZMQ_DONTWAIT); //发最后帧
零拷贝
只能在发送做,不能在接收做
void my_free (void *data, void *hint) {
free(data);
}
auto len = pstServerMsg->ByteSizeLong();
char *buffer = (char *)malloc(len);
pstServerMsg->SerializeToArray(buffer, len);
zmq_msg_t msg;
zmq_msg_init_size(&msg, len);
zmq_msg_init_data(&msg, buffer, len, my_free, NULL);
zmq_msg_send(&msg, socket, ZMQ_DONTWAIT);
在接收消息的时候是无法使用零拷贝的:
ZMQ会将收到的消息放入一块内存区域供你读取,但不会将消息写入程序指定的内存区域 。
至于性能有多大提升,还有待确认。
Tips
线程间不可以共享socket,但可以共享上下文context。
谁bind谁connect,遵循“服务端是稳定的,客户端是灵活的“原则。
zmq_bind可以仅使用一个套接字就能绑定至多个端点,但不能多次绑定同一个端点。
不同的消息模式可以组合,从而适应具体的应用场景。
zmq_send是异步发送。函数有返回值,并不能代表消息已经发送。
发送端缓存消息,可以通过阈值控制消息处理策略。
socket断掉后,可以通过参数控制缓存中未发送的消息保存保留多长时间。
心跳
一般来说,应用层建议加心跳,“快速失败“思想的体现。
灵活端发ping
稳定端回pong
幂等性
常用的解决方法是在服务端检测并拒绝重复的请求:
-
客户端为每个请求附加唯一的标识,包括客户端标识和消息标识;
-
服务端在发送应答时使用客户端标识和消息标识作为键,保存应答内容;
-
当服务端发现收到的请求已在应答哈希表中存在,它会跳过该次请求,直接返回应答内容。
核心思想
当解决一个复杂且多面化的问题时,单个通用型的解决方案可能并不是最好的方式。
可以把问题的领域想象成一个抽象层,并基于分层提供多个实现,每种实现只致力于解决一种情况。
划分范围时,明确什么在范围内,什么不在范围内。
过于严格则应用性受到限制。
过于宽泛则功能变得复杂,带来混乱。
https://github.com/anjuke/zguide-cn

浙公网安备 33010602011771号