什么是服务发现

微服务的框架体系中,服务发现是不能不提的一个模块。我相信了解或者熟悉微服务的童鞋应该都知道它的重要性。这里我只是简单的提一下,毕竟这不是我们的重点。我们看下面的一幅图片:

服务发现之consul - 图1

图中,客户端的一个接口,需要调用服务A-N。客户端必须要知道所有服务的网络位置的,以往的做法是配置是配置文件中,或者有些配置在数据库中。这里就带出几个问题:

  • 需要配置N个服务的网络位置,加大配置的复杂性
  • 服务的网络位置变化,都需要改变每个调用者的配置
  • 集群的情况下,难以做负载(反向代理的方式除外)

总结起来一句话:服务多了,配置很麻烦,问题多多
既然有这些问题,那么服务发现就是解决这些问题的。话说,怎么解决呢?我们再看一张图

服务发现之consul - 图2

与之前一张不同的是,加了个服务发现模块。图比较简单,这边文字描述下。服务A-N把当前自己的网络位置注册到服务发现模块(这里注册的意思就是告诉),服务发现就以K-V的方式记录下,K一般是服务名,V就是IP:PORT。服务发现模块定时的轮询查看这些服务能不能访问的了(这就是健康检查)。客户端在调用服务A-N的时候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务。这样的方式是不是就可以解决上面的问题了呢?客户端完全不需要记录这些服务网络位置,客户端和服务端完全解耦!

这个过程大体是这样,当然服务发现模块没这么简单。里面包含的东西还很多。这样表述只是方便理解。

图中的服务发现模块基本上就是微服务架构中服务发现的作用了。

consul 简介

做服务发现的框架常用的有

  • zookeeper
  • eureka
  • etcd
  • consul

这里就不比较哪个好哪个差了,需要的童鞋自己谷歌百度。
那么consul是啥?consul就是提供服务发现的工具。然后下面是简单的介绍:
consul是分布式的、高可用、横向扩展的。consul提供的一些关键特性:

  • service discovery:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。
  • health checking:健康检测使consul可以快速的告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。
  • key/value storage:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。
  • multi-datacenter:无需复杂的配置,即可支持任意数量的区域。

我们这里会介绍服务发现,健康检查,还有一些基本KV存储。多数据中心有机会另一篇文章再说。
总结:只要知道它是解决我上一部分提出的问题就行,其它的东西慢慢理解

consul的几个概念

服务发现之consul - 图3
上图是我从consul官方文档抠出来的。
我们只看数据中心1,可以看出consul的集群是由N个SERVER,加上M个CLIENT组成的。而不管是SERVER还是CLIENT,都是consul的一个节点,所有的服务都可以注册到这些节点上,正是通过这些节点实现服务注册信息的共享。除了这两个,还有一些小细节,一一简单介绍。

  • CLIENT

CLIENT表示consul的client模式,就是客户端模式。是consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到SERVER,本身是不持久化这些信息。

  • SERVER

SERVER表示consul的server模式,表明这个consul是个server,这种模式下,功能和CLIENT都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的。

  • SERVER-LEADER

中间那个SERVER下面有LEADER的字眼,表明这个SERVER是它们的老大,它和其它SERVER不一样的一点是,它需要负责同步注册的信息给其它的SERVER,同时也要负责各个节点的健康监测。

  • 其它信息

其它信息包括它们之间的通信方式,还有一些协议信息,算法。它们是用于保证节点之间的数据同步,实时性要求等等一系列集群问题的解决。这些有兴趣的自己看看官方文档
上图是官网提供的一个事例系统图,图中的Server是consul服务端高可用集群,Client是consul客户端。consul客户端不保存数据,客户端将接收到的请求转发给响应的Server端。Server之间通过局域网或广域网通信实现数据一致性。每个Server或Client都是一个consul agent。Consul集群间使用了GOSSIP协议通信和raft一致性算法。上面这张图涉及到了很多术语:

  • Agent——agent是一直运行在Consul集群中每个成员上的守护进程。通过运行 consul agent 来启动。agent可以运行在client或者server模式。指定节点作为client或者server是非常简单的,除非有其他agent实例。所有的agent都能运行DNS或者HTTP接口,并负责运行时检查和保持服务同步。
  • Client——一个Client是一个转发所有RPC到server的代理。这个client是相对无状态的。client唯一执行的后台活动是加入LAN gossip池。这有一个最低的资源开销并且仅消耗少量的网络带宽。
  • Server——一个server是一个有一组扩展功能的代理,这些功能包括参与Raft选举,维护集群状态,响应RPC查询,与其他数据中心交互WAN gossip和转发查询给leader或者远程数据中心。
  • DataCenter——虽然数据中心的定义是显而易见的,但是有一些细微的细节必须考虑。例如,在EC2中,多个可用区域被认为组成一个数据中心?我们定义数据中心为一个私有的,低延迟和高带宽的一个网络环境。这不包括访问公共网络,但是对于我们而言,同一个EC2中的多个可用区域可以被认为是一个数据中心的一部分。
  • Consensus——在我们的文档中,我们使用Consensus来表明就leader选举和事务的顺序达成一致。由于这些事务都被应用到有限状态机上,Consensus暗示复制状态机的一致性。
  • Gossip——Consul建立在Serf的基础之上,它提供了一个用于多播目的的完整的gossip协议。Serf提供成员关系,故障检测和事件广播。更多的信息在gossip文档中描述。这足以知道gossip使用基于UDP的随机的点到点通信。
  • LAN Gossip——它包含所有位于同一个局域网或者数据中心的所有节点。
  • WAN Gossip——它只包含Server。这些server主要分布在不同的数据中心并且通常通过因特网或者广域网通信。
  • RPC——远程过程调用。这是一个允许client请求server的请求/响应机制。

在每个数据中心,client和server是混合的。一般建议有3-5台server。这是基于有故障情况下的可用性和性能之间的权衡结果,因为越多的机器加入达成共识越慢。然而,并不限制client的数量,它们可以很容易的扩展到数千或者数万台。
  同一个数据中心的所有节点都必须加入gossip协议。这意味着gossip协议包含一个给定数据中心的所有节点。这服务于几个目的:第一,不需要在client上配置server地址。发现都是自动完成的。第二,检测节点故障的工作不是放在server上,而是分布式的。这是的故障检测相比心跳机制有更高的可扩展性。第三:它用来作为一个消息层来通知事件,比如leader选举发生时。
  每个数据中心的server都是Raft节点集合的一部分。这意味着它们一起工作并选出一个leader,一个有额外工作的server。leader负责处理所有的查询和事务。作为一致性协议的一部分,事务也必须被复制到所有其他的节点。因为这一要求,当一个非leader得server收到一个RPC请求时,它将请求转发给集群leader。
  server节点也作为WAN gossip Pool的一部分。这个Pool不同于LAN Pool,因为它是为了优化互联网更高的延迟,并且它只包含其他Consul server节点。这个Pool的目的是为了允许数据中心能够以low-touch的方式发现彼此。这使得一个新的数据中心可以很容易的加入现存的WAN gossip。因为server都运行在这个pool中,它也支持跨数据中心请求。当一个server收到来自另一个数据中心的请求时,它随即转发给正确数据中想一个server。该server再转发给本地leader。
  这使得数据中心之间只有一个很低的耦合,但是由于故障检测,连接缓存和复用,跨数据中心的请求都是相对快速和可靠的。

consul 基本使用

自己就一台机子,所以这里就演示下docker下部署使用consul。容器与宿主机的端口映射忽略,正常生产环境每个宿主机一个consul,端口需要映射到宿主机

部署

拉取镜像

  1. docker search consul

咱们用官方的镜像玩玩

  1. docker pull consul

不指定tag就拉取last,当前版本是0.8.0

启动consul

  • 启动节点1(server模式)

    1. docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node1 consul agent -server -bind=172.17.0.2 -bootstrap-expect=3 -node=node1

    -node:节点的名称, 每个结点ID, 必须是唯一的 -bind:绑定的一个地址,用于server节点之间通信的地址,可以是内外网,必须是可以访问到的地址 -client: 客户端的ip地址 -server:以server身份启动 -bootstrap-expect:这个就是表示期望提供的SERVER节点数目,集群要求的最少server数量,当低于这个数量,集群即失效。 -data-dir:data存放的目录,更多信息请参阅consul数据同步机制

  • 启动节点2-3(server模式)

    1. docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node2 consul agent -server -bind=172.17.0.3 -join=172.17.0.2 -node-id=$(uuidgen | awk '{print tolower($0)}') -node=node2
    2. docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node3 consul agent -server -bind=172.17.0.4 -join=172.17.0.2 -node-id=$(uuidgen | awk '{print tolower($0)}') -node=node3 -client=172.17.0.4

    -join:这个表示启动的时候,要加入到哪个集群内,这里就是说要加入到节点1的集群 -node-id:这个貌似版本8才加入的,这里用这个来指定唯一的节点ID,可以查看这个issue -client:这个表示注册或者查询等一系列客户端对它操作的IP,如果不指定这个IP,默认是127.0.0.1。

  • 启动节点4(client模式)

    1. docker run -d -e 'CONSUL_LOCAL_CONFIG={"leave_on_terminate": true}' --name=node4 consul agent -bind=172.17.0.5 -retry-join=172.17.0.2 -node-id=$(uuidgen | awk '{print tolower($0)}') -node=node4

    除了没有-server,其它都是一样的,没有这个就说明这个节点是CLIENT

  • 查看下集群的状态

    1. docker exec -t node1 consul members


    服务发现之consul - 图4

    4个节点都列出来了。Status表示它们的状态,都是alive。Type表示它们的类型,三个SERVER一个CLIENT,和我们之前启动的一样。DC表示数据中心,都是dc1。
    docker exec -t node1 consul info | grep leader
    服务发现之consul - 图5

    其中,ip 地址172.17.0.2是leader, 也就是node1.

  • 节点异常consul的处理

  • LEADER 挂了
    leader挂了,consul会重新选取出新的leader,只要超过一半的SERVER还活着,集群是可以正常工作的。node1是leader,所以把这个容器停了。

    1. docker stop node1
  • 看看其他节点的日志(node2):
    docker attach node3

    服务发现之consul - 图6


日志打印,心跳检查node1的ip超时,接着开始选举。node3被选举为新的leader。我们查看下现在的leader:

  1. curl http://172.17.0.3:8500/v1/status/leader

返回的内容:

  1. "172.17.0.4:8300"

172.17.0.4 就是 node3节点的IP

(补充内容)节点与宿主机的端口映射

指定node1与宿主机(192.168.99.100)的端口映射

  1. docker run -d --net=host -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node1 -p 8300:8300 -p 8301:8301 -p 8301:8301/udp -p 8302:8302/udp -p 8302:8302 -p 8400:8400 -p 8500:8500 -p 53:53/udp -h consul consul agent -server -bind=172.17.0.2 -bootstrap -node=node1

node2进入的IP指定为宿主机的IP

  1. docker run -d -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' --name=node2 consul agent -server -bind=172.17.0.3 -join=192.168.99.100 -node-id=$(uuidgen | awk '{print tolower($0)}') -node=node2

正常加入,node1的日志:

  1. 2017/04/09 05:53:08 [INFO] serf: EventMemberJoin: node2 172.17.0.3
  2. 2017/04/09 05:53:08 [INFO] consul: Adding LAN server node2 (Addr: tcp/172.17.0.3:8300) (DC: dc1)
  3. 2017/04/09 05:53:08 [INFO] raft: Updating configuration with AddStaging (172.17.0.3:8300, 172.17.0.3:8300) to [{Suffrage:Voter ID:172.17.0.2:8300 Address:172.17.0.2:8300} {Suffrage:Voter ID:172.17.0.3:8300 Address:172.17.0.3:8300}]
  4. 2017/04/09 05:53:08 [INFO] raft: Added peer 172.17.0.3:8300, starting replication
  5. 2017/04/09 05:53:08 [INFO] consul: member 'node2' joined, marking health alive
  6. 2017/04/09 05:53:08 [INFO] serf: EventMemberJoin: node2.dc1 172.17.0.3
  7. 2017/04/09 05:53:08 [INFO] consul: Handled member-join event for server "node2.dc1" in area "wan"
  8. 2017/04/09 05:53:08 [WARN] raft: AppendEntries to {Voter 172.17.0.3:8300 172.17.0.3:8300} rejected, sending older logs (next: 1)
  9. 2017/04/09 05:53:08 [INFO] raft: pipelining replication to peer {Voter 172.17.0.3:8300 172.17.0.3:8300}
  1. Node Address Status Type Build Protocol DC
  2. node1 172.17.0.2:8301 alive server 0.8.0 2 dc1
  3. node2 172.17.0.3:8301 alive server 0.8.0 2 dc1

使用

部署完了,那么可以看看怎么用这个东东了。

注册个服务

使用HTTP API 注册个服务,使用接口APIAPI)调用
调用 http://consul:8500/v1/agent/service/register PUT 注册一个服务。request body:

  1. {
  2. "ID": "userServiceId", //服务id
  3. "Name": "userService", //服务名
  4. "Tags": [ //服务的tag,自定义,可以根据这个tag来区分同一个服务名的服务
  5. "primary",
  6. "v1"
  7. ],
  8. "Address": "127.0.0.1",//服务注册到consul的IP,服务发现,发现的就是这个IP
  9. "Port": 8000, //服务注册consul的PORT,发现的就是这个PORT
  10. "EnableTagOverride": false,
  11. "Check": { //健康检查部分
  12. "DeregisterCriticalServiceAfter": "90m",
  13. "HTTP": "http://www.baidu.com", //指定健康检查的URL,调用后只要返回20X,consul都认为是健康的
  14. "Interval": "10s" //健康检查间隔时间,每隔10s,调用一次上面的URL
  15. }
  16. }

使用curl调用

  1. curl http://172.17.0.4:8500/v1/agent/service/register -X PUT -i -H "Content-Type:application/json" -d '{
  2. "ID": "userServiceId",
  3. "Name": "userService",
  4. "Tags": [
  5. "primary",
  6. "v1"
  7. ],
  8. "Address": "127.0.0.1",
  9. "Port": 8000,
  10. "EnableTagOverride": false,
  11. "Check": {
  12. "DeregisterCriticalServiceAfter": "90m",
  13. "HTTP": "http://www.baidu.com",
  14. "Interval": "10s"
  15. }
  16. }'

OK,注册了一个服务

发现个服务

刚刚注册了名为userService的服务,我们现在发现(查询)下这个服务

  1. curl http://172.17.0.4:8500/v1/catalog/service/userService

返回的响应:

  1. [
  2. {
  3. "Address": "172.17.0.4",
  4. "CreateIndex": 880,
  5. "ID": "e6e9a8cb-c47e-4be9-b13e-a24a1582e825",
  6. "ModifyIndex": 880,
  7. "Node": "node3",
  8. "NodeMeta": {},
  9. "ServiceAddress": "127.0.0.1",
  10. "ServiceEnableTagOverride": false,
  11. "ServiceID": "userServiceId",
  12. "ServiceName": "userService",
  13. "ServicePort": 8000,
  14. "ServiceTags": [
  15. "primary",
  16. "v1"
  17. ],
  18. "TaggedAddresses": {
  19. "lan": "172.17.0.4",
  20. "wan": "172.17.0.4"
  21. }
  22. }
  23. ]

内容有了吧,这个就是我们刚刚注册的服务的信息,就可以获取到

服务的名称是“userService” 服务地址是“127.0.0.1” 服务的端口是“8000”

存储个K/V

设置一个值到user/config/connections 内容为5

  1. docker exec -t node1 consul kv put user/config/connections 5

获取特定的值
 docker exec -t node1 consul kv get user/config/connections
详细信息  

  1. docker exec -t node1 consul kv get -detailed user/config/connections

服务发现之consul - 图7

值的内容为5,还有key等相关的值

总结

服务发现以及配置共享的简单样例展示了下,详细的使用还是需要看官方文档,这里只是列举了一些样例,用于理解和简单的使用consul。

Spring Cloud 结合consul使用

如果是使用spring cloud来使用consul,可以查看我的相关样例:http://git.oschina.net/buxiaoxia/spring-demo
spring cloud 结合consul的使用,下一篇文章再进行描述吧

相关文档连接

CONSUL:https://www.consul.io/
CONSUL HTTP API:https://www.consul.io/api/index.html
CONSUL CLI:https://www.consul.io/docs/commands/info.html
CONSUL Health Checks:https://www.consul.io/docs/agent/checks.html