Spring Cloud: 注册中心Consul使用
什么是Consul
Consul是HashiCorp公司推出的开源工具,提供了分布式系统的服务注册和发现、配置等功能。与其他分布式服务注册与发现的方案相比,Consul的方案更“一站式”,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案。这些功能每一个都可以根据需要单独使用,也可以一起使用以构建完整的服务网格。
基本术语
- 代理(Agent):是一直运行在Consul集群中每个节点上的守护进程,通过运行consul agent命令来启动。代理可以以客户端或服务端模式运行。无论是客户端节点,还是服务端节点,都必须运行代理,因此将节点称为客户端或服务器更容易理解。所有代理都可以通过DNS或HTTP接口来检查服务,并保持服务同步。
- 客户端(Client):客户端是所有RPC转发到服务端的代理。这个客户端是相对无状态的。客户端唯一执行的后台活动是加入LAN gossip池,资源开销很小。
- 服务端(Server):服务端是具有扩展责任的代理,包括参与Raft选举、维护集群状态、响应RPC查询、与其他数据中心交换WAN,以及将查询转发给领导者(Leader)或远程数据中心。考虑到容错和收敛,一般部署 3 ~ 5 个比较合适。
- 数据中心(Data Center):是一个私有的、低延迟且高带宽的网络环境,由多个客户端和服务端构成。
- 共识(Consensus):在文档中,我们使用共识来表示对当选领导者的协议以及交易顺序的协议。由于这些事务应用于有限状态机,因此我们对共识的定义意味着复制状态机的一致性。
- Gossip:Consul建立在Serf的基础上,它提供了一个完整的Gossip协议
- LAN Gossip:是指局域网Gossip,包含位于同一个数据中心的所有节点。
- WAN Gossip:是指仅包含服务端的WAN Gossip池。这些服务端位于不同的数据中心,通常通过互联网或广域网进行通信。
- 远程调用(RPC):是一个允许客户端请求服务端的请求-响应机制。RPC解释
Consul的功能
- 服务发现:有了consul,服务可以通过DNS或者HTTP直接找到它所依赖的服务。
- 健康检查:consul提供了健康检查的机制,从简单的服务端是否返回200的响应代码到较为复杂的内存使用率是否低于90%。
- K/V存储:应用程序可以根据需要使用consul的Key/Value存储。Consul提供了简单易用的http接口来满足用户的动态配置、特征标记、协调、leader选举等需求,使Consul有称为配置中心的可能。
- 多数据中心:Consul原生支持多数据中心。这意味着用户不用为了多数据中心自己做抽象。
Consul的基本架构
上图为Consul的基本架构,包含两个datacenter,每一个datacenter中客户端和服务端都是共存的,建议在数据中心中使用奇数个服务端,如3或5个。在出现故障的情况下,可用性和性能之间取得了平衡,因为随着添加更多计算机,共识逐渐变慢。但是,客户数量没有限制,他们可以轻松扩展到成千上万。
数据中心了的所有代理都加入到Lan Gossip协议中,也就是说Gossip协议池中包含数据中心的所有节点,这要有几个目的:
- 不需要在客户端上配置服务端地址,服务发现都是自动完成
- 检测节点故障的工作没有放到服务端,而是分布式的,这种故障监测比心跳机制有更高的可扩展性,而且还为节点提供故障检测,代理不可用说明节点发生故障
- 数据中心可用作数据传递呈,用来通知重要事件的发生,如重新选举Leader
数据中心中的每个服务端节点都是Raft节点集合的一部分。它们共同选举一个领导者,即一个具有额外职责的选定服务端。领导者负责处理所有查询和事务,作为共识协议的一部分,还必须将事务复制到所有对等体。因此当非领导者的服务端收到RPC请求时,它会将请求转发给群集的领导者。
服务端节点也作为WAN Gossip协议池的一部分,不同于LAN Gossip协议池的是,它对较高的网络延迟进行了优化,并且只包含其他Consul服务端节点。这个协议池的作用是允许数据中心能够以低接触的方式发现彼此,使得一个新数据中心可以很容易地加入现存的WAN Gossip协议池。因为服务端都运行在这个协议池中,支持跨数据中心请求。当一个服务端收到来自另一个数据中心的请求时,它会将其转发到正确数据中心的随机服务端。该服务端再转发给本地领导者。
数据中心之间的耦合度非常低,但由于故障检测、连接缓存和多路复用等机制,跨数据中心请求相对快速且可靠。
通常,数据不会在不同的Consul数据中心之间复制。当请求另一个数据中心中的资源时,本地Consul服务器将RPC请求转发到该资源的远程Consul服务器并返回结果。如果远程数据中心不可用,那么那些资源也将不可用,但是不会以其他方式影响本地数据中心。在某些特殊情况下,可以复制有限的数据子集,例如使用Consul的内置 ACL复制功能,或使用外部工具(例如consul-replicate)。
在某些地方,客户端代理可能会缓存来自服务器的数据,以使其在本地可用于性能和可靠性。示例包括连接证书和意图,这些证书和意图使客户端代理可以在无需往返服务器的情况下就入站连接请求做出本地决策。一些API端点还支持可选的结果缓存。这有助于提高可靠性,因为即使与服务器的连接中断或服务器暂时不可用,本地代理也可以继续响应某些查询,例如服务发现或来自缓存的连接授权。
Consul的注册流程
由于Eureka 2.0 闭源,consul多用来作为注册中心替换Eureka。
Consul作为注册中心,类似于中介的作用,接收服务提供者的注册后,定期检查注册成员状态,是否可以提供服务
Provider 服务提供者:在启动的时候会向注册中心发起注册请求并将其节点信息以及检查元数据发送给注册中心,于注册中心建立联系,等待注册中心介绍工作
Consumer 服务消费者:从注册中心中获取服务注册列表,根据服务提供者名称获得具体服务者提供者信息,在通过负载均衡调用服务,完成消费过程。
Consul的使用
可以到官网下载需要版本https://www.consul.io/downloads.html,下载后不需要安装直接运行即可。
这里演示使用docker启动,单机使用dev模式即可:
docker run -p 8500:8500 consul agent -dev -bind=0.0.0.0 -client=0.0.0.0
这时访问http://localhost:8500/ui
可以看到如下操作界面:
为了方便启动docker,这里编写一个使用docker-compose启动文件:
version: '3'
services:
consul:
image: consul
restart: always
networks:
- spring
volumes:
- ./consul/data:/consul/data
- ./consul/config:/consul/config
ports:
- "8600:8600"
- "8500:8500"
- "8502:8502"
- "8300:8300"
- "8301:8301"
- "8302:8302"
command: agent -dev -bind=0.0.0.0 -client=0.0.0.0 -ui
networks:
spring:
对于端口开放:
- 8600:用于dns端口
- 8500:用于http的连接,web ui的访问
- 8502:用于gRPC的连接
- 8300:server rpc 端口,同一数据中心 consul server 之间通过该端口通信;
- 8301:serf lan 端口,同一数据中心 consul client 通过该端口通信; 用于处理当前datacenter中LAN的gossip通信;
- 8302:serf wan 端口,不同数据中心 consul server 通过该端口通信; agent Server使用,处理与其他datacenter的gossip通信;
通过docker-compose启动:
Spring Cloud Consul简介
Spring Cloud Consul通过自动配置、对Spring Environment绑定和其他惯用的Spring模块编程,为Spring Boot应用程序提供了Consul集成。只需要一些简单注解,你就可以快速启用和配置Consul,并用它来构建大型分布式系统。
Spring Cloud Consul作为Spring Cloud 与 Consul之间的桥梁,对二者有良好的支持,其主要功能有:
- 发现服务,实例可以向Consul注册,客户端可以使用spring bean来发现provider
- 支持Ribbon来做负载均衡
- 支持Zuul作为网关
- 分布式配置中心,使用的是Consul的K/V存储
- 控制总线,使用的是Consul events
创建provider
这里通过idea创建spring项目,使用gradle作为构建工具,依赖使用:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery:2.2.2.RELEASE'
}
项目中的配置文件, application.yml:
server:
port: 8700
spring:
application:
name: consul-provider
cloud:
consul:
host: consul
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
配置说明
-
host: consul
因为使用docker将host地址指向link的consul -
prefer-ip-address: true
通过这个设置将docker的ip注册到consul服务上 -
service-name: ${spring.application.name}
通过这个设置consul上显式的service名称为app的名称
启动类添加@EnableDiscoveryClient注解,开启服务发现的功能:
编写一个controller实现hello的服务:
/**
* @author ffzs
* @describe provider, 实现hello服务
* @date 2020/6/4
*/
@RestController
public class HelloController {
@Value("${server.port}")
String port;
@GetMapping("hello")
public String hello(@RequestParam String name) {
return "hello" + name + "i am ffzs from port: " + port;
}
}
使用gradle将spring项目build成jar包,新建一个目录,将jar包copy过来,编写一个Dockerfile:
FROM openjdk:11-oracle
VOLUME /tmp
COPY consul-provider-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
运行docker build -t consul-provider .
,创建provider的镜像:
docker-compose的配置文件中添加两个provider:
provider1:
image: consul-provider
networks:
- spring
links:
- consul
ports:
- 8701:8701
command: --server.port=8701
consul-provider2:
image: consul-provider
networks:
- spring
links:
- consul
ports:
- 8702:8702
command: --server.port=8702
由于使用docker,在本地访问,因此使用端口需要错开,一个8701,一个8702
使用docker-compose启动:
访问consul服务的ui,出现consul-provider的服务,说明注册成功:
点击进入以后出现两个provider的服务,通过使用端口足以区分:
点击进去会有健康检查相关内容:
这是访问provider的api看看能否正常访问:
8701端口:
8702端口:
都可以正常访问。
创建Consumer
过程和provider的创建基本相同,配置文件略微更改了使用端口和app名称:
server:
port: 8710
spring:
application:
name: consul-consumer
cloud:
consul:
host: consul
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
启动类同样加上@EnableDiscoveryClient
注解
创建控制器CallHelloController:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
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;
/**
* @author ffzs
* @describe
* @date 2020/6/4
*/
@RestController
public class CallHelloController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@RequestMapping("call")
public String call () {
// 通过 LoadBalancerClient 进行轮询获取指定服务
ServiceInstance serviceInstance = loadBalancerClient.choose("consul-provider");
return new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello?name=ubiquitin", String.class);
}
}
- 通过LoadBalancer查询服务
- 通过RestTemplate远程调用服务
同样的方式生成docker镜像,然后配置docker-compose文件添加consumer内容:
consul-consumer:
image: consul-consumer
networks:
- spring
links:
- consul
ports:
- 8710:8710
运行之后结果访问consul网页,provider和consumer都已经出现:
这时访问http://localhost:8710/call
通过consumer访问provider:
通过刷新网址,轮询获得不同port的信息。
Consul作为配置中心
consul多用来作为注册中心,同时consul支持键值对的存储,可以用来作为分布式的配置中心,作为测试修改一下provider:
添加依赖:
implementation 'org.springframework.cloud:spring-cloud-starter-consul-config:2.2.2.RELEASE'
application.yml中加上以下配置:
foo:
bar:
name: user
spring:
profiles:
active: dev
上述配置指定了启动时读取的profiles为dev模式,创建启动配置文件bootstrap.yml文件中加上以下配置:
spring:
application:
name: consul-provider
cloud:
consul:
host: consul
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
config:
enabled: true #config是否使用,默认为true
format: yaml # 配置各式yaml或properties
prefix: config # 配置目录 ,默认是config
profile-separator: ':' # 配置分隔符,默认为“,”
default-context: application # 设置默认的配置查找, 默认是application
data-key: data # 为应用配置的key名字, 默认为data
HelloController控制类修改如下:
@RestController
@RefreshScope // 添加刷新
public class HelloController {
@Value("${server.port}")
private String port;
@Value("${foo.bar.name}")
private String name; // 配置中获取 name
@GetMapping("hello")
public String hello() {
return "<h1>hello " + name + ", i am ffzs from port: " + port + "</h1>";
}
}
设置key/value, 添加key:config/consul-provider:dev/data
内容为:
foo:
bar:
name: test_consul_config
通过docker-compose运行,通过consumer访问:
上面动图可见,我们对consul中键值对的修改会起到动态修改配置文件的作用。
ps:Consul支持的KV存储的value值不能超过512KB;在dev模式启动下,所有数据都存储在内存中,重启Consul时会导致所有数据丢失;使用非dev模式启动时,Consul的数据会持久化,数据不会丢失。
推荐阅读
-
详解spring cloud使用Hystrix实现单个方法的fallback
-
spring cloud实现Eureka注册中心的HA的方法
-
详解spring cloud Feign使用中遇到的问题总结
-
使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务的方法(推荐)
-
跟我学SpringCloud | 第六篇:Spring Cloud Config Github配置中心
-
JSP spring boot / cloud 使用filter防止XSS
-
详解Spring Cloud Consul 实现服务注册和发现
-
Spring Cloud Config实现分布式配置中心
-
详解Spring cloud使用Ribbon进行Restful请求
-
Spring-cloud 注册服务提供者搭建方法