中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Config中怎么實現配置熱刷新

發布時間:2021-06-26 14:08:17 來源:億速云 閱讀:685 作者:Leah 欄目:web開發

Config中怎么實現配置熱刷新,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

問題1. 如何實現配置熱刷新重點 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實現,用來實現配置、實例熱加載。

類似的有:

  • RequestScope:是從當前web request中獲取實例的實例

  • SessionScope:是從Session中獲取實例的實例

  • ThreadScope:是從ThreadLocal中獲取的實例

RefreshScope是從內建緩存中獲取的。

2. ContextRefresher.refresh()

當有配置更新的時候,觸發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(); }

①提取標準參數(SYSTEM,JNDI,SERVLET)之外所有參數變量

②把原來的Environment里的參數放到一個新建的Spring  Context容器下重新加載,完事之后關閉新容器(重點:可以去debug跟蹤下,實際上是重啟了個SpringApplication)

③提起更新過的參數(排除標準參數)

④比較出變更項

⑤發布環境變更事件

⑥RefreshScope用新的環境參數重新生成Bean,重新生成的過程很簡單,清除refreshscope緩存幷銷毀Bean,下次就會重新從BeanFactory獲取一個新的實例(該實例使用新的配置)

3. RefreshScope.refreshAll()

RefreshScope.refreshAll方法實現,即上面的第⑥步調用:

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(); }

清空緩存后,下次訪問對象時就會重新創建新的對象并放入緩存了。

而在清空緩存后,它還會發出一個 RefreshScopeRefreshedEvent 事件,在某些 Spring Cloud  的組件中會監聽這個事件并作出一些反饋。

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(未重新啟動,實現了配置更新)

問題2.  Nacos客戶端如何實時監聽到Nacos服務端配置更新了

這里可以去看下Nacos源碼,使用的是長輪詢,什么是長輪詢以及其其他替代協議?

  • RocketMQ

  • Nacos

  • Apollo

  • Kafka

自己花了幾個小時去看Nacos長輪詢源碼,太多了不太好理解,有興趣的自行google。一般我們都是基于Spring  Boot的后臺了,各種google后,發現Apollo實現較為簡單,所以直接拿Apollo的代碼借鑒。

1. Apollo 實現方式

實現方式如下:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. 客戶端會發起一個Http請求到Config  Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService

  3. NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起

  4. 如果在60秒內沒有該客戶端關心的配置發布,那么會返回Http狀態碼304給客戶端

  5. 如果有該客戶端關心的配置發布,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回。客戶端從返回的結果中獲取到配置變化的namespace后,會立即請求Config  Service獲取該namespace的最新配置。

解讀下:

  • 關鍵詞DeferredResult,使用這個特性來實現長輪詢

  • 超時返回的時候,是返回的狀態碼Http Code 304

釋義:自從上次請求后,請求的網頁未修改過。服務器返回此響應時,不會返回網頁內容,進而節省帶寬和開銷。

2. 什么是DeferredResult

異步支持是在Servlet 3.0中引入的,簡單來說,它允許在請求接收器線程之外的另一個線程中處理HTTP請求。

從Spring 3.2開始可用的DeferredResult有助于將長時間運行的計算從http-worker線程卸載到單獨的線程。

盡管另一個線程將占用一些資源來進行計算,但不會阻止工作線程,并且可以處理傳入的客戶端請求。

異步請求處理模型非常有用,因為它有助于在高負載期間很好地擴展應用程序,尤其是對于IO密集型操作。

DeferredResult是對異步Servlet的封裝

具體可以參考我在CSDN寫的Spring Boot 使用DeferredResult實現長輪詢

這里借助互聯網上的一個圖就更清晰些。

Servlet異步流程圖

Config中怎么實現配置熱刷新

接收到request請求之后,由tomcat工作線程從HttpServletRequest中獲得一個異步上下文AsyncContext對象,然后由tomcat工作線程把AsyncContext對象傳遞給業務處理線程,同時tomcat工作線程歸還到工作線程池,這一步就是異步開始。在業務處理線程中完成業務邏輯的處理,生成response返回給客戶端。

3. 模擬造輪子

這里我們通過使用 Spring Boot 來簡單的模擬一下如何通過 Spring Boot DeferredResult 來實現長輪詢服務推送的。

代碼如下,僅供參考:

/**  * 模擬Config Service通知客戶端的長輪詢實現原理  */ @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 狀態碼告訴客戶端當前命名空間的配置文件并沒有更新         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;     }     /**      * 模擬發布配置      */     @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一旦執行了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中怎么實現配置熱刷新

關于Config中怎么實現配置熱刷新問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

合江县| 克什克腾旗| 安阳县| 鲁山县| 宜宾市| 都兰县| 郓城县| 襄樊市| 郎溪县| 巴林右旗| 彩票| 汝州市| 天水市| 柘荣县| 土默特左旗| 宜川县| 鱼台县| 德阳市| 崇明县| 特克斯县| 探索| 财经| 枞阳县| 哈密市| 昭觉县| 巨野县| 册亨县| 新津县| 剑河县| 伊吾县| 镇远县| 凯里市| 陆河县| 宜良县| 丰顺县| 芦溪县| 阜平县| 黔江区| 佛坪县| 青浦区| 静宁县|