上一篇(基于zookeeper实现分布式配置中心(一))讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性,简单实现一个分布式配置中心。

配置中心的优势

1、各环境配置集中管理。

2、配置更改,实时推送,jvm环境变量及时生效。

3、依靠配置变更,动态扩展功能,减少二次上线带来的成本。

4、减少开发人员、运维人员修改配置带来的额外开销。

配置中心架构图

  

配置中心功能

1、配置管理平台中,操作人员可以创建项目所属系统、应用名称、实例名称、配置分组等信息。

2、配置管理平台中,操作人员可以上传配置文件,对属性有增、删、改、查的操作。

3、配置内容通过配置管理平台后台服务进行持久化(保存到数据库中)。

4、操作人员通过配置平台进行推送操作,将配置推送到zk集群相应结点(/cfgcenter/系统名称/应用名称/实例名称/分组名称)。

5、配置中心客户端监听zk集群中对应结点数据发生变化,读取变更后的内容,解析内容,刷新本地备份(分布式容灾)和Spring环境变量。

6、配置中心客户端如果和zk集群丢失连接,将加载本地本分配置到Spring环境变量。

7、配置中心客户端重新和zk集群建立连接,从zk集群中拉取最新配置内容,解析配置,刷新本地备份(分布式容灾)和Spring环境变量。

8、配置中心客户端将Spring环境变量刷新之后,动态刷新依赖配置中心配置的bean。

配置中心代码视图  

  

配置中心客户端设计解析

配置中心客户端初始化

  1. @Component
  2. public class CfgcenterInit implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, ApplicationListener<ApplicationEvent> {
  3.  
  4. private static Logger LOGGER = LoggerFactory.getLogger(CfgcenterInit.class);
  5.  
  6. @Override
  7. public void onApplicationEvent(ApplicationEvent event) {
  8. if (event instanceof ContextRefreshedEvent) {
  9. LOGGER.info("初始化配置中心客户端监听器...");
  10. ZKClient.getInstance()
  11. .init();
  12. } else if (event instanceof RefreshEvent) {
  13. ZKClient.getInstance()
  14. .getAeb()
  15. .post(event);
  16. } else if (event instanceof ContextClosedEvent) {
  17. if (null != ZKClient.getInstance().getCw()) {
  18. ZKClient.getInstance()
  19. .getCw()
  20. .close();
  21. }
  22. }
  23. }
  24.  
  25. @Override
  26. public void initialize(ConfigurableWebApplicationContext cac) {
  27. try {
  28. ZookeeperProperties zookeeperProperties = ConfigurationBinder
  29. .withPropertySources(cac.getEnvironment())
  30. .bind(ZookeeperProperties.class);
  31. if (!zookeeperProperties.isEnabled()) {
  32. LOGGER.info("未开启配置中心客戶端...");
  33. return;
  34. }
  35. ZKClient.getInstance()
  36. .binding(
  37. zookeeperProperties
  38. , new ZookeeperConfigProperties()
  39. , cac
  40. );
  41. } catch (Exception e) {
  42. LOGGER.error("配置中心客户端初始化异常...", e);
  43. }
  44. }
  45. }

1、ApplicationContextInitializer#initialize方法中,获取zk连接信息配置,如果开启配置中心客户端,将ZookeeperProperties(zk集群连接信息)、ZookeeperConfigProperties(客户端监听zk集群结点信息)、ConfigurableWebApplicationContext (应用上下文)绑定到ZKClient实例中去。

2、ApplicationListener#onApplicationEvent方法中监听ContextRefreshedEvent(初始化配置中心客户端监听器)、RefreshEvent(配置刷新事件,通过guava的事件总线进行推送)、ContextClosedEvent(关闭配置中心客户端资源)。

配置中心客户端监听器

  1. public class ConfigWatcher implements Closeable, TreeCacheListener {
  2.  
  3. private static Logger LOGGER = LoggerFactory.getLogger(ConfigWatcher.class);
  4.  
  5. private AtomicBoolean running = new AtomicBoolean(false);
  6. private String context;
  7. private CuratorFramework source;
  8. private HashMap<String, TreeCache> caches;
  9.  
  10. public ConfigWatcher(String context, CuratorFramework source) {
  11. this.context = context;
  12. this.source = source;
  13. }
  14.  
  15. public void start() {
  16. if (this.running.compareAndSet(false, true)) {
  17. this.caches = new HashMap<>();
  18. if (!context.startsWith("/")) {
  19. context = "/" + context;
  20. }
  21. try {
  22. TreeCache cache = TreeCache.newBuilder(this.source, context).build();
  23. cache.getListenable().addListener(this);
  24. cache.start();
  25. this.caches.put(context, cache);
  26. // no race condition since ZookeeperAutoConfiguration.curatorFramework
  27. // calls curator.blockUntilConnected
  28. } catch (KeeperException.NoNodeException e) {
  29. // no node, ignore
  30. } catch (Exception e) {
  31. LOGGER.error("Error initializing listener for context " + context, e);
  32. }
  33. }
  34. }
  35.  
  36. @Override
  37. public void close() {
  38. if (this.running.compareAndSet(true, false)) {
  39. for (TreeCache cache : this.caches.values()) {
  40. cache.close();
  41. }
  42. this.caches = null;
  43. }
  44. }
  45.  
  46. @Override
  47. public void childEvent(CuratorFramework client, TreeCacheEvent event) {
  48. TreeCacheEvent.Type eventType = event.getType();
  49. switch (eventType) {
  50. case INITIALIZED:
  51. LOGGER.info("配置中心客户端同步服务端状态完成...");
  52. refreshEnvAndBeans(event);
  53. break;
  54. case NODE_REMOVED:
  55. case NODE_UPDATED:
  56. refreshEnvAndBeans(event);
  57. break;
  58. case CONNECTION_SUSPENDED:
  59. case CONNECTION_LOST:
  60. LOGGER.info("配置中心客户端与服务端连接异常...");
  61. break;
  62. case CONNECTION_RECONNECTED:
  63. LOGGER.info("配置中心客户端与服务端重新建立连接...");
  64. break;
  65. }
  66. }
  67.  
  68. private void refreshEnvAndBeans(TreeCacheEvent event) {
  69. //刷新环境变量
  70. ZKClient.getInstance()
  71. .refreshEnvironment();
  72. //刷新Bean
  73. ZKClient.getInstance()
  74. .getAep()
  75. .publishEvent(
  76. new RefreshEvent(this, event, getEventDesc(event))
  77. );
  78. }
  79.  
  80. private String getEventDesc(TreeCacheEvent event) {
  81. StringBuilder out = new StringBuilder();
  82. out.append("type=").append(event.getType());
  83. TreeCacheEvent.Type eventType = event.getType();
  84. if (eventType == NODE_UPDATED
  85. || eventType == NODE_REMOVED) {
  86. out.append(", path=").append(event.getData().getPath());
  87. byte[] data = event.getData().getData();
  88. if (data != null) {
  89. out.append(", data=").append(new String(data, StandardCharsets.UTF_8));
  90. }
  91. }
  92. return out.toString();
  93. }
  94. }

1、通过TreeCache监听路径/cfgcenter/系统名称/应用名称/实例名称/分组名称(该路径下可能会存在多个子节点,每个子节点对应一份配置,每一份配置大小不能超过64k)。

2、TreeCache监听事件类型如下

  • INITIALIZED(完成同步服务端状态,同步状态【NODE_REMOVED、 NODE_UPDATED、CONNECTION_RECONNECTED】之后触发)
  • NODE_REMOVED(结点移除触发)
  • NODE_UPDATED(结点数据更新触发)
  • CONNECTION_SUSPENDED(连接丢失触发)
  • CONNECTION_LOST(完全丢失连接触发)
  • CONNECTION_RECONNECTED(重新连接触发)

3、监听到INITIALIZED、NODE_UPDATED、NODE_REMOVED事件之后,执行refreshEnvAndBeans方法,刷新spring环境变量,同时刷新spring容器相关的Bean。

配置中心客户端刷新spring环境变量

  1. public class ZookeeperPropertySourceLocator {
  2.  
  3. public static final String ZOOKEEPER_PREPERTY_SOURCE_NAME = "cfg-zookeeper";
  4.  
  5. private ZookeeperConfigProperties properties;
  6.  
  7. private CuratorFramework curator;
  8.  
  9. private static Logger LOGGER = LoggerFactory.getLogger(ZookeeperPropertySourceLocator.class);
  10.  
  11. public ZookeeperPropertySourceLocator(CuratorFramework curator, ZookeeperConfigProperties properties) {
  12. this.curator = curator;
  13. this.properties = properties;
  14. }
  15.  
  16. public String getContext() {
  17. return this.properties.getContext();
  18. }
  19.  
  20. public PropertySource getCfgcenterPropertySource(Environment environment) {
  21. ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
  22. return env.getPropertySources().get(ZOOKEEPER_PREPERTY_SOURCE_NAME);
  23. }
  24.  
  25. public void locate(Environment environment) {
  26. if (environment instanceof ConfigurableEnvironment) {
  27. ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
  28. String context = properties.getContext();
  29. CompositePropertySource composite = new CompositePropertySource(ZOOKEEPER_PREPERTY_SOURCE_NAME);
  30. try {
  31. PropertySource propertySource = create(context);
  32. composite.addPropertySource(propertySource);
  33. if (null != env.getPropertySources().get(ZOOKEEPER_PREPERTY_SOURCE_NAME)) {
  34. LOGGER.info("替换PropertySource: " + ZOOKEEPER_PREPERTY_SOURCE_NAME);
  35. env.getPropertySources().replace(ZOOKEEPER_PREPERTY_SOURCE_NAME, composite);
  36. } else {
  37. LOGGER.info("添加PropertySource: " + ZOOKEEPER_PREPERTY_SOURCE_NAME);
  38. env.getPropertySources().addFirst(composite);
  39. }
  40. } catch (Exception e) {
  41. if (this.properties.isFailFast()) {
  42. ReflectionUtils.rethrowRuntimeException(e);
  43. } else {
  44. LOGGER.error("Unable to load zookeeper config from " + context, e);
  45. }
  46. }
  47. }
  48. }
  49.  
  50. @PreDestroy
  51. public void destroy() {
  52. }
  53.  
  54. private void backupZookeeperPropertySource(ZookeeperPropertySource zps) {
  55. String backupDir = BASE_BACKUP_DIR + this.properties.getContext();
  56. String backupFile = String.format("%s/%s", backupDir, APP_NAME + ".properties");
  57. File bakFile = new File(backupFile);
  58. StringBuilder data = new StringBuilder();
  59. for (String propertyName : zps.getPropertyNames()) {
  60. data.append(propertyName)
  61. .append("=")
  62. .append(zps.getProperty(propertyName))
  63. .append(System.lineSeparator());
  64. }
  65. try {
  66. FileUtils.writeStringToFile(bakFile, data.toString(), Charsets.UTF_8);
  67. LOGGER.info("配置中心客户端刷新本地备份完成, path: " + backupDir);
  68. } catch (IOException e) {
  69. LOGGER.error("配置中心客户端刷新本地备份异常..., path: " + backupDir, e);
  70. }
  71. }
  72.  
  73. private PropertySource<CuratorFramework> create(String context) {
  74. ZookeeperPropertySource zps;
  75. if (ZKClient.getInstance().isConnected()) {
  76. zps = new ZookeeperPropertySource(context, this.curator, false);
  77. this.backupZookeeperPropertySource(zps);
  78. } else {
  79. zps = new ZookeeperPropertySource(context, this.curator, true);
  80. }
  81. return zps;
  82. }
  83. }

ZookeeperPropertySourceLocator会创建ZookeeperPropertySource,然后放入Spring的Environment变量中。如果配置中心客户端和zk集群处于连接状态,加载完ZookeeperPropertySource之后,备份到本地。

  1. public class ZookeeperPropertySource extends AbstractZookeeperPropertySource {
  2.  
  3. private static Logger LOGGER = LoggerFactory.getLogger(ZookeeperPropertySource.class);
  4.  
  5. private Map<String, String> properties = new LinkedHashMap<>();
  6.  
  7. public ZookeeperPropertySource(String context, CuratorFramework source, boolean backup) {
  8. super(context, source);
  9.  
  10. //加载本地配置
  11. if (backup) {
  12. String backupDir = BASE_BACKUP_DIR + this.getContext();
  13. String backupFile = String.format("%s/%s", backupDir, APP_NAME + ".properties");
  14. try {
  15. InputStream is = FileUtils.openInputStream(new File(backupFile));
  16. InputStreamReader isr = new InputStreamReader(is);
  17. Properties properties = new Properties();
  18. properties.load(isr);
  19. properties.forEach((k, v) -> this.properties.put((String) k, (String) v));
  20. } catch (Exception e) {
  21. LOGGER.error("配置中心客户端本地配置加载异常...", e);
  22. }
  23.  
  24. }
  25. //加载远程配置
  26. else {
  27. findProperties(this.getContext(), null);
  28. }
  29. }
  30.  
  31. @Override
  32. public Object getProperty(String name) {
  33. return this.properties.get(name);
  34. }
  35.  
  36. private byte[] getPropertyBytes(String fullPath) {
  37. try {
  38. byte[] bytes = null;
  39. try {
  40. bytes = this.getSource().getData().forPath(fullPath);
  41. } catch (KeeperException e) {
  42. if (e.code() != KeeperException.Code.NONODE) {
  43. throw e;
  44. }
  45. }
  46. return bytes;
  47. } catch (Exception exception) {
  48. ReflectionUtils.rethrowRuntimeException(exception);
  49. }
  50. return null;
  51. }
  52.  
  53. @Override
  54. public String[] getPropertyNames() {
  55. Set<String> strings = this.properties.keySet();
  56. return strings.toArray(new String[strings.size()]);
  57. }
  58.  
  59. private void findProperties(String path, List<String> children) {
  60. try {
  61. LOGGER.info("entering findProperties for path: " + path);
  62. if (children == null) {
  63. children = getChildren(path);
  64. }
  65. if (children == null || children.isEmpty()) {
  66. return;
  67. }
  68. for (String child : children) {
  69. String childPath = path + "/" + child;
  70. List<String> childPathChildren = getChildren(childPath);
  71.  
  72. byte[] bytes = getPropertyBytes(childPath);
  73. if (!ArrayUtils.isEmpty(bytes)) {
  74. registerKeyValue(childPath, new String(bytes, Charset.forName("UTF-8")));
  75. }
  76. // Check children even if we have found a value for the current znode
  77. findProperties(childPath, childPathChildren);
  78. }
  79. LOGGER.info("leaving findProperties for path: " + path);
  80. } catch (Exception exception) {
  81. ReflectionUtils.rethrowRuntimeException(exception);
  82. }
  83. }
  84.  
  85. private void registerKeyValue(String path, String value) {
  86. String key = sanitizeKey(path);
  87. LOGGER.info(String.format("配置中心客户端解析配置节点(%s),数据:%s", key, value));
  88. try {
  89. Properties properties = new Properties();
  90. properties.load(new StringReader(value));
  91. properties.forEach((k, v) -> this.properties.put((String) k, (String) v));
  92. } catch (IOException e) {
  93. LOGGER.info(String.format("配置中心客户端解析配置节点(%s)异常...", key));
  94. }
  95. }
  96.  
  97. private List<String> getChildren(String path) throws Exception {
  98. List<String> children = null;
  99. try {
  100. children = this.getSource().getChildren().forPath(path);
  101. } catch (KeeperException e) {
  102. if (e.code() != KeeperException.Code.NONODE) {
  103. throw e;
  104. }
  105. }
  106. return children;
  107. }
  108.  
  109. }

ZookeeperPropertySource通过构造参数backup来判断是加载zk集群中的配置还是本地备份配置。

配置中心客户端刷新Spring容器Bean

  1. public abstract class BaseCfgcenterBean implements InitializingBean {
  2.  
  3. private static Logger LOGGER = LoggerFactory.getLogger(BaseCfgcenterBean.class);
  4.  
  5. @PostConstruct
  6. public void init() {
  7. //注册到时间总线中
  8. ZKClient.getInstance()
  9. .getAeb()
  10. .register(this);
  11. }
  12.  
  13. /**
  14. * z
  15. * 绑定自身目标
  16. **/
  17. protected void doBind() {
  18. Class<? extends BaseCfgcenterBean> clazz = this.getClass();
  19. if (org.springframework.util.ClassUtils.isCglibProxy(this)) {
  20. clazz = (Class<? extends BaseCfgcenterBean>) AopUtils.getTargetClass(this);
  21. }
  22. BaseCfgcenterBean target = binding(clazz, this.getDefaultResourcePath());
  23. this.copyProperties(target);
  24. }
  25.  
  26. private void copyProperties(BaseCfgcenterBean target) {
  27. ReflectionUtils.doWithFields(this.getClass(), field -> {
  28. field.setAccessible(true);
  29. field.set(this, field.get(target));
  30. }, field -> AnnotatedElementUtils.isAnnotated(field, ConfigField.class));
  31. }
  32.  
  33. /**
  34. * 绑定其他目标
  35. *
  36. * @param clazz 目标类
  37. **/
  38. protected <T> T doBind(Class<T> clazz) {
  39. T target = binding(clazz, this.getDefaultResourcePath());
  40. if (target instanceof InitializingBean) {
  41. try {
  42. ((InitializingBean) target).afterPropertiesSet();
  43. } catch (Exception e) {
  44. LOGGER.error(String.format("属性初始化失败[afterPropertiesSet], class=%s", ClassUtils.getSimpleName(clazz), e));
  45. }
  46. }
  47. return target;
  48. }
  49.  
  50. private <T> T binding(Class<T> clazz, String defaultResourcePath) {
  51. Optional<PropertySource> propertySource = Optional.empty();
  52.  
  53. //加载配置中心配置
  54. if (ZKClient.getInstance().isZkInit()) {
  55. propertySource = Optional.ofNullable(
  56. ZKClient.getInstance()
  57. .resolvePropertySource()
  58. );
  59. }
  60. //加载本地配置
  61. else {
  62. Optional<ResourcePropertySource> resourcePropertySource = ResourceUtils.getResourcePropertySource(defaultResourcePath);
  63. if (resourcePropertySource.isPresent()) {
  64. propertySource = Optional.ofNullable(resourcePropertySource.get());
  65. }
  66. }
  67. if (propertySource.isPresent()) {
  68. T target;
  69. try {
  70. target = ConfigurationBinder
  71. .withPropertySources(propertySource.get())
  72. .bind(clazz);
  73. } catch (Exception e) {
  74. LOGGER.error(String.format("属性绑定失败, class=%s", ClassUtils.getSimpleName(clazz)), e);
  75. return null;
  76. }
  77. return target;
  78. }
  79. return null;
  80. }
  81.  
  82. @Override
  83. public void afterPropertiesSet() {
  84. Class<?> target = this.getClass();
  85. if (AopUtils.isAopProxy(this)) {
  86. target = AopUtils.getTargetClass(this);
  87. }
  88. LOGGER.info(String.format("%s->%s模块引入配置中心%s..."
  89. , this.getModuleName()
  90. , ClassUtils.getSimpleName(target)
  91. , (ZKClient.getInstance()
  92. .isConnected() ? "生效" : "无效")
  93. ));
  94. }
  95.  
  96. public String getModuleName() {
  97. return StringUtils.EMPTY;
  98. }
  99.  
  100. @Subscribe
  101. public void listenRefreshEvent(RefreshEvent refreshEvent) {
  102. this.afterPropertiesSet();
  103. LOGGER.info(refreshEvent.getEventDesc());
  104. this.refresh();
  105. }
  106.  
  107. //通过事件进行刷新
  108. protected void refresh() {
  109. this.doBind();
  110. }
  111.  
  112. //获取本地配置默认路径
  113. protected abstract String getDefaultResourcePath();
  114. }

1、对象自身实现guava事件总线监听,监听RefreshEvent事件,触发对象属性刷新操作。

2、对象初始化时,注册自身目标到guava的事件总线对象中。

3、对象属性刷新,获取到PropertySource对象(配置中心配置或者项目自身静态配置),通过ConfigurationBinder工具类将配置重新绑定的对象属性。

配置管理平台接口

  1. @RestController
  2. @RequestMapping("cfg")
  3. public class CfgController {
  4. private static Logger LOGGER = LoggerFactory.getLogger(CfgController.class);
  5.  
  6. private static final String ZK_PATH_PATTERN0 = "/wmhcfg/projects/%s/%s";
  7. private static final String ZK_PATH_PATTERN1 = ZK_PATH_PATTERN0 + "/%s";
  8. private static final String ZK_PATH_PATTERN = ZK_PATH_PATTERN1 + "/%s";
  9.  
  10. @Autowired
  11. private CfgMapper mapper;
  12.  
  13. @GetMapping(value = "/search", produces = MediaType.TEXT_PLAIN_VALUE)
  14. public String findCfgContents(@RequestBody @Validated SearchVO searchVO
  15. , @RequestParam(required = false) String cfgId) {
  16. List<CfgRecord> records = mapper.findRecords(searchVO);
  17. if (CollectionUtils.isEmpty(records)) {
  18. return StringUtils.EMPTY;
  19. }
  20. if (StringUtils.isNotBlank(cfgId)) {
  21. records = records.stream().filter(record -> cfgId.equals(record.getCfgId())).collect(Collectors.toList());
  22. }
  23. StringBuilder response = new StringBuilder();
  24. Properties properties = new Properties();
  25. records.forEach(record -> {
  26. try {
  27. properties.clear();
  28. properties.load(new StringReader(record.getCfgContent()));
  29. properties.forEach((key, value) -> response.append(key)
  30. .append("=")
  31. .append(value)
  32. .append(System.lineSeparator())
  33. .append(System.lineSeparator())
  34. );
  35. } catch (IOException e) {
  36. LOGGER.error("配置解析异常...", e);
  37. }
  38. });
  39. return response.toString();
  40. }
  41.  
  42. @PostMapping(value = "/send/{systemId}/{appId}/{groupId}/{cfgId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  43. public BaseResponse sendCfgContent(@RequestBody String cfgContent
  44. , @PathVariable String systemId
  45. , @PathVariable String appId
  46. , @PathVariable String groupId
  47. , @PathVariable String cfgId) {
  48. BaseResponse baseResponse = new BaseResponse();
  49. baseResponse.setRestStatus(RestStatus.SUCCESS);
  50.  
  51. SearchVO searchVO = new SearchVO();
  52. searchVO.setSystemId(systemId);
  53. searchVO.setAppId(appId);
  54. searchVO.setGroupId(groupId);
  55.  
  56. List<CfgRecord> records = mapper.findRecords(searchVO);
  57. CfgRecord record = null;
  58.  
  59. if (!CollectionUtils.isEmpty(records)) {
  60. for (CfgRecord cfgRecord : records) {
  61. if (cfgId.equals(cfgRecord.getCfgId())) {
  62. record = cfgRecord;
  63. record.setCfgContent(cfgContent);
  64. break;
  65. }
  66. }
  67. }
  68.  
  69. if (null == record) {
  70. record = new CfgRecord();
  71. record.setSystemId(systemId);
  72. record.setAppId(appId);
  73. record.setGroupId(groupId);
  74. record.setCfgId(cfgId);
  75. record.setCfgContent(cfgContent);
  76. }
  77.  
  78. StringBuilder cfgContentSB = new StringBuilder();
  79. Properties properties = new Properties();
  80. try {
  81. properties.load(new StringReader(record.getCfgContent()));
  82. } catch (IOException e) {
  83. LOGGER.error("配置解析异常...", e);
  84. baseResponse.setErrors(e.getMessage());
  85. baseResponse.setRestStatus(RestStatus.FAIL_50001);
  86. return baseResponse;
  87. }
  88. properties.forEach((key, value) -> cfgContentSB.append(key)
  89. .append("=")
  90. .append(value)
  91. .append(System.lineSeparator())
  92. );
  93.  
  94. record.setCfgContent(cfgContentSB.toString());
  95.  
  96. if (null == record.getId()) {
  97. mapper.insertRecord(record);
  98. } else {
  99. mapper.updateRecord(record);
  100. }
  101. return baseResponse;
  102. }
  103.  
  104. @PostMapping(value = "/push", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  105. public BaseResponse pushCfgContent(@RequestBody @Validated PushVO pushVO) {
  106. BaseResponse baseResponse = new BaseResponse();
  107. baseResponse.setRestStatus(RestStatus.SUCCESS);
  108. String path = String.format(ZK_PATH_PATTERN
  109. , pushVO.getSystemId()
  110. , pushVO.getAppId()
  111. , pushVO.getGroupId()
  112. , pushVO.getCfgId()
  113. );
  114.  
  115. try {
  116. SearchVO searchVO = new SearchVO();
  117. searchVO.setSystemId(pushVO.getSystemId());
  118. searchVO.setAppId(pushVO.getAppId());
  119. searchVO.setGroupId(pushVO.getGroupId());
  120.  
  121. List<CfgRecord> records = mapper.findRecords(searchVO);
  122. StringBuilder cfgContent = new StringBuilder();
  123. records.forEach(record -> cfgContent.append(record.getCfgContent()).append(System.lineSeparator()));
  124. if (!ZKHelper.setData(path, cfgContent.toString().getBytes())) {
  125. baseResponse.setRestStatus(RestStatus.FAIL_50001);
  126. }
  127. } catch (Exception e) {
  128. LOGGER.error("配置推送异常...", e);
  129. baseResponse.setRestStatus(RestStatus.FAIL_50001);
  130. baseResponse.setErrors(e.getMessage());
  131. }
  132. return baseResponse;
  133. }
  134.  
  135. @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  136. public BaseResponse createCfg(@RequestBody @Validated PushVO pushVO) {
  137. BaseResponse baseResponse = new BaseResponse();
  138. String path = String.format(ZK_PATH_PATTERN
  139. , pushVO.getSystemId()
  140. , pushVO.getAppId()
  141. , pushVO.getGroupId()
  142. , pushVO.getCfgId()
  143. );
  144. if (ZKHelper.createPath(path)) {
  145. baseResponse.setRestStatus(RestStatus.SUCCESS);
  146. } else {
  147. baseResponse.setRestStatus(RestStatus.FAIL_50001);
  148. }
  149. return baseResponse;
  150. }
  151.  
  152. @PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  153. public BaseResponse deleteCfg(@RequestBody @Validated DeleteVO deleteVO) {
  154. BaseResponse baseResponse = new BaseResponse();
  155. String path;
  156. if (StringUtils.isBlank(deleteVO.getGroupId())) {
  157. path = String.format(ZK_PATH_PATTERN0
  158. , deleteVO.getSystemId()
  159. , deleteVO.getAppId()
  160. );
  161. } else if (StringUtils.isNotBlank(deleteVO.getGroupId()) && StringUtils.isBlank(deleteVO.getCfgId())) {
  162. path = String.format(ZK_PATH_PATTERN1
  163. , deleteVO.getSystemId()
  164. , deleteVO.getAppId()
  165. , deleteVO.getGroupId()
  166. );
  167. } else {
  168. path = String.format(ZK_PATH_PATTERN
  169. , deleteVO.getSystemId()
  170. , deleteVO.getAppId()
  171. , deleteVO.getGroupId()
  172. , deleteVO.getCfgId()
  173. );
  174. }
  175.  
  176. if (ZKHelper.deletePath(path)) {
  177. baseResponse.setRestStatus(RestStatus.SUCCESS);
  178. } else {
  179. baseResponse.setRestStatus(RestStatus.FAIL_50001);
  180. }
  181. return baseResponse;
  182. }
  183.  
  184. @GetMapping(value = "/getdata", produces = MediaType.TEXT_PLAIN_VALUE)
  185. public String getData(@RequestParam String path) {
  186. return ZKHelper.getData(path);
  187. }
  188. }

  为配置管理前端提供配置保存、配置推送、配置删除等操作。

配置中心测试

  1. @Component
  2. @ConfigurationProperties(prefix = "cfg.test")
  3. public class TestCfgcenterBean extends BaseCfgcenterBean {
  4.  
  5. @ConfigField
  6. private String text;
  7.  
  8. @ConfigField
  9. private Map<String, List<String>> map;
  10.  
  11. public String getText() {
  12. return text;
  13. }
  14.  
  15. public void setText(String text) {
  16. this.text = text;
  17. }
  18.  
  19. public Map<String, List<String>> getMap() {
  20. return map;
  21. }
  22.  
  23. public void setMap(Map<String, List<String>> map) {
  24. this.map = map;
  25. }
  26.  
  27. @Override
  28. protected String getDefaultResourcePath() {
  29. return StringUtils.EMPTY;
  30. }
  31.  
  32. @Override
  33. protected void refresh() {
  34. super.refresh();
  35. System.out.println("text=" + this.text);
  36. System.out.println("map=" + JSON.toJSONString(map));
  37. }
  38. }

TestCfgcenterBean继承BaseCfgcenterBean,配置中心配置变更后可以自动将新的配置绑定到对象上。

  1. @SpringBootApplication(exclude = RedissonAutoConfiguration.class)
  2. @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
  3. @EnableRetry
  4. public class SpringbootApplication {
  5.  
  6. public static void main(String[] args) {
  7. System.setProperty("xxx.system.id", "test_system");
  8. System.setProperty("xxx.app.id", "test_app");
  9. System.setProperty("groupenv", "x");
  10. SpringApplication.run(SpringbootApplication.class, args);
  11. }
  12.  
  13. }

启动类设置配置中心客户端需要的环境变量:系统标识、项目标识、分组标识。

客户端与zk第一建立连接,同步完状态之后,触发INITIALIZED事件,刷新bean属性配置。

客户端与zk断开重连之后,同步完状态后触发INITIALIZED事件,刷新bean属性配置。

需要源码

请关注订阅号,回复:cfgcenter, 便可查看。

基于zookeeper实现分布式配置中心(二)的更多相关文章

  1. 基于zookeeper实现分布式配置中心(一)

    最近在学习zookeeper,发现zk真的是一个优秀的中间件.在分布式环境下,可以高效解决数据管理问题.在学习的过程中,要深入zk的工作原理,并根据其特性做一些简单的分布式环境下数据管理工具.本文首先 ...

  2. Zookeeper笔记之基于zk的分布式配置中心

    一.场景 & 需求 集群上有很多个节点运行同一个任务,这个任务会有一些可能经常改变的配置参数,要求是当配置参数改变之后能够很快地同步到每个节点上,如果将这些配置参数放在本地文件中则每次都要修改 ...

  3. 基于ZK构建统一配置中心的方案和实践

    背景: 近期使用Zk实现了一个简单的配置管理的小东西,在此开源出来,有兴趣的希望提出您的宝贵意见.如果恰巧您也使用或者接触过类似的东西, 也希望您可以分享下您觉得现在这个项目可以优化和改进的地方. 项 ...

  4. SrpingCloud 之SrpingCloud config分布式配置中心

    Config架构 当一个系统中的配置文件发生改变的时候,我们需要重新启动该服务,才能使得新的配置文件生效,spring cloud config可以实现微服务中的所有系统的配置文件的统一管理,而且还可 ...

  5. spring cloud 入门系列七:基于Git存储的分布式配置中心

    我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...

  6. spring cloud 入门系列七:基于Git存储的分布式配置中心--Spring Cloud Config

    我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...

  7. 基于winserver的Apollo配置中心分布式&集群部署实践(正确部署姿势)

    基于winserver的Apollo配置中心分布式&集群部署实践(正确部署姿势)   前言 前几天对Apollo配置中心的demo进行一个部署试用,现公司已决定使用,这两天进行分布式部署的时候 ...

  8. SpringCloud搭建分布式配置中心(基于git)

    1.简介 Spring Cloud Config.它用来为分布式系统中的基础设施和微服务提供集中化的外部配置支持,分为服务端和客户端两个部分. 其中服务端也称为分布式配置中心,他是独立的微服务应用,用 ...

  9. 分布式配置中心 携程(apollo)

    1.传统配置文件与分布式配置文件区别 传统配置文件:如果修改了配置文件,需要重新打包发布,重新发布服务,而且每个环境的变更配置文件,比较繁琐. 分布式配置文件:将配置文件注册到配置中心上去,可以使用分 ...

随机推荐

  1. Android面试精华

    SIM卡的EF文件有什么作用? SIM卡里的全部文件按树来组织: 主文件MF(Master File)--主文件仅仅有文件头,里面存放着整个SIM卡的控制和管理信息 专用文件DF(Dedicated ...

  2. 自动关闭Messbox

    /// <summary> /// 自动关闭Messbox /// </summary> public class MessageBoxAutoClose { System.T ...

  3. PostgreSQL Replication之第四章 设置异步复制(5)

    4.5 使流复制更健壮 当连接到master时,slave要做的第一件事情是赶上master.但是,这会一直工作吗?我们已经看到,我们可以使用由基于流和基于文件组成的混合设置.这给了我们一些额外的安全 ...

  4. java 8 , merge()

    import java.util.HashMap; import java.util.Map; public class j8merge { public static void main(Strin ...

  5. vue 实现 点击取消监控内容是否发生修改 若修改提示 是否需要保存

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. codeforces 501 B Misha and Changing Handles 【map】

    题意:给出n个名字变化,问一个名字最后变成了什么名字 先用map顺着做的,后来不对, 发现别人是将变化后的那个名字当成键值来做的,最后输出的时候先输出second,再输出first 写一下样例就好理解 ...

  7. python-生成器即send()用法

    参考链接: http://www.mamicode.com/info-detail-2399245.html 作者首先介绍了生成器的作用:是为了让程序员可以更简单的编写用来产生值的序列的代码,然后又介 ...

  8. PatentTips - Posting interrupts to virtual processors

    BACKGROUND Generally, the concept of virtualization in information processing systems allows multipl ...

  9. Memcache启动&amp;存储原理&amp;集群

    一. windows下安装启动 首先将memcache的bin文件夹增加到Path环境变量中.方便后面使用命令: 然后运行 memcached –dinstall 命令安装memcache的服务: 然 ...

  10. linux中的硬连接和软连接

    linux中的硬连接和软连接 linux中的硬连接和软连接 背景 连接 硬连接 软连接 example reference 背景 linux中的文件主要分3块, - 真正的数据 - 索引节点号(ino ...