原创 雷泽 2025-07-09 18:30 上海
在云原生架构高速迭代的背景下,基础设施的性能瓶颈与安全隐患成为技术演进的关键挑战。本文系统记录团队基于Rust语言改造Nginx组件的完整技术路径,为超大规模流量调度场景提供全新解决方案,也为同类基础设施升级提供可复用的工程经验。
🚀 通过FFI技术,将Rust核心业务逻辑与Nginx C模块集成,实现了Rust与C语言的交互,提升了系统的性能和稳定性。
🛠️ 开发ngx_http_rust_module SDK,为Rust模块开发提供了便利,实现了纯Rust编码的业务功能,提高了工程价值。
💡 采用Cloudflare的Pingora框架构建DLB 2.0流量调度平台,该平台具有云原生架构、高性能、协议生态、安全演进和扩展能力等优势。
⚙️ DLB 2.0采用声明式配置管理,通过YAML文件进行配置,支持热加载机制,实现了流量无损的配置更新,并与Nginx的server/path路由匹配逻辑完全兼容。
🛡️ DLB 2.0通过模块化Filter链设计,支持按需插拔流量处理组件,并采用线程安全的运行时结构和高效前缀树路由,确保了性能和可维护性。
原创 雷泽 2025-07-09 18:30 上海
在云原生架构高速迭代的背景下,基础设施的性能瓶颈与安全隐患成为技术演进的关键挑战。本文系统记录团队基于Rust语言改造Nginx组件的完整技术路径,为超大规模流量调度场景提供全新解决方案,也为同类基础设施升级提供可复用的工程经验。
配置解析在DLB 2.0的配置模型中, server 、 location 、 upstream 三者构成层次化路由架构: server 作为虚拟服务单元,通过 Vec<LocationConf> 聚合任意数量的 location 路由规则。 location 作为请求路径处理器,可独立关联至不同的 upstream 服务组。 upstream 采用原子引用计数机制( Arc )封装配置,通过Arc::strong_count() 实时监控引用状态,避免冗余配置拷贝,基于Rust的并发安全特性,最终设计为 Arc<Mutex<UpstreamConf>> 结构: Mutex 保障多线程环境下的内部可变性,支撑配置热更新需求。 Arc 维持跨线程的只读共享能力,确保访问高效性。main thread解析完server.yaml与upstream.yaml后,将生成两个核心哈希映射: server 配置映射表:关联域名与路由规则集。 upstream 线程安全容器:托管负载均衡服务组状态。- id: "hjob.shizhuang-inc.com"
server_name: "hjob.shizhuang-inc.com"
service_in:
- "default_80"
- "default_443"
redirect: true
location:
- path: "/"
access_rule_names:
- "access_allow_d803a06f39ad4dcd8dfe517359a33a61"
- "access_deny_all"
client_max_body_size: "100M"
proxy_headers:
- "clientport:$remote_port"
- "Upgrade:$http_upgrade"
- "Connection:$http_connection"
- "Host:$host"
- "X-Forwarded-For:$proxy_add_x_forwarded_for"
- "X-Forwarded-Proto:$scheme"
proxy_pass: "http://hangzhou-csprd-hjob-8899"
- name: "hangzhou-csprd-hjob-8899"
peers:
- server: "1.1.1.1:8899"
weight: 100
backup: false
down: false
- server: "2.2.2.2:8899"
weight: 1
backup: false
down: false
max_fails: 3
fail_timeout: "10s"
max_connections: 1000
运行时配置转化上述的 ServerConf 与 UpstreamConf 面向的是用户,特点是易于理解与维护、支持YAML反序列化。而为了专注运行时效率(比如负载均衡策略中的字符串转化为枚举类型),我们会将 UpstreamConf 转化为 RunTimeUpStream 结构, ServerConf 同理。/// A map of server names to their respective configurations.
#[serde(skip)]
pub servers: HashMap<String, Arc<Mutex<ServerConf>>>,
/// A map of upstream names to their respective configurations.
#[serde(skip)]
pub upstreams: HashMap<String, Arc<Mutex<UpstreamConf>>>,
转化之后得到全局唯一的 GlobalConf :impl TryFrom<&UpstreamConf> for RunTimeUpStream {
type Error = Error;
fn try_from(value: &UpstreamConf) -> std::result::Result<Self, Self::Error> {
}
}
pub static GLOBAL_CONF: Lazy<RwLock<GlobalConf>> = Lazy::new(|| {
RwLock::new(GlobalConf {
main_conf: MainConf::default(),
runtime_upstreams: HashMap::with_capacity(16),
runtime_servers: HashMap::with_capacity(16),
host_selectors: HashMap::with_capacity(16),
})
});
#[derive(Default)]
pub struct GlobalConf {
// main static configuration
pub main_conf: MainConf,
//one-to-one between upstreams and runtime_upstreams
pub runtime_upstreams: HashMap<String, Arc<RunTimeUpStream>>,
//one-to-one between servers and runtime_servers;
pub runtime_servers: HashMap<String, Arc<RunTimeServer>>,
//one service one host selector
pub host_selectors: HashMap<String, Arc<HostSelector>>,
}
其中需要留意的是成员 prefix_nested_map ,为了确保最长匹配优先,我们将 prefixes: Vec<(String, String)> 转化为了 NestedHashMap 结构, NestedHashMap 为一个嵌套哈希结构,可基于域名分段实现高效检索。/// A struct to manage server selection based on host names.
///
/// This struct contains three fields: `equal`, `prefix`, and `regex`.
/// The `equal` field is a HashMap that stores server names and their corresponding IDs
/// when the server name exactly matches the host.
/// The `prefix` field is a Vec of tuples, where each tuple contains a prefix and its corresponding server ID.
/// The `regex` field is a Vec of tuples, where each tuple contains a Regex and its corresponding server ID.
///
/// The `HostSelector` struct provides methods to insert server names and IDs,
/// and to match a given host name with a server ID based on the rules defined in the struct.
#[derive(Clone)]
pub struct HostSelector {
pub equal: HashMap<String, String>,
pub prefixes: Vec<(String, String)>, //原始前通配符数据
pub prefix_nested_map: NestedHashMap, // 嵌套哈希结构优化匹配效率
pub regex: Vec<(Regex, String)>,
}
路由匹配讲完了域名匹配,我们再深入路由匹配,在开始之前,我们先回顾一下Nginx的location指令。#[derive(Debug, Clone)]
pub struct NestedHashMap {
data: HashMap<String, NestedHashMap>, //层级域名节点
value: Option<String>, // 终端节点关联服务器ID
}
impl NestedHashMap
{
/// 基于域名分段实现高效检索(从右向左匹配)
pub(crate) fn find(&self, key: &str) -> Option<String> {
let tokens = key.split('.').collect::<Vec<&str>>();
let mut current_map = self;
let mut result = None;
// 遍历域名层级(如 www.example.com → [com, example, www])
for token in tokens.iter().rev() {
// 优先记录当前层级的有效值(实现最长匹配)
if current_map.value.is_some() {
result = Some(current_map.value.as_ref().unwrap());
}
// 向下一级域名跳转
let child = current_map.data.get(*token);
match child{
Some(child) => {
current_map = child;
}
None => {
break;
}
}
}
result.map(|value| value.to_owned())
}
}
location 通常在 server{} 块内定义,也可以嵌套到 location{} 内,虽然这不是一种推荐的配置方式,但它确实是被语法规则支持的, localtion 语法主要有如下几种形式:※ 修饰符语义及优先级(依匹配精度降序排列) = :精确匹配(Exact Match),URI必须与模式完全一致时生效(最高优先级)。 ^~ :最佳前缀匹配(Prefix Match),选中最长非正则路径后终止搜索(优先级次于=)。 ~ :区分大小写的正则匹配(Case-Sensitive Regex)。 ~* :不区分大小写的正则匹配(Case-Insensitive Regex)。 @ :内部定位块(Named Location),仅限 try_files 或 error_page 指令调用,不对外暴露。Nginx在解析完 location 之后会进行一系列的工作,主要包括:分类: 根据location的修饰符参数标识不同的类型,同时去除name前面的修饰符排序: 对一个server块内的所有location进行排序,经过排序之后将location分为了3类通用类型,通用类型的location将建立一棵最长前缀匹配树正则类型,顺序为配置文件中定义的顺序,正则会用pcre库先进行编译内部跳转类型,顺序也为配置文件中定义的顺序拆分:将分类的3种类型拆分,分门别类的处理其中最复杂的是最长前缀匹配树的构建,假设location规则如下,构造一棵最长前缀匹配树会经过如下几个步骤:把locations queue变化locations list,假设一个location的name是A的话,所有以A前缀开头的路由节点都会放到A节点的list里(最长前缀匹配)。2.按照上述步骤递归初始化A节点的所有list节点,最终得到下面的list。3.在上述创建的list基础上,确定中间节点,然后从中间节点把location分成两部分,然后递归创建左右子树,最后处理list队列,list队列创建的节点会加入到父节点的tree中,最终将生成一棵多叉树。现在你应该已经明白了最长前缀匹配树的构建流程,让我们回到2.0的设计上来,这部分同样维护了三个结构分别对应精确匹配、正则匹配以及最长前缀匹配。Syntax:location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default:—
Context:server, location
精确匹配、正则匹配比较简单,我们重点介绍最长前缀匹配,最长前缀匹配树的构建基本上是把Nginx代码原原本本的翻译过来,通过 create_list() 分组节点、 create_tree() 生成多叉树。通过 find_location 遍历树结构查找最长有效路径,其中路径比较函数 path_cmp() 确保按字典序定位子树,匹配成功时返回( need_stop, location ),其中 need_stop 标志是否中止搜索(模拟 ^~ 行为)。#[derive(Clone, Default)]
#[allow(unused)]
/// A struct representing a shared runtime server configuration.
pub struct RunTimeServer {
/// Unique identifier for the server.
pub id: String,
/// Name of the server.
pub server_name: String,
/// Indicates whether the server should redirect requests.
pub redirect: bool,
/// A HashMap storing equal-matched locations, where the key is the path and the value is the location.
pub equal_match: HashMap<String, Arc<RunTimeLocation>>,// 精确匹配字典
/// A Vec storing regex-matched locations, where each tuple contains a Regex and the location.// 正则匹配队列
pub regex_match: Vec<(Regex, Arc<RunTimeLocation>)>,
/// The root node of the static location tree.
pub prefix_root: Option<Arc<static_location_tree::TreeNode>>,
}
路由重写路由重写是实现请求路径动态转换的核心能力,在语义层面,我们完全兼容Nginx的配置语义。 regex replacement [flag] ,同时采用预编译正则引擎,在路由加载期完成规则编译。pub fn find_location(path: &str, node: &Arc<TreeNode>) -> Option<(bool, Arc<RunTimeLocation>)> {
let mut node = Some(node);
let mut uri_len = 0;
let mut search_node = None;
while let Some(current) = node {
let n = std::cmp::min(current.path.len(), path.len() - uri_len);
let node_path = ¤t.path[..n];
let temp_path = &path[uri_len..uri_len + n];
match path_cmp(node_path, temp_path) {
std::cmp::Ordering::Equal => {
uri_len += n;
search_node = Some((current.need_stop, current.val.clone()));
node = current.tree.as_ref();
if uri_len >= path.len() { break; }
}
std::cmp::Ordering::Greater => node = current.left.as_ref(),
std::cmp::Ordering::Less => node = current.right.as_ref(),
}
}
search_node
}
模块化Filter链Pingora 引擎已经将请求生命周期划分了足够细的各个阶段,为了更精细化控制同一phase执行的各个Filter,可通过自定义的 ProxyFilter trait,与 Pingora 引擎的phase关联起来。pub enum RewriteFlags {
Break,
Last,
Redirect,
Permanent,
NONE,
}
pub struct RewriteRule {
pub reg_source: String,
pub reg: Regex,
pub target: String,
pub flag: RewriteFlags,
}
ProxyFilter 主要包含四个方法: phase : Filter 的执行阶段, 生命周期阶段锚点,可以根据实际需要进行扩展插入更细粒度的阶段进行请求处理。 name : Filter的名称。 order : 在同一个phase内Filter的执行顺序。 handle : Filter 的执行逻辑,若返回的是 HandleResult::Continue ,则表示当前filter执行完成,继续执行下一个 filter,否则停止filter chain 的执行动作。#[async_trait]
pub trait ProxyFilter: Sync + Send {
fn phase(&self) -> ProxyFilterPhase;
fn name(&self) -> ProxyFilterName;
fn order(&self) -> i32;
async fn handle(&self, _session: &mut Session, _ctx: &mut ProxyContext) -> HandleResult {
HandleResult::Continue
}
}
目前我们已经实现的Filter包括但不限于:#[derive(Debug, PartialEq, Clone, EnumString)]
pub enum HandleResult {
/// 表示当前filter执行完成,继续执行下一个 filter。
Continue,
/// 表示当前filter操作被中断,停止filter chain 的执行动作。
Break,
}
AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。
鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑