微服务管理及consul使用
服务发现
为什么要使用服务发现
设想下,我们写了一些通过REST API或者Thrift API调用某个服务的代码,为了发起这个请求,代码需要知道服务实例的网络地址(IP 地址和端口号)。在传统运行在物理机器上的应用中,某个服务实例的网络地址一般是静态的,比如,代码可以从只会偶尔更新的配置文件中读取网络地址。
然而在现在流行的基于云平台的微服务应用中, 有更多如下图所示的困难问题需要去解决:
服务实例需要动态分配网络地址,而且,一组服务实例可能会因为自动扩展、失败或者升级发生动态变化,因此客户端代码应该使用更加精细的服务发现机制。
在生产实践中,主要有两种服务发现机制:客户端发现和服务端发现。我们分别来介绍这两种机制:
客户端发现模式
当我们使用客户端发现的时候,客户端负责决定可用服务实例的网络地址并且在集群中对请求负载均衡, 客户端访问服务登记表,也就是一个可用服务的数据库,然后客户端使用一种负载均衡算法选择一个可用的服务实例然后发起请求。该模式如下图所示:
服务实例的网络地址在服务启动的时候被登记到服务注册表中,当实例终止服务时从服务注册表中移除。服务实例的注册一般是通过心中机制阶段性的时行刷新。
客户端发现机制的优势:
该模式中只增加了服务注册表,整体架构也相对简单;
客户端可以使用更加智能的,特定于应该的负载均衡机制,如一致性哈希。
客户端发现机制的缺点:
客户端发现机制中,客户端与服务注册表紧密耦合在一起,开发者必须为每一种消费服务的客户端对应的编程语言和框架版本都实现服务发现逻辑。
客户端发现模式的应用:
往往大公司会采用客户端发现机制来实现服务的发现与注册的模式。
服务端发现模式
与客户端发现模式对应的,另外一种服务发现模式称之为服务端发现模式,整体架构如下:
客户端通过一个负载均衡器向服务发送请求,负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。和客户端发现一样,服务实例通过服务注册表进行服务的注册和注销。
服务端发现模式的优势和缺点:
服务端发现模式的优点:
服务发现的细节对客户端来说是抽象的,客户端仅需向负载均衡器发送请求即可。
这种方式减少了为消费服务的不同编程语言与框架实现服务发现逻辑的麻烦,很多部署环境已经提供了该功能。
服务端发现模式的缺点:
除非部署环境已经提供了负载均衡器,否则这又是一个需要额外设置和管理的可高可用的系统组件。
服务注册表
服务实例必须使用服务注册表来进行服务的注册和注销,在实践过程中有不同的方式来实现服务注册和注销:
self-registration模式:这种模式下,服务实例自己负责通过服务注册表对自己进行注册和注销,另外如果有必要的话,服务实例可以通过发送心跳包请求防止注册过期。该种模式的架构实现如下:
self-registration模式有一些优势也有一些劣势:优势之一是它相对简单,而且不强制使用其他的系统组件。然而,一个很大的劣势是 它使得服务实例和服务注册表强耦合 ,你必须在每一个使用服务的客户端编程语言和架构代码中实现注册逻辑。
third-party registration模式:当使用third-party registration模式的时候,服务实例本身并不负责通过服务注册表注册自己,相反的,通过另一个被称作service registrar系统组件来处理注册。service registrar通过轮询或者订阅事件来检测一些运行实例的变化,当它检测到一个新的可用服务实例时就把该实例注册到服务注册表中去,service registrar还负责注销已经被终止的服务实例,下图展示了该模式的架构:
third-party registration模式也有一些优势和劣势:主要优势是使得服务从服务注册表中被解耦,你不必为开发者使用的每种开发语言和框架实现服务注册的逻辑,相反,服务实例的注册被一个专有服务以集中式的方式处理。该模式的劣势是,除非它被内置在部署环境中,不然这又是一个需要被设置和管理的高可用系统组件。
总结
在一个微服务应用中,一组运行的服务实例是动态变化的,实例有动态分配的网络地址,因此,为了使得客户端能够向服务发起请求,必须要要有服务发现机制。
服务发现的关键是服务注册表,服务注册表是可用服务实例的数据库,它提供了管理和查询使用的API。服务实例使用这些管理API进行服务的注册和注销,系统组件使用查询API来发现可用的服务实例。
客户端发现的案例:Eureka、ZooKeeper
服务端发现的案例:consul+nigix
Consul
Consul概述
consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务,consul属于微服务架构的基础设置中用于发现和配置服务的一个工具。Consul提供如下的几个核心功能:
服务发现:Consul的某些客户端可以提供一个服务,其他客户端可以使用Consul去发现这个服务的提供者。
健康检查:Consul客户端可以提供一些健康检查,这些健康检查可以关联到一个指定的服务,比如心跳包的检测。
键值存储:应用实例可以使用Consul提供的分层键值存储,比如动态配置,特征标记,协作等。通过HTTP API的方式进行获取。
多数据中心:Consul对多数据中心有非常好的支持。
主从模式分布式系统
分布式系统中,一般采用主从模式进行部署
单点故障
通常分布式系统采用主从模式,就是一个主控机连接多个处理节点。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,整个系统就瘫痪了。这就是单点故障。
单点故障(single point of failure),从英文字面上可以看到是单个点发生的故障,通常应用于计算机系统及网络。实际指的是单个点发生故障的时候会波及到整个系统或者网络,从而导致整个系统或者网络的瘫痪。这也是在设计IT基础设施时应避免的。
对应到上文,我们所说的服务注册与发现组件,如果我们引入的服务注册与发现组件出现了问题,则会导致系统乃至整个链路瘫痪,这是不可容忍的。需要采用新的方案解决此问题。
传统解决方案
传统的解决方案是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包以后向备用节点发送回复ACK,当备用节点收到回复时就会认为当前主节点还活着,让他继续提供服务。
当主节点停止服务以后,这个时候备用节点收不到回复了,备用主节点认为主节点就宕机了,备用节点会代替主节点成为主节点,如下图:
但是这种方案有个问题,如果仅仅是网络故障引起的ACK返回延时,这种方案就会面临着同时存在两个主节点的问题。
Consul中的Raft
Raft是一种基于Paxos的一致性算法。和Paxos相比,Raft的状态更少,算法更简单易懂。
Raft中的节点总是处于以下三种状态之一: follower、candidate或leader。所有的节点最初都是follower。在这种状态下,节点可以接受来自leader的日志条目并进行投票。如果在一段时间内没有收到条目,节点将自动提升到候选状态。在候选状态中,节点请求同级的选票。如果一个候选人获得了法定人数的选票,那么他就被提升为领袖。领导者必须接受新的日志条目,并将其复制给所有其他的追随者。此外,如果不能接受过时的读取,则还必须对leader执行所有查询。
Consul内部原理
我们可以通过如下的原理图来理解Consul的原理:
首先Consul支持多数据中心,在上图中有两个DataCenter,他们通过Internet互联,同时请注意为了提高通信效率,只有Server节点才加入跨数据中心的通信。
在单个数据中心中,Consul分为Client和Server两种节点(所有的节点也被称为Agent),Server节点保存数据,Client负责健康检查及转发数据请求到Server。
Server节点有一个Leader和多个Follower,Leader节点会将数据同步到Follower,Server的数量推荐是3个或者 5个,在Leader挂掉的时候会启动选举机制产生一个新的 Leader。
集群内的Consul节点通过gossip协议(流言协议)维护成员关系,也就是说某个节点了解集群内现在还有哪些节点,这些节点是Client还是Server。
单个数据中心的流言协议同时使用TCP和UDP通信,并且都使用8301端口。跨数据中心的流言协议也同时使用TCP和UDP 通信,端口使用8302。
集群内数据的读写请求既可以直接发到Server,也可以通过Client使用RPC转发到Server,请求最终会到达Leader节点。
在允许数据轻微陈旧的情况下,读请求也可以在普通的Server节点完成,集群内数据的读写和复制都是通过TCP的8300端口完成。
Consul服务发现原理
1、部署集群。首先需要有一个正常的Consul集群,有Server,有Leader。这里在服务器Server1、Server2、Server3上分别部署了Consul Server。
2、选举Leader节点。假设他们选举了Server2上的 Consul Server 节点为Leader。这些服务器上最好只部署Consul程序,以尽量维护Consul Server的稳定。
3、注册服务。然后在服务器Server4和Server5上通过Consul Client分别注册Service A、B、C,这里每个Service 分别部署在了两个服务器上,这样可以避免Service的单点问题。服务注册到Consul可以通过 HTTP API(8500 端口)的方式,也可以通过 Consul 配置文件的方式。
4、Consul client转发注册消息。Consul Client 可以认为是无状态的,它将注册信息通过RPC转发到Consul Server,服务信息保存在Server的各个节点中,并且通过Raft实现了强一致性。
5、服务发起通信请求。最后在服务器Server6中Program D需要访问Service B,这时候Program D首先访问本机Consul Client提供的HTTP API,本机Client会将请求转发到 Consul Server。
6、Consul Server查询到Service B当前的信息返回,最终Program D拿到了Service B的所有部署的IP和端口,然后就可以选择Service B的其中一个部署并向其发起请求了。
启动Consul
consul agent -dev
查看consul节点信息
在consul启动后,可以通过命令查看节点的信息。在原有已经启动consul的终端窗口之外,重新开启新的终端窗口,执行如下命令:
consul members
UI界面访问
终端命令行下启动consul的dev模式后,通过members命令查看节点信息,除此以外,还可以使用Http的浏览器访问的模式,查看节点信息。
consul启动,正常运行后,打开浏览器,在地址栏中键入:http://localhost:8500。可以查看节点信息
consul dev模式示意图
上诉consul agent -dev模式下的启动与运行consul节点。集群中只包含一个节点,唯一的节点被选举成为Leader节点。
定义一个服务
服务的定义通过一个.json的json文件来进行定义,该文件中使用json格式定义所要注册服务的相关内容,以下为服务的json格式示例:
{ "service": { "id": "firstservice", "name": "firstservice", "tags": ["dev"], "port": 80, } }
服务注册
sudo mkdir /etc/consul.d
说明:.d做后缀,表示一系列配置文件的存放目录
vim firstservice.json
说明:每一个服务都是以json文件格式的形式被单独声明在一个文件中,然后集中放到一个目录下。供consul启动时读取。
mv firstservice.json /etc/consul.d/
通过如上命令将自定义的firstservice.json服务文件移动至集中存放consul集群启动时要启动的服务目录中,即/etc/consul.d
服务查询
1、启动consul
由于我们添加了服务,启动的服务是以配置文件的形式进行配置的,因此,在启动时有必要指定服务配置文件所对应的目录,如下所示:
consul agent -dev -config-dir /etc/consul.d/
2、服务查询
服务的查询支持两种方式的查询,分别为:DNS和HTTP
a、第一种:DNS
dig @127.0.0.1 -p 8600 dev.firstservice.service.consul
-
1、dev.firstservice.service.consul是固定的格式组合,具体格式为:tag.servicename.service.consul,即tag和servicename为服务创建时自定义配置内容。
-
2、DNS访问的端口是8600
b、第二种:HTTP
curl http://localhost:8500/v1/catalog/service/firstservice
- 1、HTTP访问路径:host:port/版本号/service/服务名。
- 2、Address:用于指定一个特定的Service的IP地址,默认情况下,使用的是该service使用的agent。
注册多个服务
1、每一个服务分别写一个json文件
{ "service": { "id": "secondservice", "name": "secondservice", "tags": ["dev"], "port": 80, } }
2、将多个服务写到一个json文件中
{ "services": [ { "id": "firstservice", "name": "firstservice", "tags": ["dev"], "port": 80 }, { "id": "secondservice", "name": "secondservice", "tags": ["dev"], "port": 80 } ] }
在实际的开发过程中,微服务数量众多。如果每个文件都放在一个文件里,文件会非常多;而如果所有服务都放在一个文件里,文件太大,也不合适。因此,在实践中,往往二者结合使用。例如,假设有100个微服务,则,放在10json文件中,每个文件中放10个微服务。
Docker安装Consul
docker pull consul
检验Docker安装Consul成功
在Docker中安装consul可以,可以通过如下命令查看docker中安装consul是否成功:
docker images
或者
docker run consul version
Docker中启动一个单独节点consul agent
Docker中安装好了consul以后,首先尝试启动一个server节点,可以通过如下命令来启动docker中的单个节点:
$ docker run -p 8500:8500/tcp consul agent -server -ui -bootstrap-expect=1 -client=0.0.0.0
如上的命令中,参数说明如下:
-
暴露了端口,分别是:HTTP端口:8500
-
-h:对应的node1为节点的名称
-
-server:表示启动的节点类型为server类型
-
-bootstrap-expect:用于server节点选举leader的参数,表示达到几个server节点时开始选举
在暴露的http端口中,还对应的映射到了主机的端口上,因此,我们可以通过在主机中访问server的信息。比如:
-
curl访问HTTP接口:
curl localhost:8500/v1/catalog/nodes
dig来和DNS接口进行交互:
dig @0.0.0.0 -p 8600 node1.node.consul
宿主机上查看节点数量
consul members
Docker搭建Consul集群
一台主机上搭建Consul集群并测试
借助Docker容器,已经启动了一个server节点,并能够与之通信。
接下来,希望借助Docker来搭建consul集群。以启动3个consul集群节点为例:
1、启动第一个节点
启动第一个节点的时候没有使用了 -bootstrap 参数, 而是使用了 -bootstrap-expect 3, 使用这个参数节点会等到所有三个端都连接到一起了才会启动并且成为一个可用的cluster。
$ docker run -d -p 8500:8500 -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_1 consul agent -server -bootstrap -ui -node=1 -client='0.0.0.0'
对如上的参数做如下说明:
-
- ui:表示启动 Web UI 管理器,默认开放端口 8500,可以在浏览器进行访问。
- –name
2、查看节点IP
我们需要知道这个container的内部IP, 使用下面的命令我们吧这个IP放到了环境变量 JOIN_IP 里。
$ JOIN_IP="$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' node1)"
也可以直接查看container的IP,如下命令:
$docker inspect -f '{{ .NetworkSettings.IPAddress }}' node1
3、启动第二个节点
启动 node2并且告诉他通过 $JOIN_IP 加入到 node1:
$ docker run -d -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_2 consul agent -server -node=2 -join='172.17.0.2'
4、启动第三个节点
按照同样的方法我们启动 node3:
$ docker run -d -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_3 consul agent -server -node=3 -join='172.17.0.2'
现在我们就有了一个拥有3个节点的运行在一台机器上的集群。注意,根据Consul Agent的名字给container起了名字。
我们没有暴露出任何一个端口用以访问这个集群, 但是我们可以使用第四个agent节点以client的模式(不是用 -server参数)。这意味着他不参与选举但是可以和集群交互。而且这个client模式的agent也不需要磁盘做持久化。
$ docker run -d -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_4 consul agent -client -node=4 -join='172.17.0.2' -client='0.0.0.0' $ docker run -d -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_5 consul agent -client -node=5 -join='172.17.0.2' -client='0.0.0.0' $ docker run -d -e CONSUL_BIND_INTERFACE='eth0' --name=consul_server_6 consul agent -client -node=5 -join='172.17.0.2' -client='0.0.0.0'
如果上述命令都能执行成功,就意味着我们的集群搭建成功了。
查看集群的状态
命令行查看节点状态:
在终端下执行如下命令:
$consul members
或者是
$docker exec consul_server_1 consul members
停止节点
活动容器状态查看
使用docker ps命令可以输出当前运行活动中的容器:
$docker ps
停止容器活动
可以使用如下命令将目前正处于活动中的容器停止:
$docker stop containerID
如果要停止多个,可以用空格隔开。
移除容器
如果想要彻底移除启动的节点容器,可以通过rm命令来实现:
$docker rm containerID
微服务定义
consul常用命令及选项
1、常用命令:command
consul命令的使用形式为:
consul command [option]
- agent:consul的节点分为client和server两类,这两类节点统称为agent节点。
- join:该命令的作用是将agent加入到consul的集群当中。当新启动一个agent节点后,往往需要指定节点需要加入到特定的consul集群中,此时使用join命令进行指定。
- members:列出consul集群中的所有的成员节点信息,包括ip,端口,状态,类型等信息。
2、常用选项:option
除command命令外,还有option选项供开发者使用,常见的和常使用的option有:
- -data-dir:该选项用于指定agent储存状态的数据目录,这是所有agent都必须的,对于server尤其重要,因为他们必须持久化集群的状态。
- -config-dir:该选项用于指定service的配置文件和检查定义所在的位置。通常会指定为"某一个路径/consul.d"(通常情况下,.d表示一系列配置文件存放的目录)
- -config-file:指定一个要装载的配置文件。该选项可以配置多次,进而配置多个配置文件。
- -dev:该选项用于创建一个开发环境下的server节点,该参数配置下,不会有任何持久化操作,即不会有任何数据写入到磁盘。dev模式仅仅是在开发和测试环境中使用,不能用于生产环境。
- -bootstrap-expect:该选项用于通知consul server类型节点,指定集群的server节点个数,该参数是为了延迟选举启动,直到全部的节点启动完毕以后再进行启动。
- -node:该node选项用于指定节点在集群中的名称,该名称在集群中需要是唯一的,推荐直接使用机器的IP。
- -bind:该选项用于指定节点所在的IP地址。
- -server:该选项用于指明consul节点类型为server类型。每个数据中心(DC)的server数量推荐3到5个。所有的server节点加入到集群后要经过选举,采用raft一致性算法来确保数据操作的一致性。
- -client:该参数用于指定consul界定为client节点类型。
- -join:英文为加入的意思,join选项用于指定要将节点添加到具体哪个集群中。
- -dc:dc是datacenter的简称,该选项用于指定节点加入的dc实例。
微服务定义标准及选项
除了命令行选项,微服务的定义和配置也可以放入文件中。在某些情况下,这可能更容易,比如当使用配置管理系统配置时。配置文件是JSON格式的,使得它们易于被人和计算机读取和编辑。配置被格式化为单个JSON对象,其中包含配置。
配置文件不仅用于设置代理,还用于提供检查和服务定义。这些配置文件同样可以被其他软件和功能所识别。它们分别记录在检查配置和服务配置下。服务和检查定义支持在重新加载期间更新。
{ "datacenter": "east-aws", "data_dir": "/opt/consul", "log_level": "INFO", "node_name": "foobar", "server": true, "watches": [ { "type": "checks", "handler": "/usr/bin/health-check-handler.sh" } ], "telemetry": { "statsite_address": "127.0.0.1:2180" } }