Nacos 限流最佳实践
Tomcat 限流
Nginx 限流
限制访问速率
限制并发连接数
黑名单
总结
作者:风卿(Nacos 社区 committer)
本文系投稿。如果胖友有想要投稿的内容,欢迎后台留言。哇咔咔。
Nacos自开源以来,版本迭代速度很快,已经发布了0.9版本,准备发1.0的正式版本,支持企业使用Nacos生产高可用。在生产环境,Nacos首先需要保证自身服务的稳定性,在正常的运行环境下不会出现服务挂掉的情况。当然在一些依赖的系统出问题的时候(比如磁盘和DB),Nacos服务会受到影响,需要监控系统发现这些问题并能及时的介入处理。
作为高性能的服务发现和配置管理服务,Nacos也是存在自己的性能基线的,当瞬时的高峰流量超过自身的性能基线的时候,需要对高峰流量进行限流,以保证整体服务的健康运行而不影响到其他核心应用。
Nacos基于spring boot使用内嵌的tomcat,tomcat线程分为acceptor线程和worker线程,acceptor线程负责从内核accept队列中取出连接并交给worker线程,而worker线程则负责处理连接(读取参数、执行处理、返回响应等)
acceptCount
当tomcat不能及时处理新的连接时,内核中新建的连接将会进入连接队列排队,acceptCount参数能够设置tomcat accept连接队列大小,当新的连接数超过acceptCount则拒绝连接,立即返回给client,不会让client一直等待造成响应很慢或超时
maxConnections
接受了的连接需要由worker线程调度处理,当worker线程处理的连接数越来越多时,处理的速度会越来越慢造成client响应时间变长,需要根据系统和机器情况设置合理的maxConnections,当连接数到达maxConnections时,不再接受新的连接,让新连接排队等待,超出队列长度则直接拒绝。
maxThreads
maxThreads参数设置tomcat的最大线程数,过高的线程数会让系统运行的负载过高、响应变慢,过低的线程数让系统的资源利用率太低,需要根据实际的运行情况(CPU、IO等)设置合理的最大线程数。
tomcat限流只能做到自身负载的调节,在实际生产环境中还远远不够,需要依赖Nacos自身的限流来提高系统的限流能力。
Nacos的open API都是基于http协议,可以很方便地使用nginx来做限流,不需要自身再开发限流模块来支持各种限流策略。nginx的基本使用以及nginx+lua模块安装网上资源很丰富,这里就不再介绍。
Nacos每个接口执行的代价不尽相同,一般来说写操作代价比读操作大,与此同时还有高频操作和低频操作之分,SDK调用的接口一般来说是高频接口,容易出现问题,所以在生产环境需要将这些接口区别对待,根据服务自身的实际情况采取合理的限流策略,以防错用方打垮Nacos服务。下面介绍一下Nacos在生产环境的几种限流场景
limit_get_config对读操作进行限流,正常使用Nacos获取动态配置一般就启动和运行时修改配置推送到client,获取配置相对来说是低频操作,如果频繁获取配置肯定是client有错用或者应用不正常(比如数据平台任务failover重试任务)
limit_req_zone $limit_key zone=limit_get_config:10m rate=10r/s;
server {
listen 8080;
server_name localhost;
location /nacos/v1/cs/configs {
if ($request_method = POST ) {
rewrite ^ /limit_publish_config_url last;
}
rewrite ^ /limit_get_config_url last;
}
location /limit_get_config_url {
set $limit_key "$remote_addr+$arg_dataid+$arg_group+$arg_tenant";
limit_req zone=limit_get_config burst=10 nodelay;
proxy_pass http://127.0.0.1:8848/nacos/v1/cs/configs;
}
}
limit_req_zone设置限流key和内存大小,以及请求速率
limit_key由[ip,dataId,group,tenant]四元组组成,可以防止client错用频繁访问单个配置
burst设置漏桶算法的桶的大小
nodelay设置非延迟模式,如果桶满了则会马上返回给客户端错误码
proxy_pass指定后端Nacos的接口url
limit_publish_config对写操作进行限流,可以有效防止热点写问题。对同一个数据的高频写会触发mysql的行锁,从而导致mysql的多线程任务因等待行锁排队,最终导致mysql所有操作都超时服务不可用。这里通过nginx lua模块获取post请求的参数,设置limit_key
limit_req_zone $limit_key zone=limit_publish_config:10m rate=5r/s;
location /limit_publish_config_url {
set $dataId $arg_dataid;
set $group $arg_group;
set $tenant $arg_tenant;
set $limit_key "$remote_addr+$dataId+$group+$tenant";
lua_need_request_body on;
rewrite_by_lua '
ngx.req.read_body()
local post_args = ngx.req.get_post_args()
if post_args["dataId"] then
ngx.var.dataId = post_args["dataId"];
ngx.var.group = post_args["group"];
ngx.var.tenant = post_args["tenant"];
ngx.var.limit_key = ngx.var.remote_addr.."+"..ngx.var.dataId.."+"..ngx.var.group;
if ngx.var.tenant then
ngx.var.limit_key = ngx.var.limit_key.."+"..ngx.var.tenant;
end
end
';
limit_req zone=limit_publish_config burst=10 nodelay;
proxy_pass http://127.0.0.1:8848/nacos/v1/cs/configs;
}
lua_need_request_body on;用于读取post请求的request body
rewrite_by_lua指令在http rewrite阶段执行lua代码
perclient对单个client的所有请求限制低于500QPS,可以有效防止单台client的重试攻击
limit_req_zone $remote_addr zone=perclient:10m rate=500r/s;
server {
listen 8080;
server_name localhost;
limit_req zone=perclient burst=250 nodelay;
location / {
proxy_pass http://127.0.0.1:8848/nacos/v1/cs/configs;
}
}
perserver限制整个Nacos服务的QPS,Nacos的服务部署在nginx之后,nginx可以保证到达Nacos的流量不会打垮Nacos
limit_req zone=perserver burst=1000 nodelay;
/nacos/v1/cs/configs/listener接口是Nacos的长连接通道,一般来说,一个client一个长连接就可以满足生产需求。limit_conn_client限制client的连接数不超过10个,limit_conn_server限制Nacos单机(8核16G内存)支撑最多9000个长连接,最多可以同时服务9000个应用节点
limit_conn_zone $remote_addr zone=limit_conn_client:10m;
limit_conn_zone $server_name zone=limit_conn_server:10m;
server {
listen 8080;
server_name localhost;
location = /nacos/v1/cs/configs/listener {
limit_conn limit_conn_client 10;
limit_conn limit_conn_server 9000;
proxy_pass http://127.0.0.1:7001/diamond-server/config.co;
tcp_nodelay on;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
当生产环境发现有错用的client影响到Nacos服务,可以使用nginx黑名单限制client的访问
deny 30.5.125.70;
从被限制的IP访问Nacos
curl -X GET "http://{IP}:8080/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test" -i
Nginx返回403状态码给client,禁止client访问
HTTP/1.1 403 Forbidden
Server: nginx/1.13.5
Date: Fri, 15 Mar 2019 08:34:33 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
403 Forbidden
403 Forbidden
nginx/1.13.5
有时候通过IP维度直接限制client访问所有Nacos接口粒度过大,会导致应用服务不可用,可以将读操作和写操作分开,禁止client写Nacos,依然允许其进行读
# 1 limit read, 0 no limit
map "$remote_addr" $limit_read {
#10.2.24.252 1;
default 0;
}
# 1 limit write, 0 no limit
map "$remote_addr" $limit_write {
#10.2.24.252 1;
default 0;
}
location /some_url_to_write {
if ($limit_write = 1) {
return 403;
}
}
map指令匹配remote_addr变量,如果$remote_addr变量在ip黑名单里面,则设置limit_read和limit_write参数为1,否则为0
在写接口中对limit_write做判断,如果禁写则返回403状态码
IP黑名单功能是nginx提供的基础能力,能够限制某些IP的访问,但是一般一个应用会有很多台机器,当一个应用出问题的时候,会有很多IP访问都有问题,通过IP的维度来限制访问达不到预期,需要有应用的维度来限制
namespace(命名空间)是一个可以区分不同应用的维度,不同的应用一般会使用不同的namespace,这样可以在namespace维度对服务的访问进行限制
map "$arg_tenant" $limit_namespace {
af884cf8-1719-4e07-a1e1-3c4c105ab237 1;
#a6c745b7-fd92-4c1d-be99-6dc98abfe3dc 1;
default 0;
}
location /some_url {
if ($limit_namespace = 1) {
return 403;
}
}
通过匹配namespace是否在黑名单中来设置limit_namespace变量,然后在访问的url中判断limit_namespace的值,如果为1返回403状态码
ak维度:使用一个ak代表一个应用,不同的应用在启动的时候设置不同的ak。client在发起请求的时候会带上ak参数到server端,在nginx层对请求的参数进行解析,对特定的应用的ak进行访问限制
map "$http_Spas_AccessKey" $limit_ak {
6839c164bb344cdc93107f08eda8a136 1;
default 0;
}
location /some_url {
if ($limit_ak = 1) {
return 403;
}
}
本文简单介绍了Nacos在实际生产环境中如何通过限流来提高自身服务的稳定性,除了自身设置tomcat参数,还可以通过高性能的nginx作为前端对流量进行过滤提高限流能力。文中难免会有个别错误或者遗漏,如果大家有更多的限流或者提高稳定性的办法可以在Nacos官网提出来。
在此我向大家推荐一个架构学习交流群。交流学习群号:993070439 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系,还能领取免费的学习资源。