参见:Swoft入门及技术指南 https://blog.csdn.net/yan_dk/article/details/90228563
参见:Consul入门及技术指南 https://blog.csdn.net/yan_dk/article/details/91488680
需求分析:我们使用应用框架Swoft,建立应用的业务服务如支付pay,发布给其他应用来调用,使用Consul组件,将服务注册到Consul数据中心,那么应用框架Swoft的服务要作为Consul客户端,向Consul中心注册服务,由Consul中心来统一暴露和分发服务,这样我们在Swoft中需要增加Consule-client服务组件,后续实现这样的流程。
Swoft框架因为设计思想的原因,目前在做减法,所以对于服务发现这块没有支持,所以我们需要为此框架提供服务发现的机制。
1. 建立服务提供者Provider
这是建立的目录结构,提供者ConsulProvider、负载均衡器(这个暂不重要)
namespace App\Components\Consul;
use Swlib\SaberGM;
class ConsulProvider{
const REGISTER_PATH = '/v1/agent/service/register'; //服务注册路径
const HEALTH_PATH = '/v1/health/service/'; //获取健康服务
public function registerServer($config) {
//echo 'http://'.$config['address'].':'.$config['port'].self::REGISTER_PATH,json_encode($config['register']);
//注册地址底层错误无法使用
if (env('AUTOLOAD_REGISTER')) {
//var_dump(json_encode($config['register']));
$this->curl_request('http://' . $config['address'] . ':' . $config['port'] . self::REGISTER_PATH, "PUT", json_encode($config['register']));
output()->writeln("Rpc service Register success by consul tcp=" . $config['address'] . ":" . $config['port'] . " ");
}
echo "启动consul注册服务".PHP_EOL;
}
/**
* 获取某个服务的列表
*/
public function getServerList($serviceName, $config) {
$query=[
'dc'=>$config['discovery']['dc']
];
if(!empty($config['discovery']['tag'])){
$query['tag']=$config['discovery']['tag'];
}
$queryStr=http_build_query($query);
//排除不健康的服务,获取健康服务
$url = 'http://' . $config['address'] . ':' . $config['port'] . self::HEALTH_PATH . $serviceName.'?'.$queryStr;
//负载机制
$serviceList = $this->curl_request($url, 'GET');
$serviceList=json_decode($serviceList, true);
$address=[];
foreach ($serviceList as $k=>$v){
//判断当前的服务是否是活跃的,并且是当前想要去查询服务
foreach ($v['Checks'] as $c){
if($c['ServiceName']==$serviceName && $c['Status']=="passing"){
$address[$k]['address']=$v['Service']['Address'].":".$v['Service']['Port'];
$address[$k]['weight']=$v['Service']['Weights']['Passing'];
}
}
}
return $address;
}
public function curl_request($url, $method = 'POST', $data = []) {
$method = strtoupper($method);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
}
2. 建立监听器Listener
建立监听器RegisterServer,实现了事件监听接口EventHandlerInterface,框架将ConsulProvider装载到Bean容器内存。
use Swlib\SaberGM;
use Swoft\Event\Annotation\Mapping\Listener;
use Swoft\Event\EventHandlerInterface;
use Swoft\Event\EventInterface;
use Swoft\Server\ServerEvent;
/**
* Class RegisterServer
* @package App\Listener
* @Listener(ServerEvent::BEFORE_START)
*/
class RegisterServer implements EventHandlerInterface{
public function handle(EventInterface $event): void {
$config = bean('config')->get('provider.consul');
bean('consulProvider')->registerServer($config);
}
}
3. 建立客户端Client
class Provider implements ProviderInterface{
protected $serviceName;
public function __construct($serviceName) {
$this->serviceName = $serviceName;
}
public function getList(): array {
$config = bean('config')->get('provider.consul');
$address = bean('consulProvider')->getServerList($this->serviceName, $config);
//负载均衡(加权随机)
$address = RandLoadBalance::select(array_values($address))['address'];
//根据服务名称consul当中获取动态地址
return [$address];
}
}
class Client extends \Swoft\Rpc\Client\Client {
protected $serviceName; //服务名称
public function getProvider(): ?ProviderInterface {
//切换成curl发生在服务启动之前
//$config = bean('config')->get('provider.consul');
//bean('consulProvider')->registerServer($config);
//不能区分当前调用的服务是哪个
return $this->provider=new Provider($this->getServiceName());
}
/*
* 获取服务名称
*/
public function getServiceName(){
return $this->serviceName;
}
}
4. 配置bean.php
bean.php文件中增加如下配置
return [
......
'consulProvider'=>[
'class'=>\App\Components\Consul\ConsulProvider::class
],
...
]
4. 启动Swoft
查看启动信息,打印出 “启动Consul注册服务”
持续完善,待续...