在云计算和容器化技术发展火热的当下,对于微服务架构,服务注册与发现组件是必不可少的。在传统的服务架构中,服务的规模处于运维人员的可控范围内。当部署服务的多个节点时,一般使用静态配置的方式实现服务信息的设定。在微服务应用中,服务实例的数量和网络地址都是动态变化的,这对系统运维提出了巨大的挑战。因此,动态的服务注册与发现就显得尤为重要。
主流服务发现软件的对比:
Software | Consul | zookeeper | etcd |
---|---|---|---|
服务健康检查 | 服务状态,内存,硬盘等 | (弱)长连接,keepalive | 连接心跳 |
多数据中心 | 支持 | - | - |
kv 存储服务 | 支持 | 支持 | 支持 |
一致性 | raft | paxos | raft |
cap | cp | cp | cp |
使用接口(多语言能力) | 支持 http 和 dns | 客户端 | http/grpc |
watch 支持 | 全量/支持long polling | 支持 | 支持long polling |
自身监控 | metrics | - | metrics |
安全 | acl /https | acl | https支持(弱) |
spring cloud 集成 | 已支持 | 已支持 | 已支持 |
优点 | 1.简单易用,不需要集成sdk 2.自带健康检查 3.支持多数据中心 4.提供web管理界面 |
1.功能强大,不仅仅只是服务发现 2.提供watcher机制能实时获取服务提供者的状态 3.dubbo等框架支持 |
1.简单易用,不需要集成sdk 2.可配置性强 |
缺点 | 1.不能实时获取服务信息的变化通知 | 1.没有健康检查 2.需在服务中集成sdk,复杂度高 3.不支持多数据中心 |
1.没有健康检查 2.需配合第三方工具一起完成服务发现 3.不支持多数据中心 |
总的来看,目前Consul 自身功能,和 spring cloud 对其集成的支持都相对较为完善,而且运维的复杂度较为简单,所以我们选择consul作为服务发现工具。
Consul是基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft算法保证服务的一致性,且支持健康检查。
Consul采用主从模式的设计,使得集群的数量可以大规模扩展,集群间通过RPC的方式调用(HTTP和DNS)。它的结构图如下所示:
Raft是一种基于Paxos的一致性算法。相比于Paxos,Raft设计采用了较少的状态,并且是一种更简单、更易于理解的算法。
Raft节点总会是Follower、candidate、Leader三个状态之一。Leader处理所有的查询和事务,并向Follower同步事务。Follower会将所有的RPC查询和事务转发给Leader处理,它仅从Leader接受事务的同步。数据的一致性以Leader中的数据为准实现。
在节点初始启动时,节点的Raft状态机将处于Follower状态等待来来自Leader节点的心跳。如果在一定时间周期内没有收到Leader节点的心跳,节点将发起选举。
Follower节点选举时会将自己的状态切换为Candidate,然后向集群中其它Follower节点发送请求,询问其是否选举自己成为Leader。当收到来自集群中过半数节点的接受投票后,节点即成为Leader,开始接收Client的事务处理和查询并向其它的Follower节点同步事务。Leader节点会定时向Follower发送心跳来保持其地位。
一致性模式
为了支持开发人员可能需要的各种权衡,Consul支持3种不同的一致性模式
Gossip协议是为了解决分布式环境下监控和事件通知的瓶颈。Gossip协议中的每个Agent会利用Gossip协议互相检查在线状态,分担了服务器节点的心跳压力,通过Gossip广播的方式发送消息。
所有的Agent都运行着Gossip协议。服务器节点和普通Agent都会加入这个Gossip集群,收发Gossip消息。每隔一段时间,每个节点都会随机选择几个节点发送Gossip消息,其他节点会再次随机选择其他几个节点接力发送消息。这样一段时间过后,整个集群都能收到这条消息。
基于Raft算法,Consul提供强一致性的注册中心服务,但是由于Leader节点承担了所有的处理工作,势必加大了注册和发现的代价,降低了服务的可用性。通过Gossip协议,Consul可以很好地监控Consul集群的运行,同时可以方便通知各类事件,如Leader选择发生、Server地址变更等。
Consul用了两种不同的Gossip池。我们把这两种池分别叫做LAN池和WAN池。
LAN池
Consul中的每个数据中心有一个LAN池,它包含了这个数据中心的所有成员,包括clients和servers。LAN池用于以下几个目的:
成员关系信息允许client自动发现server, 减少了所需要的配置量。
分布式失败检测机制使得由整个集群来做失败检测这件事, 而不是集中到几台机器上。
gossip池使得类似Leader选举这样的事件变得可靠而且迅速。
WAN池
WAN池是全局唯一的,因为所有的server都应该加入到WAN池中,无论它位于哪个数据中心。由WAN池提供的成员关系信息允许server做一些跨数据中心的请求。一体化的失败检测机制允许Consul优雅地去处理:整个数据中心失去连接, 或者仅仅是别的数据中心的某一台失去了连接。
建立compose文件consul.yml
docker-compose文件如下:
version: '3.6'
services:
consul1:
image: consul:latest
container_name: consul1
restart: always
network_mode: mynet
command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul1 -datacenter=dc1
consul2:
image: consul:latest
container_name: consul2
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul2 -datacenter=dc1
consul3:
image: consul:latest
container_name: consul3
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul3 -datacenter=dc1
consul4:
image: consul:latest
container_name: consul4
network_mode: mynet
restart: always
ports:
- 8500:8500
command: agent -client=0.0.0.0 -retry-join=consul1 -ui -node=client1 -datacenter=dc1
consul5:
image: consul:latest
container_name: consul5
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul5 -datacenter=dc2
consul6:
image: consul:latest
container_name: consul6
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul5 -node=consul6 -datacenter=dc2
consul7:
image: consul:latest
container_name: consul7
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul5 -node=consul7 -datacenter=dc2
consul8:
image: consul:latest
container_name: consul8
network_mode: mynet
restart: always
ports:
- 8501:8500
command: agent -client=0.0.0.0 -retry-join=consul5 -ui -node=client2 -datacenter=dc2
可以看到network_mode是我自己建立的一个网段,也可以使用已有的网段,建立网段的命令如下:
docker network create --driver bridge --subnet 10.10.0.0/24 mynet
安装docker-compose,
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
上面安装的是稳定版本1.23.2的 docker-compose,可以到github上找最新版 https://github.com/docker/compose/releases
然后使用命令启动:
$ docker-compose -f compose文件名称 up -d
我们这里部署了两个数据中心,使用命令查看各自数据中心的成员:
//以consul1为例
$ docker exec consul1 consul members
打印如下:
Node Address Status Type Build Protocol DC Segment
consul1 172.19.0.6:8301 alive server 1.4.0 2 dc1 <all>
consul2 172.19.0.8:8301 alive server 1.4.0 2 dc1 <all>
consul3 172.19.0.5:8301 alive server 1.4.0 2 dc1 <all>
client 172.19.0.2:8301 alive client 1.4.0 2 dc1 <default>
可以看到显示了dc1数据中心的所有角色。
而数据中心的WAN池只显示server的成员,想要两个数据中心之间建立通信,只需要加入同一个WAN池就可以,命令如下:
//以dc1为例,加入dc2
$ docker exec consul1 consul join -wan consul5
查看WAN池的成员:
$ docker exec consul1 consul members -wan
打印如下:
Node Address Status Type Build Protocol DC Segment
consul1.dc1 172.19.0.6:8302 alive server 1.4.0 2 dc1 <all>
consul2.dc1 172.19.0.8:8302 alive server 1.4.0 2 dc1 <all>
consul3.dc1 172.19.0.5:8302 alive server 1.4.0 2 dc1 <all>
consul5.dc2 172.19.0.4:8302 alive server 1.4.0 2 dc2 <all>
consul6.dc2 172.19.0.7:8302 alive server 1.4.0 2 dc2 <all>
consul7.dc2 172.19.0.3:8302 alive server 1.4.0 2 dc2 <all>
这样应用程序连接dc1也可以调用dc2的服务实例,但是数据的一致性还是只存在于各自的集群之中,也是说如果dc2的server全部挂掉,dc1中是没有dc2的服务实例的,这个时候也就无法调用dc2的服务。
ACL是Consul用来控制访问API与data的。
首先,我们创建一个uuid,也可以使用consul自带的生成key的功能
$ docker exec consul1 consul genkey
使用这个token,我们创建一个acl.json和acl_client.json文件,分别给与server节点和client节点使用
acl.json:
{
"acl_datacenter": "dc1",
"acl_master_token": "16c5413b-276c-45ab-9b0e-9664126f1161",
"acl_default_policy": "deny"
}
acl_client.json
{
"acl_datacenter": "dc1",
"acl_token": "16c5413b-276c-45ab-9b0e-9664126f1161"
}
我这里比较懒,继续使用之前的docker-compose文件,所以要先关闭之前的容器
$ docker-compose -f compose文件 down
然我们修改一下compose文件,这里为了方便就不加第二个数据中心了
version: '3.6'
services:
consul1:
image: consul:latest
container_name: consul1
restart: always
network_mode: mynet
command: agent -server -client=0.0.0.0 -bootstrap-expect=3 -node=consul1 -config-dir=/home/acl.json
volumes:
- /opt/consul/acl.json:/home/acl.json
consul2:
image: consul:latest
container_name: consul2
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul2 -config-dir=/home/acl.json
volumes:
- /opt/consul/acl.json:/home/acl.json
consul3:
image: consul:latest
container_name: consul3
network_mode: mynet
restart: always
command: agent -server -client=0.0.0.0 -retry-join=consul1 -node=consul3 -config-dir=/home/acl.json
volumes:
- /opt/consul/acl.json:/home/acl.json
consul4:
image: consul:latest
container_name: consul4
network_mode: mynet
restart: always
ports:
- 8500:8500
command: agent -client=0.0.0.0 -retry-join=consul1 -ui -node=client1 -config-dir=/home/acl_client.json
volumes:
- /opt/consul/acl_client.json:/home/acl_client.json
然后启动,这个时候无论是服务注册还是rpc调用都需要有token才能调用,打开ui界面:
http://IP:8500
使用我们刚才的token进入,可以在acl里面创建相应权限的角色,这个在官方网站有详细的说明,然后给这个角色创建一个token,我们的服务注册的时候就可以在配置中加入这个acl_token,然后调用接口或注册服务。
这里说明一下,我这是演示用法,因为是一个虚拟机部署了全部的服务并且一次性全部,所以用的全是同一个token,在实际的生产过程中,流程应该是先启动一台server,然后进入ui界面给每一台server生成一个token并配置相应的权限,然后再启动加入集群,服务实例也是一样,应该给每个服务创建token并配置权限,这样才比较安全
由于同一服务的多个实例常常在微服务体系架构中同时运行,因此我们需要一种策略,以便在处理健康状态的更改、实例数量的更改和集群状态的更改时,均衡地平衡服务的所有健康实例的流量。这是负载均衡层的工作。下面说一下Consul负载均衡的一些常见策略
Consul Directly
使用Consul的内置负载均衡功能。Consul集成健康检查与服务发现。这意味着不健康的主机永远不会从查询返回到服务发现层。在这种模式下,每当应用程序和服务希望在数据中心中找到其他服务时,它们都直接与Consul进行通信。
每当应用程序或内核解析DNS条目时,它将收到一组IP地址的随机round-robin响应,这些地址对应于集群中的健康服务。DNS接口基本上提供了与任何应用程序的零接触服务发现集成。
优点
缺点
Fabio
Fabio是一个开源工具,它为Consul管理的服务提供了快速、现代、零配置负载均衡HTTP(S)和TCP路由器。用户注册服务和健康检查到Consul,fabio将自动路由流量到他们,不需要额外的配置。
优点
缺点
Nginx/HAProxy与Consul Template
Consul负载均衡的另一种方法是使用第三方工具(如Nginx或HAProxy)来平衡流量,以及使用开源工具(如 Consul Template)来管理配置。在这种模式下, Consul Template动态地管理nginx.conf或haproxy.conf配置文件,它定义负载均衡器和服务器列表。这个列表是模板化的,Consul Template作为一个服务运行,以保持模板是最新的。Consul Template与Consul集群有持续的连接,当发生对服务池的更改时, Consul Template写入一个新的配置文件,并向Nginx或HAProxy进程发送信号,以重新加载其配置。
优点
缺点