wmproxy

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

项目地址

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

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

HTTP限流

HTTP限流是在HTTP请求处理过程中,对请求进行限制的一种技术手段。其目的是防止系统过载,保护系统的稳定性和可用性。HTTP限流可以基于不同的策略和方法,例如基于时间窗口、令牌桶、漏桶等。

常见的HTTP限流方法包括:

  • 基于时间窗口:这种方法将一段时间划分为若干个时间窗口,每个时间窗口内只允许一定数量的请求通过。例如,每秒只允许20个请求通过。
  • 令牌桶:令牌桶算法允许突发流量,只要有令牌就可以处理请求,当没有令牌时,请求就被拒绝。这种方法适用于处理突发流量的情况。和时间窗口结合时,如果当前时间段已经有20个请求,此时触发令牌桶brust,将当前的流量进行延时处理。
  • 漏桶:漏桶算法不允许突发流量,无论何时都只能按照一定的速率处理请求。这种方法适用于处理稳定流量的情况。

在进行HTTP限流时,需要考虑系统的实际情况和需求,选择合适的限流策略和方法。同时,还需要对系统的性能和负载进行充分的测试和评估,以确保系统的稳定性和可用性。

方案选择

在此项目中,选择的是基于时间窗口及令牌桶做组合使用进行限制,以下做个例子,配置

limit="rate=10r/s brust=10"

效果将是每秒钟限制10条请求,可以允许突发的10个令牌桶做一秒的延时,在下一秒允许通行。

sequenceDiagram
participant C
participant S
C->>S: 第一秒请求数据10条
Note right of S: 当前记录请求10条
S->>C: 返回成功

C->>S: 第一秒继续请求数据1条
Note right of S: 当前记录请求10条+1条令牌桶
S->>C: 延时一秒再进行后续处理返回成功

C->>S: 第一秒继续请求数据9条
Note right of S: 当前记录请求10条+10条令牌桶
S->>C: 延时一秒再进行后续处理返回成功

C->>S: 第一秒继续请求数据(1条-N条)
Note right of S: 当前记录当前秒已满,返回拒绝
S->>C: 直接返回错误,频率过快,已超时

C-->>S: 第二秒请求数据1条
Note right of S: 清除第一秒的记录10条<br>10条令牌桶转化第二秒10条记录<br>故当前为请求数10条+1条令牌桶
S->>C: 延时一秒过时行后续处理返回成功

C-->>S: 第三秒请求数据1条
Note right of S: 清除第二秒的记录10条<br>1条令牌桶转化第三秒1条记录<br>故当前为请求数1条
S->>C: 直接返回成功

以上是时序加令牌的请求数据和返回情况

限流配置

类似于nginx中的limit_req配置,分为limit_req_zonelimit_req两部分,可分为两个类,一个为zone,一个为关联到zone名称的具体项目

#[derive(Debug, Clone)]
pub struct LimitReqZone {
/// 键值的匹配方式
pub key: String,
/// IP个数
pub limit: u64,
/// 周期内可以通行的数据
pub nums: u64,
/// 每个周期的时间
pub per: Duration,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LimitReq {
zone: String,
burst: u64,
}

然后在http的根目录下配置当前的zone空间,为一个HashMap结构,可以配置多种zone结构

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpConfig {
// ...
#[serde_as(as = "HashMap<_, DisplayFromStr>")]
#[serde(default = "HashMap::new")]
pub limit_req_zone: HashMap<String, LimitReqZone>,
} #[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CommonConfig {
// ...
#[serde_as(as = "Option<DisplayFromStr>")]
pub limit_req: Option<LimitReq>,
}

因为并不是任何的请求都要进行限流,所以此处为Option,如果子级未配置,父级有配置,子级将会应用父级的配置。

以下展示在toml格式的配置

# 反向代理相关,七层协议为http及https
[http] [http.limit_req_zone]
limit = "{client_ip} limit=10m rate=10r/s"
less = "{client_ip} limit=10m rate=10r/min" # 反向代理中的具体服务,可配置多个多组
[[http.server]]
bind_addr = "0.0.0.0:82"
server_name = "soft.wm-proxy.com"
limit_req = "zone=limit brust=10" # 按请求路径进行rule匹配,可匹配method,看具体的处理的内容如文件服务或者负载均衡
[[http.server.location]]
limit_req = "zone=less brust=1"
rule = "/root"
file_server = { browse = true } [[http.server.location]]
rule = "/api"
file_server = { browse = true }

这样子就可以实现api不同的进行不同的限速方案,可以实现更好的通用效果。

配置解析

  • LimitReqZone解析

    需要将"{client_ip} limit=10m rate=10r/s"转成LimitReqZone结构,此处我们用的是FromStr接口,用空格分割,第一个字段为key,后续用=做分割,得取相应的值
impl FromStr for LimitReqZone {
type Err = ProxyError; fn from_str(s: &str) -> Result<Self, Self::Err> {
let v = s.split(" ").collect::<Vec<&str>>();
let key = v[0].to_string();
let mut limit = 0;
let mut nums = 0;
let mut per = Duration::new(0, 0);
for idx in 1..v.len() {
let key_value = v[idx].split("=").map(|k| k.trim()).collect::<Vec<&str>>();
if key_value.len() <= 1 {
return Err(ProxyError::Extension("未知的LimitReq"));
}
match key_value[0] {
"limit" => {
let s = ConfigSize::from_str(key_value[1])?;
limit = s.0;
}
"rate" => {
let rate_key = key_value[1]
.split("/")
.map(|k| k.trim())
.collect::<Vec<&str>>();
if rate_key.len() == 1 {
return Err(ProxyError::Extension("未知的LimitReq"));
} let rate = rate_key[0].trim_end_matches("r");
nums = rate
.parse::<u64>()
.map_err(|_e| ProxyError::Extension("parse error"))?; let s = ConfigDuration::from_str(rate_key[1])?;
per = s.0;
}
_ => {
return Err(ProxyError::Extension("未知的LimitReq"));
}
}
} Ok(LimitReqZone::new(key, limit, nums, per))
}
}
  • LimitReq解析

需要将"zone=less brust=1"转成LimitReq结构,此处我们用的是FromStr接口,用空格分割,将每个值用=做分割,得取相应的值

impl FromStr for LimitReq {
type Err = ProxyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let v = s.split(" ").collect::<Vec<&str>>();
let mut zone = String::new();
let mut brust = 0;
for idx in 0..v.len() {
let key_value = v[idx].split("=").map(|k| k.trim()).collect::<Vec<&str>>();
if key_value.len() <= 1 {
return Err(ProxyError::Extension("未知的LimitReq"));
}
match key_value[0] {
"zone" => {
zone = key_value[1].to_string();
}
"brust" => {
brust = key_value[1]
.parse::<u64>()
.map_err(|_e| ProxyError::Extension("parse error"))?;
}
_ => {
return Err(ProxyError::Extension("未知的LimitReq"));
}
}
} Ok(LimitReq::new(zone, brust))
}
}

限制实现

首先我们配置一个静态可访问的全局变量,因为所有的线程操作都需要汇总到此时判定是否合格

每个命名空间里,都将存储不超过规格数据的IP,如果超过将直接返回失败

pub struct LimitReqData {
/// 记录所有的ip数据的限制情况
ips: HashMap<String, InnerLimit>,
/// IP个数
limit: u64,
/// 周期内可以通行的数据
nums: u64,
/// 每个周期的时间
per: Duration,
/// 最后清理IP的时间
last_remove: Instant,
}

全局静态数据

lazy_static! {
static ref GLOABL_LIMIT_REQ: RwLock<HashMap<&'static str, LimitReqData>> =
RwLock::new(HashMap::new());
}

返回结果

#[derive(Debug)]
pub enum LimitResult {
Ok,
Refuse,
Delay(Duration),
}

所以的判断是否通过,我们将通过以下函数返回相应的结果,从而使外部的函数可以进行相应的处理。

impl LimitReqData {
pub fn recv_new_req(key: &str, ip: &String, burst: u64) -> ProtResult<LimitResult> {
let mut write = GLOBAL_LIMIT_REQ
.write()
.map_err(|_| ProtError::Extension("unlock error"))?;
if !write.contains_key(&*key) {
return Ok(LimitResult::Ok);
}
write.get_mut(key).unwrap().inner_recv_new_req(ip, burst)
}
}

小结

我们通过全局共享数据,需要加锁获取该数据,来判定整体的KEY的流量情况,可能是IP,可能是IP+Cookie等,来灵活的针对用户限流还是针对IP限流或者其它的业务情况进行合理的安排。

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

31. 干货系列从零用Rust编写正反向代理,HTTP限流的实现(limit_req)的更多相关文章

  1. (转)Spring Boot干货系列:(七)默认日志logback配置解析

    转:http://tengj.top/2017/04/05/springboot7/ 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候,是带着下面几个问题来查资料的, ...

  2. (转)Spring Boot干货系列:(四)开发Web应用之Thymeleaf篇

    转:http://tengj.top/2017/03/13/springboot4/ 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boot对Web开发的支持. 正文 Sp ...

  3. 【转】Spring Boot干货系列:(一)优雅的入门篇

    转自Spring Boot干货系列:(一)优雅的入门篇 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做 ...

  4. Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用

    Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用 原创 2017-04-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章介绍了一些基础,但都是静 ...

  5. Spring Boot干货系列:(七)默认日志框架配置

    Spring Boot干货系列:(七)默认日志框架配置 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候, ...

  6. Spring Boot干货系列:(五)开发Web应用JSP篇

    Spring Boot干货系列:(五)开发Web应用JSP篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 上一篇介绍了Spring Boot中使用Thymeleaf模板引擎,今天 ...

  7. Spring Boot干货系列:(四)Thymeleaf篇

    Spring Boot干货系列:(四)Thymeleaf篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boo ...

  8. Spring Boot干货系列:(一)优雅的入门篇

    Spring Boot干货系列:(一)优雅的入门篇 2017-02-26 嘟嘟MD 嘟爷java超神学堂   前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社 ...

  9. Spring Boot干货系列:(十二)Spring Boot使用单元测试(转)

    前言这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求 Service层单元测试 Controller层单元测试 新断言assertThat使用 单元 ...

  10. 【WEB API项目实战干货系列】- 导航篇(十足干货分享)

    在今天移动互联网的时代,作为攻城师的我们,谁不想着只写一套API就可以让我们的Web, Android APP, IOS APP, iPad APP, Hybired APP, H5 Web共用共同的 ...

随机推荐

  1. uniapp 只选择月份与日的时间选择器

    1.使用 <picker> 组件的 mode 属性设置为 "multiSelector",然后通过设置 range 属性来提供可选的月份和日的列表. <templ ...

  2. Feign的超时时间如何设置,我研究了4种情况

    大家好,我是三友~~ 今天来聊一聊前段时间看到的一个面试题,也是在实际项目中需要考虑的一个问题,Feign的超时时间如何设置? Feign的超时时间设置方式并不固定,它取决于Feign在项目中是如何使 ...

  3. 产品代码都给你看了,可别再说不会DDD(三):战略设计

    这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...

  4. 【路由器】OpenWrt 配置使用

    目录 Web 界面 汉化 root 密码 ssh 升级 LuCI 美化 锐捷认证 MentoHUST MiniEAP 防火墙 开放端口 端口转发 IPv6 USB 安装 USB 驱动 自动挂载 Ext ...

  5. 日志开源组件(六)Adaptive Sampling 自适应采样

    业务背景 有时候日志的信息比较多,怎么样才可以让系统做到自适应采样呢? 拓展阅读 日志开源组件(一)java 注解结合 spring aop 实现自动输出日志 日志开源组件(二)java 注解结合 s ...

  6. Go 并发编程 - 并发安全(二)

    什么是并发安全 并发情况下,多个线程或协程会同时操作同一个资源,例如变量.数据结构.文件等.如果不保证并发安全,就可能导致数据竞争.脏读.脏写.死锁.活锁.饥饿等一系列并发问题,产生重大的安全隐患,比 ...

  7. 《SQL与数据库基础》17. InnoDB引擎

    目录 InnoDB引擎 逻辑存储结构 架构 内存结构 磁盘结构 后台线程 事务原理 事务基础 redo log undo log MVCC 基本概念 隐式字段 undo log版本链 readView ...

  8. 文盘Rust -- 生命周期问题引发的 static hashmap 锁

    2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了.项目地址:https://github.com/jiashiwen/interactc ...

  9. Oracle为表添加约束

    转载自:https://blog.csdn.net/qq_38662525/article/details/94192475 创建一个学生表和院系表:院系表为主表,学生表为从表   create ta ...

  10. mpi转以太网Plus模块连接300PLC实现MPI转modbus通信

    西门子200/300PLC转以太网同时实现PPI/MPI/DP转modbus通信 产品简介 MPI-ETH-XD1.0plus是在MPI-ETH-XD1.0的基础上,以太网口增加了支持与西门子带网口P ...