Java/Spring配置中的ZooKeeper是什么?

21

有没有关于使用Apache ZooKeeper来分发Java应用程序配置的详细文档,特别是Spring服务的使用案例?

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

更新

最终,我编写了一些代码,将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;
        }

    }

}

你想要推送哪种配置更改?像字符串/整数/布尔值这样的基本内容?还是对象连接/依赖注入更改? - sourcedelica
坦白地说,最常见的更改可能是日志记录级别,尽管系统管理员对数据存储的连接URL之类的事情表示了兴趣。 - DeejUK
请提供您的ResourcePropertySourcecontext.xml示例,还是使用ConfigurableApplicationContext?由于缺少属性,我无法启动上下文。先感谢您! - Anton Kirillov
抱歉,Anton,我不再能访问那段代码了。 - DeejUK
阅读此内容:https://dzone.com/articles/spring-cloud-config-part-3-zookeeper-backend - Apurva Singh
6个回答

6

你应该考虑使用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 Cloud Config会在配置更改时导致应用程序重新读取配置吗? - ben3000
@ben3000 是的,你可以配置这个(使用 Spring Cloud Bus 和 @RefreshScope - C-Otto
1
在更改后,您应该使用http://localhost:xxxx/actuator/refresh对您的REST服务进行POST请求,这将反映在配置文件中所做的更改。 - user1428716

2

1

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


1
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

要在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")));
       }  
}

0
上周我参加了James Strachen的Apache Camel演讲,他提到在他们基于Java的云服务器中使用ZooKeeper作为配置信息的来源。
我曾经听过SpringSource的CTO Adrian Colyer关于Spring运行时配置更改的演讲,但是今天Spring是否支持这一点呢?
在我看来,如果你从一个典型的Spring应用程序开始,我认为你很难在其上方轻松地添加动态配置更改。

Spring允许JMX配置更改,但我遇到的问题是JMX似乎是点对点的,我需要知道有多少服务器正在运行,并依次连接到每个服务器并应用更改。是否有什么办法可以解决这个问题? - DeejUK
2
实际上,Zookeeper与JMX的集成将非常有趣。使用@Managed*进行JMX的改装相对容易。 - sourcedelica

0

在找到一种使用 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<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;}
}

spring-config.xml 文件中,你可以按照以下方式创建 beans:
<bean id="zkProperties" class="fms.ZkPropertiesFactoryBean" p:zkConnect="localhost:2181" p:path="/app/zk-properties"/>
<context:property-placeholder properties-ref="zkProperties"/>

如何在Zookeeper中配置属性...路径属性[p:path="/app/zk-properties"/]从哪里加载...您能否详细说明一下? - Pavan Kumar Varma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接