服务治理
服务治理主要包括三部分,熔断器、服务注册与发现、服务降级。
熔断器
三种状态
熔断器有三种状态,开启、关闭、半开,每种状态都有不同对应的操作,目前内部使用状态模式实现的。
- 关闭状态 正常逻辑处理,当失败次数达到上限可以切换到开启状态
- 开启状态 服务降级处理,一定时间延迟后尝试切换到半开状态
- 半开状态 正常逻辑处理,当业务失败次数达到一定值,切换到开启,当业务成功次数达到一定值,切换到关闭。
注解
@Breaker
定义熔断器
- name 熔断器名称,缺省默认是类名
定义熔断器
定义熔断器需要继承CircuitBreaker类,配置属性信息,配置信息可以通过@value和@Config两个注解实现,注解详解请看配置章节。
/**
* the breaker of user
*
* @Breaker("user")
*/
class UserBreaker extends CircuitBreaker
{
/**
* The number of successive failures
* If the arrival, the state switch to open
*
* @Value(name="${config.breaker.user.failCount}", env="${USER_BREAKER_FAIL_COUNT}")
* @var int
*/
protected $switchToFailCount = 3;
/**
* The number of successive successes
* If the arrival, the state switch to close
*
* @Value(name="${config.breaker.user.successCount}", env="${USER_BREAKER_SUCCESS_COUNT}")
* @var int
*/
protected $switchToSuccessCount = 3;
/**
* Switch close to open delay time
* The unit is milliseconds
*
* @Value(name="${config.breaker.user.delayTime}", env="${USER_BREAKER_DELAY_TIME}")
* @var int
*/
protected $delaySwitchTimer = 500;
}
RPC使用
RPC中默认是根据@Reference注解指定的服务名称,加载名称相同的熔断器,进行逻辑处理
任意使用
可以再任意逻辑中使用熔断器
\breaker('name')->call($handler, $params, $fallback);
- $handler 可调用的callback
- $params 调用传递的参数
- $fallback 参考服务降级章节的普通服务降级
服务降级
服务降级,其实对功能的一种容错机制,这里指的是RPC调用服务降级和普通的服务降级。
普通服务降级
比如内部服务调用失败后,为不影响正常请求,可以实现 Fallback,实现对服务的降级。Fallback 定义有目前有三种形式。
- 全局函数
- 匿名函数
可以调用的 Callback
// 全局函数 function method() { // .. } // 匿名函数 $fallback = function(){ // ... }; // callback class xxx { public function method() { } }
RPC服务降级
注解
@Fallback
定义RPC服务降级处理的接口实现。
- name 降级功能名称,缺省使用类名
定义降级服务
/**
* Fallback demo
*
* @Fallback("demoFallback")
* @method ResultInterface deferGetUsers(array $ids)
* @method ResultInterface deferGetUser(string $id)
* @method ResultInterface deferGetUserByCond(int $type, int $uid, string $name, float $price, string $desc = "desc")
*/
class DemoServiceFallback implements DemoInterface
{
public function getUsers(array $ids)
{
return ['fallback', 'getUsers', func_get_args()];
}
public function getUser(string $id)
{
return ['fallback', 'getUser', func_get_args()];
}
public function getUserByCond(int $type, int $uid, string $name, float $price, string $desc = "desc")
{
return ['fallback', 'getUserByCond', func_get_args()];
}
}
- 需要使用注解定义defer函数,此处不做详细描述,请查看RPC客户单章节
- DemoInterface 是RPC章节的lib接口,每个接口可以实现多个不用的降级服务
- RPC服务调用失败后,就会调用实现的服务降级里面函数
使用实例
/**
* rpc controller test
*
* @Controller(prefix="rpc")
*/
class RpcController
{
/**
* @Reference(name="user", fallback="demoFallback")
*
* @var DemoInterface
*/
/**
* @return array
*/
public function fallback()
{
$result1 = $this->demoService->getUser('11');
$result2 = $this->demoService->getUsers(['1','2']);
$result3 = $this->demoService->getUserByCond(1, 2, 'boy', 1.6);
return [
$result1,
$result2,
$result3,
];
}
/**
* @return array
*/
public function deferFallback()
{
$result1 = $this->demoService->deferGetUser('11')->getResult();
$result2 = $this->demoService->deferGetUsers(['1','2'])->getResult();
$result3 = $this->demoService->deferGetUserByCond(1, 2, 'boy', 1.6)->getResult();
return [
'defer',
$result1,
$result2,
$result3,
];
}
}
- @Reference注解,指定fallback服务降级实现,如果不是同一个接口实现,会抛出异常
负载均衡
负载均衡主要是配合服务发现一起使用,暂时 Swoft 只支持随机策略,后续会提供更多选择。
配置
通常情况可以不用配置,系统已经配置了默认值。app/config/beans/base.php
return [
// ...
'balancerSelector' => [
'class' => \Swoft\Sg\BalancerSelector::class,
'balancer' => 'random',
'balancers' => [
'random' => \Swoft\Sg\Balancer\RandomBalancer::class
]
],
// ...
];
- balancer 定义默认负载类型
- balancers 配置定义的负载,通过KEY名字使用
consul 负载均衡配置说明
就用例子中user服务器实现负载均衡举例说明,内网机器(192.168.7.197)分别开启两台user服务器,端口分别为8099和8089
1, 打开端口为8099服务器配置文件app/config/properties/provider.php配置如下:
return [
'consul' => [
'address' => '127.0.0.1',
'port' => 8500,
'register' => [
'id' => 'user',
'name' => 'user',
'tags' => [user],
'enableTagOverride' => false,
'service' => [
'address' => '192.168.1.197',
'port' => '8099',
],
'check' => [
'id' => 'user',
'name' => 'user',
'tcp' => '192.168.1.197:8099',
'interval' => 10,
'timeout' => 1,
],
],
'discovery' => [
'name' => 'user',
'dc' => 'dc',
'near' => '',
'tag' =>'',
'passing' => true
]
],
];
2, 打开端口为8089服务器配置文件app/config/properties/provider.php配置如下:
return [
'consul' => [
'address' => '127.0.0.1',
'port' => 8500,
'register' => [
'id' => 'user1',
'name' => 'user',
'tags' => [user1],
'enableTagOverride' => false,
'service' => [
'address' => '192.168.1.197',
'port' => '8089',
],
'check' => [
'id' => 'user1',
'name' => 'user',
'tcp' => '192.168.1.197:8089',
'interval' => 10,
'timeout' => 1,
],
],
'discovery' => [
'name' => 'user',
'dc' => 'dc',
'near' => '',
'tag' =>'',
'passing' => true
]
],
];
- 前提条件,consul已经安装好了并启动,服务器已经开启了注册发现配置,配置开启请参考.env文件
- id 定义consul服务的唯一id
- name 定义consul服务名字,发现服务时会用到这个名字找对应服务,name是一对多,多服务器可以同名不同tags实现负载均衡
- tags 服务标记,多台同name服务时,可以用tags来区分
- PS注意,discovery.tag 如果负载均衡时, 请配置为空, 如果配置了,就会只找这台tag,导致负载均衡不成功
3, 分别打开端口为8099和8089服务器配置文件app/config/properties/service.php配置如下:
return [
'user' => [
'name' => 'user',
'uri' => [
'192.168.7.197:8099',
'192.168.7.197:8089',
],
'minActive' => 8,
'maxActive' => 8,
'maxWait' => 8,
'maxWaitTime' => 3,
'maxIdleTime' => 60,
'timeout' => 8,
'useProvider' => true,
'balancer' => 'random',
'provider' => 'consul',
]
- name 连接池的名字, 这里配置为user
- uri 服务器ip和端口,可以配置多个实现负载均衡,走配置负载均衡
- useProvider 开启consul负载均衡,这个参数要配置为true,才会走consul,这个参数为true后uri失效,服务器列表会走consul拉取
- balancer 制定负载均衡算法,swoft只实现了random算法,可以自己扩展实现算法
服务注册
RPC 启动的时候,可以配置是否启动的时候注册到第三方比如 consul/etcd ,使用服务也可通过第三方获取。
初始化配置
.env
中配置是否启动注册 RPC服务,默认false
,如果配置true
,必须配置服务提供方
AUTO_REGISTER=false
服务提供方
通常情况可以不用配置,系统已经配置了默认值
配置文件:./config/beans/base.php
return [
// ......
'providerSelector' => [
'class' => \Swoft\Sg\ProviderSelector::class,
'provider' => 'consul',
'providers' => [
'consul' => \Swoft\Sg\Provider\ConsulProvider::class
]
],
// ......
];
- provider 配置服务提供方,默认consul
- providers 配置自定义服务,通过KEY名称使用
Consul服务方配置
Consul 服务提供方,在.env
和./config/properties/provider.php
都可以配置,但是env
会覆盖properties
env
## Consul
CONSUL_ADDRESS=http://127.0.0.1
CONSUL_PORT=8500
CONSUL_REGISTER_NAME=user
CONSUL_REGISTER_ETO=false
CONSUL_REGISTER_SERVICE_ADDRESS=127.0.0.1
CONSUL_REGISTER_SERVICE_PORT=8099
CONSUL_REGISTER_CHECK_NAME=user
CONSUL_REGISTER_CHECK_TCP=127.0.0.1:8099
CONSUL_REGISTER_CHECK_INTERVAL=10
CONSUL_REGISTER_CHECK_TIMEOUT=1
properties
return [
'consul' => [
'address' => '',
'port' => 8500,
'register' => [
'id' => '',
'name' => '',
'tags' => [],
'enableTagOverride' => false,
'service' => [
'address' => 'localhost',
'port' => '8099',
],
'check' => [
'id' => '',
'name' => '',
'tcp' => 'localhost:8099',
'interval' => 10,
'timeout' => 1,
],
],
'discovery' => [
'name' => 'user',
'dc' => 'dc',
'near' => '',
'tag' =>'',
'passing' => true
]
],
];
- 参数具体含义,参考 Consul 相关文档
Consul服务器配置
微服务带来最大的好处就是把整个大项目分割成不同的服务,运行在不同服务器上,实现解耦和分布式处理。微服务虽然有很多好处,但是也会有不好的一方面。任何事物都会有两面性,在微服务里面运维会是一个很大的难题,如果有一天我们的服务数量非常的多,然后我们又不知道哪一个服务在什么机器上。可能会有人说这部分直接写在程序的配置里面就好了,当我们服务少的时候是可以这么做的,也允许这么做,但是在实际当中我们要尽量避免这么做,比如说我们某一个服务,地址换了,那么我们设计的相关代码就得修改重新部署;又或者说我们有一天上线一个新服务或者下线一个服务,这时候我们又得修改程序代码,这是非常不合理的做法。那么有没有什么可以解决这样的问题呢?这里就需要用到我们的服务注册和发现了。
结构对比
没有服务注册发现的结构
上面图片我们可以看到在没有服务注册发现的时候一个调用者需要维护多个服务的ip和端口,这是非常不好的做法,当我们服务进行调整的时候就有可能导致服务调用失败,还有服务器更换服务器,上下新服务,都会受到影响。将来某一个服务节点出现问题,排查对于程序和运维人员来说都是一场很大的灾难,因为不知道哪一个节点出了问题,需要每一台服务器的去排查。
而当我们有使用服务注册发现之后的结构体是什么样子的呢?
有服务注册发现的结构
我们从上图可以发现,当我们有注册中心之后调用者不需要自己去维护所有服务的信息了,仅需要向注册中心请求获取服务,就可以拿到想要的服务信息。这样当我们的服务有所调整,或者上线下线服务,都要可以轻松操作,并且可以在注册中间检查到服务的健康情况,帮助运维人员快速定位到故障的服务器。
关于 Consul
Swoft 推荐使用的就是 Consul,Consul是一个使用go写的服务注册、发现、配置管理系统。
Agent:Agent是Consul集群中一个常驻后台的程序。Agent有两种模式,一种是服务端,一种是客户端。所有Agent都可以运行DNS或者HTTP接口,并且负责检查服务是否存活,和保持服务同步。
Client:Client是一种Agent的运行模式,把所有RPC转发到Agent服务器的代理者,客户端会在后台有一个用最小带宽消耗把请求转发到后端的Agent服务,减轻Agent服务器的压力。
Server:Server是另一种Agent的运行模式,包括使用Raft算法处理数据,维护集群状态,响应RPC的请求,与其他集群的server交换数据或者远程数据中心。
RPC:远程过程调用,这是一种允许客户端发出服务器请求的请求/响应机制。
以上是我们本次比较重要的一些概念性的东西。知道每一个组件每一个部分干嘛的我们看以下的配置才会简单,才会事半功倍。
在 Consul 里面有很多组件,在这里我们暂时使用一个组件就够了就是agent,对于 Consul 的安装也是非常简单的毕竟只有一个二进制文件包,所以直接下载就可以使用了。
安装步骤
1、登录官网进行下载,下载地址
wget https://releases.hashicorp.com/consul/1.2.1/consul_1.2.1_linux_amd64.zip
unzip consul_1.2.1_linux_amd64.zip
2、设置环境变量,如果不设置可以直接把consul执行文件移动到/usr/bin目录下
mv consul /usr/bin
ok, 安装成功后,我们接下来进行一些配置来启用consul
Server配置
单机配置
- 服务器1,IP 192.168.1.100
这种方式适合用于搭建服务调试使用
consul agent -bootstrap-expect 1 -server -data-dir /data/consul -node=swoft01 -bind=0.0.0.0 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -client=0.0.0.0 -ui
可以通过 http://192.168.1.100:8500 查看服务信息
集群配置
这种方式适用于生产环境
服务器1,IP 192.168.1.100
consul agent -bootstrap-expect 2 -server -data-dir /data/consul -node=swoft01 -bind=0.0.0.0 -client=0.0.0.0 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -client=0.0.0.0
上面这个命令是以服务端模式启动一个代理,集群有两个扩展机器,设置集群持久化数据存放在/data/consul0下面,节点名称是swoft01,绑定0.0.0.0地址,服务配置文件存放在/etc/consul.d,开启检查心跳,数据中心的名称是dc1,可访问的客户端地址是0.0.0.0
服务器2,IP 192.168.1.110
consul agent -server -data-dir /data/consul -node=swoft02 -bind=0.0.0.0 -client=0.0.0.0 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.100
服务器3,IP 192.168.1.120
consul agent -server -data-dir /data/consul -node=swoft03 -bind=0.0.0.0 -client=0.0.0.0 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -join 192.168.1.100
以上服务器2和服务3使用 -join
加入集群,并且使用同一个数据名称 dc1
Client配置
服务器4,IP 192.168.1.130
consul agent -ui -data-dir /data/consul -node=swoft04 -bind=0.0.0.0 -config-dir /etc/consul.d -enable-script-checks=true -datacenter=dc1 -client=0.0.0.0 -join 192.168.1.100
客户端如果不使用-server就是客户端模式运行,其他参数同上,服务端和客户端都启动了之后可以在浏览器输入 http://192.168.1.130:8500 来查看信息
查看集群成员:
consul members
查看集群信息:
consul info
命令解析
更多命令解析
-bootstrap-expect
数据中心中预期的服务器数。该值必须与集群中的其他服务器一致。提供后,Consul将等待指定数量的服务器可用,然后引导群集。这允许自动选择初始领导者。这不能与传统-bootstrap标志一起使用。此标志需要在服务端模式下运行。-server
以服务端模式启动-data-dir
数据存放位置,这个用于持久化保存集群状态-node
群集中此节点的名称。这在群集中必须是唯一的。默认情况下,这是计算机的主机名。-bind
绑定服务器的ip地址-config-dir
指定配置文件服务,当这个目录下有 .json 结尾的文件就会加载进来,更多配置可以参考 配置模版-enable-script-checks
检查服务是否处于活动状态,类似开启心跳-datacenter
数据中心名称-client
客户端可访问ip,包括HTTP和DNS服务器。默认情况下,这是“127.0.0.1”,仅允许环回连接。-ui
开启web的ui界面-join
加入到已有的集群中
关于consul更多的信息请大家移步到 Consul官网