小时候村里一到夏天,全村都开空调,村里总闸的保险丝就会由于用电量太大,自动熔断了,直接停服。股市也有一些极端状况开启熔断处理,不到万不得已,不会熔断。在 Web 工程中熔断的最小单元,也不必定是整个应用,可能只是某个服务。这里不深究学术名词定义。php
限流场景咱们常常遇到,有时候地铁里就被保安人员给我限流了,双十一抢购也被爸爸限流了。坐地铁之因此能限流是由于咱们都要安检,有这个统一的地铁入口;浏览网站被限流是由于访问有统一的域名入口。html
当咱们须要根据路由规则进行限流,只要把握好网关就很方便的实现限流了,如下方案都可行前端
生活中我也是消费降级的小伙,原来天猫,后来淘宝,如今拼多多和淘宝特价版。消费降级真香,话说回来,重点是又不是不能用。Web 项目中降级的案例,好比微博 feed 流中,用户基本信息压力比较大,而用户的勋章也在该接口中对前端输出,服务降级的重点是又不是不能用。nginx
若是是按照如今微服务的理念,勋章查询多是一个独立的服务,因此降级对应的颗粒度能够是服务降级。既然是独立的服务单元,请求的拦截就又回到了和限流同样的场景;
若是不是微服务架构,接口依赖的用户的勋章输出只是一个独立的函数或者方法,如何进行拦截呢?git
若是后端服务是 PHP 的脚本语言,咱们能够快速的单独发布须要修改的文件,达到快速降级的目的。
若是后端服务是 JAVA 须要编译的,对于这种简单场景的修改,也是支持热部署,单独发布一个 class 文件,也不须要重启也 OK,好比 arthas 就提供相似的功能。github
若是发布系统不支持热部署,也不支持单文件发布,只支持发布软件包的方式,那么快速降级就须要 15 ~ 30分钟(业务复杂一点的 Java 应用)才能部署完成,这是互联网应用不能接受的。web
在 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
是否开启用户自定义的方法和函很多天志记录,能够用日志处理工具收集好比阿里云 SLSsentinel.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; }