0、说在前面的话
本篇blog的主要内容是将zeroMQ的云时代消息通信库的这本书的第三章中关于负载均衡模式的一个实例的讲解与代码编写
1、基本原理
举个例子,通常我们的对于服务器来说,有很多个相同的服务来处理来自不同客户端的相同的请求,但是我们不能将所有的请求都发送给同一个服务,我们应当实现负载的均衡的模式,实现均匀分配。具体可以采用下面的这张图进行解释。
2、关于套接字的说明
req:用于发送请求,并且得到相应,而router作为一个可以并行处理很多的请求的套接字,它首先可以接收很多客户端的请求,然后,将请均匀公平的分配给worker们,实际上,采用的策略也就是操作系统中经常提到的概念”最近最少使用" 。
3、代码讲解
废话补多少直接进行代码讲解,我们首先与书中的例子相同,首先采用多线程的方式的方式对这个概念进行理解。
(书中采用c,此处采用python,beacause life is too short)
实际:代码主要由三部分组成:
client 部分
worker 部分
中间的 代理部分
3.1 client 部分
实际的代码如下:对每一句话的注释如下:
#工人的子任务 我们可以启用多个这样的多个线程,然后
def client_task(ident):
"""Basic request-reply client using REQ socket."""
socket = zmq.Context().socket(zmq.REQ)
socket.identity = u"Client-{}".format(ident).encode("ascii") #设定一个客户端的身份
socket.connect("ipc://frontend.ipc") #连接到前端段的router
# Send request, get reply
socket.send(b"HELLO") #这个hello 相当于请求,希望得到word的回复
reply = socket.recv() #收回答
print("{}: {}".format(socket.identity.decode("ascii"),
reply.decode("ascii")))
#对返回的身份信息进行打印,然后对回答的请求,进行进行打印
3.2 worker部分讲解
def worker_task(ident):
"""Worker task, using a REQ socket to do load-balancing."""
socket = zmq.Context().socket(zmq.REQ)
socket.identity = u"Worker-{}".format(ident).encode("ascii")
socket.connect("ipc://backend.ipc")
# Tell broker we're ready for work
socket.send(b"READY") #首先告诉 中间的代理 我 已经准备好了
#我就开始一直等待请求,然后处理请求。
while True:
address, empty, request = socket.recv_multipart() #接受多部分消息,
print("{}: {}".format(socket.identity.decode("ascii"),
request.decode("ascii"))) #打印是来自哪个客户端发来的请求
socket.send_multipart([address, b"", b"OK"]) # 发送‘多部分’ 消息,然后返回给发给请求的那个客户端。
3.3 中间路由部分
中间的路由部分的讲解的过程如下代码的注释所示:
def main():
"""Load balancer main loop."""
# Prepare context and sockets
context = zmq.Context.instance()
frontend = context.socket(zmq.ROUTER) #建立前端的套接字
frontend.bind("ipc://frontend.ipc")
backend = context.socket(zmq.ROUTER) #建立后端用来平衡worker的套接字
backend.bind("ipc://backend.ipc")
# Start background tasks
def start(task, *args): #multiproces 支持进程,这个在windows上不行的
process = multiprocessing.Process(target=task, args=args)
process.daemon = True
process.start()
for i in range(NBR_CLIENTS):
start(client_task, i)
for i in range(NBR_WORKERS):
start(worker_task, i)
# Initialize main loop state
count = NBR_CLIENTS #有多少个clients
workers = [] #注册到worker的工人
poller = zmq.Poller() #
# Only poll for requests from backend until workers are available
poller.register(backend, zmq.POLLIN) #将一个socket 可以注册到poller当中去 然后对所监控的信息 如果有活动了 他们才会到poll当中去
while True:
#关于dict函数:将传入的关键字变成字典 也就是socker
sockets = dict(poller.poll())
print('打印这次的workers',workers)
if backend in sockets:
# Handle worker activity on the backend
request = backend.recv_multipart()
worker, empty, client = request[:3]
if not workers: #wokers 如果workers当中现在不是空集 这个时候我们可以开放前段为req的请求
# Poll for clients now that a worker is available
poller.register(frontend, zmq.POLLIN) #只有当wokers为空的时候,这个时候才来监控一下后端的socket是否有新的变化
workers.append(worker) # worker 发过来的就一下这两种类型
if client != b"READY" and len(request) > 3: #需要注意worker发回来的信息 id ‘’ id '' 'ok'
# If client reply, send rest back to frontend # 'id' '' ready
empty, reply = request[3:] #如果不是ready 信号,那id '' 'ok'
frontend.send_multipart([client, b"", reply])
count -= 1
if not count:
break
if frontend in sockets:
# Get next client request, route to last-used worker
client, empty, request = frontend.recv_multipart()
worker = workers.pop(0) #pop(0)的作用是返回当前数组中的第一个
backend.send_multipart([worker, b"", client, b"", request])
if not workers:
# Don't poll clients if no workers are available
poller.unregister(frontend) #这一行命令实际上,就在告诉我们是否接收某个套接字所发过来的所有的消息
# Clean up
backend.close()
frontend.close()
context.term()
4、结果展示
从结果我们可以看到,由于采用的是多进程的方案。