• nginx源码分析--基数树


    1. typedef struct {
    2. ngx_radix_node_t *root;
    3. ngx_pool_t *pool;
    4. ngx_radix_node_t *free;
    5. char *start;
    6. size_t size;
    7. } ngx_radix_tree_t;

    预备知识

    1.基数树也是一种二叉查找树,目前官方模块中仅geo模块使用了基数树.
    2.ngx_radix_tree_t基数树要求存储的每个节点都必须以32位整型作为区别任意两个节点的唯一标识
    3. 基数树的每个节点中可以存储的值只是 1 个指针,它指向实际的数据
    4.基数树实际是按二进制位来建立树的
    5.基数树具备二叉查找树的所有优点:基本操作速度快(如检索、插入、删除节点)、支
    持范围查询、支持遍历操作等。但基数树不像红黑树那样会通过自身的旋转来达到平衡,基
    数树是不管树的形态是否平衡的,因因此,它插入节点、删除节点的速度要比红黑树快得多
    6.节点的key关键字已经决定了这个节点处于树中的位置。决定节点位置的方法很简单,先
    将这个节点的整型关键字转化为二进制,从左向右数这 32 个位, 遇到 0时进入左子树,遇到1 时进入右子树。因此,ngx_radix_tree_t树的最大深度是32

    基本数据结构

    1. struct ngx_radix_node_s {
    2. ngx_radix_node_t *right;
    3. ngx_radix_node_t *left;
    4. ngx_radix_node_t *parent;
    5. uintptr_t value;
    6. };
    1. typedef struct {
    2. ngx_radix_node_t *root;
    3. ngx_pool_t *pool;
    4. ngx_radix_node_t *free;
    5. char *start;
    6. size_t size;
    7. } ngx_radix_tree_t;
    结构成员分析:
    节点:
    value 字段指向用户自定义的、有意义的数据结构
    root          根节点
    pool          内存池
    free           已分配内存中还未使用内存的首地址
    start          已分配内存中还未使用的内存大小
    size            已分配内存中使用的内存大小
    内存布局:

     

    操作函数

    创建

    1. ngx_radix_tree_t *ngx_radix_tree_create(ngx_pool_t *pool,
    2. ngx_int_t preallocate);
    3. {
    4. uint32_t key, mask, inc;
    5. ngx_radix_tree_t *tree;
    6. tree = ngx_palloc(pool, sizeof(ngx_radix_tree_t));
    7. if (tree == NULL) {
    8. return NULL;
    9. tree->pool = pool;
    10. tree->free = NULL;
    11. tree->start = NULL;
    12. tree->size = 0;
    13. tree->root = ngx_radix_alloc(tree);
    14. if (tree->root == NULL) {
    15. return NULL;
    16. }
    17. tree->root->right = NULL;
    18. tree->root->left = NULL;
    19. tree->root->parent = NULL;
    20. tree->root->value = NGX_RADIX_NO_VALUE;
    21. //只创建结构体ngx_radix_tree_t,没有创建任何基数树节点*
    22. if (preallocate == 0) {
    23. return tree;
    24. }
    25. }
    26. //根据下面的情况创建基数树节点*
    27. f (preallocate == -1) {
    28. switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {
    29. /* amd64 */
    30. case 128:
    31. preallocate = 6;
    32. break;
    33. /* i386, sparc64 */
    34. case 256:
    35. preallocate = 7;
    36. break;
    37. /* sparc64 in 32-bit mode */
    38. default:
    39. preallocate = 8;
    40. }
    41. }
    42. mask = 0;
    43. inc = 0x80000000;
    44. while (preallocate--) {
    45. key = 0;
    46. mask >>= 1;
    47. mask |= 0x80000000;
    48. do {
    49. if (ngx_radix32tree_insert(tree, key, mask, NGX_RADIX_NO_VALUE)
    50. != NGX_OK)
    51. {
    52. return NULL;
    53. }
    54. key += inc;//当preallocate=0时,是最后一层,构建的节点个数为2^preallocate
    55. } while (key);
    56. inc >>= 1;
    57. }
    58. return tree;
    59. }
    函数解析:
    1.pool是内存池指针 preallocate:预分配的基数树节点树 如果传递的值为-1,那么将会根据当前操作系统中一个页面的大小来预分配基数树节点
    2
    #define NGX_RADIX_NO_VALUE   (uintptr_t) -1
    

    3.

    * amd64上的6位(64位平台和4K页面)

    * i386上的7位(32位平台和4K页面)

    * 64位模式下sparc64上的7个比特位(8K页)

    * 32位模式下sparc64上的8个比特位(8K页)

    1. if (preallocate == -1) {
    2. switch (ngx_pagesize / sizeof(ngx_radix_node_t)) {
    3. /* amd64 */
    4. case 128:
    5. preallocate = 6;
    6. break;
    7. /* i386, sparc64 */
    8. case 256:
    9. preallocate = 7;
    10. break;
    11. /* sparc64 in 32-bit mode */
    12. default:
    13. preallocate = 8;
    14. }
    15. }

    3.循环如下:

    1. //加入preallocate=7,最终建的基数树的节点总个数为2^(preallocate+1)-1,每一层个数为2^(7-preallocate)
    2. //循环如下:
    3. //preallocate = 7 6 5 4 3 2 1
    4. //mask(最左8位)= 10000000 11000000 11100000 11110000 11111000 11111100 11111110
    5. //inc = 10000000 01000000 00100000 00010000 00001000 00000100 00000010
    6. //增加节点个数 = 2 4 8 16 32 64 128

    插入

    nginx的基数树只处理key值为整形的情况,所以每个整形被转化为二进制数,并且树的最大深度是32层。根据二进制位数从左到右,如果当前位为1,就向右子树,否则向左子树插入。当然有时候我们不想构建深度为32的基数树,nginx为此提供了一个掩码mask,这个掩码中1的个数决定了基数树的深度。
     

    1. ngx_int_t
    2. ngx_radix32tree_insert(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask,
    3. uintptr_t value)
    4. {
    5. uint32_t bit;
    6. ngx_radix_node_t *node, *next;
    7. bit = 0x80000000;从最左位开始,判断key
    8. //10000000000000000000000000000000
    9. node = tree->root;
    10. next = tree->root;
    11. //32位 一位一位来
    12. //1->right
    13. //0->left
    14. while (bit & mask) {
    15. if (key & bit) {
    16. next = node->right;
    17. } else {
    18. next = node->left;
    19. }
    20. if (next == NULL) {
    21. break;
    22. }
    23. bit >>= 1;
    24. node = next;
    25. }
    26. //判断是否初始化(是否为空)
    27. if (next) {
    28. if (node->value != NGX_RADIX_NO_VALUE) {
    29. return NGX_BUSY;
    30. }
    31. node->value = value;
    32. return NGX_OK;
    33. }
    34. //如果next为中间节点,且为空,继续查找且申请路径上为空的节点
    35. //比如找key=1000111,在找到10001时next为空,那要就要申请三个节点分别存10001,100011,1000111,
    36. //1000111最后一个节点为key要插入的节点
    37. while (bit & mask) {
    38. next = ngx_radix_alloc(tree);
    39. if (next == NULL) {
    40. return NGX_ERROR;
    41. }
    42. next->right = NULL;
    43. next->left = NULL;
    44. next->parent = node;
    45. next->value = NGX_RADIX_NO_VALUE;
    46. if (key & bit) {
    47. node->right = next;
    48. } else {
    49. node->left = next;
    50. }
    51. bit >>= 1;
    52. node = next;
    53. }
    54. node->value = value;
    55. return NGX_OK;
    56. }

    删除节点

    删除一个节点和插入节点的操作几乎一样,不过要注意两点:

    1)如果删除的是叶子节点,直接从基数树中删除,并把这个节点放入free链表

    2)如果不是叶子节点,把value值置为NGX_RADIX_NO_VALUE

    1. ngx_int_t
    2. ngx_radix32tree_delete(ngx_radix_tree_t *tree, uint32_t key, uint32_t mask)
    3. {
    4. uint32_t bit;
    5. ngx_radix_node_t *node;
    6. bit = 0x80000000;
    7. node = tree->root;
    8. //根据key和掩码查找
    9. while (node && (bit & mask)) {
    10. if (key & bit) {
    11. node = node->right;
    12. } else {
    13. node = node->left;
    14. }
    15. bit >>= 1;
    16. }
    17. if (node == NULL) {//没有找到
    18. return NGX_ERROR;
    19. }
    20. //node不为叶节点直接把value置为空
    21. if (node->right || node->left) {
    22. if (node->value != NGX_RADIX_NO_VALUE) {//value不为空
    23. node->value = NGX_RADIX_NO_VALUE;//置空value
    24. return NGX_OK;
    25. }
    26. return NGX_ERROR;//value为空,返回error
    27. }
    28. //node为叶子节点,直接放到free区域
    29. for ( ;; ) {//删除叶子节点
    30. if (node->parent->right == node) {
    31. node->parent->right = NULL;//
    32. } else {
    33. node->parent->left = NULL;
    34. }
    35. //把node链入free链表
    36. node->right = tree->free;//放到free区域
    37. tree->free = node;//free指向node
    38. //假如删除node以后,父节点是叶子节点,就继续删除父节点,
    39. //一直到node不是叶子节点
    40. node = node->parent;
    41. if (node->right || node->left) {//node不为叶子节点
    42. break;
    43. }
    44. if (node->value != NGX_RADIX_NO_VALUE) {//node的value不为空
    45. break;
    46. }
    47. if (node->parent == NULL) {//node的parent为空
    48. break;
    49. }
    50. }
    51. return NGX_OK;
    52. }

    查找

    这个函数是这四个函数中最简单的一个,就是根据key值查询,如果找到返回value值,没有找到返回NGX_RADIX_NO_VALUE。

    1. uintptr_t
    2. ngx_radix32tree_find(ngx_radix_tree_t *tree, uint32_t key)
    3. {
    4. uint32_t bit;
    5. uintptr_t value;
    6. ngx_radix_node_t *node;
    7. bit = 0x80000000;
    8. value = NGX_RADIX_NO_VALUE;
    9. node = tree->root;
    10. while (node) {
    11. if (node->value != NGX_RADIX_NO_VALUE) {
    12. value = node->value;
    13. }
    14. if (key & bit) {
    15. node = node->right;
    16. } else {
    17. node = node->left;
    18. }
    19. bit >>= 1;//往下层查找
    20. }
    21. return value;
    22. }

    申请节点

    ngx_radix_alloc为基数树申请节点:

    1)如果free链表不为空,直接从上面取下一个空闲节点

    2)free链表为空,则申请一个节点

    1. static void *
    2. ngx_radix_alloc(ngx_radix_tree_t *tree)
    3. {
    4. char *p;
    5. if (tree->free) {//如果free中有可利用的空间节点
    6. p = (char *) tree->free;//指向第一个可利用的空间节点
    7. tree->free = tree->free->right;//修改free
    8. return p;
    9. }
    10. if (tree->size < sizeof(ngx_radix_node_t)) {//如果空闲内存大小不够分配一个节点就申请一页大小的内存
    11. tree->start = ngx_pmemalign(tree->pool, ngx_pagesize, ngx_pagesize);
    12. if (tree->start == NULL) {
    13. return NULL;
    14. }
    15. tree->size = ngx_pagesize;//修改空闲内存大小
    16. }
    17. //分配一个节点的空间
    18. p = tree->start;
    19. tree->start += sizeof(ngx_radix_node_t);
    20. tree->size -= sizeof(ngx_radix_node_t);
    21. return p;
    22. }

  • 相关阅读:
    OpenJudge NOI题库 入门 116题 (二)
    如何对 Kubernetes 节点进行运维
    虚拟机安装
    【SpringBoot】68、SpringBoot解决HttpServletRequest中输入流不能重复读的问题
    【面试题】Callable使用
    js数据类型、节流/防抖、点击事件委派优化、过渡动画
    深度学习之 10 卷积神经网络3
    开源大数据集群部署(十三)Ranger 集成Trino
    千寻简Git连接Gitee
    Javase.String 类
  • 原文地址:https://blog.csdn.net/qq_62309585/article/details/128143360