SpringCloud利用Consul实现分布式配置中心

  |     |  

consul介绍

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

consul安装起来也是非常简单,直接去官网下载对于系统的安装包即可。

windows下启动命令

1
consul.exe agent -dev

启动完毕,访问localhost:8500,即可看到consul的管理节目。当然你要用docker安装也可以!

使用consul配置中心

接下来,我就要用它来与SpringBoot结合,搭建分布式的公共配置中心。
首先,导入依赖:

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.cfg4j</groupId>
<artifactId>cfg4j-consul</artifactId>
<version>4.4.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

将服务注册到consul上

然后,需要配置bootstrap.yml。这里简单接受下这个配置文件。
其实yml和properties文件是一样的原理,主要是说明application和bootstrap的加载顺序。且一个项目上要么yml或者properties,二选一的存在。
Bootstrap.yml(bootstrap.properties)在application.yml(application.properties)之前加载,就像application.yml一样,但是用于应用程序上下文的引导阶段。它通常用于“使用Spring Cloud Config Server时,应在bootstrap.yml中指定spring.application.name和spring.cloud.config.server.git.uri”以及一些加密/解密信息。技术上,bootstrap.yml由父Spring ApplicationContext加载。父ApplicationContext被加载到使用application.yml的之前。
在本文中,需要从服务器加载“real”配置数据。为了获取URL(和其他连接配置,如密码等),需要一个较早的或“bootstrap”配置。因此,将配置服务器属性放在bootstrap.yml中,该属性用于加载实际配置数据(通常覆盖application.yml [如果存在]中的内容)。
这里贴出本例子的bootstrap.yml

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
# Server configuration
server:
port: 8081
spring:
application:
name: test-consul
# consul 配置
cloud:
consul:
# consul服务器地址
host: localhost
# consul服务端口
port: 8500
config:
# enabled为true表示启用配置管理功能
enabled: true
# watch选项为配置监视功能,主要监视配置的改变
watch:
enabled: true
delay: 10000
wait-time: 30
# 表示如果没有发现配置,是否抛出异常,true为是,false为否,当为false时,consul会打印warn级别的日志信息
fail-fast: false
# 表示使用的配置格式
format: key_value
# 配置所在的应用目录名称
prefix: config
name: ${spring.application.name}
# 服务发现配置
discovery:
# 启用服务发现
enabled: true
# 启用服务注册
register: true
# 服务停止时取消注册
deregister: true
# 表示注册时使用IP而不是hostname
prefer-ip-address: true
# 执行监控检查的频率
health-check-interval: 30s
# 设置健康检查失败多长时间后,取消注册
health-check-critical-timeout: 30s
# 健康检查的路径
health-check-path: /actuator/info
# 服务注册标识,格式为:应用名称+服务器IP+端口
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
logging:
config: classpath:logback-develop.xml

上面的配置主要是指定consul服务地址,并且注册实例设置健康检查,并指定生成key-value形式和位置,相对较简单。

推送配置到consul配置中心

接着需要将配置注册到指定的配置中心上,这里提供一个配置类,可以根据指定的application.yml或.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
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
@Configuration
@RefreshScope
public class ConsulConfiguration {

private static final Logger log = LoggerFactory.getLogger(ConsulConfiguration.class);
@Autowired
private ConsulClient consulClient;
/**
* 是否用本地配置覆盖consul远程配置,默认不覆盖, 覆盖: true / 不覆盖: false
*/
@Value("${spring.cloud.consul.config.cover: false}")
private Boolean cover;
/**
* key所在的目录前缀,格式为:config/应用名称/
*/
@Value("#{'${spring.cloud.consul.config.prefix}/'.concat('${spring.cloud.consul.config.name}/')}")
private String keyPrefix;
/**
* 加载配置信息到consul中
*
* @param key 配置的key
* @param value 配置的值
* @param keyList 在consul中已存在的配置信息key集合
*/
private void visitProps(String key, Object value, List<String> keyList) {
if (value.getClass() == String.class || value.getClass() == JSONArray.class) {
// 覆盖已有配置
if (cover) {
this.setKVValue(key, value.toString());
} else {
if (keyList != null && !keyList.contains(key)) {
this.setKVValue(key, value.toString());
}
}
} else if (value.getClass() == LinkedHashMap.class) {
Map<String, Object> map = (LinkedHashMap) value;
for (Map.Entry<String, Object> entry : map.entrySet()) {
visitProps(key + "." + entry.getKey(), entry.getValue(), keyList);
}
} else if (value.getClass() == HashMap.class) {
Map<String, Object> map = (HashMap) value;
for (Map.Entry<String, Object> entry : map.entrySet()) {
visitProps(key + "." + entry.getKey(), entry.getValue(), keyList);
}
}
}


/**
* 封装配置信息到map中
*
* @param map 要封装的配置信息
* @return 配置信息map
*/
private Map<String, Object> formatMap(Map<String, Object> map) {
Map<String, Object> newMap = new HashMap<>(16);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue().getClass() == LinkedHashMap.class) {
Map<String, Object> subMap = formatMap((Map<String, Object>) entry.getValue());
newMap.put(entry.getKey(), subMap);
} else if (entry.getValue().getClass() == ArrayList.class) {
JSONArray jsonArray = new JSONArray((ArrayList) entry.getValue());
newMap.put(entry.getKey(), jsonArray);
} else {
newMap.put(entry.getKey(), entry.getValue().toString());
}
}
return newMap;
}

/**
* 解析yml配置
*
* @param inputStream 要解析的yml文件输入流
* @return 解析结果
*/
private Map<String, Object> paserYml(InputStream inputStream) {
Map<String, Object> newMap = new HashMap<>(16);
try {
Yaml yaml = new Yaml();
Map map = yaml.load(inputStream);
newMap = formatMap(map);
} catch (Exception e) {
log.warn("解析Yml文件出现异常!");
}
return newMap;
}

/**
* 启动时加载application.yml配置文件信息到consul配置中心
* 加载到Consul的文件在ClassPathResource中指定
*/
@PostConstruct
private void init() {
Map<String, Object> props = getProperties(null);
List<String> keyList = this.getKVKeysOnly();
log.info("Found keys : {}", keyList);
for (Map.Entry<String, Object> prop : props.entrySet()) {
//判断有spring.profiles.active则读取对应文件下的配置
if (prop.getKey().equals("spring.profiles.active")) {
Map<String, Object> props2 = getProperties((String) prop.getValue());
for (Map.Entry<String, Object> prop2 : props2.entrySet()) {
visitProps(prop2.getKey(), prop2.getValue(), keyList);
}
continue;
}
visitProps(prop.getKey(), prop.getValue(), keyList);
}
}

/**
* 读取配置文件中的内容
*
* @param fixed
* @return
*/
private Map<String, Object> getProperties(String fixed) {
PropertiesProviderSelector propertiesProviderSelector = new PropertiesProviderSelector(
new PropertyBasedPropertiesProvider(), new YamlBasedPropertiesProvider(), new JsonBasedPropertiesProvider()
);
ClassPathResource resource;
if (fixed != null && !fixed.isEmpty()) {
resource = new ClassPathResource("application-" + fixed + ".properties");
} else {
resource = new ClassPathResource("application.properties");
}
String fileName = resource.getFilename();
String path = null;
Map<String, Object> props = new HashMap<>(16);
try (InputStream input = resource.getInputStream()) {
log.info("Found config file: " + resource.getFilename() + " in context " + resource.getURL().getPath());
path = resource.getURL().getPath();
if (fileName.endsWith(".properties")) {
PropertiesProvider provider = propertiesProviderSelector.getProvider(fileName);
props = (Map) provider.getProperties(input);

} else if (fileName.endsWith(".yml")) {
props = paserYml(resource.getInputStream());
}
} catch (IOException e) {
log.warn("Unable to load properties from file: {},message: {} ", path, e.getMessage());
}
return props;
}
/**
* 将应用的配置信息保存到consul中
*
* @param kvValue 封装的配置信息的map对象
*/

public void setKVValue(Map<String, String> kvValue) {
for (Map.Entry<String, String> kv : kvValue.entrySet()) {
try {
this.consulClient.setKVValue(keyPrefix + kv.getKey(), kv.getValue());
} catch (Exception e) {
log.warn("SetKVValue exception: {},kvValue: {}", e.getMessage(), kvValue);
}
}
}

public void setKVValue(String key, String value) {
try {
this.consulClient.setKVValue(keyPrefix + key, value);
} catch (Exception e) {
log.warn("SetKVValue exception: {},key: {},value: {}", e.getMessage(), key, value);
}
}

/**
* 获取应用配置的所有key-value信息
*
* @param keyPrefix key所在的目录前缀,格式为:config/应用名称/
* @return 应用配置的所有key-value信息
*/

public Map<String, String> getKVValues(String keyPrefix) {
Map<String, String> map = new HashMap<>(16);

try {
Response<List<GetValue>> response = this.consulClient.getKVValues(keyPrefix);
if (response != null) {
for (GetValue getValue : response.getValue()) {
int index = getValue.getKey().lastIndexOf("/") + 1;
String key = getValue.getKey().substring(index);
String value = getValue.getDecodedValue();
map.put(key, value);
}
}
return map;
} catch (Exception e) {
log.warn("GetKVValues exception: {},keyPrefix: {}", e.getMessage(), keyPrefix);
}
return null;
}


public Map<String, String> getKVValues() {
return this.getKVValues(keyPrefix);
}

/**
* 获取应用配置的所有key信息
*
* @param keyPrefix key所在的目录前缀,格式为:config/应用名称/
* @return 应用配置的所有key信息
*/

public List<String> getKVKeysOnly(String keyPrefix) {
List<String> list = new ArrayList<>();
try {
Response<List<String>> response = this.consulClient.getKVKeysOnly(keyPrefix);

if (response.getValue() != null) {
for (String key : response.getValue()) {
int index = key.lastIndexOf("/") + 1;
String temp = key.substring(index);
list.add(temp);
}
}
return list;
} catch (Exception e) {
log.warn("GetKVKeysOnly exception: {},keyPrefix: {}", e.getMessage(), keyPrefix);
}
return null;
}

public List<String> getKVKeysOnly() {
return this.getKVKeysOnly(keyPrefix);
}
}

个人习惯使用application.properties文件,如果是yml类型的自己更换上面涉及到的后缀。
可能有人会对@RefreshScope配置不解,它的作用是支持不停机动态刷新配置,也就是当注册中心的配置更改后,项目会感知到配置的变化,从而刷新有标记此注解的类或方法对配置的引用。
当然,前提是你还得开启定时调度注解,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Key value application
* <p/>
* Created in 2018.08.29
* <p/>
* 启用定时调度功能,Consul需要使用此功能来监控配置改变
* @author Liaodashuai
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableScheduling
@EnableAutoConfiguration
public class ConsulKeyValueApplication {

/**
* The entry point of application.
*
* @param args the input arguments
*/
public static void main(String[] args) {
SpringApplication.run(ConsulKeyValueApplication.class, args);
}
}

@EnableDiscoveryClient注解是将服务标记为客户端,可被发现注册并注册到consul上。
@EnableScheduling 开启定时调度功能,也就是隔一段时间回去扫描配置中心,如果配置有发生,有通知并刷新有标记@RefreshScope的类或方法所引用的配置。是不是很高大上。

效果图

这里附上结果图:
image

github源码: https://github.com/liaozihong/SpringCloud-Learning/tree/master/SpringCloud-Consul-Config-Server
参考链接:
https://www.cnblogs.com/EasonJim/p/7589546.html

文章目录
  1. 1. consul介绍
  2. 2. 使用consul配置中心
  3. 3. 将服务注册到consul上
  4. 4. 推送配置到consul配置中心
  5. 5. 效果图
作者共写了53.1k个字 本站总访问量  |   您是访问本站的第个小伙伴