分布式環境下的統一配置框架,已經有不少了,比如百度的disconf,阿里的diamand。今天來看下spring cloud對應的解決方案:
如上圖,從架構上就可以看出與disconf之類的有很大不同,主要區別在于:
- 配置的存儲方式不同
- disconf是把配置信息保存在mysql、zookeeper中,而spring cloud config是將配置保存在git/svn上 (即:配置當成源代碼一樣管理)
- 配置的管理方式不同
- spring cloud config沒有類似disconf的統一管理界面,既然把配置都當成git之類的源碼來看待了,git的管理界面,就是配置的管理界面
- 配置變化的通知機制不同
- disconf中配置變化后,依賴zk的事件watcher來通知應用,而spring cloud config則是依賴git每次push后,觸發webhook回調,最終觸發spring cloud bus(消息總線),然后由消息總線通知相關的應用。
另外,spring cloud config server本身也是一個微服務,跟其它的微服務一樣,也可以注冊到eureka server上,讓其它使用方從注冊中心來發現,單純從解決的問題/場景來看,disconf與spring cloud config server是高度重合的,很難說哪個好,那個差,只是設計哲學不同。
但有一點,從配置變化的通知機制上看,如果有100個應用節點,都依賴于統一配置,如果修改了配置,只想讓某幾個節點"灰度"更新配置,spring cloud config server更容易做到,這一點相對disconf更靈活(后面會詳細講解)。
使用步驟:
一、在git/svn上創建一個配置項目(用于保存配置文件)
以https://github.com/yjmyzz/spring-cloud-config-repository 這個為例,上面就放了幾個配置文件(推薦用新的yml格式,對中文支持更好碼)。
application.yml里的內容如下:
demo: title: "default title"
其它幾個文件application_xxx.yml,里面的xxx,代表不同的profile.
二、創建config-server微服務
2.1 添加依賴項
dependencies { compile 'org.springframework.cloud:spring-cloud-starter-eureka' compile 'org.springframework.cloud:spring-cloud-config-server' compile 'org.springframework.boot:spring-boot-starter-actuator' }
關鍵是第2個依賴項
2.2 application.yml
spring: application: name: config-server profiles: active: server1 cloud: config: server: git: uri: https://github.com/yjmyzz/spring-cloud-config-repository # username: ***** # password: ***** eureka: instance: prefer-ip-address: true instance-id: ${spring.application.name}:${server.port} client: service-url: defaultZone: http://yjmyzz:123456@server1:8100/eureka,http://yjmyzz:123456@server2:8200/eureka management: security: enabled: false
注意上面的cloud.config.server這段,里面配置了git配置項目的位置。另外:config-server服務本身也需要HA,所以本示例中起了2個實例,分別對應server1、server2 這二個profile,用不同的端口,在本機跑2個實例,以模擬高可用。
application-server1.yml
server: port: 8004
application-server2.yml
server: port: 8005
2.3 main入口類
package com.cnblogs.yjmyzz.spring.cloud.study.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * Created by yangjunming on 2017/7/5. */ @SpringBootApplication @EnableEurekaClient @EnableConfigServer public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); } }
關鍵是@EnableConfigServer 這個注解。
2.4 跑起來看看
可以看到2個config-server已經注冊到eureka上了,然后單獨瀏覽一下: http://localhost:8004/application-dev.yml
已經把git上的application-dev.yml的內容輸出了。
三、使用config-server
3.1 在之前的service-provider中添加依賴項
compile 'org.springframework.cloud:spring-cloud-starter-config'
3.2 創建一個簡單的配置類
package com.cnblogs.yjmyzz.spring.cloud.study.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * Created by yangjunming on 2017/7/5. */ @Component @Data @ConfigurationProperties(prefix = "demo") public class DemoConfig { private String title; }
然后找一個示例服務,使用這個配置:
package com.cnblogs.yjmyzz.spring.cloud.study.service.impl; import com.cnblogs.yjmyzz.spring.cloud.study.api.UserService; import com.cnblogs.yjmyzz.spring.cloud.study.config.DemoConfig; import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Autowired DemoConfig config; @Override public UserDTO findUser(Integer userId) { UserDTO user = new UserDTO(); user.setUserId(userId); user.setUserName("菩提樹下的楊過(" + config.getTitle() + ")"); return user; } }
3.3 添加bootstrap.yml 配置文件
spring: application: name: application cloud: config: profile: dev label: master discovery: enabled: true service-id: config-server eureka: instance: prefer-ip-address: true client: service-url: defaultZone: http://yjmyzz:123456@server1:8100/eureka,http://yjmyzz:123456@server2:8200/eureka
注意spring.cloud這一節的內容,里面指定了profile為dev,讀取的git配置文件分支為master,同時允許從eureka上自動發現config-server這個實例。另外 spring.applicatin.name 即為配置文件的名稱(即:application_xxx.yml)
3.4 跑起來看看
說明已經從config-server取到了配置。
四、配置更新
4.1 Controller上添加@RefreshScope
@RestController @RefreshScope public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public UserDTO findUser(@PathVariable Integer id) { return userService.findUser(id); } }
這個注解,根據源碼上的說法:Beans annotated this way can be refreshed at runtime and any components that are using them will get a new instance on the next method call, fully initialized and injected with all dependencies. 使用該注解后,可以在運行時直接刷新Bean,并在下次方法調用時,得到一個全新的實例。
4.2 手動刷新/refresh
可以嘗試把git配置項目里的application-dev.yml修改下內容,再瀏覽剛才的http://localhost:8001/user/1 發現內容并沒有變化。
http://localhost:8001/refresh 手動向這個地址,發一個post請求(可以用postman或 curl -d '' http://localhost:8001/refresh),可以看到
說明demo.title這個配置項被刷新了,再瀏覽http://localhost:8001/user/1 可以看到有變化了
但是這樣顯然不是個辦法,比如有10個service-provider組成的集群,如果要1臺臺手動刷新,太low了(除了做配置灰度更新,可以先刷新1臺這種場景外)
4.3 集成spring cloud bus來批量刷新
spring cloud bus目前僅支持rabbitmq 及 kafka,我們以kafka為例,先在service-provider的application.yml里,加入下面的配置
然后依賴項里,加入:
compile 'org.springframework.cloud:spring-cloud-starter-bus-kafka'
注:關于kafka的環境搭建,網上有很多資料,大家可以參考下。
配置好這些后,本機啟動kafka,然后再重啟service-provider,就會多出一個/bus/refresh的端點,即:http://xxx:port/bus/refresh ,只要向集群中的任何一臺機器的/bus/refresh發起post請求,就會同步刷新其它所有節點。原理大致就是,這臺機器會發一條消息到kafka中,然后其它機器都是掛在消息總線上的,也會監聽到該消息,然后刷新各自的配置。
最后一個問題:就算有/bus/refresh,也需要有人或系統觸發。這個很好解決,github或gitlab上一般都有webhook功能,可以配置在代碼push時,觸發一些地址的回調。
這樣,只要配置的代碼提交了,就會觸發自動刷新。
注:低版本的spring-cloud-dependencies有一個嚴重bug,調用/bus/refresh后,會導致所有服務節點,從eureka server的實例列表中永久下線,無法自動恢復,除非再次訪問某個服務的/health端點,建議使用Dalston.SR2 或以上版本。
文章列表