配置中心,老生长谈的问题。之前在平安的时候,就调研过各种开源实现,发现了携程的apollo很不错。spring cloud生态也提供了spring cloud config。下面会先讲解spring cloud config,再讲解apollo,然后再对比两个,看各自优缺点。
spring cloud config
spring cloud config分为服务端 config server和客户端 config client。config server要多节点部署以保证要高可用,config client就是我们具体的应用。spring cloud config依赖于github或者gitlab或者svn,会把配置文件放在相应仓库地址的相应目录中,然后config server会去对应目录中读取文件。
config server搭建
1、引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、在配置文件中添加
server.port=8001
spring.application.name=spring-cloud-config-server
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/
spring.cloud.config.server.git.uri=https://github.com/koushr/spring-cloud-examples.git
spring.cloud.config.server.git.search-paths=config-repo
spring.cloud.config.server.git.username=koushr
spring.cloud.config.server.git.password=xxx
git.uri指定github/gitlab/svn的仓库地址,git.search-paths指定仓库中存放配置文件的目录,可以是多个,因为不同客户端应用的配置文件一般会放在不同的目录中。假如不同应用的配置文件放在同一目录的话,就显得太乱,不太好管理。如果分开放,又会有一个很致命的问题,那就是每新建一个项目,config server都要修改代码,然后重启。这点不能接受。还需要指定访问仓库时的用户名和密码。
本例子假定有一个占用8000端口的eureka server,把config server也注册到注册中心。
3、在启动类上添加@EnableEurekaClient、@EnableConfigServer注解。
启动应用,config server就算搭建好了。
config client
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、在配置文件中添加
注意,config client要额外用bootstrap.properties配置文件。
spring.cloud.config.name=neo-config
spring.cloud.config.profile=dev
spring.cloud.config.label=master
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=spring-cloud-config-server
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/
config.name指定要读取的配置文件的名称,可以设置多个,用逗号分开。config.profile指定要读取的环境,如dev、test、prd。如果config.name=neo-config,config.profile=dev,则会读取neo-config-dev.properties文件中的配置项。如果config.name=neo-config,neo-config2,config.profile=dev,则会读取neo-config-dev.properties、neo-config2-dev.properties文件中的配置项。
config.label指定仓库的分支。config.discovery.enabled=true表示启用注册中心发现,config.discovery.serviceId指定要发现的配置中心名称,当然前提条件是必须把自己也注册到注册中心。
3、在启动类上添加@EnableDiscoveryClient注解。
那么怎么用呢?直接用@Value("${key}")即可读取配置项的值
@RestController public class HelloController { @Value("${neo.hello}") private String hello; @RequestMapping("/helloConfig") public String hello() { return this.hello; } }
调用/helloConfig接口就返回neo.hello的值。
现在应用已经可以读取配置中心的配置项了,但是读取不了更新后的值。什么意思呢?编辑neo-config.properties文件,修改neo.hello的值。再次调用/helloConfig接口,拿到的是旧值,新值获取不了。
解决办法:
1、在使用配置项的类上添加@RefreshScope注解。
2、修改完配置项的值后调用客户端的/actuator/refresh接口。这就要求客户端应用放开/actuator/refresh接口,引入actuator依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
调用客户端应用的/actuator/refresh接口后,客户端应用会重新从config server拉取最新配置项。
到这一步,有两个问题。
第一个问题:客户端应用节点有很多,要想让所有节点都能获取到最新的配置项值,得调用所有节点的接口。这可不太容易。通过ip调用的话,太麻烦,漏一个就麻烦了,又如果这些ip是内网ip的话,可能根本还调不通,甚至有时候我们根本还拿不到这些ip。假如通过域名调用的话,由于负载均衡策略的存在,根本不能调用到所有的节点。
第二个问题:发现客户端在处理/actuator/refresh请求时会把自己从注册中心注销掉,然后又注册上去。
利用消息总线可以解决第一个问题。
原理是我们不再调用客户端的接口,而是调用conifg server的接口,服务端会向消息队列中写入消息,每个客户端启动后都会新建一个不同的消费组,收到消息后会从服务端重新拉取配置项值。
整改步骤:
1、在config server和config client都引入spring cloud bus依赖,由于要调用服务端的actuator接口,所以服务端还得引入actuator依赖。消息总线要依赖消息队列,目前只有两种实现,kafka和rabbitmq,我们还是用熟悉的kafka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、在配置文件中添加spring cloud bus相关的配置。config server在application.properties文件中添加,config client在bootstrap.properties文件中添加
spring.cloud.bus.enabled=true
spring.cloud.stream.kafka.binder.brokers=192.168.56.100:9092
management.endpoints.web.exposure.include=*
第一行表示启用spring cloud bus,第二行指定kafka集群地址,kafka集群版本最好跟上面依赖引入的kafka客户端版本一致,否则可能会出现一些奇怪的问题。
按照以上方案整改后,修改完配置项,调用服务端的/actuator/bus-refresh接口,客户端就会重新从服务端拉取配置项的值,这就解决了第一个问题。
第二个问题仍然存在,而且更严重了,现在不光是客户端要注销再注册,就连收到/actuator/bus-refresh请求的服务端都要注销再注册了。