基于Consul的服务注册与发现

  created  by  鱼鱼 {{tag}}
创建于 2019年09月05日 09:09:56 最后修改于 2020年01月10日 23:41:07

注:文章基于Consul1.6.0版本,部分版本可能会有误差。本文中项目集成部分采用Java语言。

Consul

什么是服务注册与发现?

    consul官网,服务注册/发现是微服务架构中不可或缺的重要组件,起初服务都是单节点的甚至是单体服务,不保障高可用性,也不考虑服务的压力承载,服务之间调用单纯的通过接口访问(HttpClient/RestTemplate),直到后面出现了多个节点的分布式架构,起初的解决手段是在服务端负载均衡,同时在网关层收束接口,使不同的请求转发到对应不同端口上,这也是前后分离防止前端跨域的手段之一:

图中的B服务也可以是多节点,注册在nginx上面的

    要命的是,nginx并不具有服务健康检查的功能,服务调用方在调用一个服务之前是无法知悉服务是否可用的,不考虑这一点分布式的架构高可用的目标就成了一个摆设,解决手段也很简单:对超时或是状态码异常的请求进行重试尝试,请求会被分发到其他可用节点,或者采用服务注册与发现机制察觉健康的服务。

服务注册与发现技术选型

    首先,注册中心的解决方案很多,例如常见的zookeeper,Cloud的重磅伙伴Eureka(在我写这篇文章时Eureka2.0已经闭源,因此不建议后续项目使用此组件)、etcd以及本文中提及的Consul,相关技术的特性见下表:

技术栈 功能和特点 CAP 通信方式(协议) 一致性保障 开发语言
Eureka 至今使用最为广泛,但是1.0不再维护,2.0已经闭源,新项目不适合使用Eureka AP http - Java
zookeeper 功能最强大的组件,三方支持也很丰富,分布式协调,但是server节点宕机会有不可用时间,靠长连接维持健康检查,提高了复杂性 CP sdk paxos Java
etcd 仅是一个分布式的键值对存储系统,若要实现服务注册功能需要其他的支持 CP/AP http/grpc raft go
Consul 开箱即用,支持多数据中心,UI界面良好,,反馈时效性略差 CP http(s)/dns raft go

    Eureka是很优秀的服务注册组件,但是Netflix将2.0闭源后,便已不是最优选择,不过现有的项目使用Eureka 1.x功能已足够使用,不必急于更换技术栈。

    zookeeper是一个很强大的k-v组件,功能应是这些技术中最多的,但是我们只需要服务注册的组件,paxos的一致性算法也不如raft高效,保持长连接会对服务造成一定的影响。

    etcd其实只是一个k-v系统,需要额外添加对服务发现的支持。

Consul中的服务发现

    consul在Java开发者眼中只有一个会被用到的功能:服务注册与发现,即Service Discovery,其核心流程如下:

    在consul中,假使是B服务调用A服务,其大致流程即为:

    A服务调用consul的API注册自己 -> consul定时检查A服务状态(默认)或A服务本身与consul维持心跳请求 -> B服务通过consul提供的DNS调用A服务(默认)或从consul获取健康的服务列表并通过某种算法选择合适的服务调用。

架构设计

Consul建议的架构

    基于Consul的leader选举模式,Consul官方建议的是consul-server采用3或5节点来保证可用性,而每台client注册在本地的服务器上负责健康检查和向server上报信息,不同概念的应用处于不同的数据中心下。下图源自Consul官方站点:

设计原则

    基于consul服务注册的整体的系统架构设计千变万化,但是无论如何都应尽量遵守如下的原则:

  • 对于多个服务集群,或是环境,为便于配置也为了更加直观,应当使用多个consul数据中心;

  • 基于consul的特性,应当使用每个独立服务器部署client,client负责健康检查并注册自己到server的,切忌直接将服务注册至server节点上,这样会失去可用性的特性;

  • 对于同一个service的服务们应保持一致的服务状态、功能和tag列表(也就是提供完全一致的服务),同样的也不能将同功能的服务注册到不同的service中去。不能重复注册;

  • 用于服务健康检查的接口应是也必须是本服务的;

常见的架构:使用nginx反向代理

    基于此,我们设计了一个基于nginx的网关层服务发现与注册(前后端通用通信接口,这不是标准的做法,其实nginx做网关负载均衡更适合在前端使用,而后台应通过consul本身提供的服务列表去直接调用具体的服务。但是考虑到应用已经归集好,后者要做的工作量稍大,所以选择了基于nginx的负载均衡),拟定的架构如下:

    图中展示的是一个服务集群,可以理解为一个数据中心的片区。

    此种方式优缺点很明显:服务端与客户端均使用nginx反向代理,http协议和基于nginx的API网关性能都比较差,性能问题注定了nginx反代只适用于前端(用于),服务间的调用应使用为后台服务定制的API网关或是通过其他协议(例如RPC)调用。

    可能后台间基于nginx网关的调用是种错误的手段,但是依旧有很多公司采用此种架构,毕竟在经历性能瓶颈前,这的确是种省时省力的选择。

Quick Start-集成步骤

    在上文中架构的基础上,后台间的调用可以直接通过调用Consul提供的API获取接口直连的地址,进而直接调用相应的服务,此处部署三台server。

    此模块仅介绍如何最简便的启动consul集群和应用集成,详细的配置在后文有介绍,包含多数据中心的使用。

准备应用

    提取consul相关的安装包,这里使用1.6.0:

wget 
unzip consul_1.6.0_linux_amd64.zip 
mv consul /usr/local/bin/

部署consul集群

    对于Consul,可以直接使用命令行(不推荐),也可以使用指定配置文件的方式。

使用配置文件

        定义一个配置文件consul-config.json,这里罗列了一些基本配置,对于一个server:

{
    "datacenter": "dc1",    //数据中心 
    "data_dir": "/var/lib/consul",      //存放consul数据(持久化)的路径
    "node_name": "testS0",		//本consul节点名
    "server": true,               //开启server模式,反之为client
    "ui": true,                    //开启UI页面,即8500端口根路径访问,一般一个节点开启即可
    "bootstrap_expect": 1,       //集群最小server数,当server小于此数不能consul集群将正常提供服务
    "bind_addr": "10.120.0.1",   //集群内部通讯ip,一般是内网ip	
    "client_addr": "0.0.0.0", //绑定client地址,默认是127.0.0.1,即只能本地访问,想要全部访问则指定为0.0.0.0
    //集群展现地址,对于不在一个网域下的服务,使用此地址相互访问,否则使用bind_addr
    "advertise_addr": "123.207.160.177",    
    "enable_debug": false,        //debug,默认关闭
    /开启时加入的节点,可以包含自己,不必包含client的ip
    "start_join":["10.120.0.1","10.120.0.2","10.120.0.3"]  
}

        启动server,注意这里consul需要后台启动并支付指定配置文件:

nohup consul agent -config-file consul-config.json >consul.log &

        启动剩余两个server,注意修改相关配置

        观察日志,待server启动并都加入集群成功后(也可直接访问UI页面查看),分别启动client,client配置如下:

{
    "datacenter": "dc1",    
    "data_dir": "/var/lib/consul",      
    "node_name": "testC0",		
    "server": false,               
    "ui": false,                   
    "bootstrap_expect": 1,       
    "bind_addr": "10.120.0.6",   
    "client_addr": "0.0.0.0",    
    "advertise_addr": "123.207.160.175",    
    "enable_debug": false,        
    "start_join":["10.120.0.1","10.120.0.2","10.120.0.3"]   
}

        启动脚本与server相同。

集成内容

    应用集成consul主要需要两方面的集成:

  1. 为应用添加endpoint(暴露接口),确保其能在服务功能正常时,此端点能返回http状态码200。可以使用现有的接口例如版本接口或是功能接口(不建议),比较建议的方式是使用actuator的health端点。

  2. 为应用添加服务注册的http调用,对于Springboot,Java有其集成好的starter,也可以手写注册流程,可以说是个一次性的方法,但是不建议写在应用外或采用手动注册的方式。

引入依赖和配置

    接下来以Springboot项目并使用actuator健康检查为例:

1. 引入相关依赖,注意SpringCloud版本的对应,version的不匹配可能导致服务注册过程失效:

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-dependencies</artifactId>
         <version>Greenwich.RELEASE</version>
         <type>pom</type>
      </dependency>
   </dependencies>
</dependencyManagement>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-consul-discovery</artifactId>
   <version>2.0.0.RELEASE</version>
</dependency>

2. 引入相关配置,注意因版本不同引起的配置不同。注意,一个Java服务只能读取到本地的内网ip,如果有跨域(跨局域网),那需要手动指定服务本身的外网ip作为注册的访问地址,即如下的指定hostname为外网ip同时不指定prefer-ip-address。

spring:
    application:
        #非必选项
      name: test-project
    cloud:
      consul:
        discovery:
          #注册服务名,即serviceId
          serviceName: test-project
          #健康检查地址,指定一个返回200状态码的接口,注意这个项目配置了contextpath
          health-check-path: ${server.servlet.context-path}/actuator/info
          #注册的hostname,依此进行服务调用和健康检查,不指定则使用DNS将服务器主机名与内网ip绑定映射关系
          #hostname: 10.0.0.1
          #是否使用自动ip注册,优先级大于hostname,会自动使用内网ip作为注册
          prefer-ip-address: true
          #是否注册
          register: true
          #指定instanceId,本身也有一个默认值
          instance-id: ${spring.application.name}-${server.port}
        #注册中心地址因为上生产几乎必然采用client本地注册的方式,所以可以不指定,保持默认的localhost    
        #host: 10.0.0.1
        port: 8500
        enabled: true

    在成功启动项目后,服务便会将本身信息注册给Consul。

跨局域网注册

    原则上所有的项目都应处在一片局域网下,这样Consul提供的DNS或是ip注册才能发挥其作用,否则跨局域网会访问受阻,在不搭建专线或是vpn的前提下,我们需要修改注册上报的url。即上面的配置文件中,指定hostname为外网ip同时不指定prefer-ip-address,同时要关注服务本身能否访问到注册中心,以及注册中心间是否能访问(温馨提示:注意服务器安全组/防火墙的配置)。如果你觉得者太过于繁琐,也可以依赖Consul的API在项目启动时通过第三方服务获取外网ip然后手动注册(后面看情况可能补上),以下是一个注册的例子:

spring:
    application:
      name: social
    cloud:
      consul:
        discovery:
          serviceName: test-project
          health-check-path: ${server.servlet.context-path}/actuator/info
          hostname: 123.207.160.177 #项目服务器的外网ip
          register: true
          instance-id: ${spring.application.name}-${server.port}   
        host: 160.60.60.125 #注册中心能访问到的ip
        port: 8500
        enabled: true

使用Consul注册信息——nginx、ribbon和zuul/多数据中心等高级应用

Consul提供了更多功能和组件可供选择,读取nginx的服务注册信息并通过服务列表调用服务和动态刷新不在此展开,详情请参考本站另一篇博客Consul高级应用:多数据中心,模板与Client(Zuul)

评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

基于Consul的服务注册与发现

基于Consul的服务注册与发现

注:文章基于Consul1.6.0版本,部分版本可能会有误差。本文中项目集成部分采用Java语言。

Consul

什么是服务注册与发现?

    consul官网,服务注册/发现是微服务架构中不可或缺的重要组件,起初服务都是单节点的甚至是单体服务,不保障高可用性,也不考虑服务的压力承载,服务之间调用单纯的通过接口访问(HttpClient/RestTemplate),直到后面出现了多个节点的分布式架构,起初的解决手段是在服务端负载均衡,同时在网关层收束接口,使不同的请求转发到对应不同端口上,这也是前后分离防止前端跨域的手段之一:

图中的B服务也可以是多节点,注册在nginx上面的

    要命的是,nginx并不具有服务健康检查的功能,服务调用方在调用一个服务之前是无法知悉服务是否可用的,不考虑这一点分布式的架构高可用的目标就成了一个摆设,解决手段也很简单:对超时或是状态码异常的请求进行重试尝试,请求会被分发到其他可用节点,或者采用服务注册与发现机制察觉健康的服务。

服务注册与发现技术选型

    首先,注册中心的解决方案很多,例如常见的zookeeper,Cloud的重磅伙伴Eureka(在我写这篇文章时Eureka2.0已经闭源,因此不建议后续项目使用此组件)、etcd以及本文中提及的Consul,相关技术的特性见下表:

技术栈 功能和特点 CAP 通信方式(协议) 一致性保障 开发语言
Eureka 至今使用最为广泛,但是1.0不再维护,2.0已经闭源,新项目不适合使用Eureka AP http - Java
zookeeper 功能最强大的组件,三方支持也很丰富,分布式协调,但是server节点宕机会有不可用时间,靠长连接维持健康检查,提高了复杂性 CP sdk paxos Java
etcd 仅是一个分布式的键值对存储系统,若要实现服务注册功能需要其他的支持 CP/AP http/grpc raft go
Consul 开箱即用,支持多数据中心,UI界面良好,,反馈时效性略差 CP http(s)/dns raft go

    Eureka是很优秀的服务注册组件,但是Netflix将2.0闭源后,便已不是最优选择,不过现有的项目使用Eureka 1.x功能已足够使用,不必急于更换技术栈。

    zookeeper是一个很强大的k-v组件,功能应是这些技术中最多的,但是我们只需要服务注册的组件,paxos的一致性算法也不如raft高效,保持长连接会对服务造成一定的影响。

    etcd其实只是一个k-v系统,需要额外添加对服务发现的支持。

Consul中的服务发现

    consul在Java开发者眼中只有一个会被用到的功能:服务注册与发现,即Service Discovery,其核心流程如下:

    在consul中,假使是B服务调用A服务,其大致流程即为:

    A服务调用consul的API注册自己 -> consul定时检查A服务状态(默认)或A服务本身与consul维持心跳请求 -> B服务通过consul提供的DNS调用A服务(默认)或从consul获取健康的服务列表并通过某种算法选择合适的服务调用。

架构设计

Consul建议的架构

    基于Consul的leader选举模式,Consul官方建议的是consul-server采用3或5节点来保证可用性,而每台client注册在本地的服务器上负责健康检查和向server上报信息,不同概念的应用处于不同的数据中心下。下图源自Consul官方站点:

设计原则

    基于consul服务注册的整体的系统架构设计千变万化,但是无论如何都应尽量遵守如下的原则:

常见的架构:使用nginx反向代理

    基于此,我们设计了一个基于nginx的网关层服务发现与注册(前后端通用通信接口,这不是标准的做法,其实nginx做网关负载均衡更适合在前端使用,而后台应通过consul本身提供的服务列表去直接调用具体的服务。但是考虑到应用已经归集好,后者要做的工作量稍大,所以选择了基于nginx的负载均衡),拟定的架构如下:

    图中展示的是一个服务集群,可以理解为一个数据中心的片区。

    此种方式优缺点很明显:服务端与客户端均使用nginx反向代理,http协议和基于nginx的API网关性能都比较差,性能问题注定了nginx反代只适用于前端(用于),服务间的调用应使用为后台服务定制的API网关或是通过其他协议(例如RPC)调用。

    可能后台间基于nginx网关的调用是种错误的手段,但是依旧有很多公司采用此种架构,毕竟在经历性能瓶颈前,这的确是种省时省力的选择。

Quick Start-集成步骤

    在上文中架构的基础上,后台间的调用可以直接通过调用Consul提供的API获取接口直连的地址,进而直接调用相应的服务,此处部署三台server。

    此模块仅介绍如何最简便的启动consul集群和应用集成,详细的配置在后文有介绍,包含多数据中心的使用。

准备应用

    提取consul相关的安装包,这里使用1.6.0:

wget 
unzip consul_1.6.0_linux_amd64.zip 
mv consul /usr/local/bin/

部署consul集群

    对于Consul,可以直接使用命令行(不推荐),也可以使用指定配置文件的方式。

使用配置文件

        定义一个配置文件consul-config.json,这里罗列了一些基本配置,对于一个server:

{
    "datacenter": "dc1",    //数据中心 
    "data_dir": "/var/lib/consul",      //存放consul数据(持久化)的路径
    "node_name": "testS0",		//本consul节点名
    "server": true,               //开启server模式,反之为client
    "ui": true,                    //开启UI页面,即8500端口根路径访问,一般一个节点开启即可
    "bootstrap_expect": 1,       //集群最小server数,当server小于此数不能consul集群将正常提供服务
    "bind_addr": "10.120.0.1",   //集群内部通讯ip,一般是内网ip	
    "client_addr": "0.0.0.0", //绑定client地址,默认是127.0.0.1,即只能本地访问,想要全部访问则指定为0.0.0.0
    //集群展现地址,对于不在一个网域下的服务,使用此地址相互访问,否则使用bind_addr
    "advertise_addr": "123.207.160.177",    
    "enable_debug": false,        //debug,默认关闭
    /开启时加入的节点,可以包含自己,不必包含client的ip
    "start_join":["10.120.0.1","10.120.0.2","10.120.0.3"]  
}

        启动server,注意这里consul需要后台启动并支付指定配置文件:

nohup consul agent -config-file consul-config.json >consul.log &

        启动剩余两个server,注意修改相关配置

        观察日志,待server启动并都加入集群成功后(也可直接访问UI页面查看),分别启动client,client配置如下:

{
    "datacenter": "dc1",    
    "data_dir": "/var/lib/consul",      
    "node_name": "testC0",		
    "server": false,               
    "ui": false,                   
    "bootstrap_expect": 1,       
    "bind_addr": "10.120.0.6",   
    "client_addr": "0.0.0.0",    
    "advertise_addr": "123.207.160.175",    
    "enable_debug": false,        
    "start_join":["10.120.0.1","10.120.0.2","10.120.0.3"]   
}

        启动脚本与server相同。

集成内容

    应用集成consul主要需要两方面的集成:

  1. 为应用添加endpoint(暴露接口),确保其能在服务功能正常时,此端点能返回http状态码200。可以使用现有的接口例如版本接口或是功能接口(不建议),比较建议的方式是使用actuator的health端点。

  2. 为应用添加服务注册的http调用,对于Springboot,Java有其集成好的starter,也可以手写注册流程,可以说是个一次性的方法,但是不建议写在应用外或采用手动注册的方式。

引入依赖和配置

    接下来以Springboot项目并使用actuator健康检查为例:

1. 引入相关依赖,注意SpringCloud版本的对应,version的不匹配可能导致服务注册过程失效:

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-dependencies</artifactId>
         <version>Greenwich.RELEASE</version>
         <type>pom</type>
      </dependency>
   </dependencies>
</dependencyManagement>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-consul-discovery</artifactId>
   <version>2.0.0.RELEASE</version>
</dependency>

2. 引入相关配置,注意因版本不同引起的配置不同。注意,一个Java服务只能读取到本地的内网ip,如果有跨域(跨局域网),那需要手动指定服务本身的外网ip作为注册的访问地址,即如下的指定hostname为外网ip同时不指定prefer-ip-address。

spring:
    application:
        #非必选项
      name: test-project
    cloud:
      consul:
        discovery:
          #注册服务名,即serviceId
          serviceName: test-project
          #健康检查地址,指定一个返回200状态码的接口,注意这个项目配置了contextpath
          health-check-path: ${server.servlet.context-path}/actuator/info
          #注册的hostname,依此进行服务调用和健康检查,不指定则使用DNS将服务器主机名与内网ip绑定映射关系
          #hostname: 10.0.0.1
          #是否使用自动ip注册,优先级大于hostname,会自动使用内网ip作为注册
          prefer-ip-address: true
          #是否注册
          register: true
          #指定instanceId,本身也有一个默认值
          instance-id: ${spring.application.name}-${server.port}
        #注册中心地址因为上生产几乎必然采用client本地注册的方式,所以可以不指定,保持默认的localhost    
        #host: 10.0.0.1
        port: 8500
        enabled: true

    在成功启动项目后,服务便会将本身信息注册给Consul。

跨局域网注册

    原则上所有的项目都应处在一片局域网下,这样Consul提供的DNS或是ip注册才能发挥其作用,否则跨局域网会访问受阻,在不搭建专线或是vpn的前提下,我们需要修改注册上报的url。即上面的配置文件中,指定hostname为外网ip同时不指定prefer-ip-address,同时要关注服务本身能否访问到注册中心,以及注册中心间是否能访问(温馨提示:注意服务器安全组/防火墙的配置)。如果你觉得者太过于繁琐,也可以依赖Consul的API在项目启动时通过第三方服务获取外网ip然后手动注册(后面看情况可能补上),以下是一个注册的例子:

spring:
    application:
      name: social
    cloud:
      consul:
        discovery:
          serviceName: test-project
          health-check-path: ${server.servlet.context-path}/actuator/info
          hostname: 123.207.160.177 #项目服务器的外网ip
          register: true
          instance-id: ${spring.application.name}-${server.port}   
        host: 160.60.60.125 #注册中心能访问到的ip
        port: 8500
        enabled: true

使用Consul注册信息——nginx、ribbon和zuul/多数据中心等高级应用

Consul提供了更多功能和组件可供选择,读取nginx的服务注册信息并通过服务列表调用服务和动态刷新不在此展开,详情请参考本站另一篇博客Consul高级应用:多数据中心,模板与Client(Zuul)


基于Consul的服务注册与发现2020-01-10鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论