ZeroMq LRU算法中间件

来源:互联网 发布:如何修改ssh的端口号 编辑:程序博客网 时间:2024/06/13 22:48

文章来源:http://blog.csdn.net/yueguanghaidao/article/details/41814163

前一段时间2014北京PyCon大会吐槽颇多,所以我就到InfoQ上找了找2013的大会视频,对网络射击手游High Noon 2基于Python的服务器架构的视频挺感兴趣,尤其是游戏服务器中的0 downtime,原理他们底层不是原生的socket,

而是基于ZeroMq的socket,由于ZeroMq的短线自动重连可以满足游戏服务器的热启动,不需要代码层面的热启动,热更新,当更新代码完成后直接重启服务器,之前未处理的请求会继续处理。瞬间觉得非常高大上,于是最近一段时间回家一直研究ZeroMq,在guide的LRU这边停留了较长时间,本篇就是谈谈ZeroMq LRU算法中间件。




对普通的请求回复代理,也就是使用了ROUTER-DEALER模式比较好理解,但这种方式有个天生缺点,DEALER会使用负载均衡的方式将客户端的请求转发给服务器端,由于服务器的处理能力各不相同,就会导致有些服务器很忙,有些很闲,这不是我们想看到的,我们希望能压榨所有服务器能力,所以就出现了通用型的LRU算法。


这里使用ROUTER-ROUTER模式,刚开始你可能很诧异,且听我慢慢道来。

那如何能充分使用每台服务器性能呢?

1.当woker启动时告诉backend自己已准备好,将该worker加入work_queue可用队列中

2.当client请求时,从work_queue中取出一个work,把请求交给该work处理

3.当work处理好后把响应发给client,并把自己再次加入到work_queue中

也就是使用work队列,记录可用work,只要work完成请求就代表已空闲,再次加入work队列。


那为什么backend需要使用ROUTER模式呢?关键点是当我要把client来的请求发给固定的work,而只有ROUTER模式具有路由标识功能,明白了这点,代码也就容易了。

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. """ 
  2.  
  3.    Least-recently used (LRU) queue device 
  4.    Clients and workers are shown here in-process 
  5.  
  6.    Author: Guillaume Aubert (gaubert) <guillaume(dot)aubert(at)gmail(dot)com> 
  7.  
  8. """  
  9. from __future__ import print_function  
  10.   
  11. import threading  
  12. import time  
  13. import zmq  
  14.   
  15. NBR_CLIENTS = 10  
  16. NBR_WORKERS = 3  
  17.   
  18. def worker_thread(worker_url, context, i):  
  19.     """ Worker using REQ socket to do LRU routing  
  20.     work的响应消息格式为 
  21.     -------------------- 
  22.     |Frame0| Client-idntity 
  23.     -------------------- 
  24.     |Frame1| 
  25.     -------------------- 
  26.     |Frame2| OK 
  27.     """  
  28.   
  29.     socket = context.socket(zmq.REQ)  
  30.   
  31.     # Set the worker identity  
  32.     socket.identity = (u"Worker-%d" % (i)).encode('ascii')  
  33.   
  34.     socket.connect(worker_url)  
  35.   
  36.     # Tell the borker we are ready for work  
  37.     socket.send(b"READY")  
  38.   
  39.     try:  
  40.         while True:  
  41.   
  42.             address = socket.recv()  
  43.             empty = socket.recv()  
  44.             request = socket.recv()  
  45.   
  46.             print("%s: %s\n" % (socket.identity.decode('ascii'), request.decode('ascii')), end='')  
  47.   
  48.             socket.send(address, zmq.SNDMORE)  
  49.             socket.send(b"", zmq.SNDMORE)  
  50.             socket.send(b"OK")  
  51.   
  52.     except zmq.ContextTerminated:  
  53.         # context terminated so quit silently  
  54.         return  
  55.   
  56.   
  57. def client_thread(client_url, context, i):  
  58.     """ Basic request-reply client using REQ socket 
  59.     client的消息格式为 
  60.     -------------------- 
  61.     |Frame0| Client-1 
  62.     -------------------- 
  63.     |Frame1| 
  64.     -------------------- 
  65.     |Frame2| HELLO 
  66.     """  
  67.   
  68.     socket = context.socket(zmq.REQ)  
  69.   
  70.     socket.identity = (u"Client-%d" % (i)).encode('ascii')  
  71.   
  72.     socket.connect(client_url)  
  73.   
  74.     #  Send request, get reply  
  75.     socket.send(b"HELLO")  
  76.     reply = socket.recv()  
  77.   
  78.     print("%s: %s\n" % (socket.identity.decode('ascii'), reply.decode('ascii')), end='')  
  79.   
  80.   
  81. def main():  
  82.     """ main method """  
  83.   
  84.     url_worker = "inproc://workers"  
  85.     url_client = "inproc://clients"  
  86.     client_nbr = NBR_CLIENTS  
  87.   
  88.     # Prepare our context and sockets  
  89.     context = zmq.Context()  
  90.     frontend = context.socket(zmq.ROUTER)  
  91.     frontend.bind(url_client)  
  92.     backend = context.socket(zmq.ROUTER)  
  93.     backend.bind(url_worker)  
  94.   
  95.   
  96.   
  97.     # create workers and clients threads  
  98.     for i in range(NBR_WORKERS):  
  99.         thread = threading.Thread(target=worker_thread, args=(url_worker, context, i, ))  
  100.         thread.start()  
  101.   
  102.     for i in range(NBR_CLIENTS):  
  103.         thread_c = threading.Thread(target=client_thread, args=(url_client, context, i, ))  
  104.         thread_c.start()  
  105.   
  106.     # Logic of LRU loop  
  107.     # - Poll backend always, frontend only if 1+ worker ready  
  108.     # - If worker replies, queue worker as ready and forward reply  
  109.     # to client if necessary  
  110.     # - If client requests, pop next worker and send request to it  
  111.   
  112.     # Queue of available workers  
  113.     available_workers = 0  
  114.     workers_list      = []  
  115.   
  116.     # init poller  
  117.     poller = zmq.Poller()  
  118.   
  119.     # Always poll for worker activity on backend  
  120.     poller.register(backend, zmq.POLLIN)  
  121.   
  122.     # Poll front-end only if we have available workers  
  123.     poller.register(frontend, zmq.POLLIN)  
  124.   
  125.     while True:  
  126.   
  127.         socks = dict(poller.poll())  
  128.   
  129.         # Handle worker activity on backend  
  130.         if (backend in socks and socks[backend] == zmq.POLLIN):  
  131.   
  132.             # Queue worker address for LRU routing  
  133.             worker_addr  = backend.recv()  
  134.   
  135.             assert available_workers < NBR_WORKERS  
  136.   
  137.             # add worker back to the list of workers  
  138.             available_workers += 1  
  139.             workers_list.append(worker_addr)  
  140.   
  141.             #   Second frame is empty  
  142.             empty = backend.recv()  
  143.             assert empty == b""  
  144.   
  145.             # Third frame is READY or else a client reply address  
  146.             client_addr = backend.recv()  
  147.   
  148.             # If client reply, send rest back to frontend  
  149.             if client_addr != b"READY":  
  150.   
  151.                 # Following frame is empty  
  152.                 empty = backend.recv()  
  153.                 assert empty == b""  
  154.   
  155.                 reply = backend.recv()  
  156.   
  157.                 frontend.send(client_addr, zmq.SNDMORE)  
  158.                 frontend.send(b"", zmq.SNDMORE)  
  159.                 frontend.send(reply)  
  160.   
  161.                 client_nbr -= 1  
  162.   
  163.                 if client_nbr == 0:  
  164.                     break  # Exit after N messages  
  165.   
  166.         # poll on frontend only if workers are available  
  167.         if available_workers > 0:  
  168.   
  169.             if (frontend in socks and socks[frontend] == zmq.POLLIN):  
  170.                 # Now get next client request, route to LRU worker  
  171.                 # Client request is [address][empty][request]  
  172.                 client_addr = frontend.recv()  
  173.   
  174.                 empty = frontend.recv()  
  175.                 assert empty == b""  
  176.   
  177.                 request = frontend.recv()  
  178.   
  179.                 #  Dequeue and drop the next worker address  
  180.                 available_workers -= 1  
  181.                 worker_id = workers_list.pop()  
  182.                 """worker_id就是work的标识,也就是需要发给worker_id,所以backend需要使用ROUTER模式 
  183.                    在所有消息之间zmq需要一个空消息作为标识,当work接受到请求时会直接读到第一个空消息, 
  184.                    也就是work接受的第一个消息就是client_addr,然后work再把处理的响应发给client_addr, 
  185.                    当backend收到消息后,直接通过forentend转发给client_addr,这也是forentend也需要是ROUTER 
  186.                    模式的原因 
  187.                 """  
  188.                 backend.send(worker_id, zmq.SNDMORE)  
  189.                 backend.send(b"", zmq.SNDMORE)  
  190.                 backend.send(client_addr, zmq.SNDMORE)  
  191.                 backend.send(b"", zmq.SNDMORE)  
  192.                 backend.send(request)  
  193.   
  194.   
  195.     # Out of infinite loop: do some housekeeping  
  196.   
  197.     frontend.close()  
  198.     backend.close()  
  199.     context.term()  
  200.   
  201.   
  202. if __name__ == "__main__":  
  203.     main()  
  204.   
  205. """ 
  206. 当需要具体转发的时候,就是ROUTER大显身手的时候 
  207. """  
0 0