1. 背景
产品服务器设置了「外网带宽使用率 >= 100%,统计粒度5分钟,连续1次满足条件则每1小时告警一次」告警策略,基本上我每天都收到多条告警信息。
2. 云平台监控数据
2.1. 服务器带宽
5mbps (Million bits per second 兆比特每秒)
// 1字节 = 8比特
= 1024 x 5 kbps
≈ 625KBps (Kilo Byte)
2.2. 外网带宽监控数据
放大一点查看数据:
2.3. 监控数据总结
2.3.1 腾讯云监控数据解读
- 腾讯云的点是基于1分钟的聚合数据(也就是1分钟里的最高带宽)
- 告警策略是5分钟内连续1次(大约也就是5分钟出现两次高点就告警了,还不确定次是按1分钟的聚合来计算还是1分钟里面两次也触发告警)
2.3.2 产品带宽使用情况解读
服务器部署了 web 服务 和 用于存储图片资源,报表导出和资源(平均200KB左右,非 kb)一张,部分页面有时候会放好几张图片展示。如果是连续的浏览充电站、商城,或者导出报表,是很容易触发2次峰值而导致报警的。
3. 问题排查
刚才只是大约估计了一下可能的情况,排查问题还是要系统地去看,需要挑选了告警的时间区间,分别进行数据统计。
我们服务器对外的带宽服务有:
- WEB 服务。通过 Nginx 访问
- Web Socket 服务。通过 socket 协议访问
- 桩 Agent 服务。通过 socket 协议访问
WEB 服务比较简单,nginx 上都有日志,可以通过日志的 bodysize 统计。其他两个是通过 socket 的,持续通讯的,所以我选择了数据日志的 log 进行大概统计。
3.1 Web 服务带宽排查
日志
以下是我的 nginx access.log 日志格式:
111.95.199.165 - - [03/Dec/2021:14:45:01 +0800] "GET /app/user?token=zDIQHuVnVFW3kabQQ2emvEVJTBlPxlsECG0FPOUkEj7/hC/TPyhnG6fbAPadjwoLmS4= HTTP/1.1" 101 276 "https://servicewechat.com/wx969418ad95ed8efa/124/page-frame.html" "Mozilla/5.0 (Linux; Android 9; vivo X21A Build/PKQ1.180819.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.99 XWEB/3148 MMWEBSDK/20211001 Mobile Safari/537.36 MMWEBID/8757 MicroMessenger/8.0.16.2040(0x28001056) Process/appbrand0 WeChat/arm32 Weixin NetType/4G Language/zh_CN ABI/arm64 MiniProgramEnv/android" "-"
可以通过配置调整 ngx_http_log_module 的 log format,如:
log_format nginx '\$remote_addr - \$remote_user [\$time_local] '
'"\$request" \$status \$body_bytes_sent \$request_time '
'"\$http_referer" "\$http_user_agent"';
$body_bytes_sent
发送给客户端的字节数,不包括响应头的大小; 该变量与Apache模块mod_log_config里的“%B”参数兼容。
$bytes_sent
发送给客户端的总字节数。
查看指令
可以看出 nginx 日志的 body_bytes_sent 是字节,所以可以通过以下指令查看数据量($10 是 body_bytes_sent 所在位置,需要根据实际调整;grep 内容是某一分钟的时间):
cat access.log|grep "2021:15:25"|awk '{BYTE+=$10}END{print "client_kbyte_out="BYTE/1024/1024"MB"}'
结果分析
最终实际查看一天下来的流量也400MB,几个高峰的分钟段也就几M,都在预期合理的范围,并且形成不了持续的拥堵情况。
3.2 Web Socket 服务
Web Socket 只有 connect 时的信息会写在 nginx access log 上,不过平时通讯的信息都有手动写相应的 access log(注意排除 业务的log信息)。经统计 web socket 的 access log 比较小,一天只有十几M的数据,基本可以忽略。
注意:心跳包也需要统计进去。
3.3 桩 Agent 服务
桩agent 是通过端口直连的,没有经过 nginx,log 比较分散,统计了几个大的agent的 log(只统计 access log),占用的大小都不大,基本不形成高峰。但由于数量多,没有最终确定某个时间段的高峰值。
4. 解决方案
4.1 限制大文件访问
通过 nginx 配置,限制最大的带宽,可以稍微缓解一次访问的压力。避免一个大文件的访问,就长期占用了所有的外网带宽。
Nginx 限流有两种方式:
- 控制速率
- 控制并发数
a) 控制速率
正常限流
ngx_http_limit_req_module 模块提供限制请求处理速率能力,使用了漏桶算法(leaky bucket)。下面例子使用 nginx limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。
==在 nginx.conf http 中添加限流配置:==
格式:limit_req_zone key zone rate
http {
limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}
==配置 server,使用 limit_req 指令应用限流==
server {
location / {
limit_req zone=myRateLimit;
proxy_pass http://my_upstream;
}
}
- key :定义限流对象,binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
- zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
- rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。
处理突发流量
上面例子限制 10r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。
server {
location / {
limit_req zone=myRateLimit burst=20;
proxy_pass http://my_upstream;
}
}
burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。
此处,burst=20 ,若同时有21个请求到达,Nginx 会处理第一个请求,剩余20个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于21,将拒绝处理多余的请求,直接返回503.
不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。
因此,burst 往往结合 nodelay 一起使用。
server {
location / {
limit_req zone=myRateLimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
}
nodelay 针对的是 burst 参数,burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。
这就达到了速率稳定,但突然流量也能正常处理的效果。
b) 限制连接数
ngx_http_limit_conn_module 提供了限制连接数的能力,利用 limit_conn_zone 和 limit_conn 两个指令即可。下面是 Nginx 官方例子:
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
...
limit_conn perip 10;
limit_conn perserver 100;
}
limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。
limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。
需要注意的是:只有当 request header 被后端server处理后,这个连接才进行计数。
c) 测试
# c 是并发数, n 是次数
ab -n 10 -c 10 [测试的URL]
4.2 云存储
使用独立的云存储,专门存放资源文件。
4.3 内网代替外网
Agent 与桩之间是通过外网连接的,可以考虑多加一台服务器,与 产品服务是一个内网的。Agent 先连接到一台转发服务器,与 ECMP直接通过内网连接。
端口转发参考:《Agent端口映像》
4.4 平均带宽降低
略
4.5 调整告警策略
鉴于当前告警实际对访问的影响可忽略,将触发次数调整为2次再告警。(后面再也没有收到告警了~)
5. 参考
《Nginx 的两种限流方式》
《常用的服务器日志分析命令》