解决Nginx和Fpm-Php等内部多进程之间共享数据问题
解决Nginx和Fpm-Php等内部多进程之间共享数据问题 概念说明: 1. MINIT :Php扩展的初始化方法,整个模块启动时候被调用一次 2. RINIT :Php扩展的初始化方法,每个请求会调用一次 3. ClusterMap(简称CM) :提供服务定位和集群地图功能,通过接收心跳和主动
解决Nginx和Fpm-Php等内部多进程之间共享数据问题
概念说明:
1. MINIT:Php扩展的初始化方法,整个模块启动时候被调用一次
2. RINIT:Php扩展的初始化方法,每个请求会调用一次
3. ClusterMap(简称CM):提供服务定位和集群地图功能,通过接收心跳和主动探测方式收集节点状态信息,统一管理多种异构集群,替换硬负载均衡设备
4. CMSubProxy:ClusterMap内部的一个订阅者客户端代理,定期和Server端通讯,获取最新的集群信息,更新内部维护的机器列表
问题描述
-
Nginx或者Php-CGI都是使用多进程提供大并发服务的,如果服务内部想要提供一个通用的功能模块,需要用户自己写一个Extension或者Module, 最近在做ClusterMap的订阅者客户端,订阅者客户端即Php的一个扩展,请求到来时,Php扩展会与CMServer通讯获取最新的机器列表,但是如果每次请求都去获取机器列表开销又特别大
-
在Apache的Module模式下,实现是简单的,Apache首先启动父进程A会调用MINIT方法,调用完成后Fork其它Httpd子进程B,A和B是父子关系,这样父进程A就可以定期更新集群信息,然后通过管道方式和子进程通讯,子进程在每个请求过来时,读取管道消息(即机器列表),实现了服务定位;但是Php-fpm模式略有不同,Php-fpm进程管理器启动进程A会调用MINIT方法,然后Fork出一个Fpm-Master进程B,进程B启动多个Php-CGI子进程C,启动工作完成后,启动进程A就退出了,子进程在每个请求过来时调用RINIT,这时父子AC进程管道通讯建立不起来,管道的数据没有办法消费,使得子进程C如果写满就会阻塞。其实这个问题很普遍,如果用修改Php的源码方式解决不见得是一个好的解决方案。
问题分析
总结一下上述问题,说白了就是多个服务进程,每个进程在接到请求后,首先需要服务定位,获得最新的机器列表(需要一次网络开销),然后再转发请求给其他的服务,接下来我们以Fpm-Php为例介绍如果解决上述问题
方案一:RInit中每个请求都先到Server端获取最新的机器列表
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 10秒钟过去了 如果这时你还没有发现问题,那么你就没有必要学习Nginx这个骑着比驴都快的玩意了,很明显,RInit中每个请求都要多一次网络开销,去Server端获取最新的机器列表,大大增加了整个请求的响应时间。 这时有人说了这个问题好解决,我不需要这么频繁的更新就好了,10请求,100请求更新一次,或者1s秒钟,10s更新一次,既不影响性能,又能达到更新效果,在性能和更新频率上做Trade-Off,这样总可以吧,于是就有了方案二
方案二:RInit中每个请求都先到Server端获取最新的机器列表,同时从Server端拿到一个过期时间,后面的请求如果没有超过过期时间则不需要再次去Server端获取更新了
方案二可以说是能解决问题,严格来说只能是部分问题,治标不治本 因为如果你想要更好性能,对于每个进程来说,就必须把更新周期变慢,失去准确性,如果想要更高的准确行,就需要每个进程频繁更新,在搜索和广告这种大并发,超时敏感的服务面前这种方案太不友好了,最重要的事,每个Worker进程都要去更新,虽然每个进程拿到的都是完全相同的信息,这里不是说Nginx的多进程模式不好,这种模式有它存在的意义,而且事实也证明了,多进程正是Nginx的高明之处,每个Worker都是独立的进程,编程简单且不需要加锁,进程间又互不影响,降低风险。
方案三:使用共享内存方式,单独启动一个更新进程,实时更新集群节点信息,写入共享内存,RInit中每个请求先读取共享内存获取最新的机器列表
方案三利用了多个Worker进程获取的机器列表相同这个特点,通过共享内存的方式在进程间共享数据,这样Worker进程既不需要网络开销,又可以快速的获取最新机器列表
解决方式
目前ClusterMap中采用的是方案三,通过共享内存的方式解决这个问题的
- CMSubProxy是一个单独的更新进程,每隔500ms会向CMServer发送一个请求,获取最新的机器列表,收到响应消息后,CMSubProxy会更新进程内部维护的机器列表,更新成功后会写到共享内存中;
- Php-fpm进程在每个请求到来时,首先会读取共享内存中的机器列表,然后再将请求转发给列表中的某一台可用机器,机器的选择有多种策略(轮询,随机,权重,一致性哈希等);
-
共享内存是mmap打开的,需要注意的是,在更新和读取的时候需要读写锁,并且锁信号量要在共享内存中,关于多个进程间共享内存锁
pthread_rwlockattr_t attr; pthread_rwlockattr_init(&attr); pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 手册上对pthread_rwlockattr_setpshared的描述 pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared); DESCRIPTION The pthread_rwlockattr_setpshared() function sets the process-shared attribute of attr to the value referenced by pshared. pshared may be one of two values: PTHREAD_PROCESS_SHARED Any thread of any process that has access to the memory where the read/write lock resides can manipulate the lock.
CMSubProxy写入共享内存中的数据是分全量和增量的,增量数据写在全量数据之后,这里就不详细讲述了
原文地址:解决Nginx和Fpm-Php等内部多进程之间共享数据问题, 感谢原作者分享。