简易但不简单的配置中心No.79

嘛小伙伴们都问我我是怎么抽那么多时间来看书的,其实说难也不难说简单其实也不简单,就是提高效率和挤时间嘛。你要相信在一天中,每个时间都有它自己应该待的位置,做好工作计划,提升工作效率,你会发现一天下来你会有稍微多个一两个小时的时间,不然就只是忙忙忙然之后到最后不知道自己在忙什么。

至于怎么看书,我看书的时间点大概就两个,一个是午饭后,第二个是睡觉前,都会看个一章或者两章,久而久之,你会发现你看的书比旁边吃鸡的同学看多了很多的书。当然呢,也别问我看什么书有用,我什么书都看。你看过的那些书,可能你会忘记,但会沉淀在你的骨头里,在你潜意识里。总有一天你会偶然看到一个东西,恍然大悟,咦这个小玩意我好像认识,虽然不知道在哪里见过但就是很眼熟。嗯。

接下来都是技术干货,非技术战斗人员请立刻左上角退出战场。




今天这个关于配置中心的小项目是早上起床抽空花了差不多两个小时写的~希望能帮大家理解理解配置中心实现的原理。

记得先启动ConfigurationCenter,再启动ConfigurationMiniServer,JDK用1.8,至于详细的内容嘛,容我细细道来。

原理就是这样,配置中心起一个 RPC 进程 ConfigurationCenterService ,用来提供注册的服务。服务器所有的配置项都从类的静态域里取,服务器本地起一个 RPC 进程 ConfigurationMiniService,用来接收来自配置中心的配置更新的 push ,取到之后替换掉静态域的值。那么下次配置项的使用方在使用的时候就能获取到新的值啦。

原理说完了,那我们看几个核心的东西。

首先定义了一个注解 Config ,这个注解的作用域是 FIELD 也就是每个类的属性。这个注解只有一个作用,就是把当前的属性标记为配置项识别出来而已,为什么要实现成注解呢?原因只有一个,就是对程序无入侵,如果想作为配置项,那就加上注解。如果某个值不想作为配置项,直接把注解去掉即可,装卸十分方便。

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

这里我们定义了一个真正的配置类,也就是我们平时开发的时候使用到的类。所有定义为配置项的地方,用我们刚刚定义的 @Config 注解进行注册。我们需要使用配置项的时候,直接从这个类的静态域获取即可。最终呢,在配置变更的时候,服务器接收到配置变更的请求的时候会直接替换类里静态域的值。

public class Configuration {
    @Config(desc = "数字配置")
    public static Integer NUMBER_CONFIG  = 5;
   @Config(desc = "开关型配置")
    public static Boolean SWITCH_CONFIG = true; 
}

ConfigurationCenterService 定义了三个行为。

第一个是 register 注册,给服务器注册自己的配置项用的。

第二个是 pushConfig 配置推送,给 client 或者 web 页面进行配置推送用的。第三个是 getAllConfig() ,给 client 或者 web 页面获取当前所有配置项用的。

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

ConfigurationMiniService 定义了三个行为。

第一个是 registerClass 注册,给本地的配置类注册到配置中心用的。

第二个是 changeConfig 配置变更,暴露给配置中心,配置中心有配置变更的请求就直接调用本地的 mini 服务器的ConfigurationMiniService 进行配置变更的才做。

第三个是 init() ,是一个普通的初始化方法。

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 服务器,首先定义了哪些类是配置类,这个我只是简单实现了,真正做的使用可以给类加一个注解,用对包进行扫描的形式发现配置类。然后实例化了一个本地 RPC 进程ConfigurationMiniService。接着把所有的类一个一个使用本地的 RPC 进程进行注册。

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

那么是怎么注册的呢?其实也不难。先在服务本地记录一下配置类,准备开始注册。获得目标类的所有的 Field,然后判断这个 Field 是不是有 @Config 注解,如果有,那么获得当前的类名,属性,值,描述,服务器信息等,调用配置中心的 ConfigurationCenterService 进行注册。

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

数据结构长这样,因为 RPC 要经过网络传输,所以一定要实现序列化。

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

ConfigurationCenterService 配置中心接收到消息之后呢,就在本地记录一下,顺便把目标 mini 服务器的 RPC 调用进行初始化。

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

到这里,一个配置项的注册就算完成了,那么如何进行配置变更呢?下面这些代码很长,但是目的只有一个,就是封装出目标服务器,目标类,目标 Field ,要变更的值,以及值的类型,然后 push 给 mini服务器就好了。

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+" 到服务器"+server+" 成功");
 }else{
            Logger.log("[error]推送配置 "+field+" ,值为 "+value+" 到服务器"+server+" 失败");
 }
    }
}

喏,简单的直接 push 给 mini 服务器。

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

当 mini 服务器接收到来自配置中心的请求的是时候,会进行本地值的替换,我们在传输的时候都是序列化的字符串,所以要转一下。原理也很简单,就是利用反射识别出目标类的目标 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; }

有人说我怎么知道它变更了呢?喇喇喇。早就给你想好了,每3秒输出一次当前的值,这样子值一变更就可以肉眼看到了。当然实际在使用的时候基本可以实现配置中心推完,就实时更新,这个要看网络延迟了。

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

看,注册成功会显示这个,给配置中心发送请求 get 也可以获取到。

image

我们试试看 push 一下值,如果成功会看到推送成功。

image

也能看到值会从 5 变更我们推过去的 6 了。

image

好啦,今天的配置中心就讲到这里,大家有什么想法都可以留言,也欢迎大家跟我一起边玩代码边学习。代码我已经放到 github上了,github 的地址发送"配置中心"获取喔,喜欢的小伙伴可以下载下来自己玩玩。

码了这么多字这么多代码,你不转发赞赏一下吗?一毛钱也是认可是不?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,581评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,500评论 25 707
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • -1- 我叫叶佳玲,今年二十五岁。 生日一过,我已扛不住压力,家人在耳边不停叨叨,以他们的眼光,我俨然已在“剩女”...
    空谷百合阅读 771评论 0 6