是我没有把方案3给描述清楚 :)
方案3是方案2的变体
P2:
直接修改http/ngx_http_write_filter_module.c的源码 在发包前 直接从全局shm中申请limit来控制发包大小
可以肯定的是 方案P2能解决我们的问题 但是 P2修改了ngx核心的代码 于是我们继续提出P2.1
P2.1:
通过改变ngx_modules.c中filter module的顺序 使global_rate_limit_body_filter正好是ngx_http_write_filter_module之前的第一个执行 即如下
top_body_filter -> ... -> global_rate_limit_body_filter -> ngx_http_write_filter_module -> NULL
global_rate_limit_body_filter的其它逻辑与P2相同 且最后不再调用如下进行返回
return ngx_http_next_body_filter(r, in);
而是它直接return状态码 相当于把老的ngx_http_write_filter_module给屏蔽了 我们自己实现了个带全局速度限顶的"ngx_http_write_filter_module"
接着 对P2.1进一步进行一般化推广,得到P3,也就是方案三
P3:
挂个body_filter的钩子函数 并且在filter中的位置如P2.1 但是仍使用如下方式返回
return ngx_http_next_body_filter(r, in);
P3能够很好的解决上述黑客攻击的问题 能够进行自适应地限速(因为body_filter链的执行是由写事件触发)
P3的伪代码描述如下:(粗略)
quota data structrue in shm {
quota_max=100000 //时间段总quota
quota=50000 //当前时间段quota剩余
refresh_prets //每隔一段时间
refresh_span //quota=quota_max
spinlock_atomic //quota消费:quota-=delta_sent
}
global_rate_limit_access_phase_handler{
初始化本连接的ctx
ctx->quota=-1024 //可调参数
}
global_rate_limit_body_filter {
如果本连接ctx->quota为负
抢shm中的配额(|ctx->quota|)
如果失败
//delay quota's refresh_span
ngx_add_timer //此处如何实现? <<----难点
return NGX_AGAIN
如果成功
记录当前连接ctx->quota=0
ret=ngx_http_next_body_filter(r, in);
发包完毕之后 计算出delta_sent_bytes
抢shm中的配额(delta_sent_bytes)
如果失败
记录当前连接ctx->quota=-delta_sent_bytes
//delay quota's refresh_span
ngx_add_timer
return NGX_AGAIN
如果成功
return NGX_OK
}
在 2015年10月30日星期五 UTC+8下午3:38:00,agentzh写道:
Hello!
2015-10-30 15:32 GMT+08:00 Yun Thanatos:
> 但是这种方案也是有弊端的:
>
> 因为单个连接的 rate = (total_quota / total_request_concurrency)
>
> 如果有黑客使用高并发的下载方式,由于其配额分配时占总体比例变大,但是其带宽是有限的,会导致大部分配额被其浪费掉了,如此服务的整体质量就会下降
>
这就需要你在 Lua 里面自己实现聪明的分摊策略了。分摊也不一定是均摊。不过我觉得直接限制某个粒度的客户端并发连接数其实更简单更有效一些,当然,看具体的业务要求了。
> 又或者 这是机制与策略的问题?
>
是。
> 另外 关于第三种方案 您是怎么看的呢?
>
你说的第三种方案,说实话,我没看懂如何能实现你要的效果。毕竟 nginx 的输出过滤器是异步的,无论如何都需要 timer 结合
delayed event 来搞下游写的限速,就是 limit_rate 的做法。
Regards,
-agentzh