簡(jiǎn)易但不簡(jiǎn)單的配置中心No.79

嘛小伙伴們都問(wèn)我我是怎么抽那么多時(shí)間來(lái)看書的岗钩,其實(shí)說(shuō)難也不難說(shuō)簡(jiǎn)單其實(shí)也不簡(jiǎn)單女坑,就是提高效率和擠時(shí)間嘛薇宠。你要相信在一天中偷办,每個(gè)時(shí)間都有它自己應(yīng)該待的位置,做好工作計(jì)劃澄港,提升工作效率椒涯,你會(huì)發(fā)現(xiàn)一天下來(lái)你會(huì)有稍微多個(gè)一兩個(gè)小時(shí)的時(shí)間,不然就只是忙忙忙然之后到最后不知道自己在忙什么回梧。

至于怎么看書废岂,我看書的時(shí)間點(diǎn)大概就兩個(gè),一個(gè)是午飯后狱意,第二個(gè)是睡覺(jué)前湖苞,都會(huì)看個(gè)一章或者兩章,久而久之详囤,你會(huì)發(fā)現(xiàn)你看的書比旁邊吃雞的同學(xué)看多了很多的書财骨。當(dāng)然呢,也別問(wèn)我看什么書有用藏姐,我什么書都看隆箩。你看過(guò)的那些書,可能你會(huì)忘記羔杨,但會(huì)沉淀在你的骨頭里捌臊,在你潛意識(shí)里《挡模總有一天你會(huì)偶然看到一個(gè)東西理澎,恍然大悟,咦這個(gè)小玩意我好像認(rèn)識(shí)曙寡,雖然不知道在哪里見(jiàn)過(guò)但就是很眼熟矾端。嗯。

接下來(lái)都是技術(shù)干貨卵皂,非技術(shù)戰(zhàn)斗人員請(qǐng)立刻左上角退出戰(zhàn)場(chǎng)秩铆。




今天這個(gè)關(guān)于配置中心的小項(xiàng)目是早上起床抽空花了差不多兩個(gè)小時(shí)寫的~希望能幫大家理解理解配置中心實(shí)現(xiàn)的原理。

記得先啟動(dòng)ConfigurationCenter,再啟動(dòng)ConfigurationMiniServer殴玛,JDK用1.8捅膘,至于詳細(xì)的內(nèi)容嘛,容我細(xì)細(xì)道來(lái)滚粟。

原理就是這樣寻仗,配置中心起一個(gè) RPC 進(jìn)程 ConfigurationCenterService ,用來(lái)提供注冊(cè)的服務(wù)凡壤。服務(wù)器所有的配置項(xiàng)都從類的靜態(tài)域里取署尤,服務(wù)器本地起一個(gè) RPC 進(jìn)程 ConfigurationMiniService,用來(lái)接收來(lái)自配置中心的配置更新的 push 亚侠,取到之后替換掉靜態(tài)域的值曹体。那么下次配置項(xiàng)的使用方在使用的時(shí)候就能獲取到新的值啦匾旭。

原理說(shuō)完了蹦渣,那我們看幾個(gè)核心的東西活喊。

首先定義了一個(gè)注解 Config 栋齿,這個(gè)注解的作用域是 FIELD 也就是每個(gè)類的屬性覆山。這個(gè)注解只有一個(gè)作用髓迎,就是把當(dāng)前的屬性標(biāo)記為配置項(xiàng)識(shí)別出來(lái)而已粥庄,為什么要實(shí)現(xiàn)成注解呢薯酝?原因只有一個(gè)狮杨,就是對(duì)程序無(wú)入侵母截,如果想作為配置項(xiàng),那就加上注解橄教。如果某個(gè)值不想作為配置項(xiàng)微酬,直接把注解去掉即可,裝卸十分方便颤陶。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
    public String desc() default "lazy"; 
}

這里我們定義了一個(gè)真正的配置類颗管,也就是我們平時(shí)開發(fā)的時(shí)候使用到的類。所有定義為配置項(xiàng)的地方滓走,用我們剛剛定義的 @Config 注解進(jìn)行注冊(cè)垦江。我們需要使用配置項(xiàng)的時(shí)候,直接從這個(gè)類的靜態(tài)域獲取即可搅方。最終呢比吭,在配置變更的時(shí)候,服務(wù)器接收到配置變更的請(qǐng)求的時(shí)候會(huì)直接替換類里靜態(tài)域的值姨涡。

public class Configuration {
    @Config(desc = "數(shù)字配置")
    public static Integer NUMBER_CONFIG  = 5;
   @Config(desc = "開關(guān)型配置")
    public static Boolean SWITCH_CONFIG = true; 
}

ConfigurationCenterService 定義了三個(gè)行為衩藤。

第一個(gè)是 register 注冊(cè),給服務(wù)器注冊(cè)自己的配置項(xiàng)用的涛漂。

第二個(gè)是 pushConfig 配置推送赏表,給 client 或者 web 頁(yè)面進(jìn)行配置推送用的检诗。第三個(gè)是 getAllConfig() ,給 client 或者 web 頁(yè)面獲取當(dāng)前所有配置項(xiàng)用的瓢剿。

public interface ConfigurationCenterService extends Remote {
    int register(ConfigDTO configDTO)  throws RemoteException;
    int pushConfig(ConfigDTO configDTO)  throws RemoteException;
    void getAllConfig() throws RemoteException; 
}

ConfigurationMiniService 定義了三個(gè)行為逢慌。

第一個(gè)是 registerClass 注冊(cè),給本地的配置類注冊(cè)到配置中心用的间狂。

第二個(gè)是 changeConfig 配置變更攻泼,暴露給配置中心,配置中心有配置變更的請(qǐng)求就直接調(diào)用本地的 mini 服務(wù)器的ConfigurationMiniService 進(jìn)行配置變更的才做鉴象。

第三個(gè)是 init() 忙菠,是一個(gè)普通的初始化方法。

public interface ConfigurationMiniService extends Remote {
    int changeConfig(ConfigDTO configDTO)  throws RemoteException;
    void registerClass(Class c) throws RemoteException;
    void init() throws RemoteException, AlreadyBoundException, MalformedURLException, NotBoundException; 
}

ConfigurationMiniServer 是真正的本地 mini 服務(wù)器纺弊,首先定義了哪些類是配置類牛欢,這個(gè)我只是簡(jiǎn)單實(shí)現(xiàn)了,真正做的使用可以給類加一個(gè)注解俭尖,用對(duì)包進(jìn)行掃描的形式發(fā)現(xiàn)配置類氢惋。然后實(shí)例化了一個(gè)本地 RPC 進(jìn)程ConfigurationMiniService洞翩。接著把所有的類一個(gè)一個(gè)使用本地的 RPC 進(jìn)程進(jìn)行注冊(cè)稽犁。

Set<Class> classesToRegister = new HashSet<>(); 
classesToRegister.add(Configuration.class); 
ConfigurationMiniService service = new ConfigurationMiniServiceImpl("127.0.0.1","8000"); 
service.init();   
for(Class currentClass : classesToRegister){
    service.registerClass(currentClass);
 }

那么是怎么注冊(cè)的呢?其實(shí)也不難骚亿。先在服務(wù)本地記錄一下配置類已亥,準(zhǔn)備開始注冊(cè)。獲得目標(biāo)類的所有的 Field来屠,然后判斷這個(gè) Field 是不是有 @Config 注解虑椎,如果有,那么獲得當(dāng)前的類名俱笛,屬性捆姜,值,描述迎膜,服務(wù)器信息等泥技,調(diào)用配置中心的 ConfigurationCenterService 進(jìn)行注冊(cè)。

public void registerClass(Class currentClass) throws RemoteException {
  this.configClasses.put(currentClass.getName(),currentClass);
  Field[] fields = currentClass.getDeclaredFields();
  for(Field field : fields){
        field.setAccessible(true); 
       if(field.isAnnotationPresent(Config.class)){
            ConfigDTO configDTO = new ConfigDTO();
            configDTO.setServer(serverUri);
            configDTO.setClassName(currentClass.getName());
            configDTO.setFiled(field.getName());
            configDTO.setDesc(field.getAnnotation(Config.class).desc());
             try {
              configDTO.setValueType(field.getType().getName());
              Object value = field.get(null);
              configDTO.setValue(String.valueOf(value));  
             } catch (IllegalAccessException e) {
                e.printStackTrace();
             }
           configurationCenterService.register(configDTO);   }
    }
}

數(shù)據(jù)結(jié)構(gòu)長(zhǎng)這樣磕仅,因?yàn)?RPC 要經(jīng)過(guò)網(wǎng)絡(luò)傳輸珊豹,所以一定要實(shí)現(xiàn)序列化。

public class ConfigDTO implements Serializable{
 private String server;
 private String className;
 private String filed;
 private String desc;
 private String valueType;
 private String value;
}

ConfigurationCenterService 配置中心接收到消息之后呢榕订,就在本地記錄一下店茶,順便把目標(biāo) mini 服務(wù)器的 RPC 調(diào)用進(jìn)行初始化。

@Override public int register(ConfigDTO configDTO) throws RemoteException {
  configs.add(configDTO);
  getOrCreateBundle(configDTO.getServer());
  Logger.log(configDTO);
  return 200; 
}

到這里劫恒,一個(gè)配置項(xiàng)的注冊(cè)就算完成了贩幻,那么如何進(jìn)行配置變更呢?下面這些代碼很長(zhǎng),但是目的只有一個(gè)段直,就是封裝出目標(biāo)服務(wù)器吃溅,目標(biāo)類,目標(biāo) Field 鸯檬,要變更的值决侈,以及值的類型,然后 push 給 mini服務(wù)器就好了喧务。

Scanner scanner = new Scanner(System.in); 
System.out.println("input\n" +
        "get //to get all configs \n" +
        "push 127.0.0.1:8000/cfg_miniserver config.Configuration NUMBER_CONFIG 6 java.lang.Integer \r\n"); while (scanner.hasNext()){
String command = scanner.nextLine();
 String[] commanArray = command.split(" ");   String cmd = commanArray[0];   
if(cmd.equals("get")){
    service.getAllConfig();
 }
    else if(cmd.equals("push")){
 String server = commanArray[1];
 String className = commanArray[2];
 String field = commanArray[3];
 String value = commanArray[4];
 String valueType = commanArray[5];  
 ConfigDTO configDTO = new ConfigDTO();
 configDTO.setFiled(field);
 configDTO.setClassName(className);
 configDTO.setServer(server);
 configDTO.setValue(value);
 configDTO.setValueType(valueType);   int result = service.pushConfig(configDTO);
 if(result == 200){
            Logger.log("[success]推送配置 "+field+" ,值為 "+value+" 到服務(wù)器"+server+" 成功");
 }else{
            Logger.log("[error]推送配置 "+field+" ,值為 "+value+" 到服務(wù)器"+server+" 失敗");
 }
    }
}

喏赖歌,簡(jiǎn)單的直接 push 給 mini 服務(wù)器。

@Override public int pushConfig(ConfigDTO configDTO) throws RemoteException {
    ConfigurationMiniService currentService =  getOrCreateBundle(configDTO.getServer());   return currentService.changeConfig(configDTO); 
}

當(dāng) mini 服務(wù)器接收到來(lái)自配置中心的請(qǐng)求的是時(shí)候功茴,會(huì)進(jìn)行本地值的替換庐冯,我們?cè)趥鬏數(shù)臅r(shí)候都是序列化的字符串,所以要轉(zhuǎn)一下坎穿。原理也很簡(jiǎn)單展父,就是利用反射識(shí)別出目標(biāo)類的目標(biāo) Field,將值變更為新的值玲昧。

@Override public int changeConfig(ConfigDTO configDTO) throws RemoteException {
    Class targetClass = this.configClasses.get(configDTO.getClassName());
 if(targetClass == null){
        return 500;
 }
try {
 Field field = targetClass.getField(configDTO.getFiled());
 field.setAccessible(true);
 switch (configDTO.getValueType()){
    case "java.lang.Integer":
            field.set(null,Integer.valueOf(configDTO.getValue()));
     break;  
     case "java.lang.Boolean":
            field.set(null,Boolean.valueOf(configDTO.getValue()));
     break;  
    }
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
 } catch (IllegalAccessException e) {
        e.printStackTrace();
 }

    return 200; }

有人說(shuō)我怎么知道它變更了呢栖茉?喇喇喇。早就給你想好了孵延,每3秒輸出一次當(dāng)前的值吕漂,這樣子值一變更就可以肉眼看到了。當(dāng)然實(shí)際在使用的時(shí)候基本可以實(shí)現(xiàn)配置中心推完尘应,就實(shí)時(shí)更新惶凝,這個(gè)要看網(wǎng)絡(luò)延遲了。

Runnable check = new Runnable() {
    @Override
 public void run() {
        Logger.log(Configuration.NUMBER_CONFIG);
        Logger.log(Configuration.SWITCH_CONFIG);
 }
};   ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate(check,0,3, TimeUnit.SECONDS); 

看犬钢,注冊(cè)成功會(huì)顯示這個(gè)苍鲜,給配置中心發(fā)送請(qǐng)求 get 也可以獲取到。

image

我們?cè)囋嚳?push 一下值玷犹,如果成功會(huì)看到推送成功混滔。

image

也能看到值會(huì)從 5 變更我們推過(guò)去的 6 了。

image

好啦箱舞,今天的配置中心就講到這里,大家有什么想法都可以留言晴股,也歡迎大家跟我一起邊玩代碼邊學(xué)習(xí)愿伴。代碼我已經(jīng)放到 github上了隔节,github 的地址發(fā)送"配置中心"獲取喔鹅经,喜歡的小伙伴可以下載下來(lái)自己玩玩怎诫。

碼了這么多字這么多代碼,你不轉(zhuǎn)發(fā)贊賞一下嗎幻妓?一毛錢也是認(rèn)可是不蹦误?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肉津,隨后出現(xiàn)的幾起案子强胰,更是在濱河造成了極大的恐慌,老刑警劉巖妹沙,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偶洋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡距糖,警方通過(guò)查閱死者的電腦和手機(jī)玄窝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)悍引,“玉大人恩脂,你說(shuō)我怎么就攤上這事÷痤恚” “怎么了东亦?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵杏节,是天一觀的道長(zhǎng)唬渗。 經(jīng)常有香客問(wèn)我,道長(zhǎng)奋渔,這世上最難降的妖魔是什么镊逝? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮嫉鲸,結(jié)果婚禮上撑蒜,老公的妹妹穿的比我還像新娘。我一直安慰自己玄渗,他們只是感情好座菠,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藤树,像睡著了一般浴滴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岁钓,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天升略,我揣著相機(jī)與錄音微王,去河邊找鬼。 笑死品嚣,一個(gè)胖子當(dāng)著我的面吹牛炕倘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翰撑,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼罩旋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了眶诈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤东帅,失蹤者是張志新(化名)和其女友劉穎靠闭,沒(méi)想到半個(gè)月后坎炼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谣光,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蟀悦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年日戈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浙炼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弯屈。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡资厉,死狀恐怖梅掠,靈堂內(nèi)的尸體忽然破棺而出店归,到底是詐尸還是另有隱情消痛,我是刑警寧澤都哭,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布欺矫,位于F島的核電站穆趴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏簿废。R本人自食惡果不足惜络它,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一化戳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扫尖,春花似錦盟步、人聲如沸躏结。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)塞关。三九已至子巾,卻和暖如春小压,著一層夾襖步出監(jiān)牢的瞬間怠益,已是汗流浹背瘾婿。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工偏陪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抱虐。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓梯码,卻偏偏與公主長(zhǎng)得像好啰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鳄抒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理椰弊,服務(wù)發(fā)現(xiàn),斷路器贤重,智...
    卡卡羅2017閱讀 134,715評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法并蝗,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法滚停,繼承相關(guān)的語(yǔ)法粥惧,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,665評(píng)論 18 399
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,322評(píng)論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,865評(píng)論 6 342
  • -1- 我叫葉佳玲草穆,今年二十五歲。 生日一過(guò)悲柱,我已扛不住壓力,家人在耳邊不停叨叨豌鸡,以他們的眼光,我儼然已在“剩女”...
    空谷百合閱讀 816評(píng)論 0 6