Config中怎么實現(xiàn)配置熱刷新,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
創(chuàng)新互聯(lián)公司是一家集網站建設,甘德企業(yè)網站建設,甘德品牌網站建設,網站定制,甘德網站建設報價,網絡營銷,網絡優(yōu)化,甘德網站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網站。
問題1. 如何實現(xiàn)配置熱刷新重點 Nacos原理:
1.在需要熱刷新的Bean上使用Spring Cloud原生注解 @RefreshScope
2.當有配置更新的時候調用contextRefresher.refresh()
代碼如下:
@RestController @RequestMapping("/config") @RefreshScope // 重點 public class ConfigController { @Value("${laker.name}") // 待刷新的屬性 private String lakerName; @RequestMapping("/get") public String get() { return lakerName; } ... }
1. @RefreshScope原理
@RefreshScope位于spring-cloud-context,源碼注釋如下:
可將@Bean定義放入org.springframework.cloud.context.scope.refresh.RefreshScope中。用這種方式注解的Bean可以在運行時刷新,并且使用它們的任何組件都將在下一個方法調用前獲得一個新實例,該實例將完全初始化并注入所有依賴項。
要清楚RefreshScope,先要了解Scope
Scope(org.springframework.beans.factory.config.Scope)是Spring 2.0開始就有的核心的概念
RefreshScope(org.springframework.cloud.context.scope.refresh), 即@Scope("refresh")是spring cloud提供的一種特殊的scope實現(xiàn),用來實現(xiàn)配置、實例熱加載。
類似的有:
RequestScope:是從當前web request中獲取實例的實例
SessionScope:是從Session中獲取實例的實例
ThreadScope:是從ThreadLocal中獲取的實例
RefreshScope是從內建緩存中獲取的。
2. ContextRefresher.refresh()
當有配置更新的時候,觸發(fā)ContextRefresher.refresh
RefreshScope 刷新過程
入口在ContextRefresher.refresh
public synchronized Set<String> refresh() { ① Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources()); ② updateEnvironment(); ④ Set<String> keys = changes(before, ③extract(this.context.getEnvironment().getPropertySources())).keySet(); ⑤ this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys)); ⑥ this.scope.refreshAll(); }
①提取標準參數(shù)(SYSTEM,JNDI,SERVLET)之外所有參數(shù)變量
②把原來的Environment里的參數(shù)放到一個新建的Spring Context容器下重新加載,完事之后關閉新容器(重點:可以去debug跟蹤下,實際上是重啟了個SpringApplication)
③提起更新過的參數(shù)(排除標準參數(shù))
④比較出變更項
⑤發(fā)布環(huán)境變更事件
⑥RefreshScope用新的環(huán)境參數(shù)重新生成Bean,重新生成的過程很簡單,清除refreshscope緩存幷銷毀Bean,下次就會重新從BeanFactory獲取一個新的實例(該實例使用新的配置)
3. RefreshScope.refreshAll()
RefreshScope.refreshAll方法實現(xiàn),即上面的第⑥步調用:
public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }
RefreshScope類中有一個成員變量 cache,用于緩存所有已經生成的 Bean,在調用 get 方法時嘗試從緩存加載,如果沒有的話就生成一個新對象放入緩存,并通過 getBean 初始化其對應的 Bean:
public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
所以在銷毀時只需要將整個緩存清空,下次獲取對象時自然就可以重新生成新的對象,也就自然綁定了新的屬性:
public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = this.locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); }
清空緩存后,下次訪問對象時就會重新創(chuàng)建新的對象并放入緩存了。
而在清空緩存后,它還會發(fā)出一個 RefreshScopeRefreshedEvent 事件,在某些 Spring Cloud 的組件中會監(jiān)聽這個事件并作出一些反饋。
4. 模擬造輪子
這里我們就可以模擬造個熱更新的輪子了;
代碼以及配置如下:
項目依賴spring-cloud-context
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> </dependency>
配置bean
@Component @RefreshScope public class User { @Value("${laker.name}") private String name; ... }
刷新接口以及查看接口
@RestController @RequestMapping("/config") public class ConfigController { @Autowired User user; @Autowired ContextRefresher contextRefresher; @RequestMapping("/get") public String get() { return user.getName(); } @RequestMapping("/refresh") public String[] refresh() { Set<String> keys = contextRefresher.refresh(); return keys.toArray(new String[keys.size()]); }
application.yml
laker: name: laker
操作流程如下:
1.瀏覽器http://localhost:8080/config/get - 瀏覽器結果:laker
2.修改application.yml里面內容為:
laker: name: lakerupdate
3.瀏覽器http://localhost:8080/config/refresh - 瀏覽器結果:laker.name
4.瀏覽器http://localhost:8080/config/get - 瀏覽器結果:lakerupdate(未重新啟動,實現(xiàn)了配置更新)
問題2. Nacos客戶端如何實時監(jiān)聽到Nacos服務端配置更新了
這里可以去看下Nacos源碼,使用的是長輪詢,什么是長輪詢以及其其他替代協(xié)議?
RocketMQ
Nacos
Apollo
Kafka
自己花了幾個小時去看Nacos長輪詢源碼,太多了不太好理解,有興趣的自行google。一般我們都是基于Spring Boot的后臺了,各種google后,發(fā)現(xiàn)Apollo實現(xiàn)較為簡單,所以直接拿Apollo的代碼借鑒。
1. Apollo 實現(xiàn)方式
實現(xiàn)方式如下:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)
客戶端會發(fā)起一個Http請求到Config Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService
NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起
如果在60秒內沒有該客戶端關心的配置發(fā)布,那么會返回Http狀態(tài)碼304給客戶端
如果有該客戶端關心的配置發(fā)布,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回??蛻舳藦姆祷氐慕Y果中獲取到配置變化的namespace后,會立即請求Config Service獲取該namespace的最新配置。
解讀下:
關鍵詞DeferredResult,使用這個特性來實現(xiàn)長輪詢
超時返回的時候,是返回的狀態(tài)碼Http Code 304
釋義:自從上次請求后,請求的網頁未修改過。服務器返回此響應時,不會返回網頁內容,進而節(jié)省帶寬和開銷。
2. 什么是DeferredResult
異步支持是在Servlet 3.0中引入的,簡單來說,它允許在請求接收器線程之外的另一個線程中處理HTTP請求。
從Spring 3.2開始可用的DeferredResult有助于將長時間運行的計算從http-worker線程卸載到單獨的線程。
盡管另一個線程將占用一些資源來進行計算,但不會阻止工作線程,并且可以處理傳入的客戶端請求。
異步請求處理模型非常有用,因為它有助于在高負載期間很好地擴展應用程序,尤其是對于IO密集型操作。
DeferredResult是對異步Servlet的封裝
具體可以參考我在CSDN寫的Spring Boot 使用DeferredResult實現(xiàn)長輪詢
這里借助互聯(lián)網上的一個圖就更清晰些。
Servlet異步流程圖
接收到request請求之后,由tomcat工作線程從HttpServletRequest中獲得一個異步上下文AsyncContext對象,然后由tomcat工作線程把AsyncContext對象傳遞給業(yè)務處理線程,同時tomcat工作線程歸還到工作線程池,這一步就是異步開始。在業(yè)務處理線程中完成業(yè)務邏輯的處理,生成response返回給客戶端。
3. 模擬造輪子
這里我們通過使用 Spring Boot 來簡單的模擬一下如何通過 Spring Boot DeferredResult 來實現(xiàn)長輪詢服務推送的。
代碼如下,僅供參考:
/** * 模擬Config Service通知客戶端的長輪詢實現(xiàn)原理 */ @RestController @RequestMapping("/config") public class LakerConfigController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //guava中的Multimap,多值map,對map的增強,一個key可以保持多個value private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create()); /** * 模擬長輪詢 */ @RequestMapping(value = "/get/{dataId}") public DeferredResult<String> watch(@PathVariable("dataId") String dataId) { logger.info("Request received"); ResponseEntity<String> NOT_MODIFIED_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_MODIFIED); // 超時時間30s 返回 304 狀態(tài)碼告訴客戶端當前命名空間的配置文件并沒有更新 DeferredResult<String> deferredResult = new DeferredResult<>(30 * 1000L, NOT_MODIFIED_RESPONSE); //當deferredResult完成時(不論是超時還是異常還是正常完成),移除watchRequests中相應的watch key deferredResult.onCompletion(() -> { logger.info("remove key:" + dataId); watchRequests.remove(dataId, deferredResult); }); deferredResult.onTimeout(() -> { logger.info("onTimeout()"); }); watchRequests.put(dataId, deferredResult); logger.info("Servlet thread released"); return deferredResult; } /** * 模擬發(fā)布配置 */ @RequestMapping(value = "/update/{dataId}") public Object publishConfig(@PathVariable("dataId") String dataId) { if (watchRequests.containsKey(dataId)) { Collection<DeferredResult<String>> deferredResults = watchRequests.get(dataId); Long time = System.currentTimeMillis(); //通知所有watch這個namespace變更的長輪訓配置變更結果 for (DeferredResult<String> deferredResult : deferredResults) { //deferredResult一旦執(zhí)行了setResult()方法,就說明DeferredResult正常完成了,會立即把結果返回給客戶端 deferredResult.setResult(dataId + " changed:" + time); } } return "success"; } }
操作流程如下:
為了簡便我用瀏覽器模擬,實際用Java Http Client,例如:okhttp、Apache http client等
正常流程:
client1瀏覽器http://localhost:8080/config/get/laker,阻塞中ing
client2瀏覽器http://localhost:8080/config/update/laker,返回success
client1瀏覽器http://localhost:8080/config/get/laker,返回laker changed:1611022736865
超時流程:
client1瀏覽器http://localhost:8080/config/get/laker,阻塞中ing
30s后
client1瀏覽器,返回http code 304
關于Config中怎么實現(xiàn)配置熱刷新問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關知識。
文章標題:Config中怎么實現(xiàn)配置熱刷新
URL地址:http://chinadenli.net/article14/gidode.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供品牌網站設計、外貿網站建設、云服務器、網頁設計公司、App設計、虛擬主機
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)