[原] 利用Radix樹作為Key-Value 鍵值對的數據路由


引言:總所周知,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 的初始化和申請太過於浪費資源,可以采用節點池的方式設計。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM