为什么要实现一个配置中心呢?
需求背景是组内的很多项目,尤其前后端分离的项目,由于前端项目打包是采用的hash的方式,打包的app.js或者css文件是带有hash值的,之前一直是每次前端有更新,就打包后将新的hash值更新到后端的application.properties
中,而后端项目每次更新就得重新发布,一连串影响结果就是每次前端更新却连累后端也得重新发布,这是有点畸形的,前后端的耦合过重。
在这样一个背景下,长痛不如短痛,于是想到使用类似配置中心的方式来管理这些前端资源的hash版本号,而如果因此就引入Apollo
、SpringCloudConfig
这种专业的配置中心又过于笨重。考虑到本身我们RMS统一平台就维护有所有产品线子系统信息,可以在此基础上,维护一份配置项信息,与各自的子系统关联,RMS平台提供一个查询给RMS-SDK,RMS-SDK新增配置中心的功能,子系统通过SDK自动动态更新配置项的值,无需重启。
虽说是一个微型的配置中心,但也分为server和client两部分。
server
server主要就是RMS平台,配置项数据的管理,以及持久化,server在内部管理数据的同时,对外提供一个查询的接口,主要用于SDK的查询。server端没什么好说的,就不放代码了,放张编辑配置项的图片意思一下:
client
client即指子系统,子系统在引用了新SDK之后便具有了配置中心client的角色,配置项数据的更新等操作都是由SDK在底层自动完成,对子系统是无感知的。
下面详细说下客户端 SDK的实现。
1.开启配置中心
定义了一个注解@EnableRmsConfigCenter
,在入口类或者@Configuration
修饰的类上使用该注解开启配置中心,该注解Import了一个RmsConfigCenterRegistrar
类,在RmsConfigCenterRegistrar
中向Spring容器注册了两个bean定义(BeanDefinition):
1 | (RetentionPolicy.RUNTIME) |
@EnableRmsConfigCenter
中的type
用于指定获取配置中心数据的方式,默认以http轮询,也可以指定redis
从rms平台的redis中获取。
RmsConfigCenterRegistrar
注册了两个bean定义,后面将一一说明。
2.SelfDefConfigProcessor
记录使用@Value
注解的类、字段
先看下SelfDefConfigProcessor
的代码:
1 | public class SelfDefConfigProcessor implements BeanPostProcessor, BeanFactoryAware { |
SelfDefConfigProcessor
类实现了BeanPostProcessor
接口,即Bean后处理器(BeanPostProcessor
的bean会被Spring上下文检测到,并将其用于后面Bean的创建,例如在postProcessBeforeInitialization()
方法中实现一些标记操作,在postProcessAfterInitialization()
方法中实现一些包装操作),这里我们主要在postProcessBeforeInitialization()
实现主要逻辑,解析每个Bean的字段,如果字段被@Value
注解修饰,则将@Value
的value值连同字段、当前Bean、Bean的名称一起封装成SelfDefConfigValue
类对象,并将其与其对应的BeanFactory
关联存储在自定义的注册表SelfDefConfigValueRegistry
中,在后面监听到配置更新时,我将从注册表中查找对应配置项所在的类和字段。
3.PropertySourceProcessor
初始化配置项数据以及长轮询监听配置项数据变化
还是先看下代码:
1 | public class PropertySourceProcessor implements EnvironmentAware, BeanFactoryPostProcessor { |
PropertySourceProcessor
实现了BeanFactoryPostProcessor
接口,实现了BeanFactoryPostProcessor
接口的Bean也会被spring上下文自动检测到,在spring创建Bean之前应用,但BeanFactoryPostProcessor
与BeanPostProcessor
不同,BeanFactoryPostProcessor
不能和bean实例交互,只可以修改bean定义(BeanDefinition),BeanFactoryPostProcessor
接口提供的postProcessBeanFactory()
可用于修改spring上下文内部的bean factory。在我定义的PropertySourceProcessor
中,在postProcessBeanFactory()
方法,主要是为了在Bean实例化之前,获取到配置中心的配置项数据,并将这些数据设置到Spring的环境(Environment)中,用于后续bean实例化之后,Spring将Environment
中的PropertySource注入到@Value
修饰的变量中。
这里通过实现Spring的PropertySource
接口,自定义了ConfigDataPropertySource
,在这里封装了初始第一次从server获取配置项,由于是扩展的PropertySource
接口,所以可以设置到Spring的Environment
中,Spring也自然可以通过getProperty()
获取配置数据并注入到bean。
同时ConfigDataPropertySource
还开启了一个定时线程池,轮询配置中心server获取配置项,有更新时,则调用定义的监听器ConfigDataChangeListener
,从之前写入的注册表SelfDefConfigValueRegistry
中找到配置项对应的bean以及字段,通过反射修改字段值。
具体代码如下:
1 | public class ConfigDataPropertySource extends PropertySource<Object> { |
获取、轮询数据以及监听数据进一步封装成了
ConfigDataRepository
,提供两种实现,即Redis读取和Http轮询:RedisConfigDataRepository
、HttpConfigDataRepository
,这里只看Http即可。
1 | public class HttpConfigDataRepository implements ConfigDataRepository { |
1 | 4j |
这样一个配置中心以及动态自动更新配置就实现了,
具体代码在 https://github.com/wetsion/study/tree/master/src/main/java/com/wetsion/study/self_def_config_center
写在后面的话。实现这样一个配置中心主要基于对Apollo(阿波罗)的学习。在有这样一个想法之后,在SpringCloudConfig和Apollo中选择了Apollo,通过学习它的源码,对自动更新原理有了较清晰的理解,也感叹别人的设计。当然我这样一个微型的配置中心也是有很多需要完善的地方,比如没有对namespace作区分处理,就会存在重名的问题,也就会发生冲突,所以后续完善过程中在轻量的同时,也要保证没有大问题。