微服务 - 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。

待续...

posted @ 2023-04-07 15:20  Sol·wang  阅读(1637)  评论(0编辑  收藏  举报