5

兼容原生SpringBoot配置规范, BRCC助你一站式解决项目配置建设需求

 3 years ago
source link: http://www.blogjava.net/xmatthew/archive/2021/04/08/435848.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1. Springboot config示例与原理介绍

1.1 Springboot config示例

Springboot给我们提供了非常便捷与方便的配置使用方式,常用的使用示例有以下两种:

1)使用@ConfigurationProperties 读取多个属性

使用方法:@ConfigurationProperties(prefix = "spring.datasource")

使用说明:提供 Setter方法 和 标记组件 Component

@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class MyDataSource {
private String url;
private String username;
private String password;
private String driverClassName;
// 提供Setter 和 Getter 方法
}

2)使用@Value 读取单个属性

使用方法:@Value("spring.datasource.XXX")

使用说明:提供 Setter方法 和 标记组件 Component

@Component
public class MyDataSource {
@Value("spring.datasource.url")
private String url;
@Value("spring.datasource.username")
private String username;
@Value("spring.datasource.password")
private String password;
@Value("spring.datasource.driver-class-name")
private String driverClassName;
// 提供Setter 和 Getter 方法
}

接下来针对两个注解@Value 和 @ConfigurationProperties, 他们是如何工作的呢?

1.2 SpringBoot 配置原理

springboot的配置是伴随Springboot的启动进行的, 主要包括配置加载、存储、替换3个基本过程。

1) SpringBoot配置加载

       在SpringBoot的启动过程中,会创建一个ConfigurableEnvironment来存放不同的配置,不同来源的配置被加载到不同的PropertySource并添加到ConfigurableEnvironment的

MutablePropertySources成员中,加载过程如下:
public class SpringApplication {
...
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 创建并配置环境,加载systemProperties, systemEnvironment, servletContextInitParams, servletConfigInitParams等配置
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 加载application.properties, application.yml等应用配置,
// 由EnvironmentPostProcessorApplicationListener#onApplicationEnvironmentPreparedEvent处理
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
// 根据webApplicationType创建不同类型的Environment实例,
// 以下3中类型的Environment都继承AbstractEnvironment,
// 在其构造方法中customizePropertySources来加载不同来源的配置,
// 比如 来源于System.getProperties()的systemProperties,来源于System.getenv()的systemEnvironment,
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
...
}

2) SpringBoot配置存储

SpringBoot使用到的配置会加载到PropertySourcesPlaceholderConfigurer的MutablePropertySources成员中, 代码如下:

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
// @Value 的配置存储
@Nullable
private MutablePropertySources propertySources;
// @ConfigurationProperties 的配置存储,
// 在postProcessBeanFactory方法中, 将propertySources赋值给了appliedPropertySources
@Nullable
private PropertySources appliedPropertySources;
...
}

 PropertySourcesPlaceholderConfigurer是一个BeanFactoryPostProcessor, 在IOC容器的启动过程中, 会主动调用其postProcessBeanFactory方法, 在此方法中完成了propertySources,appliedPropertySources的初始化, 代码如下:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) {
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
// 以environment为配置源的environmentProperties
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
@Nullable
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
try {
// 通过loadProperties(Properties props)加载本地资源文件作为属性源的localProperties
PropertySource<?> localPropertySource =
new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
if (this.localOverride) {
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
// 遍历处理bean属性
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
// 将propertySources赋值给appliedPropertySources,为ConfigurationPropertiesBinder提供配置源
this.appliedPropertySources = this.propertySources;
}
// 处理每个beanFactory中的每个BeanDefinition尝试替换 ${...} 属性.
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
// 占位符设置
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
// 定义StringValueResolver
StringValueResolver valueResolver = strVal -> {
// 通过propertyResolver获取配置的值
String resolved = (this.ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (this.trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(this.nullValue) ? null : resolved);
};
// 通过doProcessProperties方法,将valueResolver添加到AbstractBeanFactory的embeddedValueResolvers中
doProcessProperties(beanFactoryToProcess, valueResolver);
}

3) SpringBoot配置替换

Spring配置替换(配置生效)主要包括@Value配置替换和@ConfigurationProperties的配置替换。

3.1)@Value配置替换过程

SpringBoot 的启动过程包括2个重要的过程, 分别如下

  1. 扫描、解析BeanDefinition并注册到IOC容器中
  2. 根据BeanDefinition实例化Bean

@Value 是在实例化Bean的过程中完成属性注入的,主要流程如下:

%E6%B3%A8%E8%A7%A3Value%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%20%283%29.png?version=1&modificationDate=1616049922000&api=v2

AbstractAutowireCapableBeanFactory:     根据beanName, beanDefinition创建bean, 完成属性填充, 初始化等操作。

AutowiredAnnotationBeanPostProcessor:     装配bean中使用注解标注的成员变量,setter方法, 任意的配置方法。比较典型的是@Autowired注解和@Value注解。

InjectionMetadata: 遍历处理每个属性

AutowireCandidateResolver: 获取@Value注解中的值

StringValueResolver: 一个定义了处理字符串值的接口,只有一个接口方法resolveStringValue,可以用来解决占位符字符串。本文中的主要实现类在PropertySourcesPlaceholderConfigurer#processProperties方法中通过lamda表达式定义的。供ConfigurableBeanFactory类使用。

PropertySourcesPropertyResolver: 属性资源处理器,主要功能是获取PropertySources属性资源中的配置键值对。

PropertyPlaceholderHelper: 一个工具类,用来处理带有占位符的字符串。形如${name}的字符串在该工具类的帮助下,可以被用户提供的值所替代。

3.2) @ConfigurationProperties 替换过程

@ConfigurationProperties的加载是通过ConfigurationPropertiesBindingPostProcessor这个Bean后置处理器的bind方法完成的,具体流程如下:

ConfigurationPropertiesBindingPostProcessor核心代码如下:

private ApplicationContext applicationContext;
private ConfigurationPropertiesBinder binder;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
return bean;
}
private void bind(ConfigurationPropertiesBean bean) {
if (bean == null || hasBoundValueObject(bean.getName())) {
return;
}
Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
+ bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
try {
// 借助于ConfigurationPropertiesBinder.bind完成属性装载
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
Binder#bindObject方法:
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
Context context, boolean allowRecursiveBinding) {
// 根据名称查找属性
ConfigurationProperty property = findProperty(name, context);
if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
return null;
}
AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
if (aggregateBinder != null) {
return bindAggregate(name, target, handler, context, aggregateBinder);
}
if (property != null) {
try {
// 绑定属性(属性替换)
return bindProperty(target, context, property);
}
catch (ConverterNotFoundException ex) {
// We might still be able to bind it using the recursive binders
Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
if (instance != null) {
return instance;
}
throw ex;
}
}
return bindDataObject(name, target, handler, context, allowRecursiveBinding);
}
private <T> Object bindProperty(Bindable<T> target, Context context, ConfigurationProperty property) {
context.setConfigurationProperty(property);
Object result = property.getValue();
// placeholdersResolver是PropertySourcesPlaceholdersResolver的实例,
// 其sources成员为PropertySourcesPlaceholderConfigurer的appliedPropertySources属性
result = this.placeholdersResolver.resolvePlaceholders(result);
result = context.getConverter().convert(result, target);
return result;
}


2、BRCC介绍

介绍到这里,大家应该对Spring boot的配置使用与原理有比较清晰的了解了。相信此时大家也会有疑问出来,目前绝大数的应用都是分布式的架构下部署的,Springboot基于本地化的配置管理,成本高,变更难度大,风险也相对要高。

基于上面的问题与风险, BRCC就此诞生了, 它是百度基于大量的实际项目应用与实践的经验,经过多次的改进与升级,可以一站式的帮助大家解决配置开发与运维等众多需求,帮你一站式解决项目配置建设需求。

2.1 简介

  BRCC是一个分布式配置中心,用于统一管理应用服务的配置信息,避免各类资源散落在各个项目中,简化资源配置的维护成本。作为一种轻量级的解决方案,部署简单,同时支持多环境、多版本、多角色的资源管理,可以在不改变应用源码的情况下无缝切换和实时生效配置信息。

  BRCC由三部分组成:管理端、服务端、SDK,其中:
(1)管理端 : 前后端分离,后端基于Spring Boot 2.0开发,支持6个维度(产品、工程、环境、版本、分组、配置项)管理key-value格式的配置;支持细粒度的权限控制层级、操作轨迹等能力。安全易用,支持插件化的扩展轻松集成任何公司/组织的账号管理系统。
(2)服务端: 基于spring boot 2.0开发,打包后可以直接运行,支持配置的分发、更新推送。
(3)SDK端:支持java、go等多种开发语言和开发框架集成,支持spring注解、配置变更监听和刷新,零业务侵入性,低门槛集成(提供spring boot starter方式接入)。

2.2 特性

1. 统一管理不同环境、不同产品线的配置

  • 提供统一界面集中式管理不同环境、不同产品线、不同工程的配置
  • 通过版本的复制,可以高效的完成新业务的配置

2. 配置修改实时生效(热发布)

  • 默认2秒接收到最新的配置
  • 主动变更通知

3. 权限管理、角色隔离

  • 多级权限、多种角色细粒度管控(产品线、工程、环境),支持读写权限
  • 重要信息加密交互
  • Token机制

4. 多语言支持

  • 支持Java、Go客户端以SDK方式接入
  • 支持OpenAPI快速接入(不限定语言,只要支持Http协议即可)

5. 可追溯

  • 操作轨迹可追踪
  • 历史配置可查询和回溯

6. 缓存加持

  • 重要接口支持缓存开关配置

7. 更轻量

  • 部署简单,目前强依赖的外部服务是MySQL
  • 更聚焦:只关注配置相关的控制

8. 管理便捷

  • 细粒度化到key-value配置项层级
  • 支持模糊匹配检索

2.3 技术架构

image2021-3-22_15-17-2.png?version=1&modificationDate=1616397423000&api=v2

3、SDK端使用

在线demo地址: https://github.com/baidu/brcc/tree/main/brcc-example

3.1 增加brcc依赖

创建一个通用的springboot应用,在pom文件中增加如下依赖:

<dependency>
<groupId>com.baidu.mapp</groupId>
<artifactId>brcc-sdk-starter</artifactId>
<version>1.0.0</version>
</dependency>

brcc sdk starter的最好使用最新版本。

3.2 增加brcc的配置

在application.yml增加brcc的配置

rcc:
cc-server-url: http://127.0.0.1:8080/ #这里brcc server的地址
project-name: example                 #工程名
cc-password: 123456                   #工程的api密码
env-name: dev                         #环境名称
cc-version-name: 1.0                  #版本名称
log-properties: true                  #是否打印配置
enable-update-callback: true          #是否启用自动更新

rcc.cc-server-url

null服务地址

rcc.project-name

null工程名称

rcc.cc-password

null
工程的api密码

rcc.env-name

null
环境名称

rcc.cc-version-name

null
版本名称

rcc.log-properties

false
是否打印配置

rcc.enable-update-callback

false
是否启用自动更新

rcc.connection-timeout

3000链接超时时间(ms)

rcc.read-timeout

10000读超时时间 (ms)

rcc.callback-interval

2000心跳探测频率(ms)

3.3 使用配置

通过spring的placeholder能力在各种注解中使用这些配置,如:

@Value("${a}")
int a = 0;
@Value("${b}")
long b = 0;
@Value("${c}")
String c;

3.4 自动刷新

当启动参数中rcc.enable-update-callback配置开启的时,自动更新功能打开。 自动更新采用的时观察者模式, 您只需要实现 com.baidu.brcc.ConfigItemChangedCallable接口,并将其以Bean的方式注册到Spring容器中, 当系统中有配置方式变更后,并且在管理平台上执行了推送变更操作, rcc会主动调用ConfigItemChangedCallable。

@Configuration
public class ExampleConfiguration {
@Bean
public ConfigItemChangedCallable configItemChangedCallable() {
return new DefaultConfigItemChangedCallable();
}
}
DefaultConfigItemChangedCallable 是rcc提供的默认变更回调实现, 其中仅仅打印变更日志,您可以实现自己的变更业务。

3.5 配置验证

启动日志中出现以下日志说明配置加载成功

image2021-3-22_15-45-49.png?version=1&modificationDate=1616399149000&api=v2

配置发送变更后执行推送变更操作后,日志中会出现以下提示

image2021-3-22_15-51-7.png?version=1&modificationDate=1616399467000&api=v2


4、BRCC管理端使用

BRCC部署比较简单,详细请参考 https://github.com/baidu/brcc/blob/main/doc/deploy-guide.md

4.1、登录管理端

如何部署BRCC服务端,请移步https://github.com/baidu/brcc/blob/main/doc/deploy-guide.md, 我们假设已经在127.0.0.1的8080端口上部署了BRCC管理端,访问 http://127.0.0.1:8080/#/login,如下图:

登录界面

4.2、增加产品线

    产品线入口, 产品线入口有3个,首页中的【全部产品线】、具体产品,个人信息菜单下拉框中的【我的产品线】如下图:

产品线入口

进入产品线列表后,点击“新建产品线”按钮,新建产品线 test。

产品线新建
 

新建后,点击test进入工程列表

4.3、增加工程

    进入工程列表后,点击"新建工程"按钮。

 

工程管理

 新建工程 example,设置好api密码。新建后在工程列表界面点击"example"进入环境页面。

4.4、增加环境

点击"新增环境",增加一个新环境dev, 如下图:

 

环境管理
 

点击dev,进入版本页面。

4.5、增加版本

新增环境 1.0,点击"1.0"进入分组页面,如下图:

版本管理

4.6、增加分组

新增分组 g1,点击"g1"进入配置页面,如下图:

 

分组管理

4.6、增加配置

增加3个配置:

a=5 b=34 c=xx13

点击“保存修改”按钮。

配置管理

本文介绍了springboot的config原理并做了源码示例,讲解了@Value和@ConfigurationProperties的生效流程。brcc作为一款分布式配置中心,用于统一管理应用服务的配置信息,它完整兼容springboot的配置模型,可以直接使用@Value和@ConfigurationProperties。brcc提供配置实时生效和配置可追溯,在管理上支持多级权限多种纬度管理配置,并提供多种sdk和api使用和管理配置。

目前BRCC已经github上开源,大家可以访问获取全部代码 https://github.com/baidu/brcc, 也可以star我们项目,以便高效获得我们持续的更新信息.

我们也非常欢迎大家反馈问题与建议给我们:  https://github.com/baidu/brcc/issues 

如在使用中遇到问题,快速沟通,可微信扫描二维码,加入brcc技术交流群,添加下列管理员微信,并备注“brcc”,管理员邀您入群:

contact.jpg

您也可以加入百度如流讨论群直接参与讨论和提问:3664772


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK