Zookeeper和Curator-Framework實踐系列之: 配置管理

五柳-先生發表於2015-07-21

看過Zookeeper相關文件後都知道它可以實現分散式叢集的配置管理,本文以一個簡單的例項來演示它是如何實現的並工作的。

情景需要,簡單理解為下圖:

一個web叢集,需要通過zk來控制叢集的日誌輸出級別,比如管理員需要在生產環境下檢視一下DEBUG日誌,他可以臨時將叢集的日誌輸出級別改為DEBUG,獲取他想要的資訊後還要將級別調回到INFO或者ERROR級別。

今天的主角是Curator-Framework,它的存在使得我們操作ZK變得簡單有趣起來!

環境,框架,工具

  1. Zookeeper叢集
  2. Zookeeper API
  3. Curator-Framework
  4. Spring-MVC
  5. Logback
  6. Maven + IDEA

POM

依賴關係和jetty外掛

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.5</version>
        <exclusions>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.0.1-incubating</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.0.1-incubating</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.13</version>
    </dependency>
    <dependency>
        <version>1.7.5</version>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.8.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <finalName>zookeeper-configuration</finalName>
    <plugins>
        <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty-maven-plugin</artifactId>
            <version>8.1.7.v20120910</version>
            <configuration>
                <stopKey>pbase</stopKey>
                <stopPort>8080</stopPort>
            </configuration>
        </plugin>
    </plugins>
</build>

WEB.XML

這個不用解釋了,地球人都知道

<web-app>
  <display-name>Archetype Created Web Application</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:/applicationContext.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:/spring-mvc.xml</param-value>
    </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Spring 配置

applicationContext.xml

<context:component-scan base-package="cn.bg">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- Curator的FactoryBean,Spring啟動時建立Curator例項。 -->
<bean id="zookeeperFactoryBean" class="cn.bg.zk.core.ZookeeperFactoryBean" lazy-init="false">
    <property name="zkConnectionString" value="hadoopmaster:2181"/>
    <!-- 設定zookeeper的事件監聽者,本例是一個logback日誌級別znode監聽器 -->
    <property name="listeners">
        <list>
            <bean class="cn.bg.zk.configuration.LogbackLevelListener">
                <constructor-arg value="/zk_test/logbacklevel"/>
            </bean>
        </list>
    </property>
</bean>

spring-mvc.xml

<context:component-scan base-package="cn.bg">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<mvc:annotation-driven></mvc:annotation-driven>

Zookeeper配置管理實現相關類

ZookeeperFactoryBean.java

在Spring Context載入過程中建立Zookeeper連結對像並設定觸發監聽,通過Curator.

package cn.bg.zk.core;

public class ZookeeperFactoryBean implements FactoryBean<CuratorFramework>, InitializingBean, DisposableBean {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private CuratorFramework zkClient;

    //設定Zookeeper啟動後需要呼叫的監聽或者,或者需要做的初始化工作。
    public void setListeners(List<IZKListener> listeners) {
        this.listeners = listeners;
    }

    private List<IZKListener> listeners;

    //設定ZK連結串
    public void setZkConnectionString(String zkConnectionString) {
        this.zkConnectionString = zkConnectionString;
    }

    private String zkConnectionString;

    @Override
    public CuratorFramework getObject() {
        return zkClient;
    }

    @Override
    public Class<?> getObjectType() {
        return CuratorFramework.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void destroy() throws Exception {
        zkClient.close();
    }

    //建立ZK連結
    @Override
    public void afterPropertiesSet(){
        //1000 是重試間隔時間基數,3 是重試次數
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        zkClient = createWithOptions(zkConnectionString, retryPolicy, 2000, 10000);
        registerListeners(zkClient);
        zkClient.start();
    }


    /**
     * 通過自定義引數建立
     */
    public CuratorFramework  createWithOptions(String connectionString, RetryPolicy retryPolicy, int connectionTimeoutMs, int sessionTimeoutMs)
    {
        return CuratorFrameworkFactory.builder()
                .connectString(connectionString)
                .retryPolicy(retryPolicy)
                .connectionTimeoutMs(connectionTimeoutMs)
                .sessionTimeoutMs(sessionTimeoutMs)
                .build();
    }        

    //註冊需要監聽的監聽者對像. 
    private void registerListeners(CuratorFramework client){
        client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
            @Override
            public void stateChanged(CuratorFramework client, ConnectionState newState) {
                logger.info("CuratorFramework state changed: {}", newState);
                if(newState == ConnectionState.CONNECTED || newState == ConnectionState.RECONNECTED){
                    for(IZKListener listener : listeners){
                        listener.executor(client);
                        logger.info("Listener {} executed!", listener.getClass().getName());
                    }
                }
            }
        });

        client.getUnhandledErrorListenable().addListener(new UnhandledErrorListener() {
            @Override
            public void unhandledError(String message, Throwable e) {
                logger.info("CuratorFramework unhandledError: {}", message);
            }
        });
    }
}

監聽事件介面

所有需要在ZK客戶端連結成功後需要做的事件,需要實現這個介面,由上面的ZookeeperFactoryBean統一排程。

package cn.bg.zk.core;

import org.apache.curator.framework.CuratorFramework;

public interface IZKListener {
    void executor(CuratorFramework client);
}

Logback監聽實現

這裡主要用到Curator的NodeCache類,它的主要功能是用來監聽znode本身的變化,並可以獲取當前值,而且會自動重複監聽,簡化了原生API開發的繁瑣過程。

package cn.bg.zk.configuration;

public class LogbackLevelListener implements IZKListener {

    //獲取logback例項
    Logger log = (Logger) LoggerFactory.getLogger(this.getClass());

    private String path;

    //Logback日誌級別ZNode
    public LogbackLevelListener(String path) {
        this.path = path;
    }

    @Override
    public void executor(CuratorFramework client) {

        //使用Curator的NodeCache來做ZNode的監聽,不用我們自己實現重複監聽
        final NodeCache cache = new NodeCache(client, path);
        cache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {

                byte[] data = cache.getCurrentData().getData();

                //設定日誌級別
                if (data != null) {
                    String level = new String(data);
                    Logger logger = (Logger) LoggerFactory.getLogger("root");
                    Level newLevel = Level.fromLocationAwareLoggerInteger(Integer.parseInt(level));
                    logger.setLevel(newLevel);
                    System.out.println("Setting logback new level to :" + newLevel.levelStr);
                }
            }
        });
        try {
            cache.start(true);
        } catch (Exception e) {
            log.error("Start NodeCache error for path: {}, error info: {}", path, e.getMessage());
        }
    }
}

通過WEB端檢視當前日誌級別

寫一個controller來讀取logback的日誌級別

MainController.java

package cn.bg.controller;

@Controller
public class MainController {

    @RequestMapping("/")
    @ResponseBody
    public String logbackLevel() throws Exception {
        Logger logger = (Logger) LoggerFactory.getLogger("root");
        String levelStr = logger.getLevel().levelStr;
        return levelStr;
    }

}

執行

在ZK叢集端啟動zkCli建立/zk_test/logbacklevel的znode,設定值為10,或20在控制檯來檢視logback的日誌輸出情況,logback日誌級別資料表示如下:

TRACE_INT = 0
DEBUG_INT = 10
INFO_INT = 20
WARN_INT = 30
ERROR_INT = 40

到此就實現了一個簡單的ZK配置管理情境,有了Curator-Framework後一切變的簡單起來,我們主要精力只要解決業務相關的的需要,而ZK相關的實現由Curator來解決。
下一篇介紹Curator分散式佇列實現。

轉載: http://www.cnblogs.com/xguo/archive/2013/06/10/3130589.html

相關文章