简易但不简单的配置中心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 也可以获取到。



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


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



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


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MiniServer是一款绿色、精简、迷你的WANMP(Windows+Apache+Nginx+MySQL+PHP)服务端程序,使用本工具可以非常方便的搭建网站服务器。 重要说明: 由于MiniServer包含组件比较复杂,所以每次更新都会或多或少对其组件进行修改,直接用新版本覆盖老版本则会产生许多不必要的问题。 更新时请注意备份数据库目录(MySQL\data)和网站文件目录(www\htdocs)到其他文件夹 删除原MiniServer文件夹,再将新版本解压,将上述两个目录复制回相应路径即可。 2013年6月6日 v2.0 1.新增:PHP更换为5.3.22和5.4.12,并更换原APACHE2HANDLER运行方式为CGI-FCGI 2.新增:全面支持Zend Guard加密 3.新增:添加一个快速便捷的文件分享服务器(基于第三方软件) 4.改进:调整虚拟目录和虚拟主机的设置功能(不再支持中文路径) 5.改进:程序细节优化,如程序未开启时,默认选中Apache等 6.改进:增加部分PHP模块,如SQLite3支持等 7.改进:调整默认主页文字说明、增加Zend Guard 5.01~6.0加密的三个测试文件 使用帮助 1.数据库管理地址为 //127.0.0.1/phpmyadmin 默认用户名 root 密码为 miniserver,菜单中有MySQL root用户密码重置功能 2.一般情况下,我个人不推荐新手更改组件配置文件或对组件进行升级操作,否则可能会导致MiniServer无法正常开启 3.网站根目录为 www/htdocs 文件夹,可以通过MiniServer菜单快速进入,也可以使用Apache的虚拟目录功能单独设置目录(Nginx暂时不支持虚拟目录) 4.菜单中有修改端口的功能,修改前请确保其他软件没有占用所修改的端口,如果遇到MiniServer中某组件无法正常启动,请使用菜单中的调试模式检查错误信息,或者检查端口占用情况 5.请勿在在含有中文路径或目录下使用MiniServer,否则会造成组件无法启动等问题 6.MiniServer支持IPv6,查看本机IPv6地址可以用 ipconfig 命令或者直接登陆 ipv6-test.com 7.内置一款探针程序,可以让您更好的浏览本机服务器信息。//127.0.0.1/tz.php
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值