ZooKeeper for Java / Spring Config?

是否有任何记录良好的Apache ZooKeeper用例用于分发Java应用程序的配置,特别是Spring服务?

像许多云服务用户一样,我需要更改可变数量的Java服务的配置,最好是在运行时,而不需要重新启动服务。

UPDATE

最后,我最终编写了一些将ZooKeeper节点作为属性文件加载的东西,并创建了一个ResourcePropertySource并将其插入到Spring上下文中。 请注意,这不会反映上下文启动后ZooKeeper节点的更改。

 public class ZooKeeperPropertiesApplicationContextInitializer implements ApplicationContextInitializer { private static final Logger logger = LoggerFactory.getLogger(ZooKeeperPropertiesApplicationContextInitializer.class); private final CuratorFramework curator; private String projectName; private String projectVersion; public ZooKeeperPropertiesApplicationContextInitializer() throws IOException { logger.trace("Attempting to construct CuratorFramework instance"); RetryPolicy retryPolicy = new ExponentialBackoffRetry(10, 100); curator = CuratorFrameworkFactory.newClient("zookeeper", retryPolicy); curator.start(); } /** * Add a primary property source to the application context, populated from * a pre-existing ZooKeeper node. */ @Override public void initialize(ConfigurableApplicationContext applicationContext) { logger.trace("Attempting to add ZooKeeper-derived properties to ApplicationContext PropertySources"); try { populateProjectProperties(); Properties properties = populatePropertiesFromZooKeeper(); PropertiesPropertySource propertySource = new PropertiesPropertySource("zookeeper", properties); applicationContext.getEnvironment().getPropertySources().addFirst(propertySource); logger.debug("Added ZooKeeper-derived properties to ApplicationContext PropertySources"); curator.close(); } catch (IOException e) { logger.error("IO error attempting to load properties from ZooKeeper", e); throw new IllegalStateException("Could not load ZooKeeper configuration"); } catch (Exception e) { logger.error("IO error attempting to load properties from ZooKeeper", e); throw new IllegalStateException("Could not load ZooKeeper configuration"); } finally { if (curator != null && curator.isStarted()) { curator.close(); } } } /** * Populate the Maven artifact name and version from a property file that * should be on the classpath, with values entered via Maven filtering. * * There is a way of doing these with manifests, but it's a right faff when * creating shaded uber-jars. * * @throws IOException */ private void populateProjectProperties() throws IOException { logger.trace("Attempting to get project name and version from properties file"); try { ResourcePropertySource projectProps = new ResourcePropertySource("project.properties"); this.projectName = (String) projectProps.getProperty("project.name"); this.projectVersion = (String) projectProps.getProperty("project.version"); } catch (IOException e) { logger.error("IO error trying to find project name and version, in order to get properties from ZooKeeper"); } } /** * Do the actual loading of properties. * * @return * @throws Exception * @throws IOException */ private Properties populatePropertiesFromZooKeeper() throws Exception, IOException { logger.debug("Attempting to get properties from ZooKeeper"); try { byte[] bytes = curator.getData().forPath("/distributed-config/" + projectName + "/" + projectVersion); InputStream in = new ByteArrayInputStream(bytes); Properties properties = new Properties(); properties.load(in); return properties; } catch (NoNodeException e) { logger.error("Could not load application configuration from ZooKeeper as no node existed for project [{}]:[{}]", projectName, projectVersion); throw e; } } } 

我上周在James Strachen的Apache Camel演讲中提到他使用ZooKeeper作为配置信息源的云端基于Java的服务器 。

我看过Adrian Colyer(SpringSource的首席技术官)关于Spring中运行时配置更改的演讲,但今天Spring支持这个吗?

在我看来,如果你从一个典型的架构Spring应用程序开始,我不认为你有一个简单的工作改进动态配置更改。

您应该考虑Spring Cloud Config:

http://projects.spring.io/spring-cloud/

Spring Cloud Config由git存储库支持的集中式外部配置管理。 配置资源直接映射到Spring Environment但如果需要,可以由非Spring应用程序使用。

源代码在这里:

https://github.com/spring-cloud/spring-cloud-config

此处的示例应用:

https://github.com/spring-cloud/spring-cloud-config/blob/master/spring-cloud-config-sample/src/main/java/sample/Application.java

特别是spring,但对于java一般来说,有一个分布式OSGI标准的CXF实现,它使用ZooKeeper作为发现服务器将更新的bundle推送到容器: http : //cxf.apache.org/dosgi-discovery.html 。

我在github创建了一组spring beans integration zookeeper和springframework作为propertyplaceholderconfigurer : https : //github.com/james-wu-shanghai/spring-zookeeper.git你可以看看。

使用Curator API在分布式应用程序中进行配置管理时,Zookeeper可以很好地利用更高的抽象。 要开始,请按照以下两个步骤操作。

 STEP 1 : Start zookeper server and then start zookeeper cli and create some znodes. Znodes are nothing but UNIX like files which contain values, and name of files depict property name. To create/fetch/update properties use these commands on zookeeper cli. create /system/dev/example/port 9091 get /system/dev/example/port set /system/dev/example/port 9092 

要在java程序中获取这些属性,请参阅此代码段。

 import java.util.HashMap; import java.util.Map; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; public class App { public static void main( String[] args ) throws Exception { final String ZK = "localhost:2181"; final Map data = new HashMap(); CuratorFramework client = CuratorFrameworkFactory.newClient(ZK, new ExponentialBackoffRetry(100, 3)); client.start(); System.out.println(new String(client.getData().forPath("/system/dev/example/port"))); } } 

在找到使用FactoryBean填充常规PropertyPlaceholderConfigurer的建议后,我构建了这个:

 package fms; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.AbstractFactoryBean; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Properties; public class ZkPropertiesFactoryBean extends AbstractFactoryBean implements Watcher { private Logger LOGGER = LoggerFactory.getLogger(ZkPropertiesFactoryBean.class); private String zkConnect; private String path; private int timeout = 1000; @Override protected Properties createInstance() throws Exception { long start = System.currentTimeMillis(); Properties p = new Properties(); p.load(new ByteArrayInputStream(loadFromZk())); double duration = (System.currentTimeMillis() - start)/1000d; LOGGER.info(String.format("Loaded %d properties from %s:%s in %2.3f sec", p.size(), zkConnect, path, duration)); return p; } @Override public Class getObjectType() { return Properties.class; } private byte[] loadFromZk() throws IOException, KeeperException, InterruptedException {Stat stat = new Stat(); ZooKeeper zk = new ZooKeeper(zkConnect, timeout, this); return zk.getData(path, false, stat); } @Override public void process(WatchedEvent event) {} public void setPath(String path) {this.path = path;} public void setZkConnect(String zkConnect) {this.zkConnect = zkConnect;} } 

spring-config.xml您可以按如下方式创建bean: