6

聊一聊限流、降级、熔断

 3 years ago
source link: https://segmentfault.com/a/1190000040057220
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

聊一聊限流、降级、熔断

小时候村里一到夏天,全村都开空调,村里总闸的保险丝就会因为用电量太大,自动熔断了,直接停服。股市也有一些极端情况开启熔断处理,不到万不得已,不会熔断。在 Web 工程中熔断的最小单元,也不一定是整个应用,可能只是某个服务。这里不深究学术名词定义。

限流场景我们经常遇到,有时候地铁里就被保安人员给我限流了,双十一抢购也被爸爸限流了。坐地铁之所以能限流是因为我们都要安检,有这个统一的地铁入口;浏览网站被限流是因为访问有统一的域名入口。

当我们需要根据路由规则进行限流,只要把握好网关就很方便的实现限流了,以下方案均可行

  • 布点在类似于 WAF(Web 应用防火墙)中,具体见 阿里云WAF手册
  • 如果不想花钱,也可以安装 nginx 限流插件来做类似的工作,自己部署,总之是在 web 应用前面布点拦截操作
  • 也可以在 web 应用里面结合路由组件开发一个限流组件,只要代码有统一入口,就可以方便控制

生活中我也是消费降级的小伙,原来天猫,后来淘宝,现在拼多多和淘宝特价版。消费降级真香,话说回来,重点是又不是不能用。Web 项目中降级的案例,比如微博 feed 流中,用户基本信息压力比较大,而用户的勋章也在该接口中对前端输出,服务降级的重点是又不是不能用

image.png

如果是按照现在微服务的理念,勋章查询可能是一个独立的服务,所以降级对应的颗粒度可以是服务降级。既然是独立的服务单元,请求的拦截就又回到了和限流一样的场景;
如果不是微服务架构,接口依赖的用户的勋章输出只是一个独立的函数或者方法,如何进行拦截呢?

方案1. 紧急发布

如果后端服务是 PHP 的脚本语言,我们可以快速的单独发布需要修改的文件,达到快速降级的目的。
如果后端服务是 JAVA 需要编译的,对于这种简单场景的修改,也是支持热部署,单独发布一个 class 文件,也不需要重启也 OK,比如 arthas 就提供类似的功能。

如果发布系统不支持热部署,也不支持单文件发布,只支持发布软件包的方式,那么快速降级就需要 15 ~ 30分钟(业务复杂一点的 Java 应用)才能部署完成,这是互联网应用不能接受的。

方案2. 限流降级中间件

在 Java 生态目前比较成熟,知名的产品有 hystrix,我用的比较多的是 sentinel。支持从路由、方法去做单机的 qps 去限流,只需要在sentinel管控台做配置变更,然后发布推送到各个机器,机器则以最后收到的限流规则单机闭环操作,中间不再需要和中间件服务进行交互。

PHP 能不能像 sentinel 一样对用户态的函数和方法进行拦截控制呢,所以弄了这个 https://github.com/zhoumengka... PHP 7.2.5 线上运行OK

$ 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;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK