python实现多进程监听同一个socket的prefork服务端模型

这两天迁移数据,没时间写博客了……   正好这两天跟同事聊了下prefork模式,就拿出来聊聊。。。 如果你跟我一样是python程序员,我很建议你用prefork+gevent协程的方式。


python实现socket服务相当的容易,但是默认是单进程状态,是堵塞的….   我想喜欢prefork这个模式的,虽然没有epoll那种基于事件的高性能,但也是可以解决单进程带来的堵塞的问题。

因为prefork用的是os.fork模式,所以可以绑定在多个核上…   我想大家最先知道prefork模型应该是apache http server上。

下面是普通的单进程socket实现…

#xiaorui.cc
import sys
import socket
import select
import os

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

try:
    s.bind(('localhost',9000) )
    s.listen(1)
except Exception, e:
    raise e

while 1:
    client,address = s.accept()
    print "%s get a client[%s] from %s" % (os.getpid(),str(client),address)
    client.close()

这是prefork的相关代码… …  需要注意的是,不要把apache的prefork想的太简单了,php-fpm就是prefork多进程管理很好的例子,可以动态的创建进程和销毁进程, 可以根据各种情况增加减少进程,以及最必须的监控子进程存活的模式。。。。


import sys
import socket
import select
import os

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(('localhost',9000) )
    s.listen(1)
except Exception, e:
    raise e

for i in range(1,10):
    pid = os.fork()
    if pid <0:
        print 'fork error'
        sys.exit(-1)
    elif pid >0:
        print 'fork process %d'  % pid
    else:
        pass

while 1:
    client,address = s.accept()
    print "%s get a client[%s] from %s" % (os.getpid(),str(client),address)
    client.close()

一次启动10个通过os.fork的子进程,然后又同事监听同一个端口, 流程是 create/bind/listen -> fork ->accept -> respones 
一般来说一个进程是只能绑定在一个端口上的,想上面的多个进程accept了同一个socket,有些匪夷所思,但是linux从操作系统层面支持了这种做法。

那么来看看Linux是这样实现accept调用的流程是怎么样的

把当前进程插入这个fd的等待队列然后阻塞
当新连接进来的时候,操作系统会唤醒这个fd的等待队列的第一个进程,只唤醒一个进程,其他的进程还是在就绪的等待状态.
这是Linux kernel 2.4引入的功能. 相关论文:Accept scalability on Linux

Prefork vs multiprocessing 对比 .

都是可以实现多进程,但是multiprocessing实现友好的数据共享,例如manager … prefork实现的方式比较的原生.

说实话关于服务端的开发,我也经过不少,一般来说都会维持一个队列,也就是tcp缓冲队列,把用户的请求放到一个队列里面,然后让先前fork出来的进程来消费这些链接。
他比prefork的方式相比,在并发大的时候,不至于大片的请求失败。 

pre-fork是一个重大的改善,极大的简化了网络server的编程,Linux可能会走得更远,Linux Kernel 3.9会引入一个新的socket option,只要设置socket的SO_REUSEPORT属性,那么不同的进程和线程都可以同时bind这个ip和port。有时间找个ubuntu高版本内核的机器测试下….

Hello boys,文章的原文地址是 http://xiaorui.cc/?p=1665


Q & A

有个朋友提了一个问题,我觉得有些典型就整理了这问题.

如果10个worker进程监听在同一个端口,那么当 netstat 的时候看到 LISTEN 是哪个进程的呢?我机器上看到 nginx 是第一个 worker,而 gunicorn 则是 master。这两者之间有什么实现上的差别? 

    他们之间用的都是一种模式 ! nginx、 gunicorn、php-fpm等用的都是SO_REUSEADDR模式,同时只能有一个进程来listen端口,剩下的worker用accept来监控fd。 如果你想多个worker都监听一个端口,那么可以使用SO_REUSEPORT模式, 但需要注意的是so_reuseport需要高版本linux内核的支持。

    现在nginx 1.9.x是支持so_reuseport socket mode模式. Nginx和Gunicorn在这方面是没有啥区别的,都只能有一个worker去监听端口。 

    另外,对于多个worker去accept数据不会产生惊群的现象。 现在的内核已经解决这个惊群问题,内核只会对第一个accept fd的进程进行唤醒。 nginx是有惊群的现象,但也只是在epoll_wait获取事件对象时发生,所以nginx会对epoll wait动作进行加锁防止惊群。


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

4 Responses

  1. roczhou 2016年4月28日 / 下午1:14

    如果10个worker进程监听在同一个端口,那么当 netstat 的时候看到 LISTEN 是哪个进程的呢?我机器上看到 nginx 是第一个 worker,而 gunicorn 则是 master。这两者之间有什么实现上的差别?[CODE][root@w1 conf.d]# netstat -antup | grep LISTtcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2000/nginxtcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 2000/nginxtcp 0 0 127.0.0.1:8081 0.0.0.0:* LISTEN 2000/nginxtcp 0 0 0.0.0.0:10002 0.0.0.0:* LISTEN 5071/gvp.apptcp 0 0 127.0.0.1:8082 0.0.0.0:* LISTEN 2000/nginxtcp 0 0 127.0.0.1:8083 0.0.0.0:* LISTEN 2000/nginxtcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 901/sshdtcp 0 0 127.0.0.1:11002 0.0.0.0:* LISTEN 12020/gunicorn: mastcp 0 0 127.0.0.1:15770 0.0.0.0:* LISTEN 1118/aegis_quartztcp 0 0 127.0.0.1:10080 0.0.0.0:* LISTEN 2000/nginxtcp 0 0 127.0.0.1:9001 0.0.0.0:* LISTEN 9183/php-cgiroot 26416 0.0 0.3 200880 3600 ? Ss 2015 0:00 nginx: master process /home/admin/install/nginx/sbin/nginx -c /etc/nginx/nginx.confadmin 2000 0.0 2.4 222092 25252 ? S 03:37 0:02 _ nginx: worker processadmin 2001 0.0 2.4 222336 25336 ? S 03:37 0:01 _ nginx: worker processadmin 2002 0.0 0.3 200880 3312 ? S 03:37 0:00 _ nginx: cache manager processadmin 12020 0.0 1.2 212828 12796 ? S 12:48 0:00 gunicorn: master [gvp_uwsgi:app]admin 12025 0.0 1.9 322880 19948 ? S 12:48 0:00 _ gunicorn: worker [gvp_uwsgi:app][/CODE]

    • 峰云就她了 2016年5月3日 / 上午10:37

      这两天出国了,没有及时的回复 (有装逼的嫌疑)! 他们之间用的都是一种模式 ! nginx、 gunicorn、php-fpm等用的都是SO_REUSEADDR模式,同时只能有一个进程来listen端口,剩下的worker用accept来监控fd。 如果你想多个worker都监听一个端口,那么可以使用SO_REUSEPORT模式, 但需要注意的是so_reuseport需要高版本linux内核的支持。现在nginx 1.9.x是支持so_reuseport socket mode模式.Nginx和Gunicorn在这方面是没有啥区别的,都只能有一个worker去监听端口。 另外,对于多个worker去accept数据不会产生惊群的现象。 现在的内核已经解决这个惊群问题,内核只会对第一个accept fd的进程进行唤醒。 nginx是有惊群的现象,但也只是在epoll_wait获取事件对象时发生,所以nginx会对epoll wait动作进行加锁防止惊群。

  2. 行为者 2015年6月22日 / 上午11:39

    prefork值得学的,但是现在不怎么用吧

发表评论

邮箱地址不会被公开。 必填项已用*标注