• 微服务 - Consul集群化 · 服务注册 · 健康检测 · 服务发现 · 负载均衡


    系列目录:

    集群化工具选择性很多,这里选 Consul 工具;官网:https://www.consul.io
    本篇计划用 Docker 辅助部署,所以需要了解点 Docker 知识;官网:https://www.docker.com

    一、Consul 概括

    Consul 是由N多个节点(台机/虚机/容器)组成,每个节点中都有 Agent 运行着,各节点间用RPC通信,所有节点内相同的 Datacenter 名称为一个数据中心,节点又分三种角色 Client/Server/Leader:

    • Agent:Consul 各成员节点的运行载体
    • Client:不是必须存在的角色,数量也无上限,少量的资源开销,建议更多的 Client 角色存在。
    • Server:必须的Server角色,每个Server下可多个Client,可以代替Client,对接收到的信息持久化;资源开销大,官方建议3/5台。
    • Leader:一个数据中心内的 Server 选举产生一个 Leader 角色,将信息下发广播给所有 Server

    默认端口

    • 8500:Consul 对外提供注册查询UI等的专用端口
    • 830x:Consul 内各节点间TCP/RPC等通信的专用端口
    • 8600:Consul DNS 使用
    • 21xxx:Consul 自动分配代理使用

    整体架构示意图

    Consul 架构运行示意图

    图解:任意的 应用服务 Join 到任意的 Consul Node;任意的 Client Join 到任意的 Server;Node之间数据共享。

    Consul 中的服务 与 注册的应用服务

    Consul Node Server:是组成 Consul 整体运行的不可缺少的一种节点角色,注册于 Consul Catalog 中,不如后续叫<节点服务>
    Agent Register Service:是被 Consul 管理、发现、健康检测的目标业务应用,注册于 Consul Agent 中,不如后续叫<应用服务>

    作者:[Sol·wang] - 博客园,原文出处:https://www.cnblogs.com/Sol-wang/p/17296278.html

    二、Consul 功能

    服务注册

    所有的应用服务,都向 Consul 报告自己的存在及具体的信息;

    新应用服务的加入,通过Client/Server上报给上级,直至Leader;Leader再向所有Server广播新服务的存在及具体信息,Consul 中所有节点共享新加入服务的信息;其中包括应用服务本身的连接及健康检测信息。

    任何注销的应用服务,Consul 也会同步到各节点,关联的健康检测一并注销。

    健康检测

    Consul向各应用服务发起的连接过程,为了提供所有健康可用的应用服务,按提供的检测方式、检测地址、检测频率等,发起通信检测,识别服务状态,踢除异常及不可用的实例,保留健康可用的实例,并把结果上报给 Consul-Server/Leader。

    检测方式分为:script / http / tcp / udp / ttl / rpc 等

    比如:脚本在各服务上的运行反馈

    比如:各服务提供HTTP请求的API

    比如:各服务提供TCP连接的端口

    服务发现

    在集群内外,任何想要连接集群内应用服务的信息,先通过 Consul-Server 拉取到健康可用的应用服务信息,才能连接到指定的应用服务;

    新应用服务不断的注册/宕机/注销等,每个时间段所提供的各应用服务信息都可能是变化的;

    这种 Consul-Server 时时检测网络上的服务, 并提供健康可用的应用服务列表的过程,就叫做服务发现。

    比如:Nginx需要时时知道[订单服务]的访问IP端口信息,才好转发请求

    比如:[订单服务]需要请求[产品服务]API,Consul提供了所有健康的[产品服务]访问IP端口信息,[订单服务]才能请求到[产品服务]API

    按[订单]服务查询出健康可用的应用服务列表,屏蔽了异常状况的应用服务,达到了 故障转移 的效果。

    K/V存储

    动态的、可维护的、持久化的、键值对的存储方式;比较独立的一项Consul功能,我们可以把需要动态的内容放入KV中存储,它就像库一样,随时可变更查询。

    key 唯一键;value 对应值;flags 64位整数可选值

    GET 查询/列表

    # 命令行 查询全部
    consul kv get -recurse
    # 命令行 查询单个[列表]
    consul kv get [-detailed] {key}
    # API 查询全部
    curl http://{host}:8500/v1/kv/?recurse
    # API 查询单个
    curl http://{host}:8500/v1/kv/{key}

    PUT 新增/修改

    # 命令行 新增/修改
    consul kv put [-flags=13] {key} {value}
    # API 新增/修改
    curl -X PUT -d '{value}' http://{host}:8500/v1/kv/{key}[?flag=13]

    DELETE 删除/全部

    # 命令行 删除一个
    consul kv delete {key}
    # 命令行 删除列表(全部)
    consul kv delete -recurse [key/prefix]
    # API 删除列表(全部)
    curl -X DELETE http://{host}:8500/v1/kv/{key/prefix}[?recurse]

    更多相关API参考:https://developer.hashicorp.com/consul/api-docs/kv

    Datacenter

    数据中心算是一个概念吧。。。

    以上几点内容大致体现了 Consul 的运作方式,综合起来也就是一个范围集群的说法,其中会按 Datacenter 名称的不同,区分为多个数据中心。比如在不同的地域提供不同的数据中心,或者相同的数据中心互通,以做候选备用等。

    三、集群相关

    负载均衡

    在集群中,每种应用服务都可能不止一个运行实例,订单服务A调用产品服务B,通过ConsulAPI给出的产品服务B可用地址会是多个,同样都是产品服务,有的资源已用90%,有的资源才用10%,为了避免这种资源利用不均匀,如何做到负载均衡呢?

    常用方式:随机、轮询、最小连接、权重 等。

    • 随机方式:实现起来比较简单,在拉取到的应用服务数据列表中,随意挑一个使用就好
    • 轮询方式:需要有个全局变量,记录当前已用到哪个地址了,下标+1的方式取列表中下个健康的地址
    • 最小连接:记录每个应用服务实例当前已产生多少连接,每次使用最小已连接的实例做为本次的连接
    • 权重方式:配置应用服务实例在整体服务中所占的使用比例上限,每次连接后计算更新已连接的占比

    当然,Consul未提供此功能,或用第三方或自己编写实现;

    比如:写一个负载均衡的类库,每个服务已连接次数记录在 Consul 的 KV 中,调用方给出要调用的服务组名,使用类库得出本次要请求的具体服务地址等。

    熔断降级

    Consul 并没有提供这样的功能,作为集群中不可忽视的点,这里只有粗略叙述,以作了解。

    熔断:存在于请求方与应用服务之间,当应用服务异常次数达到指定值,下次请求就在熔断处直接返回,不用再连接到异常应用服务上。

    降级:也就是备用方案的启用;比如:DB异常时,用缓存的数据;缓存异常时,或保留请求信息做延迟处理;或默认数据的返回等。

    Snapshot

    对于集群的灾难与备份,上述有提到多数据中心同步可达到备份的效果,Consul 的快照方式也是一个可选项:

    # 命令行 生成快照
    consul snapshot save {backup-name}.snap
    # API 生成快照
    curl http://{host}:8500/v1/snapshot?dc={dc-name} --output {backup-name}.snap
    # 命令行 恢复快照
    consul snapshot restore {backup-name}.snap
    # API 恢复快照
    curl -X PUT --data-binary @{backup-name}.snap http://{host}:8500/v1/snapshot
    # 命令行 快照详细
    consul snapshot inspect {backup-name}.snap

    定期生成快照consul snapshot agent仅企业版可用

    四、Consul 部署

    以下用 docker 部署,docker 拉取镜像:docker pull consul

    不管是 Client / Server / Leader 哪种角色,都是 Agent 运行起来的 Node;以下通过两种方式来创建维护 Consul Node:

    CLI 方式管理节点

    CLI 命令行创建节点的格式如下:

    docker run -d --name={容器名称} -p 8500:8500 {image} agent -server -ui -node={节点名称} -bootstrap-expect=3

    下表列出了常用各参数的作用说明:

    agent 必须;Consul 的应用,于每个节点中
    -server 必须,服务角色;无:被视为Client角色
    -node 必须;本节点名称
    -bootstrap-expect 必须;定义Server角色的数量,必须够数,才能成为一个集群,否则集群不会运行
    -datacenter 数据中心名称(群名称),默认 dc1
    -join 加入的节点IP地址(Client/Server)
    -retry-join 尝试重新加入时的节点IP(Client/Server)
    -data-dir 指定运行时的数据存放目录
    -config-file 使用指定的配置文件启动运行(文件内容与此表参数项作用相似)
    -ui 带管理Web页面;访问服务器IP http://{ip}:8500 进入页面管理方式
    -client 连接限制,开放连接的客户端;浏览器连接打开UI、Client连接Server等

    下面来部署一个 Consul Datacenter,计划有3个 Server 节点,3个 Client 节点。

    创建3个 Server 节点

    节点名称分别定为:ser-a / ser-b / ser-c;由于 Datacenter 的默认值都是 dc1,所以就形成了一个名为 dc1 的数据中心。

    # 第一个 Server Node,所以 Consul 会默认为 Leader 角色
    docker run -d --name=cons-ser-a -p 8501:8500 consul agent -server -ui -node=ser-a -bootstrap-expect=3 -client=0.0.0.0
    # 以下的 Server Node 都 Join 到 Leader Node 上
    docker run -d --name=cons-ser-b -p 8502:8500 consul agent -server -node=ser-b -ui -client=0.0.0.0 -retry-join=172.17.0.2
    docker run -d --name=cons-ser-c -p 8503:8500 consul agent -server -node=ser-c -ui -client=0.0.0.0 -retry-join=172.17.0.2

    起初创建 Leader Node 时bootstrap-expect=3,现在已经运行了3个 Server Node;
    所以 Consul 已经启动完成并运转;可以打开UI界面:http://{宿主机IP}:8501/

    再创建3个 Client 节点,并加入到不同的 Server Node

    docker run -d --name=cons-cli-a -p 8511:8500 consul agent -node=cli-a -client=0.0.0.0 -retry-join=172.17.0.2
    docker run -d --name=cons-cli-b -p 8512:8500 consul agent -node=cli-b -client=0.0.0.0 -retry-join=172.17.0.3
    docker run -d --name=cons-cli-c -p 8513:8500 consul agent -node=cli-c -client=0.0.0.0 -retry-join=172.17.0.4

    以上节点的创建过程,是用宿主机的不同端口映射到各自容器节点的8500端口,所以用支持UI的对应宿主机端口都可以打开UI界面

    计划的所有 Consul 节点创建完成,Consul 就是用这些节点来管理应用服务集群的,应用服务等后续再加入;以下先阐述节点的维护

    查看 Datacenter 成员

    # 命令行 列出所属 Datacenter 中的全部成员 [详细]
    docker exec -t {容器名称} consul members [-detailed]
    # 命令行 列出所属 Datacenter 中的 Server 角色成员
    docker exec -t {容器名称} consul operator raft list-peers

    Server 加入到 Leader 下

    # 如果要加 -datacenter 的话,必须与 join 参数目标的dc名称一致
    docker run -d --name={容器名称} {image} agent -server -node={node-name} -join={leader-ip}

    Client 加入到 Server 下

    # 创建时加入
    docker run -d --name={容器名称} {image} agent -datacenter={有要一致} -node={name} -join {server-ip}
    # 创建后加入
    docker exec -t {容器名称} consul join {server-ip}

    下线指定节点

    # 命令行 移除所处节点
    consul leave
    # 命令行 强制移除指定节点 [清楚未运行的节点]
    consul force-leave {node-name} [-prune]

    不止命令行方式,Consul 也提供了 API 方式来管理节点。

    API 方式管理节点

    # API 列出所有成员
    curl http://{host}:8500/v1/catalog/nodes
    # API 加入新节点
    # 参数文件 json 指明了节点名称/地址/端口等必要项
    curl -X PUT -d @cli-d.json http://{host}:8500/v1/catalog/register
    # API 注销节点
    curl -X PUT -d '{"Datacenter":"dc1","Node":"cli-d"}' http://{host}:8500/v1//catalog/deregister

    有没有麻烦了点。。。 这种方式应该用的不多吧。。。

    Consul 6个节点已部署完成,接下来该在 Consul Node 上部署微服务了。

    五、应用服务部署

    Consul Node 部署完成以后呢,剩下的就是告知 Consul 有哪些应用服务需要你管理,这告知 Consul 的方式有以下几种:

    命令行方式注册服务

    假设应用服务已经运行起来,然后按 Consul 定义的应用服务配置格式,编写配置文件,放到 Consul 任意节点的配置目录下,文件名称自定义,每次 Consul 启动的时候,都会读取配置目录下的所有文件。

    配置内容也就是告诉 Consul 我是谁、我在哪、怎么与我联系、我的检测方式等;

    1、配置示例格式如下:

    {
      "service": [
        {
          // 服务身份定义
          "id": "AppService-Redis-aaaaa-bbbb-cccc-dddd-eeeee",
          "name": "AppService-Redis",
          "port": 80,
          // 健康检测定义
          "check": {
            "id": "AppService-Redis-Check-TCP",
            "name": "Redis Check TCP on port 80",
            "tcp": "172.17.0.10:80",
            "interval": "10s",
            "timeout": "3s"
          }
        }
      ]
    }

    2、重载此节点配置:docker exec -t cons-ser-a consul reload

    至此完成应用服务注册;如下效果图:

    以上配置文件:有 Service 项 就是 服务注册,有 Check 项 就是 健康检测注册。

    那。。。注销健康检测呢?注销服务呢?

    删除 Check 项 就是注销健康检测;删除文件 就是 注销服务;记得重新加载配置consul reload

    API 方式注册服务

    几乎命令行能做的事情,Consul 提供的 API 方式也可以做到。

    假设应用服务已经运行起来了,同样是编写配置文件,以传参的方式请求注册的 API,完成 服务注册、健康注册、服务注销、健康注销等。

    以下案例准备了服务注册所需的参数文件:AppService-kafka.json

    {
      "service": [
        {
          // 服务身份定义
          "id": "AppService-Kafka-xxxxx-yyyy-zzzz-wwww-vvvvv",
          "name": "AppService-Kafka",
          "port": 80
        }
      ]
    }

    带文件参数请求注册服务的API接口:

    curl -X PUT -d @AppService-kafka.json http://{host}:8500/v1/agent/service/register

    当然,也可以单独请求注册健康检测的API接口:

    curl -X PUT -d @AppService-health.json http://{host}:8500/v1/agent/check/register

    同样的,API注销接口:

    curl -X PUT http://{host}:8500/v1/agent/service/deregister/{app-service-id}

    curl -X PUT http://{host}:8500/v1/agent/check/deregister/{app-check-id}

    更所相关API接口参考官网:https://developer.hashicorp.com/consul/api-docs

    引用类库方式注册服务

    1、创建 .NET 项目,并启用 Docker 方式,假设叫[订单服务],Nuget 安装 Consul

    [订单服务]中必须要完成开发的事情:服务注册动作,健康检测定义,服务注销动作。

    其实就是把 Consul 类库相关的参数赋值,由 Consul 类库自动完成注册/检测/注销。

    2、appsettings 相关配置

    "Consul": {
        "Service-Name": "AppService-Order",
        "Service-Port": 80,
        "Service-Health": "/Health",
        // 应用服务 Join to Node Address
        // 未来 Join 的 Node 不固定;所以 创建容器时 再传参
        "Register-Address": null
    }

    3、扩展 IApplicationBuilder 实现 注册/检测/注销 并启用

    以下扩展方法创建后,并在管道中启用:app.UseConsul(app.Configuration, app.Lifetime);

    public static IApplicationBuilder UseConsul(this IApplicationBuilder app, IConfiguration conf, IHostApplicationLifetime lifetime)
    {
        // 取本机IP(告诉 Consul 健康检测的地址)
        string? _local_ip = NetworkInterface.GetAllNetworkInterfaces()
            .Select(p => p.GetIPProperties())
            .SelectMany(p => p.UnicastAddresses)
            .FirstOrDefault(p =>
                p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address)
            )?.Address.ToString();
    
    
        // 指明注册到的 Consul Node
        var client = new ConsulClient(options =>
        {
            options.Address = new Uri(conf["Consul:Register-Address"]);
        });
        
        
        // 应用服务的 服务信息 / 健康检测
        var registration = new AgentServiceRegistration
        {
            Name = conf["Consul:Service-Name"],
            ID = $"Order-{Guid.NewGuid().ToString()}",
            Address = _local_ip,
            Port = Convert.ToInt32(conf["Consul:Service-Port"]),
            Check = new AgentServiceCheck
            {
                Timeout = TimeSpan.FromSeconds(5),
                Interval = TimeSpan.FromSeconds(10),
                DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),
                HTTP = $"http://{_local_ip}:{conf["Consul:Service-Port"]}{conf["Consul:Service-Health"]}"
            }
        };
        
        
        // 应用服务启动时 - 注册
        lifetime.ApplicationStarted.Register(() =>
        {
            client.Agent.ServiceRegister(registration).Wait();
        });
        // 应用服务停止时 - 注销
        lifetime.ApplicationStopping.Register(() =>
        {
            client.Agent.ServiceDeregister(registration.ID).Wait();
        });
    
        return app;
    }

    4、编写健康检测 API

    这里健康检测选用 HTTP API 方式;为此,需要编写 WebApi。

    # 服务中追加健康检测接口,供 Consul 调用
    # 与 appsettings 配置保持一致的控制器
    # 比较简单,达到通信正常,就被视为服务运行正常
    public class HealthController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok();
        }
    }

    5、生成 Docker 镜像并运行

    每个应用服务都会运行多个实例,把已开发的[订单服务]用Docker运行多个实例,模仿集群环境:

    # 项目根目录 生成 docker 镜像
    docker build -t order.serv.cons:dev -f ./Order-Web/Dockerfile .
    # docker 运行 [订单服务],这里运行2个吧(多实例)
    # 还记得 appsettings 中的注册节点地址么...这里传参指明注册的节点
    docker run -d --name=order-serv-cons.a -p 5001:80 order.serv.cons:dev --Consul:Register-Address='http://172.17.0.6:8500'
    docker run -d --name=order-serv-cons.b -p 5002:80 order.serv.cons:dev --Consul:Register-Address='http://172.17.0.7:8500'

    测试验证效果

    运行2个 [订单服务] 后的 Consul-UI 图示:

    新的服务 AppService-Order 下有两个运行健康的实例,注册与检测都是类库帮助实现的,并显示出IP及注册节点。

    相同的服务,有多个运行实例;不同的服务,组成完整的微服务集群;被 Consul 的6个 Node 时时管理着。

    测试服务发现

    1、Consul API 方式 指定服务的健康实例查询:http://{host}:8501/v1/health/service/{service-name}?passing

    2、Consul 类库方式 拉取指定服务的健康实例地址:

    // 指明一个节点地址
    var _consul_node = new ConsulClient(options =>{ options.Address = new Uri(_conf["Consul:Node-Address"]); });
    // 向 Consul 拉取 指定服务 健康实例的列表
    var _order_result = await _consul_node.Health.Service("AppService-Order", null, true, _query_options);
    var _order_instance_list = _order_result.Response.Select(i => $"http://{i.Service.Address}:{i.Service.Port}");

    以上两种方式都发现了两个健康运行的应用服务;测试通过

    服务异常测试

    docker 停止一个容器后,只剩一个运行实例:

    以上停掉一个应用服务后,剩下一个健康运行的应用服务;测试通过。当然,docker 容器再启动后,实例还会再回来。

    服务间的相互调用,X服务 -> 订单服务,通过以上方式得出多个可用的实例地址,假如用轮询方式实现了负载均衡
    后续也可以把各应用服务的可用列表,时时的提供给网关(如Nginx),实现网关中的负载均衡

    Consul:Service Mesh & Gateways

    consul connect proxy:集群代理,通过调用代理者的端口,实现与被代理者的连接(像是两台机的端口映射似的,被代理者端口不被暴露)

    Consul Service Mesh:在多个集群和环境间建立连接,创建全球化的跨平台服务网络;下有多个Mesh Gateway,每个Mesh Gateway 下是 Datacenter。

    待续...

  • 相关阅读:
    Linux 下静态库与动态库的制作与使用
    记录局域网内金蝶云无法访问服务器故障
    SpringMVC之框架搭建&开发实例&请求的处理流程
    PX4模块设计之三十七:MulticopterRateControl模块
    WZOI-227我家的门牌号
    seata 1.5.2 保姆级教程
    java面试宝典2019
    嵌入式学习笔记(1)ARM的编程模式和7种工作模式
    minio文件迁移
    含文档+PPT+源码等]精品微信小程序旅游服务平台+后台管理系统|前后分离VUE[包运行成功]微信小程序项目源码Java毕业设计
  • 原文地址:https://www.cnblogs.com/Sol-wang/p/17296278.html