引言:總所周知,NoSQL,Memcached等作為Key—Value 存儲的模型的數據路由都采用Hash表來達到目的。如何解決Hash沖突和Hash表大小的設計是一個很頭疼的問題。
借助於Radix樹,我們同樣可以達到對於uint32_t 的數據類型的路由。這個靈感就來自於Linux內核的IP路由表的設計。
作為傳統的Hash表,我們把接口簡化一下,可以抽象為這么幾個接口。
void Hash_create( size_t Max ); int Hash_insert( uint32_t hash_value , value_type value ) ; value_type *Hash_get( uint32_t hashvalue );
int Hash_delete( uint32_t hash_value );
接口的含義如其名,創建一個Hash表,插入,取得,刪除。
同樣,把這個接口的功能抽象后,利用radix同樣可以實現相同的接口方式。
1 int mc_radix_hash_ini(mc_radix_t *t ,int nodenum ); 2 3 int mc_radix_hash_insert( mc_radix_t *t , unsigned int hashvalue , void *data ,size_t size ); 4 5 int mc_radix_hash_del( mc_radix_t *t , unsigned int hashvalue ) ; 6 7 void *mc_radix_hash_get( mc_radix_t *t , unsigned int hashvalue ) ;
那我們簡單介紹一下Radix樹:
Radix Tree(基樹) 其實就差不多是傳統的二叉樹,只是在尋找方式上,利用比如一個unsigned int 的類型的每一個比特位作為樹節點的判斷。
可以這樣說,比如一個數 1000101010101010010101010010101010 (隨便寫的)那么按照Radix 樹的插入就是在根節點,如果遇到 0 ,就指向左節點,如果遇到1就指向右節點,在插入過程中構造樹節點,在刪除過程中刪除樹節點。如果覺得太多的調用Malloc的話,可以采用池化技術,預先分配多個節點,本博文就采用這種方式。
1 typedef struct _node_t 2 { 3 char zo ; // zero or one 4 int used_num ; 5 struct _node_t *parent ; 6 struct _node_t *left ; 7 struct _node_t *right ; 8 void *data ;//for nodes array list finding next empty node 9 int index ; 10 }mc_radix_node_t ;
節點的結構定義如上。
zo 可以忽略,父節點,坐指針,右指針顧名思義,data 用於保存數據的指針,index 是作為 node 池的數組的下標。
樹的結構定義如下:
1 ypedef struct _radix_t 2 { 3 mc_radix_nodes_array_t * nodes ; 4 mc_radix_node_t * root ; 5 6 mc_slab_t * slab ; 7 8 9 /* 10 pthread_mutex_t lock ; 11 */ 12 int magic ; 13 int totalnum ; 14 size_t pool_nodenum ; 15 16 mc_item_queue queue ; 17 }mc_radix_t ;
暫且不用看 nodes 的結構,這里只是作為一個node池的指針
root 指針顧名思義是指向根結構,slab 是作為存放數據時候的內存分配器,如果要使用內存管理來減少開銷的話(參見slab內存分配器一章)
magic用來判斷是否初始化,totalnum 是葉節點個數,poll_nodenum 是節點池內節點的個數。
queue是作為數據項中數據的隊列。
我們采用8421編碼的宏來作為每一個二進制位的判斷:
#define U01_MASK 0x80000000 #define U02_MASK 0x40000000 #define U03_MASK 0x20000000 #define U04_MASK 0x10000000
.
.
.
.
#define U31_MASK 0x00000002
#define U32_MASK 0x00000001
類似這樣的方式來對每一位二進制位做判斷,還有其他更好的辦法,這里只是作為簡化和快速。
unsigned int MASKARRAY[32] = { U01_MASK,U02_MASK,U03_MASK,U04_MASK,U05_MASK,U06_MASK,U07_MASK,U08_MASK, U09_MASK,U10_MASK,U11_MASK,U12_MASK,U13_MASK,U14_MASK,U15_MASK,U16_MASK, U17_MASK,U18_MASK,U19_MASK,U20_MASK,U21_MASK,U22_MASK,U23_MASK,U24_MASK, U25_MASK,U26_MASK,U27_MASK,U28_MASK,U29_MASK,U30_MASK,U31_MASK,U32_MASK };
我們為Radix 提供了一些靜態函數,不對外聲明:
初始化節點池
static int mc_radix_nodes_ini(mc_radix_nodes_array_t *par_nodearray ,size_t par_maxnum )
取得一個節點:
static mc_radix_node_t *mc_get_radix_node(mc_radix_nodes_array_t *par_nodearray )
歸還一個節點:
static void mc_free_radix_node( mc_radix_nodes_array_t *par_nodearray , mc_radix_node_t * par_free_node )
這里是初始化radix 樹:
1 int mc_radix_hash_ini(mc_radix_t *t ,size_t nodenum ) 2 { 3 /* init the node pool */ 4 t->nodes = (mc_radix_nodes_array_t *)malloc( sizeof(mc_radix_nodes_array_t) ); //為節點池分配空間 5 t->slab = mc_slab_create(); //使用slab分配器 6 mc_radix_nodes_ini( t->nodes , nodenum ); //初始化節點 7 t->magic = MC_MAGIC ; 8 t->totalnum = 0 ; 9 t->pool_nodenum = nodenum ; 10 t->root = NULL ; 11 12 13 t->queue.head = NULL ; 14 t->queue.pear = NULL ; 15 t->queue.max_num = nodenum ; 16 t->queue.cur_num = 0 ; 17 }
1 int mc_radix_hash_insert( mc_radix_t *t , unsigned int hashvalue , void *data ,size_t size ) 2 { 3 unsigned int i = 0 ; 4 mc_radix_node_t * root = t->root ; 5 6 if( t->root == NULL ) 7 { 8 t->root = mc_get_radix_node( t->nodes ) ; 9 } 10 11 /* LRU */ 12 /*其中涉及到LRU算法,原理是將所有的葉子節點鏈接為雙向隊列,然后更新和插入放入隊列頭,按照一定的比例從隊列尾刪除數據*/ 13 if( t->queue.cur_num >= (t->queue.max_num)*PERCENT ) 14 { 15 for( i = 0 ; i < (t->queue.max_num)*(1-PERCENT) ; i++ ) 16 { 17 mc_del_item( t , t->queue.pear ); 18 } 19 } 20 mc_radix_node_t * cur = t->root ; 21 for(i = 0 ; i < 32 ; i++ ) 22 { 23 /* 1 ---> right */ 24 /*按位來探測樹節點*/ 25 if( hashvalue & MASKARRAY[i] ) 26 { 27 28 if( cur -> right != NULL ) 29 { 30 cur->used_num++ ; 31 cur->right->parent = cur ; 32 cur = cur->right ; 33 } 34 else 35 { 36 cur->right = mc_get_radix_node( t->nodes ) ; 37 if( cur->right == NULL ) 38 { 39 fprintf(stderr,"mc_get_radix_node error\n"); 40 return -1; 41 } 42 cur->used_num++ ; 43 cur->right->parent = cur ; 44 cur = cur->right ; 45 } 46 } 47 /* 0 ---> left */ 48 else 49 { 50 51 if( cur->left != NULL ) 52 { 53 cur->used_num++; 54 cur->left->parent = cur ; 55 cur = cur->left ; 56 } 57 else 58 { 59 cur->left = mc_get_radix_node( t->nodes ) ; 60 if( cur->left == NULL ) 61 { 62 fprintf(stderr,"mc_get_radix_node error\n"); 63 return -1; 64 } 65 66 cur->used_num++; 67 cur->left->parent = cur ; 68 cur = cur->left ; 69 } 70 } 71 } 72 73 t->totalnum ++ ; 74 mc_slot_t * l_slot = mc_slot_alloc( t->slab, size ) ; 75 cur->data = ( mc_slot_t *)(cur->data); 76 memcpy( l_slot->star , data , size ); 77 cur->data = l_slot ; 78 79 /*add to t->queue */ 80 if( t->queue.head == NULL ) 81 { 82 t->queue.head = cur ; 83 t->queue.pear = cur ; 84 cur->left = NULL ; 85 cur->right = NULL ; 86 87 t->queue.cur_num++ ; 88 } 89 else 90 { 91 cur->left = NULL ; 92 cur->right = t->queue.head ; 93 t->queue.head->left = cur ; 94 t->queue.head = cur ; 95 96 t->queue.cur_num++ ; 97 } 98 return 1; 99 }
刪除一個節點,通過hashvalue作為其value,顧名思義
1 int mc_radix_hash_del( mc_radix_t *t , unsigned int hashvalue ) 2 { 3 if( t == NULL || t->root == NULL ) 4 { 5 return -1; 6 } 7 /* non initialized */ 8 if( t->magic != MC_MAGIC ) 9 { 10 return -1; 11 } 12 mc_radix_node_t * cur = t->root ; 13 mc_radix_node_t * cur_par ; 14 int i = 0 ; 15 for( ; i < 32 ; i++ ) 16 { 17 if( hashvalue & MASKARRAY[i] ) 18 { 19 20 if( cur->right != NULL ) 21 { 22 cur->used_num-- ; 23 cur = cur->right ; 24 } 25 else 26 return -1; 27 } 28 else 29 { 30 31 if( cur->left != NULL ) 32 { 33 cur->used_num-- ; 34 cur = cur->left ; 35 } 36 else 37 return -1; 38 } 39 } 40 41 if( cur->used_num >= 0 ) 42 mc_slot_free(cur->data); 43 44 /*remove from t->queue */ 45 if( cur == t->queue.pear && cur == t->queue.head ) 46 { 47 t->queue.pear = NULL ; 48 t->queue.head = NULL ; 49 t->queue.cur_num -- ; 50 } 51 /* the last item */ 52 else if( cur == t->queue.pear && cur != t->queue.head) 53 { 54 cur->left->right = NULL ; 55 cur->left = NULL ; 56 t->queue.cur_num -- ; 57 } 58 else if( cur != t->queue.pear ) 59 { 60 cur->left->right = cur->right ; 61 cur->right->left = cur->left ; 62 t->queue.cur_num -- ; 63 } 64 else 65 { 66 cur->left->right = cur->right ; 67 cur->right->left = cur->left ; 68 t->queue.cur_num -- ; 69 } 70 71 for(;;) 72 { 73 74 if( cur->used_num == 0 ) 75 { 76 cur_par = cur->parent ; 77 mc_free_radix_node( t->nodes , cur ); 78 cur = cur_par ; 79 } 80 if( cur == NULL ) 81 break ; 82 if( cur->used_num > 0 ) 83 break ; 84 85 } 86 87 return 1; 88 89 }
取得值:通過void * 指向
1 void *mc_radix_hash_get( mc_radix_t *t , unsigned int hashvalue ) 2 { 3 if( t == NULL || t->root == NULL ) 4 { 5 fprintf(stderr,"t == NULL || t->root == NULL\n"); 6 return (void *)(0); 7 } 8 /* non initialized */ 9 if( t->magic != MC_MAGIC ) 10 { 11 fprintf(stderr,"t->magic != MC_MAGIC\n"); 12 return (void *)(0); 13 } 14 mc_radix_node_t * cur = t->root ; 15 mc_slot_t *ret_slot ; 16 int i = 0 ; 17 for( ; i < 32 ; i++ ) 18 { 19 if( hashvalue & MASKARRAY[i] ) 20 { 21 if( cur->right == NULL ) 22 break; 23 else 24 cur = cur->right ; 25 } 26 else 27 { 28 if( cur->left == NULL ) 29 break; 30 else 31 cur = cur->left ; 32 } 33 } 34 if( i == 32 ) 35 { 36 ret_slot = cur->data; 37 38 /* update LRU queue*/ 39 if( cur->left != NULL ) 40 { 41 if( cur->right != NULL ) 42 { 43 cur->left->right = cur->right ; 44 cur->right->left = cur->left ; 45 cur->left = t->queue.head ; 46 t->queue.head->left = cur ; 47 t->queue.head = cur ; 48 } 49 else 50 { 51 /* cur->right == NULL last element of LRU queue */ 52 cur->left->right = NULL ; 53 cur->left = t->queue.head ; 54 t->queue.head->left = cur ; 55 t->queue.head = cur ; 56 57 } 58 } 59 return (void *)(ret_slot->star) ; 60 } 61 else 62 { 63 fprintf(stderr,"i = %d \n",i); 64 return (void *)(0); 65 } 66 }
1 int mc_free_radix( mc_radix_t *t ) 2 { 3 mc_free_all_radix_node(t->nodes); 4 mc_slab_free(t->slab); 5 free(t->nodes); 6 } 7 8 static void mc_del_item( mc_radix_t *t , mc_radix_node_t * cur ) 9 { 10 if( cur->left == NULL ) 11 { 12 fprintf(stderr,"item number in LRU queue is too small \n"); 13 return ; 14 } 15 if( cur->right != NULL ) 16 { 17 fprintf(stderr,"cur should be the last of LRU queue \n"); 18 } 19 /* remove from LRU queue */ 20 mc_radix_node_t * pcur = cur->left ; 21 cur->left = NULL ; 22 pcur->right = NULL ; 23 24 pcur = cur->parent ; 25 /* remove from radix tree */ 26 while( pcur != NULL ) 27 { 28 cur->used_num -- ; 29 if( cur->used_num <=0 ) 30 { 31 mc_free_radix_node( t->nodes , cur ); 32 } 33 cur = pcur ; 34 pcur = pcur->parent ; 35 } 36 37 }
總結:radix 樹作為key-value 路由最大的好處就是在於減少了hash表的動態和一部分碰撞問題等。還可以在此結構上方便的擴展 LRU算法,淘汰數據等。
如果擔心node 的初始化和申請太過於浪費資源,可以采用節點池的方式設計。