nginx学习笔记(5):高级数据结构ngx_rbtree_t

本文详细介绍了Nginx核心模块使用的红黑树ngx_rbtree_t,包括红黑树的基本概念、ngx_rbtree_t的结构及操作方法,并提供了一个具体的使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ngx_rbtree_t是使用红黑树实现的一种关联容器,nginx的核心模块(如定时器管理、文件缓存模块等)在需要快速检索、查找的场合下都使用了ngx_rbtree_t容器。

什么是红黑树

在介绍ngx_rbtree_t之前,我们先来了解一下红黑树的相关知识。

红黑树实际上是一种自平衡二叉查找树,那么自平衡、二叉树、二叉查找树又是什么呢?

二叉树是每个节点最多有两个子树的树结构,每个节点都可用于存储数据,可以由任一个节点访问它的左右子树或者父节点。

二叉查找树或者是一棵空树,或者是具有下列性质的二叉树:
1)每个节点都有一个作为查找依据的值,所有节点的值互不相同;
2)若左子树不空,则左子树上所有节点的值均小于它的根结点的值;
3)若右子树不空,则右子树上所有节点的值均大于它的根结点的值;
4)左、右子树也分别为二叉排序树;
(注:有些二叉查找树的定义中允许有相同值的节点存在)
这样,一棵二叉查找树的所有元素节点都是有序的。

关于自平衡的概念,我们通过一个例子来理解。
我们知道,一般情况下,二叉查找树的查询复杂度是与目标节点到根节点的距离(即深度)有关的。然而,不断地增加、删除节点,可能造成二叉查找树形态非常不平衡,在极端情形下它会变成单链表,检索效率也就会变得低下。

例如,依次将1、6、8、11、13、15、17、22、25、27添加到一棵普通的空二叉查找树中,它的最终形态如下图:
这里写图片描述

其最终形态相当于单链表了,由于树的深度太大,因此各种操作的效率都会很低下。

什么是自平衡二叉查找树?在不断地向二叉查找树中添加、删除节点时,二叉查找树自身通过形态的变换,始终保持着一定程度上的平衡,即为自平衡二叉查找树。自平衡二叉查找树只是一个概念,它有许多种不同的实现方式,如AVL树和红黑树。

回到我们所讨论的主角——红黑树,红黑树除了符合二叉查找树的一般要求外,它还有如下的额外的特性:
1)节点是红色或黑色;
2)根节点是黑色;
3)所有叶子节点都是黑色(叶子节点是NIL节点,也叫“哨兵”);
4)每个红色节点的两个子节点都是黑色(每个叶子节点到根节点的所有路径上不能有两个连续的红色节点);
5)从任一节点到其每个叶子节点的所有简单路径都包含相同数目的黑色节点。

这些约束加强了红黑树的关键性质从根节点到叶子节点的最大可能路径长度不大于最短可能路径的两倍,这样这个树大致上就是平衡的了。特性4)实际上决定了1个路径不能有两个毗邻的红色节点,那么最短的可能路径都是黑色节点,最长的可能路径有交替的红色节点和黑色节点。根据特性5)可知,所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能大于其他路径长度的两倍。

仍以添加上述元素为例,将其按顺序添加到空的ngx_rbtree_t红黑树容器中,红黑树的最终形态如下图:
这里写图片描述

它的形态相对平衡,满足红黑树的5个特性,最长路径长度不大于最短路径的2倍。
(ngx_rbtree_t红黑树在发现自身满足不了上述的特性时,便会通过旋转子树来使树达到平衡)

红黑树的使用方法

1.ngx_rbtree_node_t结构体
ngx_rbtree_node_t结构体用来表示红黑树中的一个节点,是红黑树实现中必须用到的数据结构,其定义如下:

typedef ngx_uint_t ngx_rbtree_key_t;

typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

struct ngx_rbtree_node_s {
    // key成员是每个红黑树节点的关键字,它必须是整型。红黑树的排序主要依据就是key成员
    ngx_rbtree_key_t       key;  //无符号整型的关键字
    ngx_rbtree_node_t     *left;  // 左子节点
    ngx_rbtree_node_t     *right;  // 右子节点
    ngx_rbtree_node_t     *parent;  // 父节点
    u_char                 color;  // 节点的颜色,0表示黑色,l表示红色
    u_char                 data;  // 仅1个字节的节点数据。由于表示的空间太小,所以一般很少使用
};

一般我们把ngx_rbtree_node_t放到结构体的第一个成员中,这样方便把自定义的结构体强制转换成ngx_rbtree_node_t类型。例如,如果希望容器中元素的数据类型是TestRBTreeNode,那么只需要在第1个成员中放上ngx_rbtree_node_t类型的node即可:

typedef struct {
    // 一般都将ngx_rbtree_node_t节点结构体放在自走义数据类型的第1位,以方便类型的强制转换 
    ngx_rbtree_node_t node;
    ngx_uint_t num;
} TestRBTreeNode;

在调用ngx_rbtree_t容器所提供的方法时,需要的参数都是ngx_rbtree_node_t类型,这时将TestRBTreeNode类型的指针强制转换成ngx_rbtree_node_t即可。

2.ngx_rbtree_t结构体
红黑树容器由ngx_rbtree_t结构体承载,ngx_rbtree_t结构体的定义如下:

typedef struct ngx_rbtree_s ngx_rbtree_t;

// 为解决不同节点含有相同关键字的元素冲突问题,红黑树设置了ngx_rbtree_insert_pt指针,这样就可以灵活地添加冲突元素
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); 

struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root;      // 指向树的根节点。注意,根节点也是数据元素
    ngx_rbtree_node_t     *sentinel;  // 指向NIL哨兵节点
    ngx_rbtree_insert_pt   insert;    // 表示红黑树添加元素的函数指针,它决定在添加新节点时的行为究竟是替换还是新增
};

ngx_rbtree_insert_pt类型的insert成员的意义在哪里呢?

红黑树是一个通用的数据结构,它的节点(或者称为容器的元素)可以是包含基本红黑树节点的任意结构体。对于不同的结构体,很多场合下是允许不同的节点拥有相同的关键字的。例如,不同的字符串可能会散列出相同的关键字,这时它们在红黑树中的关键字是相同的,然而它们又是不同的节点,这样在添加时就不可以覆盖原有同名关键字节点,而是作为新插入的节点存在。因此,在添加元素时,需要考虑到这种情况。将添加元素的方法抽象出ngx_rbtree_insert_pt函数指针可以很好地实现这一思想,用户也可以灵活地定义自己的行为。Nginx帮助用户实现了3种简单行为的添加节点方法,如下表:
这里写图片描述

以ngx_str_rbtree_insert_value为例,其节点的标识符是字符串,红黑树第一排序依据仍是节点的key关键字,第二排序依据则是节点的字符串。因此,使用ngx_str_rbtree_insert_value时表示红黑树节点的结构体必须是ngx_str_node_t:

typedef struct {
    ngx_rbtree_node_t  node;
    ngx_str_t          str;
} ngx_str_node_t;

3.红黑树容器、红黑树节点的操作方法
红黑树容器操作的方法如下表:
这里写图片描述

在初始化红黑树时,需要先分配好保存红黑树的ngx_rbtree_t结构体,以及ngx_rbtree_node_t类型的哨兵节点,并选择或者自定义ngx_rbtree_insert_pt类型的节点添加函数。

对于红黑树的每个节点来说,它们都具备下表中的7个方法:
这里写图片描述

红黑树使用示例

1)初始化
首先分配rbtree红黑树容器结构体以及哨兵节点sentinel,本例以key关键字作为每个节点的唯一标识,这样就可以采用预设的ngx_rbtree_insert_value方法了,然后调用ngx_rbtree_init方法初始化红黑树:

ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
ngx_rbtree_init(&rbtree, &sentinel, ngx_rbtree_insert_value);

2)添加元素
本例的结构体采用上述自定义的TestRBTreeNode结构体,向红黑树中依次添加1、6、8、11、13、15、17、22、25、27:

TestRBTreeNode rbTreeNode[10];
rbTreeNode[0].num = 1;
rbTreeNode[1].num = 6;
rbTreeNode[2].num = 8;
rbTreeNode[3].num = 11;
rbTreeNode[4].num = 13;
rbTreeNode[5].num = 15;
rbTreeNode[6].num = 17;
rbTreeNode[7].num = 22;
rbTreeNode[8].num = 25;
rbTreeNode[9].num = 27;
for(i = 0; i < 10; i++)
{
    rbTreeNode[i].node.key = rbTreeNode[i].num;
    ngx_rbtree_insert(&rbtree, &rbTreeNode[i].node);
}

3)节点操作

// 找出当前红黑树中最小的节点,参数不使用根节点,使用任一节点也是可以的
ngx_rbtree_node_t *tmpnode = ngx_rbtree_min(rbtree.root, &sentinel);
// 检索节点,例如寻找key为13的节点
ngx_uint_t lookupkey = 13;
tmpnode = rbtree.root;
TestRBTreeNode *lookupNode;
while (tmpnode != &sentinel) {
    if (lookupkey != tmpnode->key) {
        // 根据key关键字与当前节点的大小比较,决定是检索左子树还是右子树
        tmpnode = (lookupkey < tmpnode->key) ? tmpnode->left: tmpnode->right;
        continue:
    }
    // 找到了值为13的树节点
    lookupNode = (TestRBTreeNode*) tmpnode;
    break;
}
// 删除节点,例如删除刚刚找到的节点
ngx_rbtree_delete(&rbtree, &lookupNode->node);

参考资料:
陶辉.深入理解Nginx 模块开发与架构解析.北京:机械工业出版社,2013

<think>嗯,用户现在问的是Nginx高级数据结构,特别是ngx_queue_t的用途。之前用户已经询问过关于request_pool_size销毁的原因,可能现在想深入了解Nginx内部的实现机制。我需要先回忆一下ngx_queue_t的相关知识。 首先,ngx_queue_t是Nginx中一个双向链表的实现。用户可能想知道为什么Nginx要自己实现这样的数据结构,而不是使用C语言标准库里的结构。这可能涉及到性能优化、内存管理或者跨平台兼容性的问题。 接着,用户可能对具体的使用场景感兴趣。比如在HTTP模块中,Nginx如何处理请求,哪些部分会用到这种链表结构。需要举一些例子,比如维护定时事件、管理HTTP请求等,说明ngx_queue_t的实际应用。 另外,用户可能想了解这种数据结构的特点,比如轻量级、不侵入数据结构的实现方式。需要解释侵入式和非侵入式链表的区别,以及为什么Nginx选择这种方式。可能还要提到内存管理的优势,比如内存池的配合使用,减少内存碎片。 还要考虑用户可能的疑问,比如与其他数据结构如红黑树的区别。这时候需要指出链表和树的不同应用场景,比如链表适合频繁插入删除,而红黑树适合快速查找。说明Nginx如何根据不同的需求选择不同的数据结构。 另外,用户可能对如何在自己的模块中使用ngx_queue_t感兴趣,可能需要简单的代码示例,但用户问题中没有明确要求,所以可能不需要详细代码,但可以简要说明其使用方式,比如初始化、插入节点等步骤。 最后,总结ngx_queue_t的优势,比如高效、灵活、节省内存,以及为什么在Nginx中这样的设计是合理的,适合高并发场景的需求。同时确保回答结构清晰,符合之前用户要求的层次分明的格式,比如分点说明用途、设计特点、优势比较等。</think>Nginx 内部设计了许多高效的自定义数据结构以适配高并发场景,`ngx_queue_t` 是其中一种双向链表的实现,其核心价值在于**轻量级、无侵入、内存友好**的设计。以下是其关键用途与设计逻辑: --- ### 一、`ngx_queue_t` 的核心用途 1. **轻量级链表操作** - 用于需要频繁插入、删除但无需复杂查找的场景(如定时器事件管理、HTTP 请求阶段处理器链)。 - 示例: - 维护超时事件(`ngx_event_timer_rbtree` 结合红黑树和队列管理)。 - HTTP 请求处理阶段(`phase_engine` 中按顺序调用各阶段处理函数)。 2. **无侵入式设计** - 链表节点(`ngx_queue_t`)**不嵌入业务数据结构**,而是通过指针关联,避免强制耦合。 例如: ```c typedef struct { ngx_queue_t queue; // 链表节点 void *data; // 实际业务数据指针 } ngx_custom_node_t; ``` - 优势:同一业务结构可同时加入多个链表,灵活性高。 3. **内存池友好** - 节点内存通常从 Nginx 的 `ngx_pool_t` 内存池分配,生命周期与请求或连接绑定,避免内存泄漏。 --- ### 二、`ngx_queue_t` 的设计特点 1. **极简结构体** 仅包含前驱(`prev`)和后继(`next`)指针,无额外开销: ```c typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; }; ``` 2. **高效操作宏** 提供宏直接操作链表,避免函数调用开销(如 `ngx_queue_insert_head`、`ngx_queue_remove`)。 3. **与红黑树互补** - **`ngx_queue_t`**:适合顺序遍历或频繁增删。 - **`ngx_rbtree_t`**:适合快速查找(如定时器、共享内存键值存储)。 - 两者常结合使用(例如定时器用红黑树管理超时时间,用队列维护活跃事件)。 --- ### 三、对比传统链表的优势 | 特性 | `ngx_queue_t` | 传统链表(如 Linux 内核 `list_head`) | |---------------------|--------------------------------|--------------------------------------| | **内存占用** | 更小(仅 2 指针) | 类似 | | **数据耦合** | 无侵入式 | 通常需嵌入结构体 | | **多链表支持** | 通过指针关联,灵活 | 需预先定义嵌入节点 | | **内存管理** | 深度整合 Nginx 内存池 | 依赖外部分配机制 | --- ### 四、典型应用场景 1. **HTTP 请求处理链** 各处理阶段(如 `NGX_HTTP_SERVER_REWRITE_PHASE`)通过链表串联处理函数。 2. **事件定时器** 超时事件按时间排序存入红黑树,活跃事件通过队列管理。 3. **模块配置合并** 多个模块的配置指令通过链表合并(如 `listen` 指令的 `ngx_listening_t` 列表)。 --- ### 五、使用示例(伪代码) ```c // 定义业务数据结构 typedef struct { int id; ngx_queue_t link; // 链表节点 } ngx_custom_data_t; // 初始化链表 ngx_queue_t head; ngx_queue_init(&head); // 插入节点 ngx_custom_data_t *node = ngx_palloc(pool, sizeof(ngx_custom_data_t)); node->id = 100; ngx_queue_insert_tail(&head, &node->link); // 遍历链表 ngx_queue_t *q; for (q = ngx_queue_head(&head); q != ngx_queue_sentinel(&head); q = ngx_queue_next(q)) { ngx_custom_data_t *data = ngx_queue_data(q, ngx_custom_data_t, link); // 处理 data->id } ``` --- ### 总结 `ngx_queue_t` 是 Nginx 为高并发场景量身定制的双向链表,其**无侵入、低开销、内存池集成**的特性,使其在事件管理、请求处理等核心流程中成为高效的基础设施。与红黑树等结构配合,共同支撑 Nginx 的高性能架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值