目录

RPC图

http://img.9ong.com/images/page/md-1617623818.047983-248.jpg

rpc服务扩展yar

  • 准备rpc服务User

    User.php

      
    namespace yar\server;
      
    class User {
        /**
        * get user name
        * @return string
        */
        public function getName() {
            return "tsingchan from port:".$_SERVER['SERVER_PORT'];
        }
          
        /**
        * get user info
        * @return array
        */
        public function getInfo(){
            return [
                "name"=>"tsingchan",
                "id"=>"350588",
                "phone"=>"1310000000"
            ];
        }
    }
    
    $server = new \Yar_Server(new User());
    $server->handle();//启动服务, 开始接受客户端的调用请求. 来自客户端的调用, 都是通过POST请求发送过来的
    
  • 准备rpc服务Operator

    Operator.php

      
    namespace yar\server;
    class Operator {
    
        /**
        * Add two operands
        * @param interge 
        * @return interge
        */
        public function add($a, $b) {
            if(!$a || !$b){
                throw new Exception("server exception:a | b不能为空.");
            }
            return $this->_add($a, $b);
        }
          
        /**
        * Sub 
        */
        public function sub($a, $b) {
            return $a - $b;
        }
    
        /**
        * Mul
        */
        public function mul($a, $b) {
            return $a * $b;
        }
          
        /**
        * 返回一个数组
        * @return array
        */
        public function returnArray(){
            return ['a'=>"apple","b"=>'banana'];
        }
      
    
        /**
        * Protected methods will not be exposed
        * @param interge 
        * @return interge
        */
        protected function _add($a, $b) {
            return $a + $b;
        }
    }
    
    $server = new \Yar_Server(new Operator());
    $server->handle();//启动服务, 开始接受客户端的调用请求. 来自客户端的调用, 都是通过POST请求发送过来的
    
  • 准备health.php,用于consul服务健康检查

    health.php

    header('Content-type: application/json');
    echo "SUCCESS";
    
  • 准备端口8091,解析rpc服务(User、Operator)

  • 准备端口8092,解析rpc服务(User、Operator),可以稍微改动,用于测试是否达到负载均衡的效果

  • 准备端口8093,负载均衡到8091与8092

    upstream yar.rpc {
        server 192.168.8.130:8091;
        server 192.168.8.130:8092;
    }
    server {
            listen       8093;
            server_name 192.168.8.130;
            access_log  /var/log/nginx/yar-rpc.access.log;
    
            location /{
                    proxy_pass        http://yar.rpc;
                    proxy_set_header   Host             $host;
                    proxy_set_header   X-Real-IP        $remote_addr;
                    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            }
    }
    server {
            listen 8091;
              
            server_name 192.168.8.130;
              
            root /var/www/html/php-shiyanchang/yar1;
            index index.html index.php;
              
            location / {
                    if (!-e $request_filename) {
                            rewrite  ^(.*)$  /index.php?s=/$1  last;
                            break;
                    }
            }
            location ~ \.php$ {
                include /etc/nginx/snippets/fastcgi-php.conf;                
                fastcgi_pass unix:/run/php/php7.1-fpm.sock;
            }
            location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|mp3)$ {
                    expires      30d;
            }
              
            location ~ .*\.(js|css)?$ {
                    expires      12h;
            }
    }
    server {
            listen 8092;
    
            server_name 192.168.8.130;
    
            root /var/www/html/php-shiyanchang/yar1;
            index index.html index.php;
    
            location / {
                    if (!-e $request_filename) {
                            rewrite  ^(.*)$  /index.php?s=/$1  last;
                            break;
                    }
            }
            location ~ \.php$ {
                include /etc/nginx/snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.1-fpm.sock;
            }
            location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|mp3)$ {
                    expires      30d;
            }
    
            location ~ .*\.(js|css)?$ {
                    expires      12h;
            }
    }    
    
    
    

consul

什么是consul?

Consul是一个分布式高可用的系统,主要用来做微服务架构的服务发现和服务注册。 它有以下特点:

  • 服务发现:用来注册服务,和查找服务。比如注册一个商品服务。客户端通过网关(gateway)获取并且调用服务,获取商品的列表。
  • 健康检查:consul可以定时的去检测注册的服务是否可用。当然也可以在调用服务主动检测。
  • key/value存储:consul可以将注册的服务,以key/value结构进行数据存储。
  • 多数据中心:支持多个数据中心。

安装consul

以ubuntu为例:

$ curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
$ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"

安装

$ sudo apt-get update && sudo apt-get install consul

确认

$ consul version
Consul v1.9.4
Revision 10bb6cb3b
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

更多安装参数参考: Install Consul | Consul - HashiCorp Learn

consul启动

完成Consul的安装后,必须运行agent。 agent可以运行为server或client模式。每个数据中心至少必须拥有一台server。 建议在一个集群中有3或者5个server。部署单一的server,在出现失败时会不可避免的造成数据丢失。

其他的agent运行为client模式。一个client是一个非常轻量级的进程。用于注册服务,运行健康检查和转发对server的查询。agent必须在集群中的每个主机上运行。

我们准备两台机器:192.168.8.129与192.168.8.130

130作为server,129作为consul的client,理论上集群需要3到5台的server。这里我们暂时以1台server,一台client为例。

  • 在130上启动consul server:

    consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=s1 -bind=192.168.8.130 -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0
    

    注意:-bootstrap-expect :在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用。

    更多参数参考Consul 使用手册

  • 在129上启动consul client,并join到130,启动集群,130就会被推举为leader:

    consul agent -data-dir /tmp/consul -node=s2 -bind=192.168.8.129 -config-dir=/etc/consul.d/ -join 192.168.8.130
    
  • consul ui

    130上启动时,会自动安装consul管理后台web服务。

    访问:http://192.168.8.130:8500/ ,即可访问consul的web服务。

    如果-client指定了内部私有地址,只允许内部访问,但外部又想访问时,可以通过nginx反向代理,nginx配置:

    server {
            listen 80;
            server_name consul.130.com;
            location / {
                    proxy_pass http://127.0.0.1:8500;
                    proxy_redirect      default;
    
            }
    }
    
    

php-consul

这里我们使用了一个半成品,方便查看源码。

我们将分别介绍两个php-consul-sdk。

tsingchan/php-consul

tsingchan/php-consul: consul http api php sdk //www.consul.io/api/index.html

这里暂时只是为了验证,还没有采用composer,后续会采用composer进行安装。

下载到extend目录

\---extend
    |
    \---Consul
        |   Agent.php
        |   autoLoad.php
        |   ConsulClient.php
        |   ConsulManage.php
        |   ConsulResponse.php
        |   Discovery.php
        |   Http.php
        |   Service.php
        |
        \---Service
                Agent.php
                Catalog.php
                Health.php
                Kv.php
                Service.php

服务配置

rpcServiceConfig.php

/**
* @param string $id   //服务id 要求唯一
* @param string $name //服务名称 可存在相同服務名稱
* @param string $ip   //服务注册到consul的IP,服务发现,发现的就是这个IP
* @param array $tags //服务tag,数组,自定义,可以根据这个tag来区分同一个服务名的服务,array('secure=false')代表是当前http协议,array('secure=true')代表当前https,数组内容自行增减
* @param object $meta //Meta KV
* @param integer $port //服务IP对应端口号,
* @param string $healthCheckIp  //健康检查ip,一般与注册ip相同,需拼接协议
* @param integer $healthCheckPort  //健康检查ip对应端口
* @param string $healthCheckPath  //与IP和port拼接作为健康检查接口,对应的path,如:consul/health
* @param string $interval  //健康检查间隔时间,默认每隔10s,调用一次拼接好的健康检查地址URL 
 */
return [
    "user"=>[
        "config"=>[
            "httpType"=>"http://",
            "secure"=>"secure=false",
            "ip"=>"192.168.8.130",
            "port"=>8093,
            "name"=>"yarrpc",
            "id"=>"user",
            "tags"=>["user.php"],
            "meta"=>["path"=>"/User.php"],
            "healthCheckPath"=>"health.php",
            "interval"=>"10s",
            "consulNode"=>"http://192.168.8.129:8500",
        ],
        "status"=>true,//是否要注册到consule
    ],
    "operator"=>[
        "config"=>[
            "httpType"=>"http://",
            "secure"=>"secure=false",
            "ip"=>"192.168.8.130",
            "port"=>8093,
            "name"=>"yarrpc",
            "id"=>"operator",
            "tags"=>["operator.php"],
            "meta"=>["path"=>"/Operator.php"],
            "healthCheckPath"=>"health.php",
            "interval"=>"10s",
            "consulNode"=>"http://192.168.8.129:8500",
        ],
        "status"=>true,//是否要注册到consule
    ],
];

服务注册、注销、发现

  • curl

    我们先看通过curl直接完成consul http api的服务注册,就更好理解php-consul的基本使用方式:

    curl --request PUT --data @register.json http://192.168.8.129:8500/v1/agent/service/register
    

    register.json

    {
        "Id":"yarrpc-id",
        "Name":"yarrpc",
        "Tags":[
            "test"
        ],
        "Address":"192.168.8.130",
        "Port":8093,
        "EnableTagOverride":false,
        "Check":{
            "DeregisterCriticalServiceAfter":"90m",
            "HTTP":"http://192.168.8.130:8093/health.php",
            "Interval":"10s"
        }
    }    
    
  • php

    根据consul http api实现封装php类库方法

    简单范例:consul.php

    use Consul\ConsulManage;
    
    include_once dirname(__DIR__). '/extend/Consul/autoLoad.php';
    
    
    $serviceId = "user";//注意要先对服务进行配置,详见ConsulManage类中的配置属性
    //$serviceId = "operator";//注意要先对服务进行配置,详见ConsulManage类中的配置属性
    
    $configFile = (dirname(__DIR__)).DIRECTORY_SEPARATOR."config".DIRECTORY_SEPARATOR."rpcServiceConfig.php";;
    $cm = new ConsulManage($configFile);
    
    //注册服务
    //$res = $cm->registerService($serviceId);
    //var_dump($res);
    
    ////注销服务
    //$res = $cm->deregisterService($serviceId);
    //var_dump($res);
    
    //发现服务
    //$service= $cm->getServiceById($serviceId);
    //var_dump($service);
    
    //var_dump($cm->getServiceByName("yarrpc"));//随机返回name为yarrpc的一个服务
    //var_dump($cm->getServicesByName("yarrpc"));
    
    ////维护 开启维护的服务将不会再被发现
    //$enable = false;//关闭维护模式
    //$reason = "maintenance 2h";
    //$res = $cm->maintenance($serviceId, $enable, $reason);
    //var_dump($res);
    

client端调用服务

//clientCommon.php

use Consul\ConsulManage;

include_once dirname(__DIR__) . '/extend/Consul/autoLoad.php';

/**
 * 
 * @param type $serviceId
 * @return string
 */
function get_yar_rpc_service_url($serviceId)
{

    $url = '';
    if ($serviceId) {
        $configFile = (dirname(__DIR__)).DIRECTORY_SEPARATOR."config".DIRECTORY_SEPARATOR."rpcServiceConfig.php";;
        $cm = new ConsulManage($configFile);
        $config = $cm->getServiceConfig($serviceId);

        $service = $cm->getServiceById($serviceId);
        if (is_array($service)) {
            $url = $config['config']['httpType'] . $service['Address'] . ":" . $service['Port'];
            $meta = $service['Meta'];
            $url = isset($meta['path']) ? ($url . $meta['path']) : $url;
        }
    }
    return $url;
}

//userClient..php
// include_once "clientCommon.php";

try {
    //$host = "http://192.168.8.130:8093";
    $url = get_yar_rpc_service_url($serviceId = "user");

    //@todo 进一步封装yar_client的封装
    //发起一个RPC调用, 并且得到返回值. 如果服务端的远程调用抛出异常, 那么本地也会相应的抛出一个Yar_Server_Exception异常. 
    $client = new yar_client($url);

    //设置调用远程服务的一些配置, 比如超时值, 打包类型等. 
    //可以是: YAR_OPT_PACKAGER、YAR_OPT_PERSISTENT (需要服务端支持keepalive)、YAR_OPT_TIMEOUT、YAR_OPT_CONNECT_TIMEOUT 
    //Set timeout to 1s
    $client->SetOpt(YAR_OPT_CONNECT_TIMEOUT, 1000);
    //Set packager to JSON
    //$client->SetOpt(YAR_OPT_PACKAGER, "json");//默认php, "php", "json", "msgpack",this server accept json packager
    //设置header
    $client->SetOpt(YAR_OPT_HEADER, array("hd1: val", "hd2: val"));  //Custom headers, Since 2.0.4


    var_dump($client->getName());
    var_dump($client->getInfo());
} catch (Yar_Server_Exception $sex) {
    //Yar_Server_Exceptioin可以捕获server端throw new exception异常
    var_dump($sex->getTraceAsString());
} catch (Yar_Client_Exception $cex) {
    //处理Yar_Client_Exception
    var_dump($cex->getTraceAsString());
}catch(\Exception $ex){
    var_dump($ex->getTraceAsString());
}


//operatorClient.php
// include_once "clientCommon.php";

try {
    //$host = "http://192.168.8.130:8093";
    $url = get_yar_rpc_service_url($serviceId = "operator");

    //@todo 进一步封装yar_client的封装
    //发起一个RPC调用, 并且得到返回值. 如果服务端的远程调用抛出异常, 那么本地也会相应的抛出一个Yar_Server_Exception异常. 
    $client = new yar_client($url);

    //设置调用远程服务的一些配置, 比如超时值, 打包类型等. 
    //可以是: YAR_OPT_PACKAGER、YAR_OPT_PERSISTENT (需要服务端支持keepalive)、YAR_OPT_TIMEOUT、YAR_OPT_CONNECT_TIMEOUT 
    //Set timeout to 1s
    $client->SetOpt(YAR_OPT_CONNECT_TIMEOUT, 1000);
    //Set packager to JSON
    //$client->SetOpt(YAR_OPT_PACKAGER, "json");//默认php, "php", "json", "msgpack",this server accept json packager
    //设置header
    $client->SetOpt(YAR_OPT_HEADER, array("hd1: val", "hd2: val"));  //Custom headers, Since 2.0.4


    /* 远程调用方式1: call directly */
    var_dump($client->add(1, 2));

    /* 远程调用方式2: call via __call */
    var_dump($client->__call("add", array(3, 2)));
    var_dump($client->returnArray());
    var_dump($client->add(0, 2));//远端服务端抛出异常
} catch (Yar_Server_Exception $sex) {
    //Yar_Server_Exceptioin可以捕获server端throw new exception异常
    var_dump($sex->getTraceAsString());
} catch (Yar_Client_Exception $cex) {
    //处理Yar_Client_Exception
    var_dump($cex->getMessage());
    var_dump($cex->getTraceAsString());
}catch(\Exception $ex){
    var_dump($ex->getTraceAsString());
}

/* public以外的方法不能被调用,so __add can not be called */
//var_dump($client->_add(1, 2));


健康检查

我们观察注册服务时的json参数中:Check.HTTP,用作于health检查,比如上面的例子:http://192.168.8.130:8093/health.php ,需要在rpc服务中可访问health.php文件

health.php

header('Content-type: application/json');
echo "SUCCESS";

consul会自动定期去访问检查服务是否有效。

sensiolabs/consul-php-sdk

  • 安装

    composer require sensiolabs/consul-php-sdk
    
  • 初步使用

    获取所有consul服务成员

    include_once dirname(__DIR__).DIRECTORY_SEPARATOR."autoLoad.php";
    
    $agent = getAgent();
    
    $services = $agent->members();
    print_r(json_decode($services->getBody()));
    
    /**
    * 获取agent实例
    * 注意:使用这个函数主要是为了IDE方便自动完成
    * @return \SensioLabs\Consul\Services\Agent
    */
    function getAgent(){
        $options = ['base_uri'=>"http://192.168.8.130:8500"];
        $sf = new \SensioLabs\Consul\ServiceFactory($options);
        return $sf->get(\SensioLabs\Consul\Services\AgentInterface::class);
    }    
    

    注册pay服务:

    include_once dirname(__DIR__).DIRECTORY_SEPARATOR."autoLoad.php";
      
    $registerData = '{"Id":"pay","Name":"webpay","Tags":["pay"],"Meta":{"path":"/pay.php"},"Address":"192.168.8.130","Port":8093,"EnableTagOverride":false,"Check":{"DeregisterCriticalServiceAfter":"90m","HTTP":"http://192.168.8.130:8093/health.php","Interval":"10s"}}';
    $client = getAgent();
    $res = $client->registerService($registerData);
    
    /**
    * 获取agent实例
    * 注意:使用这个函数主要是为了IDE方便自动完成
    * @return \SensioLabs\Consul\Services\Agent
    */
    function getAgent(){
        $options = ['base_uri'=>"http://192.168.8.130:8500"];
        $sf = new \SensioLabs\Consul\ServiceFactory($options);
        return $sf->get(\SensioLabs\Consul\Services\AgentInterface::class);
    }    
    

    获取yarrpc服务信息:

    include_once dirname(__DIR__).DIRECTORY_SEPARATOR."autoLoad.php";
    
    $client = getHealth();
    $services = $client->service("yarrpc");
    print_r(json_decode($services->getBody()));
    
    /**
    * 获取health实例
    * 注意:使用这个函数主要是为了IDE方便自动完成
    * @return \SensioLabs\Consul\Services\Health
    */
    function getHealth(){
        $options = ['base_uri'=>"http://192.168.8.130:8500"];
        $sf = new \SensioLabs\Consul\ServiceFactory($options);
        return $sf->get(\SensioLabs\Consul\Services\HealthInterface::class);
    }
    
    
  • 使用感受

    • 对consul http api的实现比较完整、易用性也比较强
    • 源码注释基本没有,对IDE支持不友好,比如初始化工厂类时的配置,没有options的相关说明,需要看源码,http的返回结果ConsulResponse类,IDE上不能自动完成识别显示相关方法,需要自己手动查看源码,才知道有getBody等方法。
    • 参数保留consul api原生,没有额外封装,比如agent的register接口,涉及的参数很多,该php sdk仍然保留和consul api一样传入一个json串
    • 由于consul api文档比较健全,所以用起来相对舒服,只要再补充上sdk里代码注释与IDE helper会更好。

consul命令

Commands | Consul by HashiCorp

查看consul所有命令:

$ consul
Usage: consul [--version] [--help] <command> [<args>]

查看agent 的所有选项:

consul agent -h

发现服务缓存

在发现服务环节,我们可以通过常规的缓存机制对consul返回的服务列表信息进行缓存,注意缓存有效时间,及主动更新策略的设计,确保在出现服务节点更新不及时或服务故障时,可以无缝自动简单快速高效地连接服务节点。

consul集群

简单易懂consul集群

如何暂停consul及再次恢复

可以考虑通过docker

参考

HTTP API | Consul by HashiCorp

Consul快速入门(二):Consul集群安装部署_风树种子的专栏-CSDN博客

Consul 使用手册 | 一个梦

在Consul Service Discovery中注册服务| 领事-HashiCorp Learn

PHP实现consul服务注册与服务发现_allen的博客-CSDN博客

allendaydayup/php-consul: 多了解官方API就好:https://www.consul.io/api/index.html

php如何用go-micro和consul实现微服务? - 行业资讯 - 亿速云