闲话配置
老司机都喜欢在程序设计中尽可能的把各种参数做成可配置的,等到产品需求发生改动的时候,优雅的修改一行配置,重新加载一次配置,就满足了需求。
配置(Configuration)是不修改代码的情况下,对程序的运行调整的能力。
简单来讲,配置可以分成两类:
- 系统配置:包括线程池大小、数据库连接等,变化频率较低
- 业务配置:功能开关,功能参数等,变化较为频繁
程序的配置一般而言,分为几个环境:
- 开发环境
- 测试环境
- 生产环境
这几个环境的配置是有差异的,所以配置管理功能应当能够根据当前环境读取对应的配置。
前面提到的不修改代码,只是最低要求,相当多的情况下,程序是不能停机的,这就提出了热更新的需求。
另外,配置的修改应该是有记录可追溯的。
Spring Boot 的配置管理
Spring 实现了非常友好的配置读取方式,
Spring Boot 程序默认使用 application.properties 进行配置。
例如一个参数:
1 | flag=0 |
在程序中可以通过:
1 | @Value("${flag}) |
来读取。
也可以通过设置多个配置文件:
1 | application.properties |
application-dev.properties 1
flag=1
1
flag=2
application-prod.properties 1
flag=3
和在 application.properties 中声明当前的活跃的 profile:
1 | spring.profiles.active=dev |
来实现根据运行环境切换配置信息。
以上只是 Spring 强大的配置能力的冰山一角,有兴趣可以参考外部配置文档。
这还不够
其实以上描述的 Spring Boot 的配置能力已经很强了,但是有个很致命的问题——无法热更新。
如果非得实现热更新,那可以把配置做成 JSON 文件,再实现一个 endpoint,重新读取一次配置。
假设有一千个实例呢?
Spring Cloud Config
项目 github:https://github.com/spring-cloud/spring-cloud-config
简介:External configuration (server and client) for Spring Cloud
这个项目包含两部分:
- Server
负责从 git/svn 等版本管理系统中读取配置,并以 http 的方式提供服务。
- Client
根据客户端配置,从指定的 Server 中读取对应配置,并且与 Spring 本身的 PropertyResource 和 Environment 无缝结合。同时,提供了统一的方式进行配置热更新。
对于这个系统的基础配置和运行,可以参阅下面两个 url:
https://spring.io/guides/gs/centralized-configuration/
http://blog.didispace.com/spring-cloud-starter-dalston-3/
实操进阶
热更新
Spring Cloud Config 实现了非常优雅的热更新。
- 客户端的配置 Bean 添加 @RefreshScope
1 | @Configuration |
在配置这个注解之后,调用客户端的 /refresh endpoint,这个 Bean 就会刷新,同时个 Bean 的依赖方下次方法调用时也会更新 Bean 引用。
有些时候,你需要监听这个更新事件,把拿到的最新配置,重新初始化一些部件。那你可以添加:
@EventListener(EnvironmentChangeEvent.class)
这个
annotation。
1 | @Component |
添加权限
Config Server 任何人都可以通过 http 访问配置,这个不大好,建议加上认证,最简单是使用 Spring-Security 添加一个 basic authentication。
- 服务器端配置
build.gradle 添加依赖: 1
2
3dependencies {
compile('org.springframework.boot:spring-boot-starter-security')
}
application.properties 添加密码(用户名默认是 user):
1
security.user.password=xxx
重启服务器,再次通过 http 访问配置的时候需要验证。
- 客户端配置
服务器端做了验证,那客户端也需要添加相应的配置:
bootstrap.yml 1
2
3
4
5
6
7
8
9spring:
application:
name: application
cloud:
config:
uri: http://yourhost.com
profile: dev
username: user
password: xxx
加载多组配置文件
很多时候,为了避免 application.properties 过于臃肿,你可能需要把一些配置文件拆出来,例如专门负责邀请奖励的配置:
1 | invite.properties |
同样,也区分了多个环境。
那在使用 Spring Cloud 的时候如何读取这个文件呢?
1 | spring: |
注意上面的 application.name 是以逗号分隔的两组配置名称。
一些问题
@EventListener 与 SpEL
我在实际使用中发现,在事件监听函数中,使用更新后的配置的时候:
1 | @Value("${consume.desc}") |
第一个配置是使用 @Value 绑定一个字符串类型,第二个配置是使用 @Value 中的 SpEL 去将配置中的字符串,切割成 List。
我发现第二个,无法切割成功。试验了多次,还没有找到答案。我已经在 Spring Cloud Config 的 github 上提交了 issue。
如果有了解这个的,请不吝赐教。
update 2017-10-26 * 我在 github 上提交了 issue: https://github.com/spring-cloud/spring-cloud-config/issues/821 得到回复:
We have only ever documented that placeholders ${} work, not SpEL.
权限管理
在不做二次开发的情况下,这个配置中心的数据是对所有的 client 开放的。某些情况下,这种设定并不合适。
JSON 文件的读取和解析
Spring Cloud Config 本身是可以通过 http 来提供 JSON 文件的访问的,但是Spring Boot 原生并不支持 JSON 配置的读取和解析。
相关选择
其实配置管理工具,选择还是不少的,下面列举一下。
- owner http://owner.aeonbits.org/
配置文件管理,可以热更新,跟 Spring 没有绑定。
- cfg4j http://www.cfg4j.org/
专注管理配置文件,也可以实现热更,跟 Spring 没有绑定。
- applolo https://github.com/ctripcorp/apollo
携程开源的,功能很全
- disconf https://github.com/knightliao/disconf
百度的一位工程师的开源项目,功能也很全
- diamond https://github.com/takeseem/diamond
来自阿里的开源,有些日子了
参考文献
http://blog.didispace.com/spring-cloud-starter-dalston-3/
http://cloud.spring.io/spring-cloud-config/single/spring-cloud-config.html
http://jm.taobao.org/2016/09/28/an-article-about-config-center/
https://blog.coding.net/blog/spring-cloud-config