梦入琼楼寒有月,行过石树冻无烟

Spring cloud Consul

Spring cloud Consul 是由 HashiCorp 公司使用 Go lang 所开发的一个 服务治理 项目,主要包含了 服务治理、健康检查、Key-value 存储、多数据中心 的功能。与 其他服务中心相比,Consul 支持多数据中心,内外网服务采用不同的端口号进行监听,除此之外还可以避免单数据中心故障所导致的问题。

Consul 所使用的是 Raft(Reliable, Replicated, Redundant,d Fault An-Tolerant,可靠、可复制、可沉余、可容错) 算法来保证一致性,相比 Poxos,Raft 的目标则是提供更加清晰的逻辑分工使得算法本身可以被更好的理解。

原理流程

工作原理

服务提供者(Service Provider)在启动时向 Consul 发送一个POST 请求,来告诉自己的 IP和端口号等信息。Consul 收到了服务提供者的注册后,默认每隔10s想服务提供者发送一个健康请求,来验证该提供者是否可以正常提供服务。

Consul 中的服务提供者临时表每隔 10s 更新一次,只包含了通过健康检查的服务提供提供者。

之后服务消费者(Service Consumer)在调用服务提供者之前会从 Consul 获取存储的服务提供者 IP 和地址的临时地址表,之后发送请求给 服务提供者。

集群原理

Consul 的常见黑话有很多,如 Agent、Client、Server、DataCenter、Consensus、Gossip、LAN Gossip、WAN Gossip、RPC…… 他们的意思如下:

Agent

Consul 集群中每个成员都会运行 Agent,他是一个守护进程。主要分为 Server 与 Client 模式,能运行 DNS 或 HTTP 接口,负责在运行时检查和保持服务同步

Client

Client 转发所有 RPC 到 Server 的代理,Client 想对于是无状态的,他唯一执行后台活动是加入 LAN Gossip 池。Client 只需要较低的资源开销,以及较少的网络流量带宽等。

RPC(Remote Procedure Call)远程过程调用,该协议允许一台计算机的程序调用另一台开放网络的计算机。在 Consul 集群中主要允许 Client 请求 Server 的请求/响应机制

LAN gooip 包含了所有与位于同一个局域网或同一个数据中心的所有节点

Gossip 主要用于实现基于 UDP 的随机点到点通信

Server

具有扩展功能的代理,功能包括Raft选举、维护集群状态、响应 RPC差需、与其他数据中心交互 WAN gossip、转发查询给 Leader\远程数据中心。
他主要是在局域网内与本地客户端进行通信,通过广域网与其他数据中心进行通信,通常 Server保存集群中的配置信息(每个数据中心的 Server 数量建议为 3~5个)

Wan gooip 只包含分布在不同数据中心的所有节点,数据中心间通常采用互联网或广域网进行通信。

DataCenter

一个私有且低延迟和高宽带的网络环境

Consensus

Consensus 用于代表复制机的状态一致性,即用于表明 Server 与 领袖选举的事务顺序达成一致。

Gossip and Wan and LAN

在 Consul 数据中心中,官方给我们最好的建议数量是三台到五台之间。这是经过深思熟虑思考之后所得到的结果,当很多台数据中心加入后,则达成共识的过程很慢,但只有1~2台数据中心可用性又不高,因此所得出的权衡利弊的数量为 3~5台之间。

Gossip (第六届ACM分布式计算原理年会论文集)

Demers, Alan; Greene, Dan; Hauser, Carl; Irish, Wes; Larson, John; Shenker, Scott; Sturgis, Howard; Swinehart, Dan; Terry, Doug (1987-01-01). Epidemic Algorithms for Replicated Database Maintenance. Proceedings of the Sixth Annual ACM Symposium on Principles of Distributed Computing. PODC ‘87. New York, NY, USA: ACM. pp. 1–12. doi:10.1145/41840.41841. ISBN 978-0897912396. S2CID 1889203.

Gossip protocol 又称 Epidemic Protocol(流行病协议),该协议早在1987年发表在 ACM 论文 《Epidemic Algorithms for Replicated Database Maintenance》中,主要用于在分布式数据库系统中各个副本节点间的同步数据。

八卦通信模型 (Gossip Protocol)

无论喜欢与否,八卦在人类社会中扮演了重要的角色。Dunbar(一位人类学家)在具有争议的一本书中声称,语言的出现原因是允许闲聊,而闲聊的整个过程是不需要仔细思考和梳理的。

因此无论什么情况,毫无疑问这些流言蜚语仍然是一种在社会活动中传播非常优秀的。特别是传播速度非常狂,而这个过程对组织信息传播的企图具有一些抵抗力。

Kimmel 在书中给出了许多关于人类八卦的例子和细节

虽然八卦通常会被认为是一种传播手段,但是实际上并不仅仅是机器上的传递,而个经过处理的。具体流程为一个人收集、处理信息并将处理后的信息传递给其他人。
在这个过程中信息至少会根据其兴趣进行过滤,这样一来最有趣的新闻在传播到每个人之前就不会停止传播。

更复杂的化来讲,信息是逐渐改变的,这增加了过程的复杂性并可能导致突发行为,其中这个平台则充当了 “集体智能”的信息处理媒介。

流行病传播模型 (Epidemics Protocol)

实际上这些八卦非常类似于流行病,病毒扮演着信息的角色,而感染者则扮演者了解信息的角色。在过去的几年当中 “病毒式营销” 的概念非常火爆,通过视频分享平台以及社交网络,广告商可以有意识的利用日益高效的传播八卦广告。

而令人震惊或非常有趣的广告,特别是设计会有极大限度的提高机会,阅读者通常会通知他们的朋友等等。

分布式系统设计灵感

第一原因
八卦对于大型分布式系统来说非常有意义,主要有两个原因。其中之一就是设计新协议的灵感来源: Gossip 有几个吸引人的特性,如简单、速度、健壮等,除此还包含了缺乏中央控制的瓶颈。

这些特性对于大型分布式系统关键组成部分的 信息分发和集体信息处理(聚合) 非常重要。

第二原因
随着当今互联网的稳步发展,病毒和蠕虫的传播策略越来越为之复杂。受感染的计算机通常会组成网络(被称为 “僵尸网络”),能够进行协作和执行攻击等手法。

这对互联网基础设施构成了非常重大的威胁,为应对这些网络的一个办法就是设防防止他们的传播,这需要对流行病有很好的了解才可预防。

在本篇论文中,我们主要关注流行病以及八卦作为设计高可用组织系统和服务的灵感作为来源。

Gossip in Consul
概述

Consul 使用 Gossip 协议来管理成员资格并向集群广播消息,通过 Serf 库所提供,所基于 “SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol” 论文并做了一些必要的修改。

WAN and Lan

Consul 使用了两个不同的八卦池,分别称之为 Lan 与 Wan 池,每个数据中心都有一个 Lan 池,其中包含数据中心的所有成员(包括客户端和服务器)

Lan 池的主要作用是成员信息允许客户端自动发现服务器,以此来减少所需的配置量。在分布式故障检测中允许检测的工作由整个集群共享,而不是单个服务器进行, 他包含了所有位于同一个局域网或者同一个数据中心的所有节点

而 Wan 池是全局唯一的,无论数据中心如何,所有服务器都应参与WAN池。WAN 池提供成员信息允许服务器执行夸数据中心的请求。

故障检测允许 Consul 优雅的处理丢失连接的整个数据中心,或仅处理远程数据中心的单个服务器,他只包含了分布在不同数据中心的所有节点

集群实现原理

Leader and Client&Server

从上图可以得知,每个数据中心的 Client 以及 Server 是混合的,一般官方建议数据中心 Server 通常为 3~5 台。

这是经过深思熟虑之后所得出的结果,因为如果有太多的机器加入则达成共识会变慢。但只有 1~2台的数据中心可用性又不高,于是 3~5台是最好的选择

数据中心的领袖(Leader)是有一个额外工作的 Server,他是由所有 Server 选出的(Raft 协议),主要用于处理所有的查询和事务。由于需要遵一致性协议的要求,所以事物也必须被复制到其他所有节点中(CAP 协议)。

每个数据中心的 Server 都是 Raft 节点集合的一部分,在这期间内如果有一个非领袖服务器收到了RPC请求,将会将请求转发给集群中的领袖服务器中。

WAN and LAN

为了允许数据中心能够以 “低耦合(Low-touch)”的方式发现彼此。那么通过 WAN gossip Pool 来只包含服务节点的信息,用于优化网络延迟,那么一个新的数据中心就很容易加入到现存的 WAN gooip 池中。

无论数据中心如何,所有服务器都应参与WAN池的原因,所以服务也支持跨数据中心请求

这个时候一个服务在收到了来自另一个数据中心的请求后,会随即将该请求转发给正确的数据中心的服务中。之后该服务再将请求转发给本地的领袖服务(Leader),这使得服务之间有一个很低的耦合。

由于 Consul 自身的一些优势,如提所提供的健康检查、链接缓存、以及复用等功能,使得跨数据中心请求都是相对快速且可靠的。

Consul 集群实现

install

1
2
3
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install consul

至于第二条命令可能很多 debian er 无法使用,我们需要安装 sudo apt-get install software-properties-common 软件包来解决你 add-repository 无法使用的情况

当你执行第三条命令的时候,也就是 update 对于一些非常纯净的发行版,你可能还需要安装 sudo apt-get install apt-transport-https

当安装完成后我们可以执行 consul 来验证是否安装成功:

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
# consul 
Usage: consul [--version] [--help] <command> [<args>]

Available commands are:
agent Runs a Consul agent
catalog Interact with the catalog
event Fire a new event
exec Executes a command on Consul nodes
force-leave Forces a member of the cluster to enter the "left" state
info Provides debugging information for operators.
join Tell Consul agent to join cluster
keygen Generates a new encryption key
keyring Manages gossip layer encryption keys
kv Interact with the key-value store
leave Gracefully leaves the Consul cluster and shuts down
lock Execute a command holding a lock
maint Controls node or service maintenance mode
members Lists the members of a Consul cluster
monitor Stream logs from a Consul agent
operator Provides cluster-level tools for Consul operators
reload Triggers the agent to reload configuration files
rtt Estimates network round trip time between nodes
snapshot Saves, restores and inspects snapshots of Consul server state
validate Validate config files/directories
version Prints the Consul version
watch Watch for changes in Consul

run

可以通过以开发者模式运行和服务器模式运行两种,可通过使用下述命令直接进行启动:

1
2
consul agent -dev
# consul agent -server
or -server``` 的区别主要是开发者模式和服务器模式两种运行模式,而 **agent** 则是用于注册服务、运行健康检查的一个功能。每个数据中心都要求至少有一台 Agent (Server 模式) ,推荐于 3~5 台。
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

他主要有 Client 模式和 Server 两种模式,其中 Client 模式主要是用于运行写上述的服务(注册服务、健康检查……)而 Server 则是主要接收这些功能查询后的数据。

> 当运行之后我们可以通过访问 [http://127.0.0.1:8500/](http://127.0.0.1:8500/) 来进行查看和管理 Consul 集群状态

**常用命令**

| ID | DA | FA |
| --- | --- | --- |
| members | 列出 Consul 下集群成员 | |
| | -datailed | 查看 Consul 集群成员详细信息 | |
| monitor | 持续打印当前 Consul 代理流日志 | |
| agent | 运行 Consul 代理 | |
| | -dev | 开发者模式 |
| | -server | 服务器模式 |
| join | 加入某个集群 | |


#### vagrant init
Consul 集群的实现也非常简单,但多出了 ```join``` 参数,主要是加入集群。而在测试环境中,在正常且资金不允许的情况下我们是不会上服务器的,所以上也有很多文章出现了一些迷惑性文章。

比如,运行 ```xxx``` 命令,之后执行 ```join```,这就很无语。首先,我们只要运行一个单个 consul 集群就会发现,他 **并不是交互式的**,你这个终端就挂着这个,之后你在开个新终端,来加入他,这也合理。

> 不要问为什么是 Vagrant 问就是 HashiCorp 项目下的(与 Consul 一个公司出的)

但是最不合理的情况发现了,就是 ```-bind``` 参数下到底要填写什么。通常情况下我们会通过 vagrant 来部署测试环境进行实现。

但幸运的是我们不需要单个手动配置 Vagrant 环境,只需要下载 Consul 所为我们提供的集群测试环境即可(实际上就是两个 debian 装个 Consul):[https://github.com/hashicorp/consul/blob/master/demo/vagrant-cluster/Vagrantfile](https://github.com/hashicorp/consul/blob/master/demo/vagrant-cluster/Vagrantfile)

我们可以选择手动安装或自动安装,即执行 ```vagrant up``` 根据 **Vagrantfile** 文件内配置执行一些命令。如 Consul 所提供的则是自动安装 Consul,则可能会造成卡住的问题,读者可自行进行解决。

当配置好测试环境后,我们即拥有了三台测试环境,分别为 vagrant box#2 以及自己本机的环境,我们要保证这三台环境内可以正常启动 consul 以及其他等等,之后即可进入下一步。

#### localhost
我们首先需要将我们本机成为整个集群中的 **领导者(server/leader)**,之后剩下的 n1、n2 则扮演客户端的角色进行:

```shell
consul agent -dev -node=consul-server -bind=172.20.20.1 -data-dir=data -client 0.0.0.0

通过上述命令,我们主要开启了 测试 模式下的 consul ui ,可通过访问 http://localhost:8500/ui 来直接进行通过可视化的方式观察集群状态。

参数为本机器的 IP 地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在实际的生产环境中,我们时候使用的则是 ```consul agent -server -bootstrap-expect=2 -node=consul-server -bind=172.20.20.1 -data-dir=data -client 0.0.0.0``` 来通过 ```bootstrap-expect``` 限制集群数量等。

而生产环境是不需要可视化 ui的,因此我们可以通过下面几个 api 接口来进行查看:

| ID | DA |
| --- | --- |
| ip:port/v1/status/leader | 显示当前集群的领袖服务器 |
| ip:port/v1/agent/members | 显示集群中所有成员信息 |
| ip:port/v1/status/peers | 显示当前集群中的 Server 成员 |
| ip:port/v1/catalog/services | 显示所有服务 |
| ip:port/v1/catalog/nodes | 显示集群节点的详细信息 |

#### vagrant up
![](https://49812933408852955071488026628034-1301075051.cos.ap-nanjing.myqcloud.com/20210619183721.png)
##### n1
在 n1 中,我们可以通过使用 ```vagrant ssh n1``` 直接进入 n1 的 debian 环境,之后建立一个目录也可以之际运行:

需要注意的是当 ```vagrant init``` 完事之后,才可以使用 ```vagrant up``` 来进行启动,最后通过 ```vagrant shell n1``` 来选择进入多个环境下进行工作。

```shell
consul agent -node=client-one -bind=172.20.20.10 -enable-script-checks=true -data-dir=data

通过使用 enable-script-checks 来开启 consul 的健康检查,我们也可以在开一个 n1 交互式 shell 进行加入服务集群:

1
consul join 172.20.20.1
n2

n2 的流程和步骤也基本上和 n1 相差无几,我们也是通过 vagrant ssh n2 进入环境,之后根据自己喜好决定是否建立新的目录(主要存储文件):

1
consul agent -node=client-two -bind=172.20.20.11 -enable-script-checks=true -data-dir=data

之后你可以在建立一个 shell n2 连接来加入 consul-server 集群中:

1
consul join 172.20.20.1

服务提供者以及服务消费者

通过 Consul 实现服务提供者和服务消费者之前,我们首先需要在构建项目中选择 Spring web、Spring Boot Actuator、Consul Discovery 等项目的依赖,当然也可以在 pom.xml 文件下进行添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
<scope>test</scope>
</dependency>
</dependencies>

之后我们的服务消费者不需要 spring-boot-starter-actuator 依赖。

服务提供者


我们首先模仿下真实环境下的三台服务器,分别运行着功能服务,因此我们主要通过 .properties 进行实现和模拟这个环境。在此之前,我们首先需要对启动类中添加 Consul 的服务注册注解 @EnableDiscoveryClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* DemoApplication.class
* @author kunlun
* @data 2021/6/19
*/
@SpringBootApplication
@EnableDiscoveryClient
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

properties

application.properties
1
2
3
4
5
6
spring.application.name=consul-provider
server.port=8501
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=service-provider
provider.name=provider
logging.level.ROOT=info

需要注意的是 spring cloud consul discovery service-name 是一个 spring cloud consul 发现服务时的名称,而 logging.level.ROOT 则是用于描述日志等级的。

application-consul-provider-one.properties
1
2
3
4
5
6
spring.application.name=consul-provider-one
server.port=8502
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=service-provider
provider.name=provider-one
application-consul-provider-two.properties
1
2
3
4
5
6
spring.application.name=consul-provider-two
server.port=8503
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=service-provider
provider.name=provider-two

Controller

HeyController.java

当配置完启动类和全局文件之后,我们首先需要实现服务提供的接口信息,所提供的也非常简单,就是 .properties 文件的内容获取:

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
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* HeyController.class
* @author kunlun
* @date 2021/06/19
*/
@RestController
public class HeyController {
@Value("${provider.name}")
private String name;

@Value("${server.port}")
private String port;

@RequestMapping("/hey")
public String hey() {
String string = "提供者名称:" + name + " 端口号为:" + port;
return string;
}
}

run

1
2
3
$ java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=consul-provider-one
$ java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=consul-provider-two
$ java -jar demo-0.0.1-SNAPSHOT.jar

读者可能非常疑惑 spring.profiles.active 的作用是什么,实际上 profilespro(perties 的缩写,与 file 相加所等于的 profiles。可以理解为当多个 properties 文件时,运行那个配置文件,之后访问 ip:8501/hey、8502/hey、8503/hey 都可以看到返回的信息。

服务消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

application.properties

1
2
3
4
5
6
spring.application.name=consul-consumer
server.port=8504
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.register=false

因为服务消费者并不需要提供服务,所以我们可以选择通过 spring.cloud.consul.discovery.register=false,让其不注册服务到 consul 集群中

controller

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
package com.example.demo.controller;

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.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.net.URI;

/**
* HeyController.java
* @author kunlun
* @date 2021/06/19
*/
@RestController
public class HeyController {

@Autowired()
private LoadBalancerClient loadBalancerClient;

@GetMapping("/hey")
public String hey() {
ServiceInstance serviceInstance = loadBalancerClient.choose("service-provider");
URI uri = serviceInstance.getUri();
String callService = new RestTemplate().getForObject(uri + "/hey", String.class);
return callService;
}
}

在这其中起到主要作用的就是 RestTemplate,他起到了服务提供者负载均衡的功能。假设我们有三台服务提供者,那么我们每次刷新服务消费者时,会出现不同的服务名称及端口号等信息。

⬅️ Go back