一種面向業務配置基於JSF廣播定時生效的工具

来源:https://www.cnblogs.com/Jcloud/archive/2023/01/20/17058545.html
-Advertisement-
Play Games

作者:京東物流 王北永 姚再毅 李振 1 背景 目前,ducc實現了實時近乎所有配置動態生效的場景,但是配置是否實時生效,不能直觀展示每個機器上jvm內對象對應的參數是否已變更為準確的值,大部分時候需要查看日誌確認是否生效。 2 技術依賴 1)Jsf:京東RPC框架,用作機器之間的通訊工具 2)re ...


作者:京東物流 王北永 姚再毅 李振

1 背景

目前,ducc實現了實時近乎所有配置動態生效的場景,但是配置是否實時生效,不能直觀展示每個機器上jvm內對象對應的參數是否已變更為準確的值,大部分時候需要查看日誌確認是否生效。

2 技術依賴

1)Jsf:京東RPC框架,用作機器之間的通訊工具

2)redis/redisson:redis,用作配置信息的存儲

3)ZK/Curator: Zookeeper,用作配置信息的存儲 和redis二選一

3)clover:定時任務集群,用作任務延遲或周期性執行

3 實現原理

1)接入方:

各個接入系統通過接入管理模塊獲取token,並指定所在系統發佈的的伺服器ip,用作後續的ip鑒權。當系統啟動時,自動在各個系統生成介面提供方,並註冊到JSF註冊中心。別名需各個系統唯一不重覆。鑒權為統一服務端做IP鑒權。

2)統一配置服務端:

提供按不同接入方、不同系統、不同環境的配置界面。業務人員可設定自動生效時間或者立即生效時間。如果是立刻生效,則通過JSF廣播或者指定機器生效配置。如果是定時生效,則新增定時器並指定生效規則,達到時間後觸發廣播通知。

整個接入方和統一配置服務端的架構如下圖

4 實現步驟

1)重寫JSF類ConsumerConfig類方法refer,將其中的輪訓調用客戶端改為廣播調用客戶端BroadCastClient。

this.client= new BroadCastClient(this);
this.proxyInvoker = new ClientProxyInvoker(this);
ProtocolFactory.check(Constants.ProtocolType.valueOf(this.getProtocol()), Constants.CodecType.valueOf(this.getSerialization()));
this.proxyIns = (T) ProxyFactory.buildProxy(this.getProxy(), this.getProxyClass(), this.proxyInvoker);

2)廣播調用客戶端方法分別獲取當前註冊中心有心跳的服務提供者和已失去連接的機器列表。對統一配置來講,要麼同時失敗,要麼同時成功,判斷如果存在不正常的服務提供方,則不同步。只有全部提供方存在才可以開始廣播配置信息

 ConcurrentHashMap<Provider, ClientTransport>  concurrentHashMap = this.connectionHolder.getAliveConnections();
        ConcurrentHashMap<Provider, ClientTransportConfig>  deadConcurrentHashMap = this.connectionHolder.getDeadConnections();
        if(deadConcurrentHashMap!=null && deadConcurrentHashMap.size()>0){
            log.warn("當前別名{}存在不正常服務提供方數量{},請關註!",msg.getAlias(),deadConcurrentHashMap.size());
            throw new RpcException(String.format("當前別名%s存在不正常服務提供方數量%s,請關註!",msg.getAlias(),deadConcurrentHashMap.size()));
        }
        if(concurrentHashMap.isEmpty()){
            log.info("當前別名{}不存在正常服務提供方",msg.getAlias());
            throw new RpcException(String.format("當前別名%s不存在正常服務提供方",msg.getAlias()));
        }
        Iterator aliveConnections = concurrentHashMap.entrySet().iterator();
        log.info("當前別名{}存在正常服務提供方數量{}",msg.getAlias(),concurrentHashMap.size());
        while (aliveConnections.hasNext()) {
            Entry<Provider, ClientTransport> entry = (Entry) aliveConnections.next();
            Provider provider = (Provider) entry.getKey();
            log.info("當前連接ip={}、port={}、datacenterCode={}",provider.getIp(),provider.getPort(),provider.getDatacenterCode());
            ClientTransport connection = (ClientTransport) entry.getValue();
            if (connection != null && connection.isOpen()) {
                try {
                    result = super.sendMsg0(new Connection(provider, connection), msg);
                } catch (RpcException rpc) {
                    exception = rpc;
                    log.warn(rpc.getMessage(), rpc);
                } catch (Throwable e) {
                    exception = new RpcException(e.getMessage(), e);
                    log.warn(e.getMessage(), e);
                }
            }
        }

3)服務配置端,當業務人員配置及時生效或者任務達到時,則根據配置,生成服務調用方,通過統一刷新介面將配置同步刷新到對應的接入系統中,如下圖為操作界面,當增刪改查時,會將屬性增量同步。

服務端在上面操作增刪改時,通過以下方式獲取服務調用方

 public static ExcuteAction createJsfConsumer(String alias, String token) {
        RegistryConfig jsfRegistry = new RegistryConfig();
        jsfRegistry.setIndex("i.jsf.jd.com");
        BroadCastConsumerConfig consumerConfig = new BroadCastConsumerConfig<>();
        Map<String, String> parameters = new HashMap<>();
        parameters.put(".token",token);
        consumerConfig.setParameters(parameters);
        consumerConfig.setInterfaceId(RefreshRemoteService.class.getName());
        consumerConfig.setRegistry(jsfRegistry);
        consumerConfig.setProtocol("jsf");
        consumerConfig.setAlias(alias);
        consumerConfig.setRetries(2);
        return  new ExcuteAction(consumerConfig);
    }

通過以上的配置的客戶端,調用服務提供方方法refreshRemoteService#refresh,將配置信息進行同步到各個接入系統

public void call(Map<String,Object> propertiesValue){
        try{
            RefreshRemoteService refreshRemoteService = (RefreshRemoteService)consumerConfig.refer();
            if(refreshRemoteService!=null){
                refreshRemoteService.refresh(propertiesValue);
            }
        }catch (Exception e){
            log.error(e.getMessage());
            throw new EasyConfigException(e);
        }finally {
            consumerConfig.unrefer(); ;
        }
    }

4)接入方啟動時,需要根據自己配置,將存在redis或者zk的配置一次載入到實例變數中。並註冊刷新介面到JSF註冊中心。

其中註冊刷新介面到JSF註冊中心代碼如下

 @Bean(name = "refreshPorpertiesService")
    public ProviderConfig createJsfProvider() throws Exception {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setId("refreshPorpertiesService");
        providerConfig.setInterfaceId(RefreshRemoteService.class.getName());
        providerConfig.setRef(new RefreshRemoteServiceDelage(applicationContext));
        providerConfig.setTimeout(30000);
        providerConfig.setAlias(EasyConfigure.getAppCode()+EasyConfigure.getEnv());
        providerConfig.setServer(serverConfig);
        providerConfig.setRegistry(jsfRegistry);
        providerConfig.setParameter("token", MD5Util.md5(EasyConfigure.getAppCode()));
        providerConfig.export();
        return providerConfig;
    }

其中
RefreshRemoteServiceDelage類提供刷新介面的實際邏輯如下,需判斷當前實例是jdk動態代理還是cglib代理

判斷邏輯如下

 if(AopUtils.isJdkDynamicProxy(object)) {
         object= AopUtil.getJdkDynamicProxyTargetObject(object);
 } else if(AopUtils.isCglibProxy(object)){ //cglib
         object= AopUtil.getCglibProxyTargetObject(object);
   }

實例對象變數值根據自定義的參數轉換方式轉換後賦值實例變數

if(autoValue.convert()!=null && !autoValue.getClass().isInterface()){
         if(!autoValue.convert().newInstance().getInClassType().isAssignableFrom(newVal.getClass()) ){
                    continue;
             }
           newVal = autoValue.convert().newInstance().convert(newVal);
            if(newVal!=null){
                  if(!autoValue.convert().newInstance().getOutClassType().isAssignableFrom(newVal.getClass()) ){
                            continue;
                      }
              field.setAccessible(true);
               Object value = ReflectionUtils.getField(field,object);
              log.info("change  properties{} for object {} before value {}",field.getName(),object.getClass().getName(),value);
               ReflectionUtils.setField(field,object,newVal);
                log.info("change  properties{} for object {} after value {}",field.getName(),object.getClass().getName(),newVal);
         }              
}

5 實踐

1)pom引入

<dependency>
   <groupId>com.jdl</groupId>
   <artifactId>easyconfig</artifactId>
   <version>1.0-SNAPSHOT</version>
</dependency>

2)配置存儲配置(比如redis方式)

refresh:
  config:
    appCode: zdzq-worker-appcode
    redisUrl: redis://:@127.0.0.1

3)類全局變數需要實時刷新配置,需在類統一指定註解PropertiryChangeListener,實例變數需要增加註解AutoValue並指定數據格式轉換器

@PropertiryChangeListener
public class ChanceServiceImpl implement ChanceService{
@AutoValue(convert = DateConvert.class,alias = "config-id")
private Date  signDate;
@AutoValue(convert = SpmKaApply Convert.class,alias = "config-id")
private SpmKaApply  spmKaApply;
}

以上convert方法自定義,支持各種複雜配置對象,舉例數據轉換為List如下

public  class Convert2 implements Convert<Map<String, String>, Set<String>> {
    public Convert2(){}
    @Override
    public Set<String> convert(Map<String, String> siteInfoMap) {
        ....你的對象值轉換
    }
    @Override
    public Class<?> getInClassType() {
        return Map.class;
    }
    @Override
    public Class<?> getOutClassType() {
        return Set.class;
    }

接入應用服務啟動後,可訪問/refreshUI 可查看應用在集群中為自動配置的實例,並顯示當前實例中變數值參數。key為實例變數名。

6 總結

1、支持jdk動態代理的實例對象和cglib代理對象的參數動態配置

2、支持定時刷新配置

3、直接查看和驗證應用集群中實例變數是否一致


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在公司年會期間我做了個抽獎小項目,我把它分享出來,有用得著的可以看下。 瀏覽鏈接:http://xisite.top/original/luck-draw/index.html 項目鏈接:https://gitee.com/xi1213/luck-draw (歡迎star!) 項目截圖: 實現 ...
  • 1、使用 Array.prototype.some() 方法代替 some() 方法會在找到第一個符合條件的元素時停止迴圈。 例如: let array = [1, 2, 3, 4, 5]; array.some(function(element, index, array) { if (eleme ...
  • 本文我們來探討下如何引入分散式鎖解決本地鎖的問題。本篇所有代碼和業務基於我的開源項目 PassJava。 本篇主要內容如下: 一、本地鎖的問題 首先我們來回顧下本地鎖的問題: 目前題目微服務被拆分成了四個微服務。前端請求進來時,會被轉發到不同的微服務。假如前端接收了 10 W 個請求,每個微服務接收 ...
  • 導讀:設計模式能夠幫助我們優化代碼結構,讓代碼更優雅靈活。有哪些常見的設計模式?如何合理運用?本文分享作者對工廠模式、單例模式、裝飾模式、策略模式、代理模式和觀察者模式的理解,介紹每種模式的模式結構、優缺點、適用場景、註意實現及代碼實現。 ...
  • 2023-01-20 一、SpringMVC消息轉換器概述 1、HttpMessageConverter<T> 消息轉換器作用: (1)將java對象與請求報文及響應報文進行相互轉化 (2)使用HttpMessageConverter<T>將請求信息轉化並綁定到處理方法的入參中或將響應結果轉為對應類 ...
  • 準備工作 原材料 Ubuntu 系統(非必須,Windows 也可以,主要是 Ubuntu 適合編譯) OpenCV 3.4.1 壓縮包 OpenCV contrib 3.4.1 壓縮包 MinGW(Windows 上運行 GCC) 版本信息 GCC 版本 7.5.0 G++ 版本 7.5.0 Op ...
  • :前言 造車輪的時候要用到中文字元串的長度辨別,發現char的識別不准,進行了一番研究。 > 開始研究 在Windows下,中文字元在C++中的記憶體占用為2位元組,此時採用字元串長度獲取函數得到的結果會將一個中文字元識別為兩個長度: #include <stdio.h> #include <strin ...
  • 2023-01-20 一、springMVC中視圖及視圖解析器對象 1、視圖解析器對象(ViewResolver) (1)概述:SpringMVC中所有視圖解析器對象均實現ViewResolver介面 (2)作用:使用ViewResolver,將View從ModelAndView中解析出來 註:在s ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...