首頁  >  文章  >  後端開發  >  PHP中RPC框架基於Redis實現流量控制系統

PHP中RPC框架基於Redis實現流量控制系統

小云云
小云云原創
2018-03-15 14:03:363166瀏覽


我們對專案模組進行了一定程度的微服務化改造,之前所有模組都放在一個專案裡(一個大資料夾),線上部署也一樣,這樣的缺點顯而易見。 後面我們按照業務功能拆分成一個個的子模組,然後子模組之間通過RPC框架進行訪問,各個子模組有各自獨立的線上機器集群、mysql及redis等存儲資源,這樣一個子模組出問題不會影響到其它模組,同時可維護性,擴展性更強。

但現實中每個子模組的服務能力是不同的, 如下圖按子模組分割之後的架構圖所示,假設到達A模組的QPS為100,A依賴B,同時每一個A模組到達B模組的請求QPS也為100, 但B模組所能提供的最大QPS能力為50, 如果不進行流量限制,則B模組因為超過負載而流量堆積導致整個系統不可用,我們的動態流量控制系統就是找到子模組的最佳服務能力,即限制A模組到達B模組的流量為50QPS,則至少保證一部分請求是能夠正常進行的,而不會因為一個子服務掛掉而拖跨整個系統。

我們的RPC框架是一個PHP實作的框架,主要支援http協定的存取。對於一個前端A模組來說,對於依賴的後端B模組, 需先對B模組進行服務化配置,再按服務名字進行引用訪問,服務配置一般形式如下:

[MODULE-B]  ; 服务名字
protocol = "http"  ;交互协议
lb_alg = "random" ; 负载均衡算法
conn_timeout_ms = 1000 ; 连接超时,所有协议使用, 单位为ms 
read_timeout_ms = 3000 ; 读超时
write_timeout_ms = 3000 ; 写超时 
exe_timeout_ms = 3000 ; 执行超时
host.default[] = "127.0.0.1" ; ip或域名
host.default[] = "127.0.0.2" ; ip或域名
host.default[] = "127.0.0.3" ; ip或域名
port = 80 ; 端口
domain = 'api.abc.com' ; 域名配置,不作真正解析,作为header host字段传给后端

對於要存取的一個服務模組,部署上一般是一個集群,我們需要配置機器集群的所有IP,當然,如果有內部DNS服務,也可以配上集群的域名。

對於一個RPC框架來說,基本的功能有負載平衡、健康檢查、降級&限流等,我們的流量控制即針對降級&限流功能,在詳細介紹它之前,先說說負載平衡與健康檢查是如何實現的,這是流量控制實現的基礎。

負載平衡我們實現了隨機與輪詢演算法,隨機演算法透過在所有IP中隨機選一個即可,比較容易實現,對於輪詢演算法,我們是基於單機輪詢,將上一個選擇的IP序號利用apcu擴充記錄在本地記憶體中,以方便找到下一個要使用的IP序號。

被存取的機器可能會失敗,我們將失敗的請求IP記錄在redis中,同時分析記錄的失敗日誌來決定是否需要將一個機器IP摘除,即認為這個IP的機器已經掛掉,不能正常提供服務了,這就是健康檢查的功能,我們透過相關服務配置項來介紹下健康檢查的具體功能:

ip_fail_sample_ratio = 1 ; 采样比例

失败IP记录采样比例,我们将失败的请求记录在redis中,为防止太多的redis请求,我们可以配一个失败采样比例

ip_fail_cnt_threshold  = 10;  IP失败次数
ip_fail_delay_time_s = 2 ;  时间区间
ip_fail_client_cnt = 3 ; 失败的客户端数

不可能一个IP失败一次就将其从健康IP列表中去掉,只有在有效的ip_fail_delay_time_s 时间范围内,请求失败了 ip_fail_cnt_threshold 次,并且失败的客户端达到ip_fail_client_cnt 个, 才认为其是不健康的IP。 

为什么要添加 ip_fail_client_cnt 这样一个配置,因为如果只是某一台机器访问后端某个服务IP失败,那不一定是服务IP的问题,也可能是访问客户端的问题,只有当大多数客户端都有失败记录时才认为是后端服务IP的问题

我们将失败日志记录在redis的list表中,并带上时间戳,就比较容易统计时间区间内的失败次数。

ip_retry_delay_time_s = 30 ; 检查失败IP是否恢复间隔时间

某个失败的IP有可能在一定时间内恢复,我们间隔 ip_retry_delay_time_s 长的时间去检查,如果请求成功,则从失败的IP列表中去除

ip_retry_fail_cnt = 10;  失败IP如果检查失败,记录的失败权重值

ip_log_ttl_s = 60000; 日志有效期时间

一般来说只有最近的失败日志才有意义,对于历史的日志我们将其自动删除。
ip_log_max_cnt = 10000; 记录的最大日志量

我们用redis记录失败日志,容量有限,我们要设定一个记录的最大日志数量,多余的日志自动删除。

在我們的程式碼實作中,除了正常的服務IP配置,我們也維護了一個失敗IP列表,這樣透過演算法選IP時先要去掉失敗IP,失敗IP記錄在一個文件中,同時利用apcu記憶體快取加速訪問,這樣我們所有的操作基本上是基於記憶體存取的,不會有效能問題。

我們只有在請求失敗時才會將日誌記錄在redis中,那在什麼時候將失敗的IP找出來呢,這涉及到查詢redis list列表中所有的失敗日誌,同時統計失敗個數,是一個較複雜的操作。我們的實作是多個PHP程序搶佔鎖的方式,誰搶到了就執行分析操作,記錄失敗的IP到檔案中。因為只有一個行程會執行分析操作,所以對正常請求不會有什麼影響。  同時只有在失敗時才會有搶佔鎖的動作,正常情況下基本上不會與redis有任何交互,沒有效能損耗。

我們的健康檢查依賴於一個中心化的redis服務,如果它掛了怎麼辦?如果判斷redis服務本身掛掉了,rpc框架會自動關閉健康檢查服務, 不再與redis交互,這樣至少不會影響正常的RPC功能。

在健康檢查實現的基礎上我們可以實現流量控制,即當我們發現大部分或全部IP失敗時,我們可以推斷是因為流量過大導致後端服務響應不過來而請求失敗,這時我們就應該以一定策略限流,一般的實現是直接將流量全部摘除,這有點粗暴,我們的實現是逐步減少流量,直至失敗的IP比例降到一定數值,後面又嘗試逐步增加流量,增加與減少可能是一個循環的過程,也就是動態的流量控制,最終我們會找到一個最佳的流量值。透過相關配置來介紹流量控制的功能:

degrade_ip_fail_ratio = 1 ; 服务开始降级时失败IP比例

即失败的IP比例达到多少时开始降级,即开始减少流量

degrade_dec_step = 0.1 ; 每次限流增加多少

即每次减少多少比例的流量

degrade_stop_ip_ratio = 0.5; 

在失败的IP已降到多少比例时开始停止减少流量,并尝试增加流量
degrade_stop_ttl_s = 10;

停止等待多长时间开始尝试增加流量
degrade_step_ttl_s = 10

流量增加或减少需要等待的时间。
每一次流量增加或减少后,下一步如何做是根据当时失败的IP比例来决定的,而且会保持当前流量值一段时间,而不是立即做决定。

degrade_add_step = 0.1

每次增加流量增加的比例值

degrade_return = false ; 降级时返回值

降级时我们不会再去访问后端服务,而是直接给调用方返回一个配置的值。

流量控制的狀態圖描述如下:
PHP中RPC框架基於Redis實現流量控制系統

如何實現控制流量在一定比例呢? 透過隨機選擇,例如獲得一個隨機數並判斷是否落在某個範圍內。 透過限制流量在一個最佳值,在影響最少的用戶情況下讓大部分請求能正常運作,同時流量控製配合監控警報,發現某個模組的流量控制比例在1以下,說明相關模組已是系統的瓶頸,下一步就應該增加硬體資源或優化我們的程式效能了。

相關推薦:

RPC框架的實例詳解

#PHP遠端呼叫以及RPC框架的程式碼詳解(圖)

PHPRPC的簡單使用

以上是PHP中RPC框架基於Redis實現流量控制系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn