Open matyhtf opened 2 years ago
在 swoole 中提供了一套服务器治理的解决方案,有以下几点优势:
file_get_cotents
curl
Swoole\Coroutine\Http\Client
支持Consul、Nacos等常见的服务注册发现组件,底层可自动替换DNS,无需应用层更改代码。
Consul
Nacos
DNS
// GET $json = file_get_contents("http://user.service/profile/?uid=1"); // POST $opts = array('http' => array( 'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => http_build_query(['mobile' => 19900000000, 'name' => 'Rango']), ) ); $context = stream_context_create($opts); $json = file_get_contents("http://user.service/profile/?uid=1", false, $context);
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://user.service/profile/?uid=1"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $rs = curl_exec($ch);
$cli = new Swoole\Coroutine\Http\Client('user.service'); $cli->get('/profile/?uid=1'); $json = $cli->getBody();
这里的user.service就是服务的名称,在服务端需要注册服务到对应的Consul或Nacos服务管理中心。 客户端会自动获取user.service所有集群的节点,未指定端口时,会自动获取服务器节点注册的端口。
user.service
const SERVICE_NAME = 'user.service'; $ns = new Swoole\NameResolver\Consul('http://127.0.0.1:8500'); $serv = new Swoole\Http\Server('127.0.0.1', 9501); $serv->on("start", function ($serv) use ($ns) { $ns->join(SERVICE_NAME, '127.0.0.1', $serv->port); }); $serv->on('request', function ($req, $resp) { $resp->end(json_encode(['code' => 0,])); }); $serv->on('beforeShutdown', function ($serv) use ($ns) { $ns->leave(SERVICE_NAME, '127.0.0.1', $serv->port); }); $serv->start();
IP
Port
服务有多个节点时,客户端会获得所有节点列表。底层会使用随机+权重的算法进行负载均衡,将请求发送到服务集群内的其中一个节点。 除了随机+权重的模式外,未来还会支持多种负载均衡模式:
随机+权重
Hash
当客户端发起请求时,TCP连接到一个节点发生超时和连接被拒绝,底层会切换到集群的另外一个节点进行连接,直到成功连接或者达到 最大重试次数时,将返回失败。
TCP
当客户端请求出现失败时,某些情况下是可以安全重试的。如TCP连接失败,或者服务器端返回503服务不可用, 客户端可以切换到另外一个节点进行重试。
503
HTTP request failed! HTTP/1.1 503
HTTP request failed! HTTP/1.1 502
Failed to open stream: Connection timed out
Failed to open stream: No route to host
Failed to open stream: Connection refused
由于 php stream、curl的默认超时时间非常长,如果出现某些请求无响应可能会导致整个进程被长期阻塞,导致服务雪崩。 底层会自动将超时时间统一修改为1秒。
php stream
1
default_socket_timeout "60" PHP_INI_ALL
参考: https://www.php.net/manual/en/filesystem.configuration.php#ini.default-socket-timeout
CURLOPT_TIMEOUT: Indefinite CURLOPT_TIMEOUT_MS: Indefinite CURLOPT_CONNECTTIMEOUT: 300 seconds CURLOPT_CONNECTTIMEOUT_MS: Indefinite
参考: https://stackoverflow.com/questions/10308915/php-default-curl-timeout-value
当客户端发起一个请求之后,底层首先会从名字服务(naming service)获得可用节点列表, 之后根据负载均衡策略挑选出一个节点作为实际访问的节点。当某个节点出现故障或者服务器高负载无法正常处理请求时, 底层的重试机制能够自动跳过这些节点,将请求发送到其他节点。
naming service
底层发现某个节点出现连续多次连接超时、服务不可用、延时过高时,会判断认为该节点短时间内不可用,将当前节点熔断, 一定时间内将自动跳过此节点。如果节点再次可用会重新向此节点发送请求。这样可以避免:
一个服务器程序受制于硬件性能、软件算法和逻辑复杂度、后端存储的吞吐能力,因此处理能力是有限的。 当请求速度超过服务的处理速度时,服务就会过载。
如果服务持续过载,会导致越来越多的请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务处于瘫痪状态。 这时如果服务器可以直接主动拒绝掉一部分请求,能够让服务能够"及时"处理更多的请求。
可以通过设置服务器的最大并发 (max_concurrency) 来设置限流,防止服务过载引起雪崩 。
max_concurrency
$serv = new Swoole\Http\Server('127.0.0.1', 9501); $serv->set(['max_concurrency' => 500]);
可以通过一个简单的公式计算出合适的max_concurrency设置,
max_concurrency = 最大 QPS(请求个数/秒) * 平均响应延时(秒)
原理请参考:little's law
little's law
通过对服务器程序进行压测可以观察到程序在能够及时并成功处理所有请求时的最大 QPS 和平均响应延时。
QPS
理论上如果服务器程序所在机器的硬件性能、程序未变更、依赖的后端数据库或其他服务的吞吐能力没有变化, 当前服务的并发是可以保持不变的,这种情况下要让服务不过载,只需在上线前进行压力测试,并通过little's law 计算出最佳的max_concurrency并设置即可。
而在复杂多变的环境下,静态设置的max_concurrency可能并不能适应所有情况。因此需要一种能够根据 服务器程序运行时产生的实时性能,自动计算并动态设置max_concurrency,可称之为自适应限流。
可将 max_concurrency 设为 SWOOLE_MAX_CONCURRENCY_AUTO 即可启用自适应限流。
SWOOLE_MAX_CONCURRENCY_AUTO
$serv = new Swoole\Http\Server('127.0.0.1', 9501); $serv->set(['max_concurrency' => SWOOLE_MAX_CONCURRENCY_AUTO]);
当服务器产生服务不可用时主动拒绝掉一部分请求,这些拒绝掉的请求与接受并正常处理的请求数量达到一定比例时, 说明当前集群可能需要扩容。
通过计算并汇总集群内所有节点拒绝请求、正常处理请求的次数,达到高水位阈值后可通知K8s等服务器容器编排系统, 将对应的服务集群进行扩容。下降到低水位阈值后,可进行缩容。
K8s
swoole_plus.enable_service_governance
On/Off
swoole_plus.max_retries
Int
3
swoole_plus.default_timeout
路径: /opt/swoole/plus/name_resolver.json,格式为json,必须为数组,可同时设置多个名字服务,通过suffix 后缀区分使用哪个NameResolver
/opt/swoole/plus/name_resolver.json
json
suffix
NameResolver
[ { "class": "Swoole\\NameResolver\\Consul", "server_url": "http://127.0.0.1:8500", "suffix": ".service" } ]
class
Redis
server_url
Swoole 底层服务治理框架
在 swoole 中提供了一套服务器治理的解决方案,有以下几点优势:
file_get_cotents
、curl
、Swoole\Coroutine\Http\Client
等 API 即可服务发现
支持
Consul
、Nacos
等常见的服务注册发现组件,底层可自动替换DNS
,无需应用层更改代码。file_get_contents
curl
SWOOLE 协程客户端
这里的
user.service
就是服务的名称,在服务端需要注册服务到对应的Consul
或Nacos
服务管理中心。 客户端会自动获取user.service
所有集群的节点,未指定端口时,会自动获取服务器节点注册的端口。服务注册
IP
和Port
注册到了Consul
中负载均衡
服务有多个节点时,客户端会获得所有节点列表。底层会使用随机+权重的算法进行负载均衡,将请求发送到服务集群内的其中一个节点。 除了
随机+权重
的模式外,未来还会支持多种负载均衡模式:Hash
Hash
故障转移
当客户端发起请求时,
TCP
连接到一个节点发生超时和连接被拒绝,底层会切换到集群的另外一个节点进行连接,直到成功连接或者达到 最大重试次数时,将返回失败。请求重试
当客户端请求出现失败时,某些情况下是可以安全重试的。如
TCP
连接失败,或者服务器端返回503
服务不可用, 客户端可以切换到另外一个节点进行重试。可安全重试的情况
HTTP request failed! HTTP/1.1 503
HTTP request failed! HTTP/1.1 502
Failed to open stream: Connection timed out
Failed to open stream: No route to host
Failed to open stream: Connection refused
超时控制
由于
php stream
、curl
的默认超时时间非常长,如果出现某些请求无响应可能会导致整个进程被长期阻塞,导致服务雪崩。 底层会自动将超时时间统一修改为1
秒。php stream 的默认超时
curl 的默认超时
服务熔断
当客户端发起一个请求之后,底层首先会从名字服务(
naming service
)获得可用节点列表, 之后根据负载均衡策略挑选出一个节点作为实际访问的节点。当某个节点出现故障或者服务器高负载无法正常处理请求时, 底层的重试机制能够自动跳过这些节点,将请求发送到其他节点。底层发现某个节点出现连续多次连接超时、服务不可用、延时过高时,会判断认为该节点短时间内不可用,将当前节点熔断, 一定时间内将自动跳过此节点。如果节点再次可用会重新向此节点发送请求。这样可以避免:
服务限流
一个服务器程序受制于硬件性能、软件算法和逻辑复杂度、后端存储的吞吐能力,因此处理能力是有限的。 当请求速度超过服务的处理速度时,服务就会过载。
如果服务持续过载,会导致越来越多的请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务处于瘫痪状态。 这时如果服务器可以直接主动拒绝掉一部分请求,能够让服务能够"及时"处理更多的请求。
可以通过设置服务器的最大并发 (
max_concurrency
) 来设置限流,防止服务过载引起雪崩 。如何设置最大并发
可以通过一个简单的公式计算出合适的
max_concurrency
设置,通过对服务器程序进行压测可以观察到程序在能够及时并成功处理所有请求时的最大
QPS
和平均响应延时。自适应限流
理论上如果服务器程序所在机器的硬件性能、程序未变更、依赖的后端数据库或其他服务的吞吐能力没有变化, 当前服务的并发是可以保持不变的,这种情况下要让服务不过载,只需在上线前进行压力测试,并通过
little's law
计算出最佳的max_concurrency
并设置即可。而在复杂多变的环境下,静态设置的
max_concurrency
可能并不能适应所有情况。因此需要一种能够根据 服务器程序运行时产生的实时性能,自动计算并动态设置max_concurrency
,可称之为自适应限流。可将
max_concurrency
设为SWOOLE_MAX_CONCURRENCY_AUTO
即可启用自适应限流。弹性伸缩
当服务器产生服务不可用时主动拒绝掉一部分请求,这些拒绝掉的请求与接受并正常处理的请求数量达到一定比例时, 说明当前集群可能需要扩容。
通过计算并汇总集群内所有节点拒绝请求、正常处理请求的次数,达到高水位阈值后可通知
K8s
等服务器容器编排系统, 将对应的服务集群进行扩容。下降到低水位阈值后,可进行缩容。Plus 配置
php.ini
swoole_plus.enable_service_governance
: 类型:On/Off
,是否开启服务治理功能swoole_plus.max_retries
: 类型:Int
,最大重试次数,默认为3
swoole_plus.default_timeout
: 类型:Int
,超时时间设置,单位为秒,默认1
NameResovler 配置
路径:
/opt/swoole/plus/name_resolver.json
,格式为json
,必须为数组,可同时设置多个名字服务,通过suffix
后缀区分使用哪个NameResolver
class
:NameResolver
的类型,目前支持Consul
、Redis
、Nacos
3
种类型server_url
: 名字服务器的访问地址suffix
: 后缀过滤器,仅指定后缀的名字使用当前的NameResolver