Consul 介绍
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
Consul 优势

- 使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接. 相比较而言, zookeeper 采用的是 Paxos, 而 etcd 使用的则是 Raft。
- 支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。 zookeeper 和 etcd 均不提供多数据中心功能的支持。
- 支持健康检查。 etcd 不提供此功能。
- 支持 http 和 dns 协议接口。 zookeeper 的集成较为复杂, etcd 只支持 http 协议。
- 官方提供 web 管理界面, etcd 无此功能。
综合比较, Consul 作为服务注册和配置管理的新星, 比较值得关注和研究。
Consul 特性
- 服务发现
- 健康检查
- Key/Value 存储
- 多数据中心
Consul 角色
- client: 客户端, 无状态, 将 HTTP 和 DNS 接口请求转发给局域网内的服务端集群。
- server: 服务端, 保存配置信息, 高可用集群, 在局域网内与本地客户端通讯, 通过广域网与其他数据中心通讯。 每个数据中心的 server 数量推荐为 3 个或是 5 个。
Consul 安装(Docker)
指定固定网络
创建自定义网络,并指定网段:172.10.0.0/16
并命名为mynetwork
1
| docker network create --subnet=172.10.0.0/16 mynetwork
|
查看网络

创建dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| FROM centos:7.4.1708 MAINTAINER kenny <wfgdlut@gmail.com>
ENV CONSUL_VERSION=1.5.2 ENV HASHICORP_RELEASES=https://releases.hashicorp.com
RUN groupadd consul && \ useradd -g consul consul
RUN yum upgrade -y && \ yum install -y net-tools && \ yum install -y firewalld firewalld-config && \ yum install -y wget && \ yum install -y unzip && \ wget ${HASHICORP_RELEASES}/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip && \ unzip consul_${CONSUL_VERSION}_linux_amd64.zip && \ rm -rf consul_${CONSUL_VERSION}_linux_amd64.zip && \ mv consul /usr/local/bin
RUN mkdir -p /Users/kwang/consul/data && \ mkdir -p /Users/kwang/consul/config && \ chown -R consul:consul /Users/kwang/consul
VOLUME /consul/data VOLUME /consul/config
EXPOSE 8300 EXPOSE 8301 8301/udp 8302 8302/udp EXPOSE 8500 8600 8600/udp EXPOSE 80
|
编译Dockerfile
1
| docker build -t consul:1.5.2 .
|
检查编译后的docker镜像

使用docker镜像创建容器
1 2 3 4 5
| docker run -itd --name test2 --network mynetwork -P --ip 172.10.0.2 --rm consul:1.5.2 docker run -itd --name test3 --network mynetwork --ip 172.10.0.3 --rm consul:1.5.2 docker run -itd --name test4 --network mynetwork --ip 172.10.0.4 --rm consul:1.5.2 docker run -itd --name test5 --network mynetwork --ip 172.10.0.5 --rm consul:1.5.2 docker run -itd --name test6 --network mynetwork --ip 172.10.0.6 --rm consul:1.5.2
|
查看结果

配置consul集群
这里我们先来配置server。从高可用上,最少要配置3个server。
进入每个容器,执行server启动命令(只有第一个主server需要指定bootstrap-expect参数):
1 2 3
| docker exec -it test2 bash
consul agent -server -ui -node=server2 -bootstrap-expect=4 -bind=172.10.0.2 -data-dir /consul/data -config-dir /consul/config -join=172.10.0.2 -client 0.0.0.0
|
1 2 3
| docker exec -it test3 bash
consul agent -server -node server3 -bootstrap-expect=4 -bind=172.10.0.3 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
1 2 3
| docker exec -it test4 bash
consul agent -server -node server4 -bootstrap-expect=4 -bind=172.10.0.4 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
1 2 3
| docker exec -it test5 bash
consul agent -server -node server5 -bootstrap-expect=4 -bind=172.10.0.5 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
1 2 3
| docker exec -it test6 bash
consul agent -server -node server6 -bootstrap-expect=4 -bind=172.10.0.6 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
查看docker test2的8500端口自动分配的本地对应端口

访问 http://localhost:32769,可以看到consul server已成功启动,并组成了集群。


加入几个client节点
1 2 3
| docker run -itd --name test7 --network mynetwork --ip 172.10.0.7 --rm consul:1.5.2 && docker run -itd --name test8 --network mynetwork --ip 172.10.0.8 --rm consul:1.5.2 && docker run -itd --name test9 --network mynetwork --ip 172.10.0.9 --rm consul:1.5.2
|
1 2
| docker exec -it test7 bash consul agent -node node1 -bind=172.10.0.7 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
1 2
| docker exec -it test8 bash consul agent -node node2 -bind=172.10.0.8 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
1 2
| docker exec -it test9 bash consul agent -node node3 -bind=172.10.0.9 -data-dir=/consul/data -config-dir=/consul/config -join=172.10.0.2
|
查看node节点
http://localhost:32769/ui/dc1/nodes

Spring Cloud 集成 Consul
新建服务端项目(Spring Cloud)
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> </parent> <groupId>com.mindex</groupId> <artifactId>consul</artifactId> <version>0.0.1-SNAPSHOT</version> <name>consul</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
|
application.properties
1 2 3 4 5
| spring.application.name=spring-cloud-consul-test spring.cloud.consul.host=localhost spring.cloud.consul.port=32769
spring.cloud.consul.discovery.serviceName=service-test
|
启动类 ConsulApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.mindex.consul;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication @EnableDiscoveryClient public class ConsulApplication {
public static void main(String[] args) { SpringApplication.run(ConsulApplication.class, args); }
}
|
测试类 ConsulController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.mindex.consul.controller;
import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import java.net.InetAddress; import java.net.UnknownHostException;
@RestController @Slf4j public class ConsulController {
@GetMapping("/ip") public String getIpAddress() { InetAddress address = null;
try { address = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); }
return "This is a service from IP: " + address.getHostAddress(); }
@RequestMapping("/hello") public String hello() { InetAddress address = null;
try { address = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } return "hello consul, IP: " + address.getHostAddress(); } }
|
发布应用
通过不同的端口-Dserver.port
将同一个服务发布到不同的端口上

新建Consumer端项目(Spring Cloud)
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> </parent> <groupId>com.mindex.consul</groupId> <artifactId>consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>consumer</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
|
application.properties
1 2 3 4 5
| spring.application.name=spring-cloud-consul-consumer spring.cloud.consul.host=localhost spring.cloud.consul.port=32769
spring.cloud.consul.discovery.register=false
|
服务测试类 ServiceController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.mindex.consul.consumer.controller;
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;
@RestController @Slf4j public class ServiceController {
@Autowired private LoadBalancerClient loadBalancer;
@Autowired private DiscoveryClient discoveryClient;
@RequestMapping("/services") public Object services() { return discoveryClient.getInstances("service-test"); }
@RequestMapping("/hello") public String call() { ServiceInstance serviceInstance = loadBalancer.choose("service-test"); System.out.println("服务地址:" + serviceInstance.getUri()); System.out.println("服务名称:" + serviceInstance.getServiceId()); String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class); log.info("callServiceResult = {}:{}", callServiceResult,serviceInstance.getPort()); return callServiceResult; } }
|
检验结果
列出所有服务
http://localhost:8503/services
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| [ { "instanceId": "spring-cloud-consul-test-8501", "serviceId": "service-test", "host": "192.168.0.103", "port": 8501, "secure": false, "metadata": { "secure": "false" }, "uri": "http://192.168.0.103:8501", "scheme": null }, { "instanceId": "spring-cloud-consul-test-8502", "serviceId": "service-test", "host": "192.168.0.103", "port": 8502, "secure": false, "metadata": { "secure": "false" }, "uri": "http://192.168.0.103:8502", "scheme": null } ]
|
调用特定服务
http://localhost:8503/hello
hello consul, IP: 192.168.0.103
查看日志,服务已自动负载均衡。
