偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Nacos或者Config是怎么實(shí)現(xiàn)配置熱刷新的?

開發(fā) 前端
文中大致介紹實(shí)現(xiàn)技術(shù)的關(guān)鍵點(diǎn),以及如何模仿造個(gè)簡易輪子(造輪子很重要,只有自己想著造輪子,才會(huì)問出很多原理問題),具體源碼細(xì)節(jié),請(qǐng)拿著文中的關(guān)鍵詞自行g(shù)oogle,然后跟著debug即可。
本文轉(zhuǎn)載自微信公眾號(hào)「Java大廠面試官」,作者laker。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java大廠面試官公眾號(hào)。 laker  
  • 前言
  • 問題1. 如何實(shí)現(xiàn)配置熱刷新
    • 1. @RefreshScope原理
    • 2. ContextRefresher.refresh()
    • 3. RefreshScope.refreshAll()
    • 4. 模擬造輪子
  • 問題2. Nacos客戶端如何實(shí)時(shí)監(jiān)聽到Nacos服務(wù)端配置更新了
    • 1. Apollo 實(shí)現(xiàn)方式
    • 2. 什么是DeferredResult
    • 3. 模擬造輪子
  • 總結(jié)

前言

文中大致介紹實(shí)現(xiàn)技術(shù)的關(guān)鍵點(diǎn),以及如何模仿造個(gè)簡易輪子(造輪子很重要,只有自己想著造輪子,才會(huì)問出很多原理問題),具體源碼細(xì)節(jié),請(qǐng)拿著文中的關(guān)鍵詞自行g(shù)oogle,然后跟著debug即可。

問題1. 如何實(shí)現(xiàn)配置熱刷新重點(diǎn) Nacos原理:

  • 1.在需要熱刷新的Bean上使用Spring Cloud原生注解 @RefreshScope
  • 2.當(dāng)有配置更新的時(shí)候調(diào)用contextRefresher.refresh()

代碼如下:

  1. @RestController 
  2. @RequestMapping("/config"
  3. @RefreshScope // 重點(diǎn) 
  4. public class ConfigController { 
  5.     @Value("${laker.name}") // 待刷新的屬性 
  6.     private String lakerName; 
  7.     @RequestMapping("/get"
  8.     public String get() { 
  9.         return lakerName; 
  10.     } 
  11.  ... 

1. @RefreshScope原理

@RefreshScope位于spring-cloud-context,源碼注釋如下:

可將@Bean定義放入org.springframework.cloud.context.scope.refresh.RefreshScope中。用這種方式注解的Bean可以在運(yùn)行時(shí)刷新,并且使用它們的任何組件都將在下一個(gè)方法調(diào)用前獲得一個(gè)新實(shí)例,該實(shí)例將完全初始化并注入所有依賴項(xiàng)。

要清楚RefreshScope,先要了解Scope

Scope(org.springframework.beans.factory.config.Scope)是Spring 2.0開始就有的核心的概念

RefreshScope(org.springframework.cloud.context.scope.refresh), 即@Scope("refresh")是spring cloud提供的一種特殊的scope實(shí)現(xiàn),用來實(shí)現(xiàn)配置、實(shí)例熱加載。

類似的有:

  • RequestScope:是從當(dāng)前web request中獲取實(shí)例的實(shí)例
  • SessionScope:是從Session中獲取實(shí)例的實(shí)例
  • ThreadScope:是從ThreadLocal中獲取的實(shí)例

RefreshScope是從內(nèi)建緩存中獲取的。

2. ContextRefresher.refresh()

當(dāng)有配置更新的時(shí)候,觸發(fā)ContextRefresher.refresh

RefreshScope 刷新過程

入口在ContextRefresher.refresh

  1. public synchronized Set<String> refresh() { 
  2. ①  Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources()); 
  3. ②  updateEnvironment(); 
  4. ④  Set<String> keys = changes(before, ③extract(this.context.getEnvironment().getPropertySources())).keySet(); 
  5. ⑤  this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys)); 
  6. ⑥       this.scope.refreshAll(); 

①提取標(biāo)準(zhǔn)參數(shù)(SYSTEM,JNDI,SERVLET)之外所有參數(shù)變量

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

③提起更新過的參數(shù)(排除標(biāo)準(zhǔn)參數(shù))

④比較出變更項(xiàng)

⑤發(fā)布環(huán)境變更事件

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

3. RefreshScope.refreshAll()

RefreshScope.refreshAll方法實(shí)現(xiàn),即上面的第⑥步調(diào)用:

  1. public void refreshAll() { 
  2.   super.destroy(); 
  3.   this.context.publishEvent(new RefreshScopeRefreshedEvent()); 

RefreshScope類中有一個(gè)成員變量 cache,用于緩存所有已經(jīng)生成的 Bean,在調(diào)用 get 方法時(shí)嘗試從緩存加載,如果沒有的話就生成一個(gè)新對(duì)象放入緩存,并通過 getBean 初始化其對(duì)應(yīng)的 Bean:

  1. public Object get(String name, ObjectFactory<?> objectFactory) { 
  2.  BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); 
  3.  this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); 
  4.  try { 
  5.   return value.getBean(); 
  6.  } 
  7.  catch (RuntimeException e) { 
  8.   this.errors.put(name, e); 
  9.   throw e; 
  10.  } 

所以在銷毀時(shí)只需要將整個(gè)緩存清空,下次獲取對(duì)象時(shí)自然就可以重新生成新的對(duì)象,也就自然綁定了新的屬性:

  1. public void destroy() { 
  2.  List<Throwable> errors = new ArrayList<Throwable>(); 
  3.  Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); 
  4.  for (BeanLifecycleWrapper wrapper : wrappers) { 
  5.   try { 
  6.    Lock lock = this.locks.get(wrapper.getName()).writeLock(); 
  7.    lock.lock(); 
  8.    try { 
  9.     wrapper.destroy(); 
  10.    } 
  11.    finally { 
  12.     lock.unlock(); 
  13.    } 
  14.   } 
  15.   catch (RuntimeException e) { 
  16.    errors.add(e); 
  17.   } 
  18.  } 
  19.  if (!errors.isEmpty()) { 
  20.   throw wrapIfNecessary(errors.get(0)); 
  21.  } 
  22.  this.errors.clear(); 

清空緩存后,下次訪問對(duì)象時(shí)就會(huì)重新創(chuàng)建新的對(duì)象并放入緩存了。

而在清空緩存后,它還會(huì)發(fā)出一個(gè) RefreshScopeRefreshedEvent 事件,在某些 Spring Cloud 的組件中會(huì)監(jiān)聽這個(gè)事件并作出一些反饋。

4. 模擬造輪子

這里我們就可以模擬造個(gè)熱更新的輪子了;

代碼以及配置如下:

  • 項(xiàng)目依賴spring-cloud-context
  1. <dependency> 
  2.   <groupId>org.springframework.cloud</groupId> 
  3.   <artifactId>spring-cloud-context</artifactId> 
  4. </dependency> 
  • 配置bean
  1. @Component 
  2. @RefreshScope 
  3. public class User { 
  4.     @Value("${laker.name}"
  5.     private String name
  6.     ... 
  • 刷新接口以及查看接口
  1. @RestController 
  2. @RequestMapping("/config"
  3. public class ConfigController { 
  4.     @Autowired 
  5.     User user
  6.     @Autowired 
  7.     ContextRefresher contextRefresher; 
  8.     @RequestMapping("/get"
  9.     public String get() { 
  10.         return user.getName(); 
  11.     } 
  12.     @RequestMapping("/refresh"
  13.     public String[] refresh() { 
  14.         Set<String> keys = contextRefresher.refresh(); 
  15.         return keys.toArray(new String[keys.size()]); 
  16.     } 
  • application.yml
  1. laker: 
  2.   name: laker 

操作流程如下:

1.瀏覽器http://localhost:8080/config/get - 瀏覽器結(jié)果:laker

2.修改application.yml里面內(nèi)容為:

  1. laker: 
  2.   name: lakerupdate 

3.瀏覽器http://localhost:8080/config/refresh - 瀏覽器結(jié)果:laker.name

4.瀏覽器http://localhost:8080/config/get - 瀏覽器結(jié)果:lakerupdate(未重新啟動(dòng),實(shí)現(xiàn)了配置更新)

問題2. Nacos客戶端如何實(shí)時(shí)監(jiān)聽到Nacos服務(wù)端配置更新了

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

  • RocketMQ
  • Nacos
  • Apollo
  • Kafka

自己花了幾個(gè)小時(shí)去看Nacos長輪詢源碼,太多了不太好理解,有興趣的自行g(shù)oogle。一般我們都是基于Spring Boot的后臺(tái)了,各種google后,發(fā)現(xiàn)Apollo實(shí)現(xiàn)較為簡單,所以直接拿Apollo的代碼借鑒。

1. Apollo 實(shí)現(xiàn)方式

實(shí)現(xiàn)方式如下:

  1. 客戶端會(huì)發(fā)起一個(gè)Http請(qǐng)求到Config Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService
  2. NotificationControllerV2不會(huì)立即返回結(jié)果,而是通過Spring DeferredResult把請(qǐng)求掛起
  3. 如果在60秒內(nèi)沒有該客戶端關(guān)心的配置發(fā)布,那么會(huì)返回Http狀態(tài)碼304給客戶端
  4. 如果有該客戶端關(guān)心的配置發(fā)布,NotificationControllerV2會(huì)調(diào)用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時(shí)該請(qǐng)求會(huì)立即返回??蛻舳藦姆祷氐慕Y(jié)果中獲取到配置變化的namespace后,會(huì)立即請(qǐng)求Config Service獲取該namespace的最新配置。

解讀下:

  • 關(guān)鍵詞DeferredResult,使用這個(gè)特性來實(shí)現(xiàn)長輪詢
  • 超時(shí)返回的時(shí)候,是返回的狀態(tài)碼Http Code 304

釋義:自從上次請(qǐng)求后,請(qǐng)求的網(wǎng)頁未修改過。服務(wù)器返回此響應(yīng)時(shí),不會(huì)返回網(wǎng)頁內(nèi)容,進(jìn)而節(jié)省帶寬和開銷。

2. 什么是DeferredResult

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

從Spring 3.2開始可用的DeferredResult有助于將長時(shí)間運(yùn)行的計(jì)算從http-worker線程卸載到單獨(dú)的線程。

盡管另一個(gè)線程將占用一些資源來進(jìn)行計(jì)算,但不會(huì)阻止工作線程,并且可以處理傳入的客戶端請(qǐng)求。

異步請(qǐng)求處理模型非常有用,因?yàn)樗兄谠诟哓?fù)載期間很好地?cái)U(kuò)展應(yīng)用程序,尤其是對(duì)于IO密集型操作。

DeferredResult是對(duì)異步Servlet的封裝

具體可以參考我在CSDN寫的Spring Boot 使用DeferredResult實(shí)現(xiàn)長輪詢

這里借助互聯(lián)網(wǎng)上的一個(gè)圖就更清晰些。

Servlet異步流程圖

 

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

3. 模擬造輪子

這里我們通過使用 Spring Boot 來簡單的模擬一下如何通過 Spring Boot DeferredResult 來實(shí)現(xiàn)長輪詢服務(wù)推送的。

代碼如下,僅供參考:

  1. /** 
  2.  * 模擬Config Service通知客戶端的長輪詢實(shí)現(xiàn)原理 
  3.  */ 
  4. @RestController 
  5. @RequestMapping("/config"
  6. public class LakerConfigController { 
  7.     private final Logger logger = LoggerFactory.getLogger(this.getClass()); 
  8.     //guava中的Multimap,多值map,對(duì)map的增強(qiáng),一個(gè)key可以保持多個(gè)value 
  9.     private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create()); 
  10.     /** 
  11.      * 模擬長輪詢 
  12.      */ 
  13.     @RequestMapping(value = "/get/{dataId}"
  14.     public DeferredResult<String> watch(@PathVariable("dataId") String dataId) { 
  15.         logger.info("Request received"); 
  16.         ResponseEntity<String> 
  17.                 NOT_MODIFIED_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_MODIFIED); 
  18.         // 超時(shí)時(shí)間30s 返回 304 狀態(tài)碼告訴客戶端當(dāng)前命名空間的配置文件并沒有更新 
  19.         DeferredResult<String> deferredResult = new DeferredResult<>(30 * 1000L, NOT_MODIFIED_RESPONSE); 
  20.         //當(dāng)deferredResult完成時(shí)(不論是超時(shí)還是異常還是正常完成),移除watchRequests中相應(yīng)的watch key 
  21.         deferredResult.onCompletion(() -> { 
  22.             logger.info("remove key:" + dataId); 
  23.             watchRequests.remove(dataId, deferredResult); 
  24.         }); 
  25.         deferredResult.onTimeout(() -> { 
  26.             logger.info("onTimeout()"); 
  27.         }); 
  28.         watchRequests.put(dataId, deferredResult); 
  29.         logger.info("Servlet thread released"); 
  30.         return deferredResult; 
  31.     } 
  32.     /** 
  33.      * 模擬發(fā)布配置 
  34.      */ 
  35.     @RequestMapping(value = "/update/{dataId}"
  36.     public Object publishConfig(@PathVariable("dataId") String dataId) { 
  37.         if (watchRequests.containsKey(dataId)) { 
  38.             Collection<DeferredResult<String>> deferredResults = watchRequests.get(dataId); 
  39.             Long time = System.currentTimeMillis(); 
  40.             //通知所有watch這個(gè)namespace變更的長輪訓(xùn)配置變更結(jié)果 
  41.             for (DeferredResult<String> deferredResult : deferredResults) { 
  42.                 //deferredResult一旦執(zhí)行了setResult()方法,就說明DeferredResult正常完成了,會(huì)立即把結(jié)果返回給客戶端 
  43.                 deferredResult.setResult(dataId + " changed:" + time); 
  44.             } 
  45.         } 
  46.         return "success"
  47.     } 

操作流程如下:

為了簡便我用瀏覽器模擬,實(shí)際用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

超時(shí)流程:

  • client1瀏覽器http://localhost:8080/config/get/laker,阻塞中ing
  • 30s后
  • client1瀏覽器,返回http code 304

在這里插入圖片描述

 

總結(jié)

  • Nacos使用長輪詢解決了實(shí)時(shí)監(jiān)聽遠(yuǎn)端配置變更
  • Nacos使用spring-cloud-context的@RefreshScope和ContextRefresher.refresh實(shí)現(xiàn)了配置熱刷新

參考:

 

  • https://ctripcorp.github.io/apollo/#/zh/README
  • https://blog.csdn.net/liuccc1/article/details/87002916
  • https://blog.csdn.net/wangxindong11/article/details/78591396
  • https://blog.csdn.net/u012410733/article/details/107119457
  • https://www.cnblogs.com/javastack/p/12049139.html

 

責(zé)任編輯:武曉燕 來源: Java大廠面試官
相關(guān)推薦

2022-06-15 16:35:02

配置共享Nacos

2021-09-22 05:55:18

Eslint disble算法

2023-09-12 13:12:23

服務(wù)器系統(tǒng)

2021-07-02 22:23:50

Nacos配置模型

2023-02-12 21:47:47

NacosRabbitMQ消費(fèi)者

2022-11-21 10:49:29

Nacos配置加密

2024-05-27 09:52:00

Nacos加密配置

2024-05-31 09:31:00

2021-08-03 08:35:36

Vuex數(shù)據(jù)熱更新

2009-02-17 18:52:06

網(wǎng)絡(luò)虛擬化路由系統(tǒng)數(shù)據(jù)中心

2024-09-18 13:49:42

2021-06-29 07:04:38

Nacos服務(wù)配置

2021-08-30 22:38:47

VscodeMarkdown預(yù)覽

2012-03-28 22:16:54

蘋果

2022-06-13 09:58:06

NacosSpring

2021-08-09 07:58:36

Nacos 服務(wù)注冊源碼分析

2021-06-10 06:57:39

Nacos配置模塊

2021-08-23 06:59:22

Nacos負(fù)載均衡客戶端

2025-01-21 11:46:26

2014-07-31 10:10:53

全息影像手機(jī)數(shù)碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)