首页 > 其他分享 >37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现

37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现

时间:2023-12-27 09:11:40浏览次数:46  
标签:files 负载 cache req uri try location 均衡 path

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

nginx中的try_files

  • 语法:try_files file … uri;try_files file … = code;
  • 作用域:server location
    • 首先:按照指定的顺序检查文件是否存在,并使用第一个找到的文件进行请求处理
    • 其次:处理是在当前上下文中执行的。根据 root 和 alias 指令从 file 参数构造文件路径。
    • 然后:可以通过在名称末尾指定一个斜杠来检查目录的存在,例如"$uri/"
    • 最后:如果没有找到任何文件,则进行内部重定向到最后一个参数中指定的 uri。

注:只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部的 URL 的指向。最后一个参数是回退 URL 且必须存在,否则会出现内部 500 错误。命名的 location 也可以使用在最后一个参数中。

应用场景

1、前端路由处理:

location / {
    try_files $uri $uri/ /index.html;
    # $uri指请求的uri路径,$uri/表示请求的uri路径加上一个/,例如访问example.com/path,则会依次尝试访问/path,/path/index.html,/index.html
    # /index.html表示如果仍未匹配到则重定向到index.html
}

这种场景多用于单页应用,例如vue.js等前端框架的路由管理。当用户在浏览器中访问一个非根路径的路径时,由于这些路径都是由前端路由管理的,nginx无法直接返回正确的静态文件,因此需要将请求重定向到统一的路径,这里是/index.html,由前端路由控制页面的展示。
2、图片服务器:

location /images/ {
  root /data/www;
  error_page 404 = /fetch_image.php;
  try_files $uri $uri/ =404;
}
location /fetch_image.php {
  fastcgi_pass 127.0.0.1:9000;
  set $path_info "";
  fastcgi_param PATH_INFO $path_info;
  fastcgi_param SCRIPT_FILENAME /scripts/fetch_image.php;
  include fastcgi_params;
}

这种场景多用于图片服务器,当用户访问图片时,先尝试在本地文件系统中查找是否有该文件,如果找到就返回;如果没有找到则会转发到fetch_image.php进行处理,从远程资源服务器拉取图片并返回给用户。

实现方案

当前nginx方案的实现,是基于文件的重试,也就是所谓的伪静态,如果跨目录的服务器就很麻烦了,比如:

location /images/ {
  root /data/upload;
  try_files $uri $uri/ =404;
}
location /images2/ {
  root /data/www;
  try_files $uri $uri/ =404;
}

上面的我们无法同时索引两个目录下的结构。即我假设我访问/images/logo.png无法同时查找/data/upload/logo.png/data/www/logo.png是否存在。

当前实现方案从try_files变成try_paths也就是当碰到该选项时,将当前的几个访问地址重新进入路由

例:

[[http.server.location]]
rate_limit = "4m/s"
rule = "/root/logo.png"
file_server = { browse = true }
proxy_pass = ""
try_paths = "/data/upload/logo.png /data/www/logo.png  /root/README.md"

[[http.server.location]]
rule = "/data/upload"
file_server = { browse = true }

[[http.server.location]]
rule = "/data/www"
file_server = { browse = true }

除非碰到返回100或者200状态码的,否则将执行到最后一个匹配路由。

源码实现

    1. 要能循环遍历路由
    1. 当try_paths时要避免递归死循环
    1. 当try_paths时可能会调用自己本身,需要能重复调用

以下主要源码均在reverse/http.rs

  • 实现循环
    主要的处理函数为deal_match_location,函数的参数为
#[async_recursion]
async fn deal_match_location(
    req: &mut Request<Body>,
    // 缓存客户端请求
    cache: &mut HashMap<
        LocationConfig,
        (Sender<Request<Body>>, Receiver<ProtResult<Response<Body>>>),
    >,
    // 该Server的配置选项
    server: Arc<ServerConfig>,
    // 已处理的匹配路由
    deals: &mut HashSet<usize>,
    // 已处理的TryPath匹配路由
    try_deals: &mut HashSet<usize>,
) -> ProtResult<Response<Body>>

当前在Rust中的异步递归会报如下错误

recursion in an `async fn` requires boxing
a recursive `async fn` must be rewritten to return a boxed `dyn Future`
consider using the `async_recursion` crate: https://crates.io/crates/async_recursion

所以需要添加#[async_recursion]或者改成Box返回。

参数其中多定义了两组HashSet用来存储已处理的路由及已处理的TryPath路由。

将循环获取合适的location,如果未找到直接返回503错误。

let path = req.path().clone();
let mut l = None;
let mut now = usize::MAX;
for idx in 0..server.location.len() {
    if deals.contains(&idx) {
        continue;
    }
    if server.location[idx].is_match_rule(&path, req.method()) {
        l = Some(&server.location[idx]);
        now = idx;
        break;
    }
}
if l.is_none() {
    return Ok(Response::status503()
        .body("unknow location to deal")
        .unwrap()
        .into_type());
}

当该路由存在try_paths的情况时:

// 判定该try是否处理过, 防止死循环
if !try_deals.contains(&now) && l.try_paths.is_some() {
    let try_paths = l.try_paths.as_ref().unwrap();
    try_deals.insert(now);
    let ori_path = req.path().clone();
    for val in try_paths.list.iter() {
        deals.clear();
        // 重写path好方便做数据格式化
        req.set_path(ori_path.clone());
        let new_path = Helper::format_req(req, &**val);
        // 重写path好方便后续处理无感
        req.set_path(new_path);
        if let Ok(res) = Self::deal_match_location(
            req,
            cache,
            server.clone(),
            deals,
            try_deals,
        )
        .await
        {
            if !res.status().is_client_error() && !res.status().is_server_error() {
                return Ok(res);
            }
        }
    }
    return Ok(Response::builder()
        .status(try_paths.fail_status)
        .body("not valid to try")
        .unwrap()
        .into_type());
}

其中会将req中的path进行格式化的重写以方便处理:

// 重写path好方便做数据格式化
req.set_path(ori_path.clone());
let new_path = Helper::format_req(req, &**val);
// 重写path好方便后续处理无感
req.set_path(new_path);

如果不存在try_paths将正常的按照路由的处理逻辑,该文件服务器或者反向代理,并标记该路由已处理。

deals.insert(now);
let clone = l.clone_only_hash();
if cache.contains_key(&clone) {
    let mut cache_client = cache.remove(&clone).unwrap();
    if !cache_client.0.is_closed() {
        println!("do req data by cache");
        let _send = cache_client.0.send(req.replace_clone(Body::empty())).await;
        match cache_client.1.recv().await {
            Some(res) => {
                if res.is_ok() {
                    log::trace!("cache client receive response");
                    cache.insert(clone, cache_client);
                }
                return res;
            }
            None => {
                log::trace!("cache client close response");
                return Ok(Response::status503()
                    .body("already lose connection")
                    .unwrap()
                    .into_type());
            }
        }
    }
} else {
    log::trace!("do req data by new");
    let (res, sender, receiver) = l.deal_request(req).await?;
    if sender.is_some() && receiver.is_some() {
        cache.insert(clone, (sender.unwrap(), receiver.unwrap()));
    }
    return Ok(res);
}

小结

try_files在nginx中提供了更多的可能,也方便了伪静态文件服务器的处理。我们在其中的基础上稍微改造成try_paths来适应处理提供多路由映射的可能性。

点击 [关注][在看][点赞] 是对作者最大的支持

标签:files,负载,cache,req,uri,try,location,均衡,path
From: https://www.cnblogs.com/wmproxy/p/wmproxy37.html

相关文章

  • 一天一个测试名词---负载测试
    一、负载测试的含义及其目的负载测试是一种测试方法,用于评估系统在正常和峰值负载情况下的性能表现。负载测试主要是通过模拟实际用户访问系统的行为,以确定系统在不同负载条件下的稳定性、可靠性和性能水平。负载测试的目的是验证系统的性能指标,例如响应时间、吞吐量、并发用户数等......
  • 解决k8s调度不均衡问题
    前言在近期的工作中,我们发现k8s集群中有些节点资源使用率很高,有些节点资源使用率很低,我们尝试重新部署应用和驱逐Pod,发现并不能有效解决负载不均衡问题。在学习了Kubernetes调度原理之后,重新调整了Request配置,引入了调度插件,才最终解决问题。这篇就来跟大家分享Kubernete......
  • ORA-01113: file 69 needs media recovery ORA-01110: data file 69: 'E:\FWPTDB\D
    继续上一篇写1、当解决了ORA-01033:ORACLEinitializationorshutdowninprogress 这个问题后重新连接此数据库的时候又出现以下问题ORA-01113:file69needsmediarecovery ORA-01110:datafile69:'E:\FWPTDB\DBFFILES\HNRZ\HNRZFW.DBF2、解决方案  ......
  • 12V/5V负载开关IC——PC9511/21可编程高精度限流集成28mΩ功率FET
    1概述PC9511/21系列电子保险丝的设计目的是保护输出(OUT)上的电路免受瞬态影响在电源总线(IN)上和大的浪涌电流。同时保护电源总线不受不希望的输出短路的影响以及意外的过载情况。当输出斜坡上升时,浪涌电流为通过限制输出电压的slew速率来限制。转换速率由位于SS引脚。内部小电流源为......
  • C# 中使用 using 关键字和不使用 using 关键字创建 FileStream
    在C#中使用using关键字和不使用using关键字创建FileStream实例之间有一些区别。使用using关键字:using(FileStreamfileStream=newFileStream(filePath,FileMode.Open,FileAccess.Read)){//使用fileStream进行操作}using关键字用于创建FileStream......
  • C++ filesystem 库使用
    一、filesystem介绍filesystem源自boost.filesystem库,在C++17合并进C++标准库中,filesystem中的所有操作是线程不安全的。二、路径相关操作在filesystem库中提供path类来对路径进行操作,后续的相关操作,如打开文件、遍历目录、判断文件类型等,都是需要用path作为参数来指定操作具......
  • 软件架构原理与实战:负载均衡技术的探讨
    1.背景介绍负载均衡(LoadBalancing)是一种在多个计算节点(如服务器、虚拟机、容器等)之间分发任务或请求的技术,以提高系统性能、提高吞吐量、提高可用性和降低单点失败的风险。在现代互联网应用中,负载均衡技术已经成为不可或缺的一部分,例如网站、电子商务、云计算、大数据处理等领域。......
  • LVS负载均衡
    LVS(LinuxvirtualServer)简介​ Linux虚拟服务器。原理​ 在Linux内核中实现了基于IP的数据请求负载均衡调度方案,其体系结构如下所示,终端互联网用户从外部访问公司的外部负载均衡服务器,终端用户的Web请求会发送给LVS调度器,调度器根据自己预设的算法决定将该请求发送给后端的......
  • 36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现
    wmproxywmproxy已用Rust实现http/https代理,socks5代理,反向代理,静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子项目地址国内:https://gitee.com/tickbh/wmproxygithub:https://github.com/......
  • 08信息打点-系统篇&端口扫描&CDN 服务&负载均衡&WAF 防火墙
    一、获取网络信息-服务厂商&网络架构1、每个不同的厂商都有不同的防护策略,获取到厂商信息可以第一时间知道目标服务器的防护情况2、理解外网和内网外网,出口IP:171.40.78.83内网:192.168.1.1WEB内网192.168.1.1可以直接访问外网,外网出口(交换机)做一个映射反向代理,web流量给到171.......