隨着系統微服務的不斷增長,首要考慮的是系統的可伸縮、可擴展性好,隨之就是一個配置管理的問題。各自管各自的開發時沒什麼問題,到了線上以後管理就會很頭疼,到了要大規模更新就更煩了。 配置中心就是一個比較好的解決方案,下圖就是一個配置中心的解決方案: 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/