ZooKeeper場景實踐:(2)集中式配置管理

1. 基本介紹

在分佈式的環境中,可能會有多個對等的程序讀取同樣的配置文件,程序可以部署在多臺機器上,如果配置採用文件的話,則需要爲部署該程序的機器也部署一個配置文件,一旦要修改配置的時候就會非常麻煩,需要修改多個配置文件,而且容易產生不一致。
集中式配置管理的思路是,將配置數據集中發佈到ZooKeeper的節點上,供訂閱者動態獲取數據。實現配置的集中式管理和動態更新。可以簡單的理解爲配置數據與程序分離。

2. 場景分析

(1).集中式配置管理

通常來說,大部分項目裏面都有約定的配置文件格式,如ini,xml等。一般都會有對應的解析庫類。這種解析庫類的基本工作模式爲:

  1. 讀取文件(open)
  2. 解析文件(parse)
  3. 對外提供參數(get)

如果我們將文件的內容全部放到ZooKeeper的某個節點上.解析類將配置數據全部下載到本地,在完成解析的話,則可以用很小的改動就完成集中式配置管理的需求。

  1. 讀取Zookeeper上對應路徑的數據(read)
  2. 解析文件(parse)
  3. 對外提供參數(get)

(2).動態更新

動態更新是希望不重啓程序就能夠實時獲取更新的配置。在單機環境中,這種配置數據通常會放在數據庫中,修改配置只需要update數據庫就可以了。
使用ZooKeeper的話,需要節點註冊一個watcher,監視配置數據的是否有變化,一定出現變化,則調用新的解析類來重新解析配置數據。
個人認爲這個特徵使用Zookeeper可以實現,但是並不是所有配置都需要這個功能,這種比較適合對配置敏感,需要實時更新配置的情況。

3. 動手實踐

這裏我只實現了集中式配置管理的功能,沒有實現動態更新,有需要的話你可以嘗試自己實現。
由於之前曾經做個一個ini文件的庫類解析,這裏就直接拿過來改了。根據場景的分析,只需要修改open這個函數就ok了。

看下原來的open函數

/*讀取文件名要改爲地址和路徑*/
int IniFile::open(const string &filename)
{    
    release();
    fname_ = filename;
    IniSection *section = NULL;
    /*讀取數據的方式需要修改*/
    FILE *fp = fopen(filename.c_str(),"r");

    if(fp == NULL ){
        return -1;
    }

    string line;
    string comment;

    //增加默認段
    section = new IniSection();
    sections_[""] = section;
    /*獲取行的方式需要修改*/
    while(getline(line,fp) > 0){

        ...//省略單行的解析

    }

    fclose(fp);

    return 0;
}


我們有三個主要需要修改的地方,分別是是入參,fopen和getline。下面是修改後的open函數

/*修改入參,host爲Zookeeper的ip及端口地址,filepath爲配置數據的路徑*/
int IniFile::open2(const string &host,const string &filepath)
{    
    release();
    fname_ = filepath;
    IniSection *section = NULL;
    char fp[2048]={0};
    /*ZooKeeper來讀取*/
    zkopen(host,filepath,fp,sizeof(fp));

    if(fp[0] == 0){
        return -1;
    }

    string line;
    string comment;

    //增加默認段
    section = new IniSection();
    sections_[""] = section;

    char *p = fp;
    /*調整getline的入參*/
    while(getline2(line,p) > 0){
          ...//省略單行的解析

    }


    return 0;
}


zkopen從Zookeeper的節點上讀取數據,並保存到fp中。代碼如下:

string zkopen(const string &host,const string &filepath,char *fp,int len)
{
    int timeout = 30000;  
    char path_buffer[512];  
    int bufferlen=sizeof(path_buffer); 
    char conf_data[2048];
    int conf_len=sizeof(conf_data); 

    zoo_set_debug_level(ZOO_LOG_LEVEL_WARN); //設置日誌級別,避免出現一些其他信息  

    zhandle_t* zkhandle = zookeeper_init(host.c_str(),NULL, timeout, 0, (char *)"Monitor Test", 0);  

    if (zkhandle ==NULL)  
    {  
        fprintf(stderr, "Error when connecting to zookeeper servers...\n");  
        exit(EXIT_FAILURE);  
    }  

    int ret = zoo_get(zkhandle,filepath.c_str(),0,conf_data,&conf_len,NULL);
    if(ret != ZOK){
        fprintf(stderr,"failed to get the data of path %s!\n",filepath.c_str());
        conf_data[0] = 0;
    }

    zookeeper_close(zkhandle); 

    strncpy(fp,conf_data,len);
    return conf_data;
}


接下來在對比下調用的變化。
原來的調用方式:

  /** read test **/
    IniFile ini;
    ini.open(g_filepath);

    //獲取指定段的指定項的值
    int ret = 0;
    string db_name = ini.getStringValue("COMMON","DB",ret);
    string db_passwd = ini.getStringValue("COMMON","PASSWD",ret);


現在的調用方式:

 /** read test **/
    IniFile ini;
    ini.open2(g_host,g_filepath);/*僅此處有變化*/

    //獲取指定段的指定項的值
    int ret = 0;
    string db_name = ini.getStringValue("COMMON","DB",ret);
    string db_passwd = ini.getStringValue("COMMON","PASSWD",ret);


由上可見,配置的改造還是很容易的,而且對程序的改動很小。
代碼詳見https://github.com/Winnerhust/ZooKeeper-Exam/tree/master/Config

5.小提示

需要注意一點,配置文件中通常有很多換行,而ZooKeeper的客戶端命令行工作不支持字符轉義。比如你要將一個配置文件test.ini的內容保存到Zookeeper上,文件內容如下。
[COMMON]
DB=mysql
PASSWD=root

你可能會在Zookeeper客戶端上輸入:
[zk: 172.17.0.36:2181(CONNECTED) 39] create /Conf/test.ini [COMMON]\nDB=mysql\nPASSWD=root\n
結果與我們希望的並不一樣:
[zk: 172.17.0.36:2181(CONNECTED) 43] get /Conf/test.ini3[COMMON]\nDB=mysql\nPASSWD=root\n

Zookeeper並沒有將字符串進行轉義,所以不能用ZooKeeper客戶端直接上傳配置文件。因此在代碼裏我還增加了一個上傳配置的功能。只需要將上一個參數-r就可以了。如將test.ini文件的內容上傳到ZooKeeper:
cat test.ini | testcase -r -p/Conf/test.ini -s172.17.0.36:2181


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章