微服务架构综合实战 一文让你了解什么是微服务 使用PHP 搭建微服务框架 最全微服务架构讲解以及演示

本文将带你从基础的微服务架构设计、网络协议、注册中心、配置中心、网关层面 渐进式讲解其微服务。

目录

一、微服务架构设计方案

架构演进

微服务概念

 拆分

​​​​​​三个火枪手原则

AKF原则

二、微服务注册中心和配置中心

为什么要使用服务发现与注册

为什么要使用配置中心

官方下载地址

设置环境变量

Server配置

单机配置

集群配置

命令解析

ThinkPHP接入Consul

配置信息中心 

三、微服务API网关设计

为什么需要网关

API网关对比

Kong与Konga的关联

konga关键概念词

​​​​​​​下载镜像

​​​​​​​安装

​​​​​​​创建网络

​​​​​​​安装postgres,kong依赖于postgres

​​​​​​​初始化kong数据表信息

​​​​​​​启动kong

​​​​​​​初始化konga数据信息

​​​​​​​创建连接节点

​​​​​​​实现负载

创建Upstreams

​​​​​​​创建服务

​​​​​​​创建路由

​​​​​​​实现Basic Auth 与 JWT 认证

​​​​​​​添加Basic Auth插件

​​​​​​​添加用户信息

​​​​​​​添加JWT 插件

​​​​​​​添加JWT 认证信息

​​​​​​​实现Oauth2 认证

​​​​​​​添加Oauth2 插件

添加用户信息

​​​​​​​获取token

​​​​​​​API限流

​​​​​​​API黑白名单

​​​​​​​延伸

​​​​​​​jwt异常

​​​​​​​虚拟机环境 IP黑白名单失效

​​​​​​​服务恢复

四、结合swoole、swoft微服务化搭建

​​​​​​​网络基础

​​​​​​​OSI七层模型与TCP/IP模型

​​​​​​​ARP 地址解析协议

​​​​​​​经典的TCP三次握手与四次挥手

​​​​​​​抓包

​​​​​​​Swoft框架介绍

​​​​​​​协程

​​​​​​​下载框架代码

​​​​​​​RPC 服务

​​​​​​​使用swoft  实现RPC 服务

​​​​​​​前期准备工作

​​​​​​​修改配置文件

​​​​​​​启动RPC服务

​​​​​​​RPC客户端实现

五、整合API与Kong网关以及consul注册中心

​​​​​​​consul服务注册

​​​​​​​新增注册中心相关配置

​​​​​​​添加监听注册

​​​​​​​创建Consul控制器

​​​​​​​启动服务

​​​​​​​Swoft 服务发现

​​​​​​​新增注册新增相关配置

​​​​​​​RPC服务发现

​​​​​​​服务限流

​​​​​​​熔断与降级

​​​​​​​consul与kong网关结合

​​​​​​​加入dns_resolver 信息

添加对应的SERVER信息

​​​​​​​后记


一、微服务架构设计方案

架构演进

在将微服务之前  我们看看目前的架构

单体架构

按照模块划分,公用一个数据库


垂直拆分架构

按业务功能划分单独的子系统,公用一个数据库

将数据库进行分拆分离,如下图:

微服务概念

微服务   即微小的服务,较小且独立的功能

微服务最早由Martin Fowler(马丁·福勒)与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。

 

 拆分

拆分的颗粒度没有标准答案,只能根据经验,实际,方法论等,合理的进行拆分。

粗颗粒度拆分

项目工期短且要求是微服务的架构

细颗粒度拆分

  1. 服务划分过细,服务间关系复杂
  2. 服务数量太多,团队效率急剧下降
  3. 没有自动化部署支撑,无法快速交付
  4. 没有服务治理,微服务达到一定数量,后台管理混乱

纵向拆分和横向拆分

纵向拆分:从业务纬度进行拆分。标准是按照业务的关联程度来决定,关系比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务。

横向拆分:从公共且独立功能纬度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不予其他业务耦合。

划分例子

  1. 第一种分为商品、帖子、新闻3个服务
  2. 第二种分为商品、订单、帖子、回帖、新闻、评论6个服务

如何选择呢?

如果你的团队只有9个人,那么分为3个是合理的,如果有18个人。那么6个服务是合理的。

为什么呢,这就是三个火枪手原则。

​​​​​​三个火枪手原则

三个火枪手原则就是一个微服务由三个人负责开发。一般来说,在进行微服务架构时,根据团队规模来划分微服务数量是合理的。

而为什么是3个人????

从系统规模来讲,3 个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能够进行分工的粒度;如果2个人开发一个系统,系统复杂度不够;如果4个人甚至更多人开发一个系统,系统复杂度又会无法让研发人员对系统的细节都了解很深。

从团队管理来说,3个人可以形成一个稳定的备份,即使1个人有事不在,2个人也可以支撑;如果2个人的话,少了一个人剩余的1个人压力就很大;如果是1个人就更惨了

从技术角度,3个人的技术小组能够形成有效的讨论,能快速达成一致意见;如果是2个人,可能会有意见无法统一问题;如果4个人或更多,就会出现有的成员不认真讨论,开会划水。

AKF原则

AKF 把系统扩展分为以下三个维度:

  1. X 轴:直接水平复制应用进程来扩展系统,将系统复制多份,即加机器得方式,大大提高系统的总体容量、解决单点问题等(A+A+A+A)
  2. Y 轴:将功能拆分出来扩展系统。微服务经常采用的按照业务逻辑拆分(B+C=A)
  3. Z 轴:基于用户信息扩展系统。 将数据做分片拆分

二、微服务注册中心和配置中心

为什么要使用服务发现与注册

微服务带来最大的好处就是把整个大项目分割成不同的服务,运行在不同服务器上,实现解耦和分布式处理。微服务虽然有很多好处,但是也会有不好的一方面。任何事物都会有两面性,在微服务里面进行运维会是一个很大的难题,如果有一天我们的服务数量非常的多,然后我们又不知道哪一个服务在什么机器上。可能会有人说这部分直接写在程序的配置里面就好了,当我们服务少的时候是可以这么做的,也允许这么做,但是在实际当中我们要尽量避免这么做,比如说我们某一个服务,地址换了,那么我们设计的相关代码就得修改重新部署;又或者说我们有一天上线一个新服务或者下线一个服务,这时候我们又得修改程序代码,这是非常不合理的做法。那么有没有什么可以解决这样的问题呢?这里就需要用到我们的服务注册和发现了。

结构对比

没有服务注册发现的结构

 上图 我们可以看到在没有服务注册发现的时候一个调用者需要维护多个服务的IP和端口,这是非常不友好的做法,当我们服务进行调整的时候就有可能导致服务调用失败,还有服务器更换服务器,上下新服务,都会受到影响。将来某一个服务节点出现问题,排查对应程序对运维人员来说都是一场很大的灾难,因为不知道哪一个节点出了问题,需要每一台服务器的去排查。

有服务注册发现的结构

 我们从上图可以发现,当我们有注册中心之后调用者不需要自己去维护所有服务的信息了,仅需要向注册中心请求获取服务,就可以拿到想要的服务信息。这样当我们的服务有所调整,或者上线下线服务,都要可以轻松操作,并且可以在注册中间检查到服务的健康情况,帮助运维人员快速定位到故障的服务器。

为什么要使用配置中心

不使用配置中心 

 使用配置中心

主流的配置中心

  1. Apollo是有协程开源的分布式配置中心
  2. Spring Cloud Config
  3. Consul

官方下载地址

https://developer.hashicorp.com/consul/downloads

wget https://releases.hashicorp.com/consul/1.15.1/consul_1.15.1_linux_386.zip
unzip consul_1.15.1_linux_386.zip

设置环境变量

如果不设置可以直接把consul执行文件移动到/usr/bin目录下

mv consul /usr/bin

注:此步骤非必要

ok, 安装成功后,我们接下来进行一些配置来启用consul

Server配置

单机配置

  • 服务器1,IP 192.168.5.189

这种方式适合用于搭建服务调试使用

consul agent -client=0.0.0.0 -dev

持久化启动   

./consul agent -server -bootstrap-expect=1 -data-dir /opt/data/consul -client=0.0.0.0 -advertise=192.168.5.189 -ui

集群配置

官方说法,一个集群3个或5个节点

  • 服务器1,IP 192.168.5.189

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.5.188
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.5.189

  

  • 服务器3,IP 192.168.5.187
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.5.189

以上服务器2和服务3使用 -join 加入集群,并且使用同一个数据名称 dc1

查看集群成员:

consul members

查看集群信息:

consul info

 在浏览器输入 http://192.168.5.189:8500 来查看信息

集群模式下  有三个实例

点击到Nodes 可以查看到如下图信息:

命令解析

 更多命令解析

  1. -bootstrap-expect 数据中心中预期的服务器数。该值必须与集群中的其他服务器一致。提供后,Consul将等待指定数量的服务器可用,然后引导群集。这允许自动选择初始领导者。这不能与传统-bootstrap标志一起使用。此标志需要在服务端模式下运行。
  2. -server 以服务端模式启动
  3. -data-dir 数据存放位置,这个用于持久化保存集群状态
  4. -node 群集中此节点的名称。这在群集中必须是唯一的。默认情况下,这是计算机的主机名。
  5. -bind 绑定服务器的ip地址
  6. -advertise  通知展现地址用来改变我们给集群中的其他节点展现的地址, 一般情况下-bind地址就是展现地址
  7. -config-dir 指定配置文件服务,当这个目录下有 .json 结尾的文件就会加载进来,更多配置可以参考 配置模版
  8. -enable-script-checks 检查服务是否处于活动状态,类似开启心跳
  9. -datacenter 数据中心名称
  10. -client 客户端可访问ip,包括HTTP和DNS服务器。默认情况下,这是“127.0.0.1”,仅允许环回连接。
  11. -ui 开启web的ui界面
  12. -join加入到已有的集群中

关于consul更多的信息请大家移步到 Consul官网

consul   在线中文参考手册   https://blog.51cto.com/u_15127518/4388334

ThinkPHP接入Consul

下载thinkphp6 并保存到服务的/opt/ww目录中

composer create-project topthink/think

定义配置

cd /opt/ww/think/config
vim common.php

在common.php 文件中加入consul 配置信息 代码如下:

<?php
return [
   'consul'=>[
      'host'=>'192.168.5.189',
      'port'=>'8500' 
   ]
];

新增一个consul的服务对象

touch /opt/ww/think/app/service/Consul.php

服务 Consul.php 代码如下:

<?php
namespace app\service;

use \think\facade\Config;

class Consul {
    private $httpUrl;

    public function __construct() {
        $consul_config = Config::get('common.consul');
        $this->httpUrl = 'http://' . $consul_config['host'] . ':' . $consul_config['port'] . '/';
    }

    //服务注册
    public function registerService($data) {
        $url = $this->httpUrl . 'v1/agent/service/register';
        return $this->curlPUT($url,$data);
    }

    //服务发现
    public function serviceInfo($serviceId) {
        $url = $this->httpUrl . 'v1/health/service/' .$serviceId;
        return $this->curlGET($url);
    }

    //配置信息
    public function configInfo($key) {
        $url = $this->httpUrl . 'v1/kv/' .$key;
        return $this->curlGET($url);
    }

    public function curlPUT($httpUrl,$data){
        $ch = curl_init();
        curl_setopt($ch,CURLOPT_URL,$httpUrl);
        curl_setopt($ch,CURLOPT_CUSTOMREQUEST,"PUT");
        curl_setopt($ch,CURLOPT_HEADER,0) ;
        curl_setopt($ch,CURLOPT_HTTPHEADER,["Content-type:application/json"]);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode($data));
        $res = curl_exec($ch);
        if($res===false) {
            var_dump(curl_error($ch));
        }
        curl_close($ch);
        return $res;
    }

    public function curlGET($httpUrl){
        $ch = curl_init();
        curl_setopt($ch,CURLOPT_URL,$httpUrl);
        curl_setopt($ch,CURLOPT_HEADER,0) ;
        curl_setopt($ch,CURLOPT_HTTPHEADER,["Content-type:application/json"]);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
        $res = curl_exec($ch);
        if($res===false) {
            var_dump(curl_error($ch));
        }
        curl_close($ch);
        return $res;
    }

}

新建一个控制器

touch /opt/ww/think/app/controller/ConsulDemo.php

ConsulDemo 控制器内容如下:

<?php
/**
 * 注册服务相关控制器
 */
namespace app\controller;

use app\BaseController;
use app\service\Consul;

class ConsulDemo {

    //注册服务
    public function regDemo(){
        //注册信息
        $data = [
            'ID'=>'demoService',     //服务ID 
            'Name'=>'demoService',   //服务名
            'Tags'=>['core.demo'],
            'Address'=>'192.168.5.189',  //服务的主ip
            'Port'=>8000,  //服务的端口号
            'Check' =>[
                //健康 心跳请求
                'HTTP'=>'http://192.168.5.189:8000',
                'Interval'=>'10s',   //心跳频率  10秒
            ]
        ];
        $consul = new Consul();
        $rs = $consul->registerService($data);
        echo json_encode($rs);
        die;
    }

    //获取服务信息
    public function serviceInfo(){
        $serviceId = 'demoService' ; 
        $consul = new Consul();
        $rs = $consul->serviceInfo($serviceId);
        echo $rs;
        die;
    }

    //获取配置信息
    public function configInfo($key=''){
        if($key=='') {
            echo json_encode(['msg'=>'key为空']);
            die; 
        }
        $consul = new Consul();
        $rs = $consul->configInfo($key);
        var_dump($rs);
        die;
    }

}

添加路由信息

vim /opt/ww/think/route/app.php
#添加如下路由信息
Route::get('consul/reg/demo','ConsulDemo/regDemo'); //注册服务
Route::get('consul/service/info','ConsulDemo/rserviceInfo'); //服务发现
Route::get('consul/config/info','ConsulDemo/configInfo'); //获取配置

启动thinkphp http server 

cd /opt/ww/think
php think run -p 8087

-p 服务启动端口  默认8000

注:这里的http服务应该与ConsulDemo控制器中的服务注册信息保持一致,包括服务ip和服务端口

访问注册地址

http://192.168.5.189:8000/consul/reg/demo

此后,我们打开consul 

在Services中  有我们刚才注册的服务名为demoService服务啦 

当我们停到服务后,此刻consul注册中心也显示服务下线了

当我们请求 http://192.168.5.189:8000/consul/service/info

配置信息中心 

在consul中创建对应的微服务目录  

这里以/   结尾即目录

这里我们创建demoService/

进去后,在分别创建dev  pro 目录  如下图:

获取对应的配置 

访问接口   http://192.168.5.189:8000/consul/config/info?key=demoService/dev/mysql/host

返回一个数组

[
   {
        "LockIndex":0,
        "Key":"demoService/dev/mysql/host",
        "Flags":0,
        "Value":"MTI3LjAuMC4x",
        "CreateIndex":410,
        "ModifyIndex":410
   }
]

注:这里返回的Value是经过base64编码的加密信息,可以使用base64_decode 进行解码 

三、微服务API网关设计

为什么需要网关

  • 集合多个API,统一API入口
  • 避免内部信息泄露
  • 提供安全认证
  • 支持混合通讯协议 HTTP RPC 
  • 降低微服务复杂度  如令牌验证

弊端

  • 集合增加额外管理和维护成本
  • 开发时需要遵循网关路由规则
  • 引起故障 单一入口,入口挂掉,整个微服务无法正常提供服务了

注册中心与微服务网关区别

谈一下个人对微服务网关与注册中心的理解。

首先,微服务网关和注册中心的区别:注册中心重点是对内的,微服务网关重点是对外的。对内的意思是,一个整体项目的内部多个微服务之间的相互调用,通过注册中心获取地址,然后进行调用。对外的意思是,整体项目建设完成后,需要对外提供接口,供外部客户端进行访问,但是整体项目是由多个微服务实例构成的,那么接口地址也就有很多,微服务网关的作用就是提供统一的对外接口,然后网关再路由转发到对应的微服务上。

上述只是微服务网关的一种作用。总之,微服务网关的核心就是在微服务之前加一层,把微服务公共的需求,权限认证,等等需要综合管理多个微服务的功能,都加到网关层

API网关对比

  • Nginx  一个高性能的HTTP和反向代理服务器
  • Zuul 是 Netflix 开源的一个API网关组件
  • Kong 是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,能提供易于使用的RESTful API来操作和配置API管理系统
  • Apache APISIX  是一个云本地、高性能、可扩展的微服务API网关。它是基于OpenResty和etcd实现的。与传统的API网关相比,APISIX具有动态路由和插件加载功能,特别适合微服务系统下的API管理

    传送门 https://blog.csdn.net/CaptainJava/article/details/125510913

Kong与Konga的关联

kong虽然很强大,但是在管理方式上比较单一,只能通过API请求来管理,如果说有一个UI界面来进行管理就比较好了,所有出现了konga

konga 特点

  • 多用户管理
  • 管理空格kong节点
  • 使用快照备份,还原和迁移Kong节点
  • 使用运行状态检查监控节点和API状态
  • 轻松的数据库集成postgresSQL

​​​​​​​konga关键概念词

  • upstream 字面意思上游,实际项目理解是对某一个服务的一个或者多个请求地址的抽象入口  和nginx一致,即负载均衡
  • trarget  目标,实际就是上游upstream的一个多个实际的某服务的请求地址(ip:port或者域名)
  • service 服务,对实际服务(比如用户服务)的抽象概念,通过host绑定upstream
  • route 路由,通过配置一定的匹配规则,来将客户端的请求,匹配到对应的service上
  • consumer 服务使用者,或用户
  • plugin 插件 可以通过kong的API,配置全局和特定的路由和服务的插件

 流程图

下面  我们以Kong+Konga 来搭建API网关

本地搭建konga 参考 https://blog.csdn.net/qq_31289187/article/details/127683144

 

推荐使用Docker 搭建konga 

docker相关知识  请查看此处  传送门

docker 离线安装 方法 https://blog.csdn.net/qq_54928486/article/details/127069180

docker  基础命令 https://blog.csdn.net/weixin_45630258/article/details/124681551

​​​​​​​下载镜像

docker pull postgres:9.6-bullseye
docker pull kong/kong-gateway:2.4.1.0-alpine
docker pull pantsel/konga:0.14.9

这3个镜像的版本必须匹配,否则初始化kong、konga数据时会出现各种问题。

若为离线环境  需要先打包镜像,然后再载入对应的打包镜像文件

镜像打包为 tar 文件

Docker 的 save 命令可将镜像打包成 tar 文件,其格式如下

docker save [OPTIONS] IMAGE [IMAGE...]

  • -O  指定输出到的文件

docker save  -o postgres.tar.gz postgres:9.6-bullseye

从 tar 文件载入镜像

Docker 的 load 命令可从 tar 文件载入镜像,其命令格式如下

docker load [OPTIONS]

  • -i  用于指定载入的镜像文件路径
  • -q  精简输出信息

docker load -i postgres.tar.gz

​​​​​​​安装

​​​​​​​创建网络

docker network create kong_net

​​​​​​​安装postgres,kong依赖于postgres

docker run -d --network=kong_net --name postgres \
    -p 5432:5432 \
    -e "POSTGRES_USER=kong" \
    -e "POSTGRES_DB=kong" \
    -e "POSTGRES_PASSWORD=kong" \
    postgres:9.6-bullseye

备注:

postgres默认端口5432,通过 -p 5432:5432,将5432暴露出来,当前主机可访问,否则只能在容器内访问。

-e "POSTGRES_DB=kong"

-e "POSTGRES_USER=kong"

-e "POSTGRES_PASSWORD=kong"

创建指定的库、用户名、密码

启动成功之后,在数据库客户端检查postgres是否安装成功

​​​​​​​初始化kong数据表信息

docker run --rm --network=kong_net \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_PG_HOST=172.17.0.1" \
  -e "KONG_PG_PASSWORD=kong" \
  -e "KONG_PASSWORD=kong" \
kong/kong-gateway:2.4.1.0-alpine kong migrations bootstrap

-e "KONG_DATABASE=postgres": 指定当前kong使用的数据库类型,这里就是postgres

  •   -e "KONG_PG_HOST=postgres":是启动posgres数据库IP,这里需要填写docker的网卡ip
  •   -e "KONG_PG_PASSWORD=kong" : 数据库密码
  •   -e "KONG_PASSWORD=kong":数据库用户名

成功之后,可以在客户端看到创建了74张表

​​​​​​​启动kong

docker run -d --name kong-ee --network=kong_net \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_PG_HOST=172.17.0.1" \
  -e "KONG_PG_PASSWORD=kong" \
  -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
  -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
  -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
  -e "KONG_ADMIN_GUI_URL=http://192.168.5.189:7002" \
    -p 7000:8000 \
    -p 7001:8001 \
    -p 7443:8443 \
    -p 7444:8444 \
    -p 7002:8002 \
  kong/kong-gateway:2.4.1.0-alpine

kong有5个端口需要对外暴露

8000:对应的http请求代理端口 ,后续所有的请求都使用这个进行调用默认8000 后面配置代理时,需要用到它,这里对外暴露端口改成了7000

8001:  http接口的管理端口

8443:   对应的https请求的代理端口

8444:   https接口的管理端口

8002:  对api做了一些数据分析

kong启动之后,可以在浏览器输入:http://127.0.0.1:7002/overview,可以通过这个链接,检查kong是否安装成功

​​​​​​​初始化konga数据信息

docker run --rm --network=kong_net  pantsel/konga:latest -c prepare -a postgres -u postgresql://kong:kong@172.17.0.1:5432/kong

 初始化成功之后,在postgres新增了11张表(现在是85张表,之前kong初始化时,创建了74张)

 

​​​​​​​启动konga

docker run -d -p 1337:1337 --network kong_net --name konga \
 -e "DB_ADAPTER=postgres" \
 -e "DB_URI=postgresql://kong:kong@172.17.0.1:5432/kong" \
 -e "DB_PASSWORD=kong" \
 -e "NODE_ENV=production" \
 pantsel/konga:0.14.9
  • konga默认端口是1337,这里也需要对外暴露
  • 启动成功之后,输入http://127.0.0.1:1337/register,检查konga是否安装成功
  • 注册konga管理员账号

​​​​​​​创建连接节点

  1. ip是当前主机ip地址,端口是7001默认是8001,本文对外映射时设置成7001了)
  2. 此刻我们在浏览器 访问http://192.168.5.189:7001

 ​​​​​​​

​​​​​​​实现负载

先来了解一下Nginx的负载均衡

 

创建Upstreams

在界面中操作

  1. 输入name,然后提交
  2. 点击刚添加的upstream点击DETAILS添加targets,然后点击ADD TARGET输入target(ip+port)后点击SUBMIT TARGET即可,ip为本地电脑ip保证kong容器内可访问,端口为本地服务端口

注:这里我们最好使用调用API的方式进行添加

可以通过接口查看信息 如

在浏览器中访问 http://192.168.5.189:7001/upstreams  发现什么也没有

使用接口进行添加

路由地址  http://192.168.5.189:7001/upstreams

请求方式  POST

请求参数  name    

添加target 目标  

路由地址 http://192.168.5.189:7001/upstreams/NAME/targets

请求方式 POST

请求参数1  target     目标  IP+端口

请求参数2  weight     权重   数字越大  优先级越高

其中NAME   动态关联上面的upstreams添加的name 名称 

​​​​​​​创建服务

路由地址 http://192.168.5.189:7001/services   

请求方式 POST

请求参数1  name     服务名

请求参数2  host      服务地址  如果需要负载均衡 填写负载均衡名称 

​​​​​​​创建路由

路由地址 http://192.168.5.189:7001/services/SERVER_NAME/routes

注:SERVER_NAME  与上面创建的服务名保持一致

请求方式 POST

请求参数1  name    路由名称

请求参数2  path[]    路由地址  必须/开头  否则添加不上

 

测试负载均衡

我们此刻访问http://192.168.5.189:7000/abc 

 再次刷新请求

 说明负载均衡配置好了

​​​​​​​实现Basic Auth 与 JWT 认证

什么是JWT

JWT (全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

令牌的组成

为了保证令牌的安全性,jwt令牌由三个部分组成,分别是:

header:令牌头部,记录了整个令牌的类型和签名算法

payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里

signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改

它们组合而成的完整格式是:header.payload.signature

比如,一个完整的jwt令牌如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9.BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc

验证网址  https://jwt.io/

详细的JWT 介绍  传送门   https://blog.csdn.net/weixin_39605455/article/details/111262370?utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase

详细的Basic Auth 认证介绍  传送门

https://blog.csdn.net/weixin_43197795/article/details/108338900

安全认证组件可以加在Service 上,可以加在Route上,也可以加在Consumers上。

​​​​​​​添加Basic Auth插件

路由地址 http://192.168.5.189:7001/routes/ROUTE_NAME/plugins   

注:ROUTE_NAME  与上面创建的路由名保持一致

请求方式 POST

请求参数1  name    插件名称  我们这里填写basic-auth

请求参数2  config.hide_credentials    

 查看对应的路由下的插件  我们可以看到刚才添加的basic-auth插件了 如下图所示:

此刻  我们访问之前的路由地址信息 如 http://192.168.5.189:7000/abc   会显示如下信息:

 说明插件已经生效   接下来我们需要配置添加用户信息

​​​​​​​添加用户信息

在页面上 Consumers 添加BASIC用户信息  如下图所示:

 点击用户名 进入到用户详情  选择credentials 添加对应的BASIC 信息 如下图:

此刻  我们在浏览器中 输入对应的Basic Auth信息 就可以正常访问接口了   也可以使用POSTMAN 工具来模拟认证。如图所示:

​​​​​​​添加JWT 插件

我们将JWT添加到Server层

路由地址 http://192.168.5.189:7001/services/SERVICES_NAME/plugins  

注:services  代表需要在Servvice层操作   SERVICES_NAME  与上面创建的服务名名保持一致

请求方式 POST

请求参数1  name    插件名称  我们这里填写jwt

 

查看对应的服务下的插件  我们可以看到刚才添加的jwt插件了 如下图所示:

 此刻  我们访问之前的路由地址信息 如 http://192.168.5.189:7000/abc   会显示如下信息:

​​​​​​​添加JWT 认证信息

准备工作  我们打开 JSON Web Tokens - jwt.io   并准备好一个随机密钥 我这里填写        RSY16m4MuSsU3yJXpXzUGdRPmhHm9gni  

      注:这里必须在payload 信息中 新增一个iss  值与下面的参数key保持一致 否则不成功

 并将刚才的随机密钥 填写到红框中   如下图  :

路由地址 http://192.168.5.189:7001/consumers/USERNAME/jwt

注:consumers  代表需要在consumers层操作   USERNAME  与上面创建的CONSUMERS

名保持一致

请求方式 POST

请求参数1   algorithm  算法类型  我们这里填写HS256

请求参数2   key    这里默认在JWT荷载信息中  ,加入iss 做关联

请求参数3  secret  密钥  这里我们填写刚才生成的密钥 如 RSY16m4MuSsU3yJXpXzUGdRPmhHm9gni

为了不影响认证  将之前的Basic-Auth 删除掉

访问接口  需要在访问接口时,加入Authorization   内容为bearer+空格+JWT    如下图:

此JWT 为

 左边计算出的JWT 

​​​​​​​实现Oauth2 认证

​​​​​​​添加Oauth2 插件

路由地址 http://192.168.5.189:7001/services/USERNAME/plugins   

注:services  代表需要在services层操作   SERVICE_NAME  与上面创建的SERVICE

名保持一致

请求方式 POST

请求参数1   name  我们这里填写oauth2

请求参数2   config.enable_authorization_code   我们这里填写true 

请求参数3    config.enable_client_credentials   我们这里填写true

请求参数4    config.token_expiration  过期时间 单位 秒

 ​​​​​​​​​​​​​​

添加用户信息

路由地址    http://192.168.5.189:7001/consumers/USER_NAME/oauth2

注:consumers  代表需要在consumers层操作   USERNAME  与上面创建的CONSUMERS

名保持一致

请求方式 POST

请求参数1   name  我们这里填写 test

 将得到client_id  与 client_secret   请保存下来   下面获取token时,需要传入该值

​​​​​​​获取token

路由地址    https://192.168.5.189:7433/Route_NAME/oauth2/token

注:Route_NAME  创建的路由   这里根据实际来填写   后面的/oauth2/token 固定的

请求协议 HTTPS 

请求端口  443

请求方式 POST 

请求参数1   grant_type  我们这里填写 client_credentials

请求参数2  client_id  客户端id  填写刚才我们生成的client_id

请求参数3  client_secret  客户端密钥  填写刚才我们生成的client_secret

 

 直接访问  提示需要填写token

 访问接口  需要在访问接口时,加入Authorization   内容为token_type+空格+access_token    如下图:

​​​​​​​API限流

我们将限流添加到Server层

路由地址 http://192.168.5.189:7001/services/SERVICES_NAME/plugins  

注:services  代表需要在Servvice层操作   SERVICES_NAME  与上面创建的服务名名保持一致

请求方式 POST

请求参数1  name    插件名称  我们这里填写rate-limiting

请求参数2 config.minute  每分钟访问次数  

请求参数3 config.limit_by  限制条件  

 

 此刻 直接访问接口  当访问6次后,提示访问失败了

​​​​​​​API黑白名单

我们将限流添加到Server层

路由地址 http://192.168.5.189:7001/services/SERVICES_NAME/plugins 

注:services  代表需要在Servvice层操作   SERVICES_NAME  与上面创建的服务名名保持一致

请求方式 POST

请求参数1  name    插件名称  我们这里填写ip-restriction

请求参数2 config.deny  禁止访问的ip  

 直接访问 

​​​​​​​延伸

​​​​​​​jwt异常

如果在上述添加JWT 环境,接口报iss未定义,需要在JWT荷载信息中  ,加入iss 做关联

​​​​​​​虚拟机环境 IP黑白名单失效

如果你使用的是宿主机+虚拟机环境模式 

若不调整,使用HTTP_HOST IP黑白名单可能会失效

解决方案:需要拿HTTP_X_FORWARDED_FOR 才是访问者真实IP  对其这个真实IP做限制,IP黑白名单功能才会有效。

​​​​​​​服务恢复

若服务器关机或docker 停止了    那我们之前的网关项目要怎么恢复启动呢?

这里使用命令 docker ps -a 查看一下以往的运行docker 进程 

docker ps -a 

也可以使用docker container ls 查看容器列表

将得到的容器id  使用docker start 容器id 或者使用docker restart 容器id启动  如:

docker start 3fdc87f60d8b

这里需要启动三个容器  待三个容器全部正常启动后  可以正常访问http://127.0.0.1:1337  即可正常访问啦

若错误 docker driver failed programming  如下图  

问题分析:

首先理清一下我做了什么操作。我记得在我开启docker后,执行 docker-compose up -d 启动完容器后,发现无法连接 MySQL 容器,经查没有关闭防火墙,未开放3306端口,因此执行 systemctl stop firewalld.service 关闭防火墙,最后我 docker-compose restart 重启了一下容器就出现了上述错误。

那毫无疑问,肯定是我关防火墙导致重启容器失败。为什么会这样呢?

原因:docker 服务启动时定义的自定义链 docker 由于 防火墙 被清掉。防火墙 的底层是使用 iptables 进行数据过滤,建立在iptables之上,这可能会与 docker产生冲突。当 防火墙 启动或者关闭的时候,将会从 iptables 中移除 docker 的规则,从而影响了 docker的正常工作。当你使用的是 Systemd (我上面关闭防火墙用的 systemctl 就是 Systemd 的主命令, 用于管理系统) 的时候, 防火墙 会在 docker 之前启动,但是如果你在 docker 启动之后再启动 或者重启 防火墙 ,你就需要重启 docker进程了。重启 docker服务及可重新生成自定义链docker

问题解决:

重启 docker 即可:systemctl restart docker

systemctl restart docker

四、结合swoole、swoft微服务化搭建

​​​​​​​网络基础

​​​​​​​OSI七层模型与TCP/IP模型

 OSI与TCP/IP模型对比

​​​​​​​ARP 地址解析协议

ARP 地址解析协议

 在计算机的实际流程

​​​​​​​经典的TCP三次握手与四次挥手

 TCP 首部

 TCP三次握手

 TCP四次挥手

 

UDP协议

 UDP 中文名是用户数据报协议,是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务

 TCP与UDP的区别

​​​​​​​抓包

tcpdump -nn -i 监听网卡 port 监听端口

tcpdump -nn -i any port 8000
  • -nn  指定将每个监听到的数据包中的域名转换成IP、端口从应用名称转换成端口号后显示
  • -i 指定监听的网络接口

我们在192.167.8.189 这个服务器上执行上述抓包命令  

 然后我们在另一台服务器  192.168.5.188 中  在其 shell 中 curl 请求一下

curl http://192.168.5.189:8000

此刻我们看到如下信息:

课外延伸  

推荐抓包工具   Wireshark

tcpdump -i  -w  host

参数说明

  • -i  指定抓包的网卡名称
  • -w  抓包存放的路径
  • host  对方服务器IP。
  • -s 表示从一个包中截取的字节数。0表示包不截断,抓完整的数据包。默认的话 tcpdump 只显示部分数据包,默认68字节。
  • -v 输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息

命令如下:

tcpdump -s 0 -i any host 192.168.5.188 -v -w test.pcap

​​​​​​​Swoft框架介绍

Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-FPM。有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP(面向切面的程序设计)、标准的 PSR 规范实现等等。

​​​​​​​协程

什么是协程

  • 协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
  • 一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。而多个线程相对独立,有自己的上下文,切换受系统控制,而协程也相对独立有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
  • 协程在阻塞的时候只是阻塞了当前这个协程,并不会阻塞整个进程,因为协程在 线程内部,即使阻塞也会让出控制权,挂起。等待当前协程的IO不阻塞在回来继续执行,相当于同步代码完成异步的功能。
  • 协程在单进程单线程中实现的,可以实现成千上万的协程,每个协程干不同的事,协作之间无需加锁,没有抢占,串行

系统要求

Swoft 框架支持 Linux、macOS 以及 Windows 10

环境要求

必要部分

  • PHP,版本 >=7.1
  • PHP 包管理器 Composer
  • PCRE 库
  • PHP 扩展 Swoole,版本 >=4.3
  • 额外扩展:PDO、Redis

安装composer 并切换其源  这里切换为阿里镜像

官方源为  http://packagist.org

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

查询composer源

composer config -g -l

​​​​​​​下载框架代码

composer create-project swoft/swoft Swoft
cp .env.example .env #编辑.env 文件,根据实际需要调整相关环境配置

 运行一个HTTP服务相关命令:

#运行一个HTTP服务
php ./bin/swoft http:start
# 以守护进程模式启动
php ./bin/swoft http:start -d
# 重启 HTTP 服务
php ./bin/swoft http:restart
# 重新加载 HTTP 服务
php ./bin/swoft http:reload
# 停止 HTTP 服务
php ./bin/swoft http:stop

 我们可以看到  其默认绑定端口为18306

​​​​​​​RPC 服务

RPC,是一种远程调用方式(Remote Procedure Call),通过 RPC 我们可以像调用本地方法一样调用别的机器上的方法,用户将无感服务器与服务器之间的通讯。RPC 在微服务当中起到相当大的作用,当然 RPC 不是微服务必须的一种方式。

RPC 协议可基于 TCP、UDP 或者 HTTP 实现,但是更推荐选择 TCP

例如调用者需要调用商品的服务就可以通过 RPC 或者 RESTful API 来调用,那么 RPC 调用和 RESTful API 两者之间的区别在哪呢?

​​​​​​​

  • TCP 支持长连接,当调用服务的时候不需要每次都进行三次握手才实现。从性能和网络消耗来说 RPC 都具备了很好的优势。
  • RESTful API 基于 HTTP 的,也就是说每次调用服务都需要进行三次握手建立起通信才可以实现调用,当我们的并发量高的时候这就会浪费很多带宽资源
  • 服务对外的话采用 RESTful API 会比 RPC 更具备优势,因此看自己团队的服务是对内还是对外

​​​​​​​使用swoft  实现RPC 服务

​​​​​​​前期准备工作

拷贝swoft目录,分别为3个新的swoft项目    分别为http服务,user用户服务,vehicle 车辆服务  如下图所示:

cp -r Swoft http

 这里为了方便  可以先把user微服务写好,然后再复制改一改vehicle

​​​​​​​修改配置文件

在user 这个目录下  修改

修改配置文件

cd user
vim app/bean.php

修改rpcServer 端口 为18308   如图所示:

 swoft 有一个默认的RPC 的UserServer  我们为了区分 将其方法返回改动一下 如下图:

vim app/Rpc/Service/UserService.php
#我们将其getList返回的 略微修改一下   这里我们方便测试 改成我们想要的  后续需要根据业务逻辑去改造了 

​​​​​​​启动RPC服务

运行命令 启动rpc服务

./bin/swoft rpc:start

输出如下信息:

此时user 用户的RPC服务已经弄好了,且启动成功正常  接下来我们看看vehicle 车辆的RPC服务

部署vehicle RPC服务

与上面一样 切换目录到vehicle项目目录

cd vehicle

 修改一下RPC服务的端口号  因为都在一台服务器上,刚才user的RPC端口是18308   此刻,我们vehicle服务的RPC 改成18309

vim app/bean.php

这里改成18309

将/vehicle/app/Rpc/Lib/UserInterface.php 案例修改成对应的接口   这里我们在vehicle服务中,故此实现的也是Vehicle的接口  命令如下:

cd app/Rpc/Lib
mv UserInterface.php VehicleInterface.php

修改VehicleInterface.php 内容 

修改注解信息与对应的class类名  如图所示:

修改对应的Service 并删除无用的服务    如下:

cd app/Rpc/Service
mv UserService.php VehicleService.php
rm UserServiceV2.php

 

 修改VehicleService.php内容  将服务接口换成VehicleInterface  与红框保持一致  如下图:

 此刻我们启动RPC 服务  运行命令:

./bin/swoft rpc:start

 这里报错了    为什么呢   是因为我们刚才删除了那个userService的服务  这里我们定位到这个报错的路径   可以看看

vim app/Http/Controller/RpcController.php

 可以看到  红框的地方  我们已经删除此文件了  故此他会启动报错  这里我们删除该控制器

rm app/Http/Controller/RpcController.php

再次运行  可以看到正常启动啦

​​​​​​​RPC客户端实现

​​​​​​修改配置文件

我们现在回到http 项目目录  修改目录   我们将httpServer 端口  设置成 18406 

cd http
vim app/bean.php

 bean.php 配置RPC 连接信息  根据实际情况 填写对应的RPC服务信息   红框的内容根据实际情况填写  这里我们填写上user和vehicle上面两个RPC服务的具体信息  如图所示

'vehicle'=> [
    'class'    => ServiceClient::class,
    'host'     => '127.0.0.1',
    'port'     => 18309,
    'settting' => [
          'timeout'     => 0.5,
          'connect_timout' => 1.0,
          'write_timeout'  => 10.0,
          'read_timeout'   => 0.5,
    ],
    'packet' => bean('rpcClientPacket')
],
'vehicle.pool' => [
   'class'  => ServicePool::class,
   'client' => bean('vehicle'),
],

​​​​​​​拷贝Lib 接口文件

两个RCP服务端下的lib下的接口文件拷贝到http服务目录下   命令如下:

cd /http/app/Rpc/Lib
cp /user/app/Rpc/Lib/UserInterface.php ./
cp /vehicle/app/Rpc/Lib/VehicleInterface.php ./

如下图:

 ​​​​​​​​​​​​​​调用RPC服务

我们还是在http 目录下操作   编写对应的控制器   这里我们使用他默认的一个控制器案例 RpcController.php

cd http/app/Http/Controller
vim RpcController.php

添加对应的VehicleInterface 命名空间 等信息  这里我用红框标记出来了  这里仅做参考,后续需要根据实际情况填写。 如下图:

 这里需要注意的是@Reference 这个注解 与 @var   这个与上面填写的bean.php中的RPC信息保持一致。

​​​​​​​​​​​​​​启动HTTP服务 

运行下面命令:

./bin/swoft http:start

说明启动正常

此时 访问 http://192.168.5.189:18406  显示如下:

说明HTTP服务正常

此后 我们在访问http://192.168.5.189:18406/rpc/getList  结果如下图:

​​​​​​​​​​​​​​非 Swoft 框架调用

默认消息协议是 json-rpc, 所以我们按照这个格式就可以了,需要注意的是,默认消息协议是以 \r\n\r\n 结尾的。

这里 method 的格式为 "{version}::{class_name}::{method_name}"。

{
    "jsonrpc": "2.0",
    "method": "{version}::{class_name}::{method_name}",
    "params": [],
    "id": "",
    "ext": []
}

示例: 如果使用默认消息协议,可以按照如下方式进行封装

<?php

const RPC_EOL = "\r\n\r\n";

function request($host, $class, $method, $param, $version = '1.0', $ext = []) {
    $fp = stream_socket_client($host, $errno, $errstr);
    if (!$fp) {
        throw new Exception("stream_socket_client fail errno={$errno} errstr={$errstr}");
    }

    $req = [
        "jsonrpc" => '2.0',
        "method" => sprintf("%s::%s::%s", $version, $class, $method),
        'params' => $param,
        'id' => '',
        'ext' => $ext,
    ];
    $data = json_encode($req) . RPC_EOL;
    fwrite($fp, $data);

    $result = '';
    while (!feof($fp)) {
        $tmp = stream_socket_recvfrom($fp, 1024);

        if ($pos = strpos($tmp, RPC_EOL)) {
            $result .= substr($tmp, 0, $pos);
            break;
        } else {
            $result .= $tmp;
        }
    }

    fclose($fp);
    return json_decode($result, true);
}

$ret = request('tcp://127.0.0.1:18307', \App\Rpc\Lib\UserInterface::class, 'getList',  [1, 2], "1.0");
var_dump($ret);

这里我们改成具体的调用 

 调用后  发现RPC的调用正常

五、整合API与Kong网关以及consul注册中心

​​​​​​​consul服务注册

​​​​​​​新增注册中心相关配置

现在回到user 这个项目  修改app/bean.php

cd user
vim app/bean.php

新增如下配置   具体根据consul 的配置填写    如图所示:

​​​​​​​添加监听注册

然后再监听注册中 填写对应的配置信息  命令如下:

vim app/Listener/RegisterServiceListener.php

根据实际情况  修改service 中的信息  如id name address 等等  并将注册的那个注释打开

 因为我们配置了HTTP方式的健康检查  故此  我们需要在user项目服务下 同时开启一个HTTP服务 端口号为18318 修改配置如下:

vim app/bean.php

 这里的端口与刚才配置的健康健康端口保持一致,并打开listener监听,这里是启动http服务后,同时启动rpc 里面的这个rpcServer服务 

​​​​​​​创建Consul控制器

这里应用于consul的健康检查

这里我们先复制那个RpcController.php 控制器  并改名ConsulController.php 命令如下:

cp app/Http/Controller/RpcController.php app/Http/Controller/ConsulController.php

ConsulController.php 控制器内容为 如下:

<?php declare(strict_types=1);

namespace App\Http\Controller;
use Swoft\Co;
use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
use Swoft\Rpc\Client\Annotation\Mapping\Reference;

/**
 * Class ConsulController
 *
 * @since 2.0
 *
 * @Controller()
 */
class ConsulController {
    
    /**
     * @RequestMapping("health")
     * 
     * @return array
     */
     public function health():array 
     {
         return ['status'=>'ok'];            
     }
}

​​​​​​​启动服务

./bin/swoft http:start

结果如下:

 此刻 我们在consul 中查看刚才的注册信息

上次我们已经实现的User项目的注册服务  接下来我们实现Vehicle下的 

刚才的步骤一样,因为都在一个服务器上  故此 端口号请勿重复,否则起不来

这里我们就不多做讲解

启动服务后,我们在consul注册中心可以看到如下信息:

​​​​​​​Swoft 服务发现

HTTP项目中 来实现的

​​​​​​​新增注册新增相关配置

 修改app/bean.php

vim app/bean.php

新增consul注册中心信息配置     具体根据consul 的配置填写    如图所示:

并在bean.php中  新增RpcProvider 命名空间  对应下面的128行,要不然会报错

use App\Common\RpcProvider;

 修改bean.php 下面的配置信息

 网上说  这里在 user 服务上,通过 provider 参数注入了一个服务提供者 RpcProvider::class (bean 名称)

​​​​​​​RPC服务发现

在app目录下,新增一个Consul目录,并新增一个ServiceHelper.php文件  命令如下:

cd app
mkdir Consul
cd Consul 
touch ServiceHelper.php

ServiceHelper.php 文件内容如下:

<?php
namespace App\Consul;

use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Bean\Annotation\Mapping\Inject;
use Swoft\Consul\Agent;
use Swoft\Consul\Health;

/**
 * Class ServiceHelper
 * @Bean()
 */
class ServiceHelper {
    /**
     * @Inject()
     *
     * @var Agent
     */
     private $agent;
     
     /**
     * @Inject()
     *
     * @var Health
     */
     private $health;
     
     /**
      * @param string $serviceName
      * @return array
      * 根据服务名 获取健康服务列表 
      */
      public function getService(string $serviceName) : array {
          $service = $this->agent->services()->getResult();
          
          $checks = $this->health->checks($serviceName,["filter"=> "Status==passing"])->getResult();
          
          $passingNode = [];
          foreach ($checks as $check) {
              $passingNode[] = $check['ServiceID'];
          }
          if(count($passingNode)==0) return [];
          return array_intersect_key($service,array_flip($passingNode));
      }
}

我们修改服务发现  命令如下:

vim app/Common/RpcProvider.php

新增命名空间
 

use App\Consul\ServiceHelper;

红色框的地方  需要添加 而这里面的swoft-user 根据实际情况填写

一下是getList方法代码

public function getList(Client $client) : array 
{  
      //Get health service from consul
     $services = $this->ServiceHelper->getService('swoft-user');
     $ret = [];
     foreach ($services as $key=>$value) {
           $ret[$key] = $value['Address'] . ':' . $value['Port'];
     }
     return $ret;
}

上面调整完后  我们复制一个RpcProvider.php  命令如下:

cp app/Common/RpcProvider.php app/Common/VehicleRpcProvider.php

VehicleRpcProvider.php 内容如下:

修改具体的bean.php 配置     

新增命名空间等  如图所示:

 

启动服务 

./bin/swoft http:start

访问之前写的RPC客户端调用  http://192.168.5.189:18406/rpc/getList

访问正常  说明consul服务发现成功了

​​​​​​​服务限流

我们在user项目中 进行限流操作实验。

使用限速器,一定要配置安装 redis 组件,且配置可用的 redis 缓存

我们先配置好对应的redis信息  如下图:

vim app/bean.php

 操作RPC服务控制器

vim app/Rpc/Service/UserService.php

新增 命名空间

use Swoft\Limiter\Annotation\Mapping\RateLimiter;

在getList方法前  加入对应的注解信息 

 * @RateLimiter(rate=1,max=2,fallback="limiterFallback")
  • name  缓存前缀
  • rate 允许多大的请求访问,请求数/秒
  • max 最大的请求数
  • default 初始化请求数
  • fallback 降级函数,和 breaker 一样

 * @RateLimiter(rate=1, max=2,fallback="limiterFallback") 

添加降级函数limiterFallback

改变的地方 使用红色框表示 如下图所示:

改完后 记得重启服务 要不然不生效

此刻我们访问http://192.168.5.189:18406/rpc/getList  多访问几次 就会限流了  如下图:

限流如此简单  更多 请查阅swoft官方手册  https://www.swoft.org/documents/v2/microservice/limit/  

​​​​​​​熔断与降级

我们在vehicle项目中 进行限流操作实验。

操作vehicle 项目中  RPC服务控制器

vim /app/Rpc/Service/VehicleService.php

新增 命名空间

use Swoft\Breaker\Annotation\Mapping\Breaker;

在getList方法前 新增注解

 * @Breaker(fallback="funcFallback",sucThreshold=3,failThreshold=1,timeout=2.0,retryTime=3)

@Breaker

标记方法开启熔断器,如下参数详解:

  • fallback  降级函数,必须和 @Breaker 标记的函数完全一样除了名称不一样且在同一个类里面
  • sucThreshold 连续成功多少次状态切换阀门
  • failThreshold 连续失败多少次状态切换阀门
  • timeout  超时时间
  • retryTime 熔断器由开启状态到半开状态尝试切换时间

这里我们方便测试  故此将getList模拟了不是每次都成功  如下图:

我们连续访问 http://192.168.5.189:18406/rpc/getList 

 

​​​​​​​consul与kong网关结合

 

首先我们访问konga

http://192.168.5.189:1337

我们使用页面  新增一个服务  名字就叫swoft-http-server  相关配置如下:

创建路由 名字 swoft-route1  paths 为/ap   如图所示:

添加完服务后  我们在http服务上 改造一下Home控制器的index方法 代码如下图:

重启服务后

此刻我们访问http://192.168.5.189:7000/ap/home/index  访问结果如下:

说明此刻接口通过网关kong 找寻路由 转发到了对应的服务上了 

但 我们后续服务地址 包括端口变化了  此时这种方法就不行了

在实验前  我们将Http项目加入到consul注册中心,步骤就不在此赘述,详情请查看第五章第1小点

结果如下图:
 

注册信息

​​​​​​​加入dns_resolver 信息

我们观察一下consul注册中心启动后 一些输出

我们kong和consul 来结合 需要DNS 这个端口配合实现  如上图 我们可以看到DNS 为8600

我们先删除容器  删除之前我们先停掉容器  如下:

docker stop 容器id

删除容器

docker rm 容器id

重新启动kong  并加入相关DNS配置   命令如下:

docker run -d --name kong-ee --network=kong_net \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_PG_HOST=172.17.0.1" \
  -e "KONG_PG_PASSWORD=kong" \
  -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
  -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
  -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
  -e "KONG_DNS_RESOLVER=192.168.5.189:8600" \
  -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
  -e "KONG_ADMIN_GUI_URL=http://192.168.5.189:7002" \
    -p 7000:8000 \
    -p 7001:8001 \
    -p 7443:8443 \
    -p 7444:8444 \
    -p 7002:8002 \
  kong/kong-gateway:2.4.1.0-alpine

这里与之前的启动  多了一个配置  -e "KONG_DNS_RESOLVER=192.168.5.189:8600"

如果创建失败  可以看看容器名是否被占用

docker container ls 
#或者运行下面命令
docker container ls -a 

 然后删除不需要的容器 

#删除指定容器
docker rm -f <containerid>

若报网络不存在,如下图:

请查询具体的docker 网络  如下命令:

docker network ls

 

然后再重新运行启动命令 当运行成功后,我们打开konga 可以在info中查询对应的DNS信息

如果有该值  说明我们配置成功​​​​​​​

添加对应的SERVER信息

在konga中 添加对应的服务

Name 可以随意取

Host    我们这里填写Consul中心对应的服务名  .service.consul     必须这样写  固定的

Port 我们填写0 要不然提交不了

如下图所示:

然后我们创建一个路由

访问http://192.168.5.189:7000/aa/home/index

完美通过 kong ->访问注册信息 ->访问实际服务 啦

 我们来实验一下当consul中的服务IP变化后,我们kong网关还是否可以访问正常。

当前swoft-http 服务端口为18406  我们改成18407 如下图:

首先我们回到http项目  修改bean.php 中的HTTP启动端口,然后将监听中的健康检查也改成对应的接口

vim app/Listener/RegisterServiceListener.php

 然后重新启动HTTP服务 在查看consul 如下图:

接下来验证一下:

我们访问之前在konga中新建的服务  采用IP+端口方式  如图所示:

接口已经不通了 

我们在访问kong+consul 新建的服务 如图所示:

依旧正常访问  

故此:我们只需要将kong与consul做关联,后续改服务,不需要修改kong网关啦 这样前端业务也不需要变动。

​​​​​​​后记

作者的话:

       学习完整个微服务框架综合实战,我们了解了什么是微服务,为什么使用微服务,以及拆分原则。对注册中心与服务发现有了一定的了解,也知道了kong与konga的关系,以及一些网络知识,基本上按照前端用户使用网关kong调用接口,网关关联注册中心consul,而内部接口进行服务注册,调用采用RPC方式 。整个流程走完,我相信你一定对微服务架构有了一定的认识。

       本文中涉及的网关以及注册中心对应的服务软件都不是一定的,可以根据实际情况来选择适合自己的项目,学习是一件枯燥且有趣的事情,学海无涯,学会站在巨人的肩膀上,本次学习就此告一段落。谢谢!

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值