zoukankan      html  css  js  c++  java
  • 不得不注意tornado多进程部署的副作用

    tornado多进程启动时,采用的是fork的方式。

    一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
    子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间(百度百科上的错误)如果采用copy on write技术,在存储的内容未发生修改前父子进程是共享同一份存储空间。
    UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX (Like)系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。

    上述内容摘自百度百科对fork函数的说明,在调用fork函数产生的子进程将会获得父进程的数据空间、堆、栈等资源的“副本”,这里的“副本”有可能是存储空间的同一份。父进程打开的文件句柄,socket等资源也会被原封不动的拷备给子进程。

    最常出现的问题就是logging模块,python的logging模块是线程安全的,但不是进程安全的。如果在fork子进程之前开始实例化logging模块,各个子进程都会共享了同一个fd,所以会出现日志错乱的问题。为了避免这个问题,各个进程打都输出到独立的文件,延后Logger的实例化。

    sockets = tornado.netutil.bind_sockets(int(options.port))
    fork_id = tornado.process.fork_processes(int(redis_info.num_processes))
    server = tornado.httpserver.HTTPServer(application)
    server.add_sockets(sockets)
    #实现logger init方式,接收fork_id来控制文件名
    logger.init(fork_id)
    1
    2
    3
    4
    5
    6
    另一点就是socket的复用导致的问题,实现了一个python版本的dubbo客户端,连接发送方式如下。在使用过程中,fork进程之前实例化了一个client, 并保持了一个socket。

    def __call__(self, command):
    json.dumps(args)[1:-1])
    self.__socket.send(command.encode('utf-8') + b' ')
    ans = bytes()
    try:
    while True:
    # ret = self.__socket.recv(1024000)
    # ans += ret
    # if ret.endswith("dubbo>"):
    # break
    ready = select.select([self.__socket], [], [], 0.5)
    if ready[0]:
    import time
    ret = self.__socket.recv(1024)
    ans = ans.join([ret])
    if ans.endswith(b"dubbo>"):
    break
    else:
    logger.error("dubbo timeout")
    break
    except Exception as e:
    pass
    try:
    msg = ans.decode('GB2312', 'ignore')
    ret_data, elapsed_time, prompt = msg.split(' ')
    print("[receive", msg, "]")
    return ret_data
    except Exception as e1:
    print("[error recive",msg, traceback.format_exc(),"]")
    try:
    msg = ans.decode('GB2312', 'ignore')
    info = msg.split("dubbo>")[1]
    ret_data = info.split(' ')[0]
    return ret_data
    except Exception as e2:
    logger.exception(e2)
    #print "e2: ", e2
    return ""
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    web接口

    class MainHandler(tornado.web.RequestHandler):

    def get(self):
    dubbo_client = DubboClient("127.0.0.1", 10192)
    resp = dubbo_client.dubboserver.getByAccountId(pid)
    print("[ session: send accountId ({}) get msg ({}) ]".format(pid, resp))
    #print("[",pid, resp, "]")
    self.write(resp)
    1
    2
    3
    4
    5
    6
    7
    8
    ab测试并发

    ab -n 20 -c 10 http://127.0.0.1:10087/
    1
    截取了一部分异常输出结果

    ...
    [ session: send accountId (1) get msg ({"accountId":3}) ]
    [ session: send accountId (3) get msg ({"accountId":0}) ]
    [error recive Traceback (most recent call last):
    File "/home/gikieng/project/TestTornado/src/commons/DubboClient.py", line 82, in __call__
    ret_data, elapsed_time, prompt = msg.split(' ')
    ValueError: not enough values to unpack (expected 3, got 1)
    ]
    ERROR:root:list index out of range
    Traceback (most recent call last):
    File "/home/gikieng/project/TestTornado/src/commons/DubboClient.py", line 82, in __call__
    ret_data, elapsed_time, prompt = msg.split(' ')
    ValueError: not enough values to unpack (expected 3, got 1)
    ...
    During handling of the above exception, another exception occurred:
    [ session: send accountId (2) get msg () ]
    ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    从输出结果中看出,各个子进程都在竞争同一个socket的读写,导致了信息的收发进程不一致。在网络波动的情况下,还有进程收抢到了报文片段。

    结论:
    在使用tornado多进程部署方式的过程中,要注意到fork带来的副作用。为了简单使用,最好还是使用单进程多实例的方式部署,然后再加上一个反射代理层对外提供统一接口。单为了提高并发能力,使用多进程的效益并不是很高。
    ---------------------
    作者:Gikieng
    来源:CSDN
    原文:https://blog.csdn.net/gikieng/article/details/79957882
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    IE8发送ajax请求无效
    Qt 桌面服务 QDesktopServices
    Qt 排序 QSort
    使用ehcache缓存可变对象时的注意事项
    Golang微服务入门到精通之路-1-Go之初体验
    机械革命 Code01 笔记本激活 Win10 方法
    IM技术分享:万人群聊消息投递方案的思考和实践
    零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能
    剑指 Offer 28. 对称的二叉树
    idea使用
  • 原文地址:https://www.cnblogs.com/ExMan/p/10310143.html
Copyright © 2011-2022 走看看