1.基本介紹
按照ZooKeeper典型應用場景一覽裏的說法,分佈式隊列有兩種,一種是常規的先進先出隊列,另一種是要等到隊列成員聚齊之後的才統一按序執行。
第二種隊列可以先建立一個/queue,賦值爲n,表達隊列的大小。然後每個隊列成員加入時,就判斷是否達到隊列要求的大小,如果是可以進行下一步動作,否則繼續等待隊列成員的加入。比較典型的情況是,當一個大的任務可能需要很多的子任務完成才能開始進行。
比如彙總賬單的時候,就必須先將用戶的消費數據,積分數據等都統計完成後才能開始。彙總賬單的程序建立一個隊列/Queue,賦值爲2,然後分別統計消費數據和積分數據的程序當完成任務時就往/Queue下創建一個臨時節點。而彙總賬單程序監測到/Queue的子節點個數爲2時,就可以開始執行任務了。
實際上,我們也可以先建立一個數目爲2的子節點。當一個子任務完成的時候,就刪除一個子節點,當所有子節點都被刪除的時候,主任務就可以開始執行了。這個過程可以形象的理解爲拆除屏障。因此這種隊列還有一個專門的詞語描述,叫做屏障(barrier)。
2.場景分析
講了那麼多的關於屏障的認識,但是並不打算就去實現它,並且Zookeeper的官方文檔也有相關的知識。這次的主要目標是常規的FIFO隊列。我將實現隊列的兩個主要操作:push和pop。
1).
int push(zhandle_t *zkhandle,const char *path,char *element)
zkhandle
爲zookeeper_init
初始化後的句柄path
爲隊列的路徑element
爲要壓入隊列的內容
2).
int pop(zhandle_t *zkhandle,const char *path,char *element_buffer,int *buffer_len)
zkhandle
爲zookeeper_init
初始化後的句柄path
爲隊列的路徑element_buffer
爲要彈出的緩衝區buffer_len
爲指向緩衝區的大小的指針
簡單來說,假設隊列的路徑爲/Queue,push
就是就是創建一個臨時有序的/Queue/queue-節點。pop就是取出/Queue/下序列號最小的節點。
我們知道在C++中stl裏有一個queue的類,實現了push,pop等操作,然而它是非線程安全的,即多個線程同時push/pop的時候可能會出現錯誤。而由於ZooKeeper保證了創建節點和刪除節點的一致性,因此可以說利用Zookeeper實現的隊列是進程安全的。
3. 場景實踐
來看push和pop的具體實現。push
的實現很簡單,就是在{path}下創建一個有序的{path}/queue-子節點.
int push(zhandle_t *zkhandle,const char *path,char *element)
{
char child_path[512] = {0};
char path_buffer[512] = {0};
int bufferlen = sizeof(path_buffer);
sprintf(child_path,"%s/queue-",path);
int ret = zoo_create(zkhandle,child_path,element,strlen(element),
&ZOO_OPEN_ACL_UNSAFE,ZOO_SEQUENCE,
path_buffer,bufferlen);
if(ret != ZOK){
fprintf(stderr,"failed to create the path %s!\n",path);
}else{
printf("create path %s successfully!\n",path);
}
return ret;
}
pop
的功能則是取出{path}下序號最小的子節點,如果沒有子節點,則返回-1.
int pop(zhandle_t *zkhandle,const char *path,char *element,int *len)
{
int i = 0;
struct String_vector children;
int ret = zoo_get_children(zkhandle,path,0,&children);
if(ret != ZOK){
fprintf(stderr,"failed to create the path %s!\n",path);
}else if (children.count == 0){
strcpy(element,"");
*len = 0;
ret = -1;
}else{
char *min = children.data[0];
for(i = 0; i < children.count; ++i){
printf("%s:%s\n",min,children.data[i]);
if(strcmp(min,children.data[i]) > 0){
min = children.data[i];
}
}
if(min != NULL){
char child_path[512]={0};
sprintf(child_path,"%s/%s",path,min);
ret = zoo_get(zkhandle,child_path,0,element,len,NULL);
if(ret != ZOK){
fprintf(stderr,"failed to get data of the path %s!\n",child_path);
}else{
ret = zoo_delete(zkhandle,child_path, -1);
if(ret != ZOK){
fprintf(stderr,"failed to delete the path %s!\n",child_path);
}
}
}
}
for(i = 0; i < children.count; ++i){
free(children.data[i]);
children.data[i] = NULL;
}
return ret;
}
最後,再來看看模擬隊列操作的程序。和其他程序類似,它的選項有
-p
:指定隊列的路徑-m
:指定操作是push還是pop-v
:只在push時有用,用與指定要push的元素的值-s
:指定Zookeeper的服務器的ip:port.
如:
向隊列/Queue中壓人一個元素,元素的值爲"Hello":
>myqueue -s 172.17.0.36:2181 -p /Queue -m push -v Hello
將隊列/Queue彈出一個元素
>myqueue -s 172.17.0.36:2181 -p /Queue -m pop
最後附上完整的源代碼:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include"zookeeper.h"
#include"zookeeper_log.h"
char g_host[512]= "172.17.0.36:2181";
char g_path[512]= "/Queue";
char g_value[512]="msg";
enum MODE{PUSH_MODE,POP_MODE} g_mode;
void print_usage();
void get_option(int argc,const char* argv[]);
/**********unitl*********************/
void print_usage()
{
printf("Usage : [myqueue] [-h] [-m mode] [-p path ] [-v value][-s ip:port] \n");
printf(" -h Show help\n");
printf(" -p Queue path\n");
printf(" -m mode:push or pop\n");
printf(" -v the value you want to push\n");
printf(" -s zookeeper server ip:port\n");
printf("For example:\n");
printf(" push the message \"Hello\" into the queue Queue:\n");
printf(" >myqueue -s172.17.0.36:2181 -p /Queue -m push -v Hello\n");
printf(" pop one message from the queue Queue:\n");
printf(" >myqueue -s172.17.0.36:2181 -p /Queue -m pop\n");
}
void get_option(int argc,const char* argv[])
{
extern char *optarg;
int optch;
int dem = 1;
const char optstring[] = "hv:m:p:s:";
g_mode = PUSH_MODE;
while((optch = getopt(argc , (char * const *)argv , optstring)) != -1 )
{
switch( optch )
{
case 'h':
print_usage();
exit(-1);
case '?':
print_usage();
printf("unknown parameter: %c\n", optopt);
exit(-1);
case ':':
print_usage();
printf("need parameter: %c\n", optopt);
exit(-1);
case 'm':
if(strcasecmp(optarg,"push")==0){
g_mode = PUSH_MODE;
}else{
g_mode = POP_MODE;
}
break;
case 's':
strncpy(g_host,optarg,sizeof(g_host));
break;
case 'p':
strncpy(g_path,optarg,sizeof(g_path));
break;
case 'v':
strncpy(g_value,optarg,sizeof(g_value));
break;
default:
break;
}
}
}
int push(zhandle_t *zkhandle,const char *path,char *element)
{
char child_path[512] = {0};
char path_buffer[512] = {0};
int bufferlen = sizeof(path_buffer);
sprintf(child_path,"%s/queue-",path);
int ret = zoo_create(zkhandle,child_path,element,strlen(element),
&ZOO_OPEN_ACL_UNSAFE,ZOO_SEQUENCE,
path_buffer,bufferlen);
if(ret != ZOK){
fprintf(stderr,"failed to create the path %s!\n",path);
}else{
printf("create path %s successfully!\n",path);
}
return ret;
}
int pop(zhandle_t *zkhandle,const char *path,char *element,int *len)
{
int i = 0;
struct String_vector children;
int ret = zoo_get_children(zkhandle,path,0,&children);
if(ret != ZOK){
fprintf(stderr,"failed to create the path %s!\n",path);
}else if (children.count == 0){
strcpy(element,"");
*len = 0;
ret = -1;
}else{
char *min = children.data[0];
for(i = 0; i < children.count; ++i){
printf("%s:%s\n",min,children.data[i]);
if(strcmp(min,children.data[i]) > 0){
min = children.data[i];
}
}
if(min != NULL){
char child_path[512]={0};
sprintf(child_path,"%s/%s",path,min);
ret = zoo_get(zkhandle,child_path,0,element,len,NULL);
if(ret != ZOK){
fprintf(stderr,"failed to get data of the path %s!\n",child_path);
}else{
ret = zoo_delete(zkhandle,child_path, -1);
if(ret != ZOK){
fprintf(stderr,"failed to delete the path %s!\n",child_path);
}
}
}
}
for(i = 0; i < children.count; ++i){
free(children.data[i]);
children.data[i] = NULL;
}
return ret;
}
int front(zhandle_t *zkhandle,char *path,char *element,int *len)
{
int i = 0;
struct String_vector children;
int ret = zoo_get_children(zkhandle,path,0,&children);
if(ret != ZOK){
fprintf(stderr,"failed to create the path %s!\n",path);
}else if(children.count == 0){
strcpy(element,"");
*len = 0;
ret = -1;
}else{
char *min = NULL;
for(i = 0; i < children.count; ++i){
if(strcmp(min,children.data[i]) > 0){
min = children.data[i];
}
}
if(min != NULL){
char child_path[512]={0};
sprintf(child_path,"%s/%s",path,min);
ret = zoo_get(zkhandle,child_path,0,element,len,NULL);
if(ret != ZOK){
fprintf(stderr,"failed to get data of the path %s!\n",child_path);
}
}
}
for(i = 0; i < children.count; ++i){
free(children.data[i]);
children.data[i] = NULL;
}
return ret;
}
int main(int argc, const char *argv[])
{
int timeout = 30000;
char path_buffer[512];
int bufferlen=sizeof(path_buffer);
zoo_set_debug_level(ZOO_LOG_LEVEL_WARN); //設置日誌級別,避免出現一些其他信息
get_option(argc,argv);
zhandle_t* zkhandle = zookeeper_init(g_host,NULL, timeout, 0, (char *)"lock Test", 0);
if (zkhandle ==NULL)
{
fprintf(stderr, "Error when connecting to zookeeper servers...\n");
exit(EXIT_FAILURE);
}
int ret = zoo_exists(zkhandle,g_path,0,NULL);
if(ret != ZOK){
ret = zoo_create(zkhandle,g_path,"1.0",strlen("1.0"),
&ZOO_OPEN_ACL_UNSAFE,0,
path_buffer,bufferlen);
if(ret != ZOK){
fprintf(stderr,"failed to create the path %s!\n",g_path);
}else{
printf("create path %s successfully!\n",g_path);
}
}
if(g_mode == PUSH_MODE){
push(zkhandle,g_path,g_value);
printf("push:%s\n",g_value);
}else{
int len = sizeof(g_value);
ret = pop(zkhandle,g_path,g_value,&len) ;
if(ret == ZOK){
printf("pop:%s\n",g_value);
}else if( ret == -1){
printf("queue is empty\n");
}
}
zookeeper_close(zkhandle);
return 0;
}