随着系统微服务的不断增长,首要考虑的是系统的可伸缩、可扩展性好,随之就是一个配置管理的问题。各自管各自的开发时没什么问题,到了线上以后管理就会很头疼,到了要大规模更新就更烦了。 配置中心就是一个比较好的解决方案,下图就是一个配置中心的解决方案: html
常见的配置中心的实现方法有:java
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>
@SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); } }
在bootstrap.yml中添加配置:nginx
spring: application: name: config-server cloud: config: server: git: uri: http://gitlab.**.cn/java/config-repository.git username: *** password: *** default-label: master search-paths: java*,ruby*,go* server: port: 9992
search-paths表示git仓库的存放配置文件的目录git
在上面的git仓库中添加一个config-client-test.yml配置文件,配置内容以下:spring
env: alpha
访问http://localhost:9992/config-client/test/master 能够查看配置信息json
表示config server启动成功bootstrap
{ "name": "config-client", "profiles": Array[1][ "test" ], "label": "master", "version": "1b28f79de8c4097dc68a4be986d13232662eb036", "state": null, "propertySources": Array[1][ { "name": "http://gitlab.**.cn/java/config-repository.git/java/config-client-test.yml", "source": { "env": "alpha" } } ] }
URL与配置文件的映射关系以下:安全
/{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties
http://localhost:9992/master/config-client-test.ymlruby
env: alpha
http://localhost:9992/master/config-client-test.json服务器
{ "env": "alpha" }
http://localhost:9992/master/config-client-test.properties
env: alpha
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies>
spring: application: name: config-client profiles: active: test cloud: config: label: master uri: http://localhost:9992/
启动类
@SpringBootApplication public class ConfigClientApplication { public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } }
Controller类
@RestController public class EnvController { @Value("${env}") private String env; @RequestMapping(value = "/hi") public String hi() { return env; } }
2017-11-08 17:56:49.730 INFO 3680 --- [ restartedMain] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:9992/ 2017-11-08 17:56:50.313 INFO 3680 --- [ restartedMain] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=config-client, profiles=[test], label=master, version=06ef59ccd30be2089bc8c7d214b57f0497f1f3f5, state=null 2017-11-08 17:56:50.313 INFO 3680 --- [ restartedMain] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource [name='configClient'], MapPropertySource [name='http://****/config-repository.git/java/config-client-test.yml']]]
访问http://localhost:8080/hi 直接返回
alpha
pom加入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
bootstrap.yml文件中加入:
security: user: name: admin password: 123
从新启动后进入页面的时候要求输入用户名和密码
config-client须要在配置文件中添加验证信息:
spring: application: name: config-client profiles: active: test cloud: config: label: master uri: http://localhost:9992/ username: admin password: 123
原理图以下所示:
负载均衡能够用nginx,这样spring.cloud.uri配置写域名就行
这里只介绍用spring cloud服务发现组件eureka配置
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies>
spring: application: name: eureka-server server: port: 9991 eureka: client: service-url: defaultZone: http://localhost:9991/eureka/ fetch-registry: false register-with-eureka: false
@SpringBootApplication @EnableEurekaServer public class EurekaServer { public static void main(String[] args) { SpringApplication application = new SpringApplication(EurekaServer.class); application.run(args); } }
修改pom文件,添加maven依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
在程序的入口Application类加上@EnableEurekaClient或者@EnableDiscoveryClient注解
配置文件中添加注册地址,将服务注册到eureka-server中
eureka: client: service-url: defaultZone: http://localhost:9991/eureka/
修改pom文件,添加maven依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
在程序的入口Application类加上@EnableEurekaClient或者@EnableDiscoveryClient注解
修改配置文件,将其注册到eureka-server服务
spring: application: name: config-client profiles: active: test cloud: config: label: master username: admin password: 123 discovery: enabled: true service-id: config-server eureka: client: service-url: defaultZone: http://localhost:9991/eureka/
经过service-id查找config-server服务,可部署多台config-server,实现集群化部署达到高可用
访问eureka-server的服务器,能够看到config-server和config-client同时都注册在上面:
访问http://localhost:8080/hi 直接返回,表示服务部署成功
alpha
config-client项目
修改pom文件,添加maven依赖,能够调用/refresh接口刷新配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
boostrap.yml添加配置,暂时去掉安全认证
management: security: enabled: false
修改配置引用的地方,添加 @RefreshScope
@RestController @RefreshScope public class EnvController { @Value("${env}") private String env; @RequestMapping(value = "/hi") public String hi() { return env; } }
从新启动 访问http://localhost:8080/hi 返回
alpha
修改git仓库里config-client-test.yml文件
env: alpha123
调用refresh接口
curl -X POST http://localhost:8080/refresh
从新访问返回 alpha123表示配置刷新成功
alpha123
每每咱们系统都是集群部署,一台一台调用/refresh接口维护成本过高,这里咱们介绍如何使用Spring Cloud Bus实现集群配置的自动刷新,它使用轻量级的消息代理(如RabbitMQ、Kafka)链接分布式系统的节点,广播配置的变化或者其余的管理指令。
修改pom文件,添加maven依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
修改配置文件,添加rabbitmq配置
spring: rabbitmq: host: localhost port: 5672 username: guest password: guest
这里咱们再启动一个8081端口的config-client服务
访问http://localhost:15672/#/ ,新增了一个Exchange,以及三个queue
由于咱们启动了1个config-server、2个config-client 每一个服务监听各自的队列消息
先访问http://localhost:8080/hi 和 http://localhost:8081/hi 返回
alpha123
修改git仓库里config-client-test.yml文件
env: alpha123123
调用config-server的/bus/refresh接口
curl -u admin:123 -X POST http://localhost:9992/bus/refresh
从新访问返回以下,表示集群配置刷新成功
alpha123123
调用/bus/refresh,会刷新全部使用spring cloud bus连到rabbitmq的服务,若是须要局部刷新,可经过/bus/refresh的destination参数来定位要刷新的应用程序。
例如 /bus/refresh?destination=config-client:test:8080
这里只刷新8080端口的config-client服务,另外一个8081端口的config-client配置没有刷新
咱们打断点发现,调用/bus/refresh的服务会向rabbitmq服务发送消息,message的body里有"destinationService":"config-client:test:8080"
由于routing key 是 # ,这样使用spring cloud bus链接rabbitmq的服务都会收到这个消息,
咱们发现有个ServiceMatcher类
public class ServiceMatcher implements ApplicationContextAware { private ApplicationContext context; private PathMatcher matcher; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } public void setMatcher(PathMatcher matcher) { this.matcher = matcher; } public boolean isFromSelf(RemoteApplicationEvent event) { String originService = event.getOriginService(); String serviceId = getServiceId(); return this.matcher.match(originService, serviceId); } public boolean isForSelf(RemoteApplicationEvent event) { String destinationService = event.getDestinationService(); return (destinationService == null || destinationService.trim().isEmpty() || this.matcher .match(destinationService, getServiceId())); } public String getServiceId() { return this.context.getId(); } }
重点看isForSelf方法,发现会拿destinationService跟serviceId比较
serviceId实际就是context的id,是由ContextIdApplicationContextInitializer生成的,格式是 name:profiles:index
根据代码可知 name是spring.application.name、index是server.port,若是相匹配就会刷新此服务的配置。
public class ContextIdApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}"; private static final String INDEX_PATTERN = "${vcap.application.instance_index:${spring.application.index:${server.port:${PORT:null}}}}"; private final String name; private int order = Ordered.LOWEST_PRECEDENCE - 10; public ContextIdApplicationContextInitializer() { this(NAME_PATTERN); } public ContextIdApplicationContextInitializer(String name) { this.name = name; } public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.order; } @Override public void initialize(ConfigurableApplicationContext applicationContext) { applicationContext.setId(getApplicationId(applicationContext.getEnvironment())); } private String getApplicationId(ConfigurableEnvironment environment) { String name = environment.resolvePlaceholders(this.name); String index = environment.resolvePlaceholders(INDEX_PATTERN); String profiles = StringUtils .arrayToCommaDelimitedString(environment.getActiveProfiles()); if (StringUtils.hasText(profiles)) { name = name + ":" + profiles; } if (!"null".equals(index)) { name = name + ":" + index; } return name; } }
http://tech.lede.com/2017/06/12/rd/server/springCloudConfig/