这两天迁移数据,没时间写博客了…… 正好这两天跟同事聊了下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高版本内核的机器测试下….
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