ZooKeeper for Java/Spring Config?

21,205

Solution 1

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 cloud as the source of configuration info.

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

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.

Solution 2

You should consider Spring Cloud Config:

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 Environment but could be used by non-Spring applications if desired.

Source code available here:

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

Solution 3

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

Solution 4

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.

Solution 5

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.

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.

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")));
       }  
}
Share:
21,205
DeejUK
Author by

DeejUK

I tell computers to do things. Sometimes I even tell them to do the right things. I also help people get better at telling computers to do things. Some of the things I tell computers to do are games, but most are related to distributed services, PaaS, NoSQL, and that sort of thing.

Updated on January 11, 2020

Comments

  • DeejUK
    DeejUK over 4 years

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

    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.

    UPDATE

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

    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;
            }
    
        }
    
    }
    
  • DeejUK
    DeejUK about 12 years
    Spring allows JMX config changes, but the bit I'm struggling with is that JMX seems to be point-to-point, and that I'd have to know how many servers were running, and the connect to each server in turn an apply a change. Any ideas if there's a way around this?
  • sourcedelica
    sourcedelica about 12 years
    Actually a Zookeeper->JMX integration would be pretty interesting. The retrofit using JMX would be relatively easy using @Managed*.
  • Pavan Kumar Varma
    Pavan Kumar Varma over 8 years
    How to configure properties in zookeeper...from where this path property being loaded [p:path="/app/zk-properties"/] ... cloud you please elaborate
  • ben3000
    ben3000 almost 8 years
    does Spring Cloud Config cause an application to reread the configuration when it is changed?
  • C-Otto
    C-Otto over 7 years
    @ben3000 yes you can configure this (using the spring cloud bus and @RefreshScope)
  • user1428716
    user1428716 about 6 years
    after the changes - you should do POST to your rest services using localhost:xxxx/actuator/refresh and this would reflect the changes made in config file