Consul
Consul是一個服務發現和註冊的工具,其具有分佈式、高擴展性能特點,它是HashiCorp公司推出的一款實用開源工具,支持Linux等平臺。
Consul主要包含如下功能:
- 服務發現: 支持 http 和 dns 兩種協議的服務註冊和發現方式。
- 監控檢查: 支持多種方式的健康檢查。
- Key/Value存儲: 支持通過HTTP API實現分佈式KV數據存儲。
- 多數據中心支持:支持任意數量數據中心。
swoft-consul 組件,整合了 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
然後配置consul集羣
主服務器: 192.168.56.107
備份服務器:
192.168.56.108
192.168.56.102
192.168.56.107的服務啓動命令
./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 &
192.168.56.108的服務啓動命令
./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 &
192.168.56.109的服務啓動命令
./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 &
192.168.56.102的客戶端服務啓動命令
./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 即可。
Consul集羣中服務查看頁面
配置項:
- -bootstrap-expect 這個就是表示期望提供的SERVER節點數目,數目一達到,它就會被激活,然後就是LEADER了。 這不能與傳統-bootstrap標誌一起使用。此標誌需要在服務端模式下運行。
- -server 以服務端模式啓動
- -data-dir 數據存放位置,這個用於持久化保存集羣狀態
- -node 羣集中此節點的名稱。這在羣集中必須是唯一的。默認情況下,這是計算機的主機名。
- -bind 綁定服務器的ip地址
- -config-dir 指定配置文件服務,當這個目錄下有 .json 結尾的文件就會加載進來,更多配置可以參考 consul api文檔
- -enable-script-checks 檢查服務是否處於活動狀態,類似開啓心跳檢測
- -datacenter 數據中心名稱
- -client 客戶端可訪問ip,包括HTTP和DNS服務器。如果是“127.0.0.1”,僅允許環回連接;這裏改爲“0.0.0.0”,允許外網訪問。
- -ui 開啓web的ui界面
- -join加入到已有的集羣中
配置http server中的consul相關項
/app/bean.php
return [
//其他配置項
…
//consul配置項 若本地起了consul客戶端代理服務,就不用填host和port
‘consul’=> [
// ‘host’ => ‘192.168.56.107’,
// ‘port’ => 8500,
‘timeout’=> 3,
]
]
啓動 swoft http 服務
php /var/www/html/swoft/bin/swoft http:start
如果發現swoft http服務啓動失敗,可以先殺掉這些swoft-http進程,具體命令如下:
sudo ps -ef|grep "swoft-http"|grep -v "grep" |awk '{print $2}'|xargs kill -9
http服務啓動時將當前服務註冊到Consul
無論是 http / rpc / ws 服務,啓動的時候只需監聽 SwooleEvent::START 事件,即可把啓動的服務註冊到第三方集羣。
文件: app/listener/RegisterServiceListener.php
<?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)
http服務關閉時,在Consul中心取消註冊該服務
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
agent默認注入的consul對象
文件: /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默認使用的本地客戶端作爲代理
consul讀寫kv及服務發現代碼示例
文件: 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"}}