用于 Java/Spring 配置的 ZooKeeper?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9940476/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 22:59:05  来源:igfitidea点击:

ZooKeeper for Java/Spring Config?

javaspringserviceconfigurationapache-zookeeper

提问by EngineerBetter_DJ

Are there any well documented use cases of Apache ZooKeeper being used to distribute configuration of Java applications, and in particular Spring services?

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

Like many users of cloud services I have a requirement to change the configuration of a variable amount of Java services, preferably at run-time without needing to restart the services.

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

UPDATE

更新

Eventually I ended up writing something that would load a ZooKeeper node as a properties file, and create a ResourcePropertySourceand insert it into a Spring context. Note that this will not reflect changes in the ZooKeeper node after the context has started.

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

public class ZooKeeperPropertiesApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    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;
        }

    }

}

采纳答案by SteveD

I was at an Apache Camel talk from James Strachen last week and he mentioned using ZooKeeper under the covers for their Java-based server in the cloudas the source of configuration info.

上周我参加了 James Strachen 的 Apache Camel 演讲,他提到在他们的云中基于 Java 的服务器的幕后使用 ZooKeeper作为配置信息的来源。

I've seen a talk from Adrian Colyer (CTO of SpringSource) about runtime config change in Spring, but does Spring support this today?

我看过 Adrian Colyer(SpringSource 的 CTO)关于 Spring 中运行时配置更改的演讲,但 Spring 今天是否支持这一点?

In my opinion, if you're starting from a typically architected Spring application, I don't see you having an easy job retrofitting dynamic config changes on top of it.

在我看来,如果您从一个典型的 Spring 应用程序架构开始,我认为在它之上改造动态配置更改并不容易。

回答by btiernay

You should consider Spring Cloud Config:

您应该考虑 Spring Cloud Config:

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

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

Spring Cloud Config Centralized external configuration management backed by a git repository. The configuration resources map directly to Spring Environmentbut could be used by non-Spring applications if desired.

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

Source code available here:

源代码可在此处获得:

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

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

Sample application here:

示例应用程序在这里:

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

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

回答by james_wu_shanghai

I created a set of spring beans integration zookeeper and springframework as propertyplaceholderconfigurer, in github: https://github.com/james-wu-shanghai/spring-zookeeper.gityou can take a look.

我创建了一套spring beans集成zookeeper和springframework as propertyplaceholderconfigurer,在githubhttps: //github.com/james-wu-shanghai/spring-zookeeper.git你可以看看。

回答by Gaurav Khare

Zookeeper can be very nicely leveraged with higher abstraction using Curator APIs for configuration management in distributed applications. To get started just follow these two steps.

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

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

To fetch these properties in java program refer this code snippet.

要在 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<String, String> data = new HashMap<String, String>();         
         CuratorFramework client = CuratorFrameworkFactory.newClient(ZK, new ExponentialBackoffRetry(100, 3)); 
         client.start();
         System.out.println(new String(client.getData().forPath("/system/dev/example/port")));
       }  
}

回答by manku

Not spring in particular but for java generally, there is a CXF implementation of the distributed OSGI standard that uses ZooKeeper as the discovery server to push updated bundles down to the container : http://cxf.apache.org/dosgi-discovery.html.

不是特别适用于 spring,而是对于 Java 而言,分布式 OSGI 标准有一个 CXF 实现,它使用 ZooKeeper 作为发现服务器将更新的包推送到容器:http: //cxf.apache.org/dosgi-discovery.html.

回答by magiconair

After finding a suggestion to use a FactoryBeanto populate a regular PropertyPlaceholderConfigurerI've built this:

在找到使用 aFactoryBean来填充常规的建议后,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<Properties> 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<Properties> 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;}
}

In the spring-config.xmlyou create the beans as follows:

spring-config.xml你创建 bean 如下:

<bean id="zkProperties" class="fms.ZkPropertiesFactoryBean" p:zkConnect="localhost:2181" p:path="/app/zk-properties"/>
<context:property-placeholder properties-ref="zkProperties"/>