⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 「风卿」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

作者:风卿(Nacos 社区 committer)

Nacos 限流最佳实践

Nacos自开源以来,版本迭代速度很快,已经发布了0.9版本,准备发1.0的正式版本,支持企业使用Nacos生产高可用。在生产环境,Nacos首先需要保证自身服务的稳定性,在正常的运行环境下不会出现服务挂掉的情况。当然在一些依赖的系统出问题的时候(比如磁盘和DB),Nacos服务会受到影响,需要监控系统发现这些问题并能及时的介入处理。

作为高性能的服务发现和配置管理服务,Nacos也是存在自己的性能基线的,当瞬时的高峰流量超过自身的性能基线的时候,需要对高峰流量进行限流,以保证整体服务的健康运行而不影响到其他核心应用。

Tomcat 限流

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等)设置合理的最大线程数。

Nginx 限流

tomcat限流只能做到自身负载的调节,在实际生产环境中还远远不够,需要依赖Nacos自身的限流来提高系统的限流能力。

Nacos的open API都是基于http协议,可以很方便地使用nginx来做限流,不需要自身再开发限流模块来支持各种限流策略。nginx的基本使用以及nginx+lua模块安装网上资源很丰富,这里就不再介绍。

Nacos每个接口执行的代价不尽相同,一般来说写操作代价比读操作大,与此同时还有高频操作和低频操作之分,SDK调用的接口一般来说是高频接口,容易出现问题,所以在生产环境需要将这些接口区别对待,根据服务自身的实际情况采取合理的限流策略,以防错用方打垮Nacos服务。下面介绍一下Nacos在生产环境的几种限流场景

限制访问速率

1、限制单个接口的请求QPS

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代码

2、限制单机访问QPS

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;
}
}

3、限制 Nacos 服务 QPS

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;
}
}

黑名单

1、IP黑名单

当生产环境发现有错用的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

<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.13.5</center>
</body>
</html>

2、读写黑名单分离

有时候通过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状态码

3、应用黑名单

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官网提出来。

文章目录
  1. 1. Nacos 限流最佳实践
  2. 2. Tomcat 限流
  3. 3. Nginx 限流
    1. 3.1. 限制访问速率
      1. 3.1.1. 1、限制单个接口的请求QPS
      2. 3.1.2. 2、限制单机访问QPS
      3. 3.1.3. 3、限制 Nacos 服务 QPS
    2. 3.2. 限制并发连接数
    3. 3.3. 黑名单
      1. 3.3.1. 1、IP黑名单
      2. 3.3.2. 2、读写黑名单分离
      3. 3.3.3. 3、应用黑名单
  4. 4. 总结