Consul是一个服务发现和注册的工具,其具有分布式、高扩展性能特点,它是HashiCorp公司推出的一款实用开源工具,支持Linux等平台。
Consul主要包含如下功能:
- 服务发现: 支持 http 和 dns 两种协议的服务注册和发现方式。
- 监控检查: 支持多种方式的健康检查。
- Key/Value存储: 支持通过HTTP API实现分布式KV数据存储。
- 多数据中心支持:支持任意数量数据中心。
swoft-consul 组件,整合了 consul 功能,开发者可以直接通过该组件使用 consul 功能。
wget https://releases.hashicorp.com/consul/1.6.2/consul_1.6.2_linux_amd64.zip
unzip consul_1.6.2_linux_amd64.zip
解压之后实际上是一个单一的文件 ./consul
主服务器: 192.168.56.107
备份服务器:
192.168.56.108
192.168.56.102
./consul agent -server -data-dir=/tmp/consul -bootstrap-expect=3 \
-node=agent-one -bind 192.168.56.107 -enable-script-checks=true \
-config-dir=/etc/consul.d -datacenter=sunny -client 0.0.0.0 \ -ui &
./consul agent -server -data-dir=/tmp/consul \
-node=agent-two \
-bind=192.168.56.108 -enable-script-checks=true \
-config-dir=/etc/consul.d -datacenter=sunny \
-ui -client 0.0.0.0 \
-join 192.168.56.107 &
./consul agent -server -data-dir=/tmp/consul \
-node=agent-three \
-bind=192.168.56.109 -enable-script-checks=true \
-config-dir=/etc/consul.d -datacenter=sunny \
-ui -client 0.0.0.0 \
-join 192.168.56.107 &
./consul agent -data-dir=/tmp/consul \
-node=client \
-bind=192.168.56.102 -enable-script-checks=true \
-config-dir=/etc/consul.d -datacenter=sunny \
-ui -client 0.0.0.0 \
-join 192.168.56.107 &
注意:
- Consul集群如果想要实现故障转移,必须要配置统一的数据中心名称 示例:-datacenter
- Consul能够实现自动故障转移,也就是说如果当前leader服务器不可用了,会重新选举出一个leader服务器。
查询集群中leader节点命令:./consul operator raft list-peers- 服务端集群的节点数目最好达到奇数个比较好,不然会导致选举leader失败。根据raft算法,需要有N/2+1个节点才能正常选举leader。
- 注意这里consul客户端应该跟应用在同一台服务器上部署,然后consul配置项不用添加 host 即可。
/app/bean.php
return [
//其他配置项
…
//consul配置项 若本地起了consul客户端代理服务,就不用填host和port
‘consul’=> [
// ‘host’ => ‘192.168.56.107’,
// ‘port’ => 8500,
‘timeout’=> 3,
]
]
php /var/www/html/swoft/bin/swoft http:start
sudo ps -ef|grep "swoft-http"|grep -v "grep" |awk '{print $2}'|xargs kill -9
无论是 http / rpc / ws 服务,启动的时候只需监听 SwooleEvent::START 事件,即可把启动的服务注册到第三方集群。
文件: app/listener/RegisterServiceListener.php
declare(strict_types=1);
/**
* This file is part of Swoft.
*
* @link https://swoft.org
* @document https://swoft.org/docs
* @contact [email protected]
* @license https://github.com/swoft-cloud/swoft/blob/master/LICENSE
*/
namespace App\Listener;
use Swoft\Bean\Annotation\Mapping\Inject;
use Swoft\Consul\Agent;
use Swoft\Event\Annotation\Mapping\Listener;
use Swoft\Event\EventHandlerInterface;
use Swoft\Event\EventInterface;
use Swoft\Http\Server\HttpServer;
use Swoft\Log\Helper\CLog;
use Swoft\Server\SwooleEvent;
/**
* Class RegisterServiceListener
*
* @since 2.0
*
* @Listener(event=SwooleEvent::START)
*/
class RegisterServiceListener implements EventHandlerInterface
{
/**
* @Inject()
*
* @var Agent
*/
private $agent;
/**
* @param EventInterface $event
* @throws
*/
public function handle(EventInterface $event): void
{
/** @var HttpServer $httpServer */
$httpServer = $event->getTarget();
$service = [
'ID' => 'swoft',
'Name' => 'swoft',
'Tags' => [
'http'
],
'Address' => '192.168.56.102',
'Port' => $httpServer->getPort(),
'Meta' => [
'version' => '1.0'
],
'EnableTagOverride' => false,
'Weights' => [
'Passing' => 10,
'Warning' => 1
]
];
// Register
$this->agent->registerService($service);
CLog::info('Swoft http register service success by consul!');
}
}
该listener能起作用,这一句不可少 @Listener(event=SwooleEvent::START)
app/Listener/DeregisterServiceListener.php
namespace App\Listener;
use Swoft\Bean\Annotation\Mapping\Inject;
use Swoft\Consul\Agent;
use Swoft\Event\Annotation\Mapping\Listener;
use Swoft\Event\EventHandlerInterface;
use Swoft\Event\EventInterface;
use Swoft\Http\Server\HttpServer;
use Swoft\Log\Helper\CLog;
use Swoft\Server\SwooleEvent;
/**
* Class DeregisterServiceListener
*
* @since 2.0
*
* @Listener(SwooleEvent::SHUTDOWN)
*/
class DeregisterServiceListener implements EventHandlerInterface
{
/**
* @Inject()
*
* @var Agent
*/
private $agent;
/**
* @param EventInterface $event
* @throws
*/
public function handle(EventInterface $event): void
{
/** @var HttpServer $httpServer */
$httpServer = $event->getTarget();
$this->agent->deregisterService('swoft');
CLog::info("arrive in here, ".__METHOD__."\n");
}
}
onShutdown事件 (@Listener(SwooleEvent::SHUTDOWN))
此事件在Server正常结束时发生
函数原型:function onShutdown(swoole_server $server);
在此之前Swoole\Server已进行了如下操作
- 已关闭所有Reactor线程、HeartbeatCheck线程、UdpRecv线程
- 已关闭所有Worker进程、Task进程、User进程(用户自定义的进程)
- 已close所有TCP/UDP/UnixSocket监听端口
- 已关闭主Reactor
文件: /vendor/swoft/consul/src/Consul.php
/**
* Class Consul
*
* @since 2.0
*
* @Bean("consul")
*/
class Consul
{
/**
* @var string
*/
private $host = '127.0.0.1';
/**
* @var int
*/
private $port = 8500;
/**
* Seconds
*
* @var int
*/
private $timeout = 3;
//下面代码省略......
}
可以看出 listener里注入的agent默认使用的本地客户端作为代理
文件: app/Model/Logic/ConsulLogic.php
public function kv(): void
{
$value = 'value content';
$this->kv->put('/test/my/key', $value);
$response = $this->kv->get('/test/my/key');
var_dump($response->getBody(), $response->getResult());
}
/**
* 根据服务名称获取微服务注册信息
* @param string $serviceName 服务名称
* @return array
* @throws ClientException
* @throws ServerException
*/
public function getServiceList(string $serviceName): array
{
$services = $this->agent->services();
$bodyContent = $services->getBody();
CLog::info(var_export($bodyContent, true));
$arrJson = JsonHelper::decode($bodyContent, true);
if (!isset($arrJson[$serviceName])) {
return [];
}
CLog::info(var_export($arrJson, true));
return $arrJson[$serviceName];
}
######测试服务发现及KV
/**
* @RequestMapping()
* @param Request $request
* @return Response
* @throws Swoft\Bean\Exception\ContainerException
* @throws Swoft\Consul\Exception\ClientException
* @throws Swoft\Consul\Exception\ServerException
* @throws \ReflectionException
*/
public function consulKv(Request $request): Response
{
$arrReq = $request->getParsedQuery();
$value = $arrReq['value'];
$this->consulBean->kv();
return context()->getResponse()->withData(['code' => 200, 'msg'=>'ok', 'data'=>['value'=>$value]]);
}
/**
* @RequestMapping()
*
* @param Request $request
* @return Response
* @throws Swoft\Consul\Exception\ClientException
* @throws Swoft\Consul\Exception\ServerException
*/
public function serviceList(Request $request): Response
{
$arrReq = $request->getParsedQuery();
$service = $arrReq['service'];
$output = $this->consulBean->getServiceList($service);
return context()->getResponse()->withData(['code' => 200, 'msg'=>'ok', 'data'=>$output]);
}
测试:
http://192.168.56.102:18306/test/serviceList?service=swoft
运行结果:
{"code":200,"msg":"ok","data":{"ID":"swoft","Service":"swoft","Tags":["http"],"Meta":{"version":"1.0"},"Port":18306,"Address":"192.168.56.102","Weights":{"Passing":10,"Warning":1},"EnableTagOverride":false}}
http://192.168.56.102:18306/test/consulKv?value=howareyou
运行结果:
{"code":200,"msg":"ok","data":{"value":"howareyou"}}