欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

解决Nginx和Fpm-Php等内部多进程之间共享数据问题

程序员文章站 2024-01-15 10:47:52
...

解决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端通讯,获取最新的集群信息,更新内部维护的机器列表

问题描述

  1. Nginx或者Php-CGI都是使用多进程提供大并发服务的,如果服务内部想要提供一个通用的功能模块,需要用户自己写一个Extension或者Module, 最近在做ClusterMap的订阅者客户端,订阅者客户端即Php的一个扩展,请求到来时,Php扩展会与CMServer通讯获取最新的机器列表,但是如果每次请求都去获取机器列表开销又特别大

  2. 在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中采用的是方案三,通过共享内存的方式解决这个问题的

解决Nginx和Fpm-Php等内部多进程之间共享数据问题

  1. CMSubProxy是一个单独的更新进程,每隔500ms会向CMServer发送一个请求,获取最新的机器列表,收到响应消息后,CMSubProxy会更新进程内部维护的机器列表,更新成功后会写到共享内存中;
  2. Php-fpm进程在每个请求到来时,首先会读取共享内存中的机器列表,然后再将请求转发给列表中的某一台可用机器,机器的选择有多种策略(轮询,随机,权重,一致性哈希等);
  3. 共享内存是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写入共享内存中的数据是分全量和增量的,增量数据写在全量数据之后,这里就不详细讲述了