为什么需要配置中心?

  1. 对 应用的配置进行统一管理;
  2. 不同环境实现不同的配置;
  3. 运行时动态修改(我们项目使用的是 apollo)

springCloud config 简介


Config是SpringCloud的统一配置中心,包括 Config Server 和 Config Client 两部分,Config Server 使用Git存储配置内容,Config Server 操作存储在 Config Server 中的配置。所有微服务都指向 Config Server,启动时请求 Config Server 获取需要的配置属性,缓存以提高性能。

服务端

springCloud 作为 config server 管理公司项目的配置文件。
有一个 git 仓库,里面全部是项目不同环境的配置文件,configServer 依赖 zk ,会配置上述 git 仓库地址。
configServer 启动后作为服务提供者把服务注册到 zk。 通过@EnableConfigServer注解将自己注册到zk.

zk 会创建 services /服务名称 文件目录。 zk 中注册的数据格式:

里面有 configServer 所服务器的 ip 端口。

  1. {
  2. "name":"ifcoin-config-server-zk",
  3. "id":"f1d6b933-88b1-4063-bb9a-2ef24d486762",
  4. "address":"47.99.177.133",
  5. "port":9911,
  6. "sslPort":null,
  7. "payload":{
  8. "@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
  9. "id":"application-1",
  10. "name":"ifcoin-config-server-zk",
  11. "metadata":{
  12. }
  13. },
  14. "registrationTimeUTC":1576582070231,
  15. "serviceType":"DYNAMIC",
  16. "uriSpec":{
  17. "parts":[
  18. {
  19. "value":"scheme",
  20. "variable":true
  21. },
  22. {
  23. "value":"://",
  24. "variable":false
  25. },
  26. {
  27. "value":"address",
  28. "variable":true
  29. },
  30. {
  31. "value":":",
  32. "variable":false
  33. },
  34. {
  35. "value":"port",
  36. "variable":true
  37. }
  38. ]
  39. }
  40. }

客户端

每个项目都有一个 bootstrap.yml 配置文件,应用启动时,会通过yml,拉取项目配置文件;
profile:代表环境;
label: 代表git 代码分支;
service-id:和 configServer 配置的service-id 保持一致;

  1. spring:
  2. application:
  3. name: ifcoin-websocket-app
  4. cloud:
  5. zookeeper:
  6. conne ct-string: ${ZOOKEEPER_SERVER:127.0.0.1:2181}
  7. config:
  8. profile: ${SPRING_PROFILE:daily}
  9. label: master
  10. discovery:
  11. enabled: true
  12. service-id: ifcoin-config-server-zk
  13. management:
  14. endpoints:
  15. web:
  16. exposure:
  17. include: "*"
  18. server:
  19. servlet:
  20. context-path: /monitor

客户端 通过 @EnableDiscoveryClient 注解,并配置相同的 service-id,可以在 zk 中找到configServer 所在服务器的 ip,端口。进而可以通过 rest +profile +label 拿到对应应用,对应环境、分支的配置文件。

原理

configServer 会提供 rest 接口出来,应用通过 zk 找到 configServer 的 ip,端口,发送 rest 请求,获取配置。

原理图:
config_server.png

源码

EnvironmentController 提供了 rest 接口,

底层核心逻辑使用 jgit客户端 操作 git 拉取对应分支, see JGitEnvironmentRepository.refresh();

  1. public String refresh(String label) {
  2. Git git = null;
  3. try {
  4. git = createGitClient();
  5. if (shouldPull(git)) {
  6. FetchResult fetchStatus = fetch(git, label);
  7. if (deleteUntrackedBranches && fetchStatus != null) {
  8. deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
  9. }
  10. // checkout after fetch so we can get any new branches, tags, ect.
  11. checkout(git, label);
  12. if (isBranch(git, label)) {
  13. // merge results from fetch
  14. merge(git, label);
  15. if (!isClean(git, label)) {
  16. logger.warn("The local repository is dirty or ahead of origin.
  17. Resetting"+ " it to origin/" + label + ".");
  18. resetHard(git, label, LOCAL_BRANCH_REF_PREFIX + label);
  19. }
  20. }
  21. }
  22. else {
  23. // nothing to update so just checkout
  24. checkout(git, label);
  25. }
  26. // always return what is currently HEAD as the version
  27. return git.getRepository().findRef("HEAD").getObjectId().getName();
  28. }
  29. catch (RefNotFoundException e) {
  30. throw new NoSuchLabelException("No such label: " + label, e);
  31. }
  32. catch (NoRemoteRepositoryException e) {
  33. throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
  34. }
  35. catch (GitAPIException e) {
  36. throw new NoSuchRepositoryException(
  37. "Cannot clone or checkout repository: " + getUri(), e);
  38. }
  39. catch (Exception e) {
  40. throw new IllegalStateException("Cannot load environment", e);
  41. }
  42. finally {
  43. try {
  44. if (git != null) {
  45. git.close();
  46. }
  47. }
  48. catch (Exception e) {
  49. this.logger.warn("Could not close git repository", e);
  50. }
  51. }
  52. }

shouldPull() 方法 会调用 git status 判断是否需要从仓库获取,否则直接checkout 目标分支。

  1. protected boolean shouldPull(Git git) throws GitAPIException {
  2. boolean shouldPull;
  3. if (this.refreshRate > 0 && System.currentTimeMillis() -
  4. this.lastRefresh < (this.refreshRate * 1000)) {
  5. return false;
  6. }
  7. Status gitStatus = git.status().call();
  8. boolean isWorkingTreeClean = gitStatus.isClean();
  9. String originUrl = git.getRepository().getConfig()
  10. .getString("remote", "origin", "url");
  11. if (this.forcePull && !isWorkingTreeClean) {
  12. shouldPull = true;
  13. logDirty(gitStatus);
  14. }
  15. else {
  16. shouldPull = isWorkingTreeClean && originUrl != null;
  17. }
  18. if (!isWorkingTreeClean && !this.forcePull) {
  19. this.logger.info("Cannot pull from remote " + originUrl
  20. + ", the working tree is not clean.");
  21. }
  22. return shouldPull;
  23. }

操作 git 依赖的 jgit 客户端;

  1. <dependency>
  2. <groupId>org.eclipse.jgit</groupId>
  3. <artifactId>org.eclipse.jgit</artifactId>
  4. </dependency>

参考:
1.官网链接
2.博客链接
3.原理