聊一聊限流、降級、熔斷

熔斷

小時候村裏一到夏天,全村都開空調,村裏總閘的保險絲就會由於用電量太大,自動熔斷了,直接停服。股市也有一些極端狀況開啓熔斷處理,不到萬不得已,不會熔斷。在 Web 工程中熔斷的最小單元,也不必定是整個應用,可能只是某個服務。這裏不深究學術名詞定義。php

限流

限流場景咱們常常遇到,有時候地鐵裏就被保安人員給我限流了,雙十一搶購也被爸爸限流了。坐地鐵之因此能限流是由於咱們都要安檢,有這個統一的地鐵入口;瀏覽網站被限流是由於訪問有統一的域名入口。html

當咱們須要根據路由規則進行限流,只要把握好網關就很方便的實現限流了,如下方案都可行前端

  • 布點在相似於 WAF(Web 應用防火牆)中,具體見 阿里雲WAF手冊
  • 若是不想花錢,也能夠安裝 nginx 限流插件來作相似的工做,本身部署,總之是在 web 應用前面布點攔截操做
  • 也能夠在 web 應用裏面結合路由組件開發一個限流組件,只要代碼有統一入口,就能夠方便控制

降級

生活中我也是消費降級的小夥,原來天貓,後來淘寶,如今拼多多和淘寶特價版。消費降級真香,話說回來,重點是又不是不能用。Web 項目中降級的案例,好比微博 feed 流中,用戶基本信息壓力比較大,而用戶的勳章也在該接口中對前端輸出,服務降級的重點是又不是不能用nginx

image.png

若是是按照如今微服務的理念,勳章查詢多是一個獨立的服務,因此降級對應的顆粒度能夠是服務降級。既然是獨立的服務單元,請求的攔截就又回到了和限流同樣的場景;
若是不是微服務架構,接口依賴的用戶的勳章輸出只是一個獨立的函數或者方法,如何進行攔截呢?git

方案1. 緊急發佈

若是後端服務是 PHP 的腳本語言,咱們能夠快速的單獨發佈須要修改的文件,達到快速降級的目的。
若是後端服務是 JAVA 須要編譯的,對於這種簡單場景的修改,也是支持熱部署,單獨發佈一個 class 文件,也不須要重啓也 OK,好比 arthas 就提供相似的功能。github

若是發佈系統不支持熱部署,也不支持單文件發佈,只支持發佈軟件包的方式,那麼快速降級就須要 15 ~ 30分鐘(業務複雜一點的 Java 應用)才能部署完成,這是互聯網應用不能接受的。web

方案2. 限流降級中間件

在 Java 生態目前比較成熟,知名的產品有 hystrix,我用的比較多的是 sentinel。支持從路由、方法去作單機的 qps 去限流,只須要在sentinel管控臺作配置變動,而後發佈推送到各個機器,機器則以最後收到的限流規則單機閉環操做,中間再也不須要和中間件服務進行交互。json

PHP 能不能像 sentinel 同樣對用戶態的函數和方法進行攔截控制呢,因此弄了這個 https://github.com/zhoumengka... PHP 7.2.5 線上運行OK後端

安裝使用

編譯安裝api

$ phpize
$ ./configure --with-php-config=/usr/local/php/bin/php-config
# 若是須要調試
# ./configure --with-php-config=/usr/local/php/bin/php-config --enable-debug
$ make && make install

配置php.ini,在其後面追加

[sentinel]
extension=sentinel.so
sentinel.api_url=https://mengkang.net/sentinel.html
sentinel.api_cache_ttl=120
sentinel.api_cache_file=/tmp/sentinel.rule
sentinel.log_enabled=1
sentinel.log_file=/tmp/sentinel.log
  • sentinel.api_url 限流查詢接口
  • sentinel.api_cache_ttl 接口查詢結果緩存 2 分鐘
  • sentinel.api_cache_file 接口查詢緩存路徑
  • sentinel.log_enabled 是否開啓用戶自定義的方法和函很多天志記錄,能夠用日誌處理工具收集好比阿里雲 SLS
  • sentinel.log_file 日誌記錄路徑

原理

PHP_MINIT階段,經過zend_set_user_opcode_handler註冊在opcode運行環節,對用戶態的方法和函數(ZEND_DO_UCALL)的運行作處理

zend_set_user_opcode_handler(ZEND_DO_UCALL, php_sentinel_call_handler)

PHP_RINIT階段,獲取中間件配置。對於緩存時長內的不發起網絡請求

php_sentinel_fetch()

限流策略閉環

php_sentinel_call_handler

當已經已經在限流列表中的方法或者函數進行攔截,同時對經過的方法和函數進行日誌記錄(當日志功能開啓的狀況)而後進行相似 sls 上報,而後中心經過分析 sls 日誌再決策是否進行限流,經過接口通知到各個 web 服務器。

註冊自定義的異常類SentinelException,當檢測到執行的方法或者函數須要被限流,則拋出該異常

zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "SentinelException", NULL);
php_sentinel_exception_class_entry =  zend_register_internal_class_ex(&ce, zend_ce_exception);
php_sentinel_exception_class_entry->ce_flags |= ZEND_ACC_FINAL;

業務層能夠對該異常在最外層進行捕獲,按照業務協議作標準錯誤輸出。

try{
    // 項目 dispatcher 調度入口
}catch (\SentinelException $exception){
    // 標準的錯誤輸出 能夠是 json 能夠 html 頁面
}

目前的方案更偏向於一個熔斷思路,只要運行到指定的方法或者函數就拋出異常,好比(以函數演示,方法也支持)。惋惜 PHP set_exception_handler 不能對運行當前行進行捕獲處理,而且接管異常。因此只能算是熔斷 ,除非被攔截的方法,在一些邏輯下沒有進入。

function demo(){
    $ res[] = aa();
    
    if (條件 1) {
        $res[] = bb(); // bb 函數被降級
    } else {
        $resp[] = cc();
    }
    
    return $res;
}
相關文章
相關標籤/搜索