歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 解決Nginx和php-fpm等內部多進程之間共享數據問題

解決Nginx和php-fpm等內部多進程之間共享數據問題

日期:2017/2/27 15:54:48   编辑:Linux教程

概念說明:

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中采用的是方案三,通過共享內存的方式解決這個問題的

  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寫入共享內存中的數據是分全量和增量的,增量數據寫在全量數據之後,這裡就不詳細講述了

Copyright © Linux教程網 All Rights Reserved