動(dòng)態(tài)刷新配置
有時(shí)候需要對(duì)配置內(nèi)容做些實(shí)時(shí)更新,那么spring cloud config是否可以實(shí)現(xiàn)呢?肯定是可以的,下面對(duì)快速入門進(jìn)行一些改造.
回顧一下快速入門,
-
config-repo-demo:在git上新建的一項(xiàng)目模塊,只要是做配置中心的,其中存儲(chǔ)了應(yīng)用名為zhihao的多環(huán)境配置文件,配置文件中有二個(gè)配置參數(shù)from和spring.datasource.name -
config-server-git:配置了git倉庫的服務(wù)端 -
config-client: 指定了config-server為配置中心的客戶端,應(yīng)用名為zhihao,用來訪問配置服務(wù)器以獲取配置信息。該應(yīng)用中提供了一個(gè)/index接口,訪問config-repo-demo/zhihao.yml中的from和spring.datasource.name屬性,
當(dāng)前配置文件:
from: git-pro-2.0
spring:
datasource:
username: user_pro
在線修改之后,
from: git-pro-3.0
spring:
datasource:
username: user_pro3
測(cè)試,http://localhost:8080/index發(fā)現(xiàn)訪問客戶端的接口,頁面顯示還是
username=user_pro,form==git-pro-2.0
接下來,我們將config-client端做一些改造以實(shí)現(xiàn)配置信息的動(dòng)態(tài)刷新。
- 在
config-client的pom文件中新增spring-boot-starter-actuator監(jiān)控依賴,其中包含/refresh端點(diǎn)的實(shí)現(xiàn),該端點(diǎn)將用于實(shí)現(xiàn)客戶端應(yīng)用配置信息的重新獲取與刷新。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 在客戶端上需要注入配置值的類上加入注解
@RefreshScope
@RestController
@RefreshScope
public class ConfigClientController {
private Logger log = LoggerFactory.getLogger(getClass());
@Value("${spring.datasource.username}")
private String username;
@Value("${from}")
private String from;
@GetMapping("/index")
public String index(){
log.info("username="+username+",form=="+from);
return "username="+username+",form=="+from;
}
}
- 重新訪問
config-client,訪問一次可以看到當(dāng)前的配置 - 修改git上的配置
- 在次訪問
http://localhost:8080/index,看到配置信息沒有改變 - 通過post請(qǐng)求發(fā)送到
localhost:8080/refresh,可以看到返回的內(nèi)容
? curl -X POST http://localhost:8080/refresh
["config.client.version","spring.datasource.username","from"]
- 再次訪問
http://localhost:8080/index,看到更新后的值了
username=user_pro3.0,form==git-pro-3.0
通過上面的介紹,該功能還可以同git倉庫的web hook功能進(jìn)行關(guān)聯(lián),當(dāng)有g(shù)it提交變化時(shí),就給對(duì)應(yīng)的配置主機(jī)發(fā)送/refresh請(qǐng)求來實(shí)現(xiàn)配置信息的實(shí)時(shí)更新。但是,當(dāng)我們的系統(tǒng)發(fā)展壯大之后,維護(hù)這樣的刷新清單也將成為一個(gè)非常大的負(fù)擔(dān),而且很容易犯錯(cuò),可以使用spring cloud bus來解決這個(gè)問題。
相關(guān)官網(wǎng)文檔的一些翻譯
環(huán)境變更
The application will listen for an EnvironmentChangedEvent and react to the change in a couple of standard ways (additional ApplicationListeners can be added as @Beans by the user in the normal way). When an EnvironmentChangedEvent is observed it will have a list of key values that have changed, and the application will use those to:
- Re-bind any @ConfigurationProperties beans in the context
- Set the logger levels for any properties in logging.level.*
應(yīng)用程序?qū)⒈O(jiān)聽一個(gè)EnvironmentChangedEvent事件,并以幾種標(biāo)準(zhǔn)方式對(duì)變化進(jìn)行響應(yīng)(用戶可以通過常規(guī)方式將額外的ApplicationListener添加為@Beans)。 當(dāng)觀察到一個(gè)EnvironmentChangedEvent事件時(shí),它將有一個(gè)已經(jīng)更改的鍵值列表,應(yīng)用程序?qū)⑹褂靡韵聝?nèi)容:
- 在上下文中重新綁定任何
@ConfigurationProperties實(shí)例 - 為
logging.level.*中的任何屬性設(shè)置日志級(jí)別。
Note that the Config Client does not by default poll for changes in the Environment, and generally we would not recommend that approach for detecting changes (although you could set it up with a @Scheduled annotation). If you have a scaled-out client application then it is better to broadcast the EnvironmentChangedEvent to all the instances instead of having them polling for changes (e.g. using the Spring Cloud Bus).
請(qǐng)注意,Config Client不會(huì)對(duì)環(huán)境中的更改進(jìn)行默認(rèn)輪詢拉取,通常我們不建議使用這種方式檢測(cè)配置修改(盡管可以使用@Scheduled注釋進(jìn)行設(shè)置)。 如果您有一個(gè)擴(kuò)展的客戶端應(yīng)用程序,那么最好將EnvironmentChangedEvent事件廣播到所有實(shí)例,而不是讓它們輪詢更改(例如使用Spring Cloud Bus)。(這個(gè)也是手動(dòng)刷新的弊端,每個(gè)應(yīng)用都需要去手動(dòng)刷新)。
The EnvironmentChangedEvent covers a large class of refresh use cases, as long as you can actually make a change to the Environment and publish the event (those APIs are public and part of core Spring). You can verify the changes are bound to @ConfigurationProperties beans by visiting the /configprops endpoint (normal Spring Boot Actuator feature). For instance a DataSource can have its maxPoolSize changed at runtime (the default DataSource created by Spring Boot is an @ConfigurationProperties bean) and grow capacity dynamically. Re-binding @ConfigurationProperties does not cover another large class of use cases, where you need more control over the refresh, and where you need a change to be atomic over the whole ApplicationContext. To address those concerns we have @RefreshScope.
EnvironmentChangedEvent涵蓋了大量的刷新用例,只要您實(shí)際可以更改環(huán)境并發(fā)布事件(這些API是公開的,并且是Spring的一部分)。 您可以通過訪問/configprops端點(diǎn)(正常的Spring Boot監(jiān)控的特征)來驗(yàn)證更改是否綁定到@ConfigurationProperties實(shí)例。 例如,DataSource可以在運(yùn)行時(shí)更改其maxPoolSize(Spring Boot創(chuàng)建的默認(rèn)DataSource是一個(gè)@ConfigurationProperties實(shí)例)并動(dòng)態(tài)增加容量。 重新綁定@ConfigurationProperties不會(huì)覆蓋另一個(gè)實(shí)例,您需要更多的控制刷新,并且您需要更改在整個(gè)ApplicationContext上是原子的。 為了解決這些問題,我們有@RefreshScope。
刷新范圍(Refresh Scope)
A Spring @Bean that is marked as @RefreshScope will get special treatment when there is a configuration change. This addresses the problem of stateful beans that only get their configuration injected when they are initialized. For instance if a DataSource has open connections when the database URL is changed via the Environment, we probably want the holders of those connections to be able to complete what they are doing. Then the next time someone borrows a connection from the pool he gets one with the new URL.
當(dāng)配置更改時(shí),標(biāo)記為@RefreshScope的Spring @Bean將受到特殊的處理。 這解決了有狀態(tài)bean在初始化時(shí)只注入配置的問題。 例如,當(dāng)我們?cè)诟臄?shù)據(jù)庫的URL的時(shí)候已經(jīng)有用戶拿到了當(dāng)前實(shí)例的連接,那么我們可能希望這些連接的持有人能夠完成他們正在做的工作。 然后下一次有人從數(shù)據(jù)庫連接池的連接就是從新的url連接上來拿。
Refresh scope beans are lazy proxies that initialize when they are used (i.e. when a method is called), and the scope acts as a cache of initialized values. To force a bean to re-initialize on the next method call you just need to invalidate its cache entry.
刷新范圍bean是在使用時(shí)初始化的懶惰代理(即當(dāng)調(diào)用方法時(shí)),并且作用域作為初始化值的緩存。 要強(qiáng)制bean重新初始化下一個(gè)方法調(diào)用,您只需要使其緩存條目無效。
The RefreshScope is a bean in the context and it has a public method refreshAll() to refresh all beans in the scope by clearing the target cache. There is also a refresh(String) method to refresh an individual bean by name. This functionality is exposed in the /refresh endpoint (over HTTP or JMX).
RefreshScope是上下文中的一個(gè)bean,它有一個(gè)公共方法refreshAll()通過清除目標(biāo)緩存來刷新作用域中的所有bean。 還有一個(gè)refresh方法來按名稱刷新單個(gè)的bean。 此功能在/refresh端點(diǎn)(通過HTTP或JMX)中公開。
注意
@RefreshScope works (technically) on an @Configuration class, but it might lead to surprising behaviour: e.g. it does not mean that all the @Beans defined in that class are themselves @RefreshScope. Specifically, anything that depends on those beans cannot rely on them being updated when a refresh is initiated, unless it is itself in @RefreshScope (in which it will be rebuilt on a refresh and its dependencies re-injected, at which point they will be re-initialized from the refreshed @Configuration).
@RefreshScope可以和@Configuration類上作用起效果(技術(shù)上來說),但可能會(huì)意想不到的行為:例如這并不保證著該類中定義的所有@Beans都是@RefreshScope。 具體來說,依賴于這些bean的任何東西都不能依賴它們?cè)谒⑿聠?dòng)時(shí)被更新,除非它本身在@RefreshScope中(它將在刷新中被重建,并且重新注入它的依賴項(xiàng),那么它們將在從刷新的@Configuration重新初始化)。
/refresh for re-loading the boot strap context and refreshing the @RefreshScope beans.
/refresh端點(diǎn)重新加載帶上下文并刷新@RefreshScope實(shí)例