一、背景介紹
在分布式系統中動态配置中,可以避免重複重新開機服務,動态更改服務參數等。一句話非常重要。 另外一篇文章也是這樣說的,哈哈。 Consul 作為Spring 推薦的分布式排程系統其也具備配置中心的功能, 我們也可以利用其作為配置中心,其client端主動定時發起與配置中心同步機制,實作動态配置的的更新。
環境依賴:
名稱 | 值 | 備注 |
---|---|---|
JDK | 1.8 | |
Consul | 1.5.2 | 注冊中心,Consul安裝及介紹 https://mp.csdn.net/mdeditor/95372805# |
SpringCloud | Greenwich.SR1 |
二、項目實戰
1) pom依賴(主要)
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<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>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
2)配置檔案
application.properties
#0表示伺服器随機端口
server.port=8090
本次示範的kv的預設值(老闆預設給你0元)
company.pay.money=0
bootstrap.properties
#服務名稱
spring.application.name=waiter-service
#consul 位址
spring.cloud.consul.host=localhost
#consul 端口
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.prefer-ip-address=true
#consul配置中心功能,預設true
spring.cloud.consul.config.enabled=true
#consul配置中心值的格式
spring.cloud.consul.config.format=yaml
3)動态參數接收類
@ConfigurationProperties("company.pay")
@RefreshScope
@Data
@Component
public class PayMoneyProperties {
//key結尾部分,以小數點為間隔
Integer money ;
}
備注:
ConfigurationProperties 表示這個類關關聯态配置,“company.pay”表示key的字首部分。
@RefreshScope 表示動态重新整理config server 值
@Component 表示将該類加載到IOC容器中
在實戰中嘗試用@Value的方式擷取動态,隻能實作服務重新開機後擷取動态的config server 的值,最終找到解決方案在相應的取值類上加@RefreshScope注解,完美解決。
4)對外接口(便于直覺驗證)
方式一:
@RestController
@RequestMapping("consul")
public class ConsulConfigController {
@Autowired
private PayMoneyProperties payMoneyProperties ;
@RequestMapping("/pay/money")
public Object getConfig(HttpRequest request){
String money ="項目順利上線,老闆開始發獎金:";
return money + payMoneyProperties.getMoney();
}
}
方式二:
@RestController
@RequestMapping(“consul”)
//啟用動态配置重新整理
@RefreshScope
public class ConsulConfigController {
//擷取配置的值
@Value("${company.pay.money}")
private String moneyConfig;
@RequestMapping("/pay/money")
public Object getConfig(HttpRequest request){
String money =“項目順利上線,老闆開始發獎金:”;
return money +moneyConfig;
}
5)啟動項目

上圖可以通過日志看出config server 的連接配接資訊
6)consul config server 還沒設定對應節點值時示範(擷取的是本地配置檔案值)
備注:Spring boot 在加載配置順序:本地配置檔案 --> Config Server -->application
7) consul 中建立資料節點
請求位址:http://localhost:8500
建立資料節點:config/waiter-service/data
注意:YAML資料中,通過空格、“:” 表示資料層級關系, 在設定這個值前,可以在網上校驗一下YAML内容的有效性;
8)驗證項目裡是有有收到動态配置
如下圖,表示已經通知到項目更新的值
在驗證接口中請求一下對應接口,發現值已經和consul config server 中動态設定的值相同了
三、總結
1) 如果在你們的微服務中已經使用consul 作為注冊中心, 那麼推薦使用上文的方案, 畢竟可以少維護一套系統。
2) consul 作為注冊中心、相比zookeeper 作為注冊中心,有了更友好的web頁面,如果有版本或復原的一些操作就更完美了。
3)client 會定時拉取consul config server 值,與本地值對比
ConfigWatch 類核心代碼
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
this::watchConfigKeyValues, this.properties.getWatch().getDelay());
}
}
@Timed("consul.watch-config-keys")
public void watchConfigKeyValues() {
if (this.running.get()) {
for (String context : this.consulIndexes.keySet()) {
// turn the context into a Consul folder path (unless our config format
// are FILES)
if (this.properties.getFormat() != FILES && !context.endsWith("/")) {
context = context + "/";
}
try {
Long currentIndex = this.consulIndexes.get(context);
if (currentIndex == null) {
currentIndex = -1L;
}
log.trace("watching consul for context '" + context + "' with index "
+ currentIndex);
// use the consul ACL token if found
String aclToken = this.properties.getAclToken();
if (StringUtils.isEmpty(aclToken)) {
aclToken = null;
}
Response<List<GetValue>> response = this.consul.getKVValues(context,
aclToken,
new QueryParams(this.properties.getWatch().getWaitTime(),
currentIndex));
// if response.value == null, response was a 404, otherwise it was a
// 200
// reducing churn if there wasn't anything
if (response.getValue() != null && !response.getValue().isEmpty()) {
Long newIndex = response.getConsulIndex();
if (newIndex != null && !newIndex.equals(currentIndex)) {
// don't publish the same index again, don't publish the first
// time (-1) so index can be primed
if (!this.consulIndexes.containsValue(newIndex)
&& !currentIndex.equals(-1L)) {
log.trace("Context " + context + " has new index "
+ newIndex);
RefreshEventData data = new RefreshEventData(context,
currentIndex, newIndex);
this.publisher.publishEvent(
new RefreshEvent(this, data, data.toString()));
}
else if (log.isTraceEnabled()) {
log.trace("Event for index already published for context "
+ context);
}
this.consulIndexes.put(context, newIndex);
}
else if (log.isTraceEnabled()) {
log.trace("Same index for context " + context);
}
}
else if (log.isTraceEnabled()) {
log.trace("No value for context " + context);
}
}
catch (Exception e) {
// only fail fast on the initial query, otherwise just log the error
if (this.firstTime && this.properties.isFailFast()) {
log.error(
"Fail fast is set and there was an error reading configuration from consul.");
ReflectionUtils.rethrowRuntimeException(e);
}
else if (log.isTraceEnabled()) {
log.trace("Error querying consul Key/Values for context '"
+ context + "'", e);
}
else if (log.isWarnEnabled()) {
// simplified one line log message in the event of an agent
// failure
log.warn("Error querying consul Key/Values for context '"
+ context + "'. Message: " + e.getMessage());
}
}
}
}
this.firstTime = false;
}
原文位址:https://blog.csdn.net/qq_36918149/article/details/99709397
</div>