connection和request
- connection 就是一个连接, TCP连接
- 客户端和服务器想要进行通信的话,有很多种方式
- 比如说, TCP的形式或者是UDP形式的
- 通常很多应用都是建立在这个TCP之上的
- 所以, 客户端和服务器通信,使用了TCP协议的话,必然涉及建立TCP连接
- 也就是通常所说的三次握手
- TCP协议它是一个有状态的一个协议,它是有一个完整的状态机的
- 通过那个状态位的不同变化来判断分析的
- 如果先建立了这个TCP连接之后,接下来就可以发送应用层的数据信息
- 比如 HTTP 请求,这就是一个 request
- 这个 request 它是必须建立在 我们的connection之上的
- HTTP协议, 它又是一个无状态的协议
- 所以说connection和request他们连个基本的关系是
- 首先要建立 connection 之后才需要去发送 request
- 这个 connection 和 request 之间对应的关系并非一对一的
- 在打开长连接的过程中,可能在一个 connection 中会发送很多的HTTP的requests请求
- 请求示意图
- 我的客户端想要跟服务器通信去获取服务器上某一些网站的信息
- 就简单以浏览网页这种形式来说,送一个请求
- 也就是在浏览器中去刷新一个URL, 它会发生哪些事呢?
- 第一个肯定需要去建立一个connection,也就发起一个TCP的一个连接
- 客户端首先会给服务器发送一个带有SYN的一个数据包
- 服务器收到这个数据包之后就知道客户端是想发起一个TCP连接
- 它会回应客户端发送的SYN包,并且也发送一个ACK同时它也有一个SYN的标志
- 客户端收到以后给服务器发送一个ACK
- 至此,三次握手就完成了,建立了一个TCP连接
- 那TCP连建立完成之后,这个客户端就可以去发送具体的一个HTTP请求
- 比如我们在 www.baidu.com 中访问
- 这个时候, 请求的是提供的web服务器上的某一个或多个文件
- 默认是80端口,对应我们服务器上的一个外部应用
- 这个时候网站首页中可能包含了很多信息,会发起多个 request 请求
- 这个时候在一个TCP连接之中,可能会对应多个 request 的请求
- 开启了这个长连接的情形下,它为了加速效率
- 会允许你一个connection连接中发送很多个 request
- 同时服务器回应很多 response 报文
- request请求都是要建立在connection之上
limit_conn 模块
1 )基本功能
- 用于限制客户端并发连接数
- 默认编译进 nginx, 通过 --without-http_limit_conn_module 禁用
- 使用共享内存,对所有worker子进程生效
- 如果需要对客户端的并发连接进行限制的话
- 对于一个特定的客户端来说,他第一次发起一个HTTP的请求
- 这个请求被某一个worker子进程所处理
- 假如, 他下一次再发起的这个HTTP请求, 有可能受会到另外一个 worker 子进程
- 因此, 想要对所有客户端连接进行一个统一的控制
- 不同的worker子进程之间必须知道某一个客户端发送过来的连接有几个
- 必须要有一块共享内存, 在共享内存中,我们需要去维护这样一个数据结构
- 这个数据结构中能够保存特定客户端所发送过来的已经建立的这个连接数
- 因此,即使某一次请求被录入到不同的work子进程之后
- 我们的worker子进程也能够去这样一个共享内存中去取出这块数据结构中
- 已经存储的客户端所建立的连接数,从而对客户端的一个连接数进行限制
2 )常用指令
limit_conn_zone
, 其实这个zone指令就是用来定义共享内存的limit_conn_status
, 主要是用来定义限制行为发生的时候,返回给客户端的一个状态limit_conn_log_level
,限制行为发生的时在日志中所记录的这个日志等级limit_con
,这个才是真正去定义客户端的限制的一个并发连接数
那我们来逐个的看每一个指令,它的一个具体用法
2.1 ) limit_conn_zone
- 语法: limit_conn_zone key zone=name:size
- 这是一个固定写法,后面空格会跟一个key
- 这个 key 是一个唯一性的标识,用来标识客户端的唯一性的
- 比如说, 可以根据客户端的一个IP来做客户端的唯一性标识
- 通常情况下,我们会使用一个变量叫一个
$remote_addr
这样一个变量 - 这个是在Nginx 中所内置的一个变量
- 也就是说,当我们部署好Nginx, 你的客户端发送一个请求过来以后
- 在请求中会带上这个
$remote_addr
, Nginx会将客户端的IP赋值给$remote_addr
- 因此用来识别客户端的一个键,可以根据客户端唯一性标识这个IP来对客户端进行限制
- 当然还有很多一些其他的限制,比如说我们可以根据客户端建立的时候
- 有很多token信息来进行一个唯一的token信息来进行一个限速
- 当然不同的形式,需要根据自己的应用场景去定义
- 在实际的应用场景,通常用的最多的都是这个
$remote_addr
- 那在很多情形下,如果存在反向代理的情形,可能通过反向代理中
- 加一个指定的 real_ip 的指令,获取到 real_ip 对 real_ip 进行限速
- 总之,key就是用来定义所限速客户端那个唯一性标识
- 之后 zone=name:size 是一个固定写法,这个name是任意名称都行
- 后面跟一个冒号 和 size,这个size是定义共享内存的空间,以M为单位
- 默认值: 无
- 上下文: http (只能定义在 http 段中,即在http的全局配置信息中定义)
- 示例:
limit_conn_zone $binary_remote_addr zone=addr:10m
- $binary_remote_addr 和 $remote_addr 都是标记客户端ip信息的
- $binary_remote_addr 好处是只使用4个字节的空间,提升处理效率
- $remote_addr 会占用 7 - 15 个字节
- 这里 10m 一般足够使用
2.2 )limit_conn_status
- 语法:limit_conn_status code
- code 是状态码,比如:201, 302,404,...
- 默认值: limit_conn_status 503;
- 上下文:http、server、location
2.3 ) limit_conn_log_level
- 语法: limit_conn_log_level info | notice | warn | error;
- 比如说当我们这个限制行为发生的时候,根据客户的IP来限速
- 我们把它限速为一个并发连接,超过一个并发连接的时候, 不处理,返回503状态码
- 同样,在对应的 log 中进行记录
- 默认值:limit_conn_log_level error;
- 上下文:http、server、location
2.4 ) limit_conn
- 语法:limit_conn zone number;
- 前面的 limit_conn_zone 已经定义了某一个名称的一个共享内存,指定了大小空间
- 这个时候可以直接使用, 这个 zone 其实是要引用前面定义的addr的名称的那个定义
- 之后number就是限速多少
- 默认值: 无
- 上下文:http、server、location
3 ) 配置示例
- 我们想要根据
$binary_remote_addr
也就是客户端的IP去限速 - 并且把每一个客户端IP限速为2,只要超过2个链接,就给它限制住
- 超过2个链接,就直接给他返回 503
示例
conf
http {
limit_conn_zone $binary_remote_addr zone=limit_addr:10m # 这个只能在 http 段中
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
limit_status 503;
limit_conn_log_level warn;
limit_conn limit_addr 2;
limit_rate 50;
}
}
}
重启
- $
/opt/nginx/sbin/nginx -s reload
- 开启 多个 curl 终端 进行
curl localhost
,前2个窗口有结果,后面就会 503
limit_req 模块
1 ) 基本功能
-
用于限制客户端处理请求的平均速率
-
默认编译进 nginx, 通过
--without-http_limit_req_module
禁用 -
使用共享内存, 对所有 woker 子进程生效
-
限流算法: leaky_bucket
上图是算法思想思路
- 它所使用的一个限流算法是一个 leaky_bucket 的算法,limit_req 模块本身类似于一个限流的模块
- 有一些很经典的一些限流算法:比如说像 leaky_bucket 算法,计算器算法(固定窗口),令排桶算法等等
- 这些都是在高并发系统中经常使用的一个限流的一个算法
- 在 Bursty data 图表中,横坐标是一个时间轴,纵坐标是一个流量的轴
- 假如说,随着时间的推移,流量是不均匀的,前两秒钟,它的流量达到了 12 Mbps
- 之后第2秒到第7秒中间没有任何流量, 之后第7秒到第10秒之间, 又平均是2 Mbps的一个流量
- 在这十秒钟,它一共有 12 * 2 + 2 * 3 = 30M 的流量,但是分布很不均匀
- 前2秒突发比较大,2 - 7 s 没有流量, 7 - 10 s 流量较少
- 这个 lkeybucket 算法就是实现了将流量削平来进行限流的动作
- 它把这些突发的流量,包括中间没有的流量给强行的限制到某一个速率上来进行处理
- 也就是 30 / 10 = 3Mbps 的速率,它类似于一个水龙头,使用这样一个漏斗来进行处理
- 从水龙头滴出来的水类似于我们的流量,水龙头的开关大小都用来模拟流量的一个大小
- 对于在这漏斗下面出水的一个速率,它是一个恒定的
- 这个漏桶算法最重要的功能就是实现一个削平流量
- 将突发的流量和不均匀的流量来进行一个强行恒定到某一个数据上来进行处理
- 上面有一个 Bursty flow, Bursty的参数类似于用来定义这样一个桶的大小
- 现在以 3Mbps 的速率来消费这些流量,但是这个流量峰值可能远远超过了3M这样一个流量
- 只要我这个桶没有满的情形下,这个服务是不会被拒绝的
- 只有说这个漏斗中的水完全满了之后,流量完全超过了这个桶的大小之后的才会被拒绝
2 )常见指令
2.1 limit_req_zone
- 用于定义共享内存
- 语法:limit_req_zone key zone=name:size rate=rate
- 默认值: 无
- 上下文:http
- 示例: limit_req_zone $binary_remote_addr zone=one:10m rate=2r/m
- rate=2r/m 每分钟2个请求
- 它会平均处理,比如30s之后再处理第二个
2.2 limit_req_status
- 语法:limit_req_status code;
- 默认值:limit_req_status 503;
- 上下文: http、server、location
2.3 limit_req_log_level
- 语法: limit_req_log_level info | notice | warn | error;
- 默认值: limit_req_log_level error;
- 上下文: http、server、location
2.4 limit_req
- 语法:limit_req zone=name [burst=number] [nodelay | delay=number];
- 这里 burst 就是用来定义桶的大小
- 想要它立即返回的话,必须加个 nodelay 选项
- 默认值: 无
- 上下文: http、server、location
- 示例
- limit_req zone=one;
- limit_conn zone=one burst=5 nodelay;
3 ) 配置示例
conf
http {
# 注意下面的定义
limit_req_zone $binary_remote_addr zone=limit_req:15m rate=2r/m; # 这里rate设置的很小,用于演示
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
error_log logs/limit_req_error.log info; # 这个配置是特意加上的,用于查看错误输出
# 注意下面的定义
limit_req_status 504;
limit_req_log_level notice;
limit_req zone=limit_req; # 0s - 30s 内第一次,30s - 1m内 第二次,如果在0s-30s收到第二次请求则返回504
# limit_req zone=limit_req burst=7 nodelay; # 定义 burst 选项,30s之内会继续返回,不会504, 直到 7次之后
}
}
}
啊,我们后面在环境中给大家做实际的演示,让大家看它们两者之间有什么具体的区别啊,它的默认值是没有的啊,适用的上下文也在defDPserver和location中。
比如说我们现在四厘一这种,我们这个时候就直接后面不写这样一个铜的定义大小。
我们直接写limitIQ我们直接用一个name的用法,这也写错了啊,我们来给大家改一下啊,这就是肉啊,就这么东西。
哇,这个也是中文等位啊,也是room等于这样一个模块啊。
ok那接下来我们给大家演示一下啊,呃我们还接着上一节的我们这个配置文件,我们直接给大家做展示啊,户下的con零x,还是我们先在这儿去定义我们这个root是吧?
定义我们limitrequest这样一个room,一个共享内存。limit,我们来REQ若这样一个指令,后面我们引用的k还是我们的客户端IP是吧?
根据不同的客户端IP去限定我们的客户端的平均速率啊之后呢,我们还是要分一个room等于多少。
比如说我们起一个名字limit,我们就有REQ吧之后,比如说我们给它定义成一个十五兆的共享内存,好不好?
之后我们知道还有一个rate,对吧?我们限定速率是多少呢?限定这个服务器处理客户端请求的一个平均速率是多少呢?
比如说我们这个时候把它限定成一个两次请求吧。我们就现在大一点啊,两次请求每分钟注意啊,大家我把这个色的请处理请求量很小,是因为我为了便于给大家演示这个效果啊。
假如说我设定成一分钟处理两个请求。那也就意味着我在三十秒内只能处理一个请求,大家一定要理解这个关系啊,它是一个消频的关系。
比如说我们在这儿,至于去引用的limitIQ我们都知道我们的IQ定义status,我们上面是返回的这个五零三,我们在这以示区别,我们让它返回五零四,好吧,继续我们的limitREQ还有什么呀?
loglevel,我们把它定义成notice吧,好吧,inthenotice这样一个选项一会儿我们可以在具体的日志中去给大家看一下。
那第三个是一个limit什么呀?直接引用我们的limitIQ这样一个对吧?
后面直接使用room,我们来引用我们前面所定义的样共享内存的名称是什么呀?
limitIQ那好,我们现在就写一个,我们再给大家写一个啊。那这个这样一个我直接引用的时候没有去写那个birth,也就是说定义我们那个桶的整体的大小,也就是我们请求的一个大小是吧?
我们IQ比如说我们在这再定义一个zone等于limitIQ这是我们定义一个birth啊,比如说birth等于七,我给大家定一首对,你是no类的denothis是什么意思?
也就是意味。比如说我现在每分钟处理两个请求,也就是意味着我三十秒只处理一个请求。
那我第一次请求之后,我在三十秒内再去请求。第二次的时候肯定是给我返回这样一个五零四的。
但是如果我要定义birars的选项之后的话,它就不会给我返回一个五零四啊,他会直接给我返回一个正确的结果,并且我们这使用是nodelte的话,就非延迟处理,它会立即给我返回结果啊。
那我们现在现在先把这个给钉先给注释掉啊,好吧,那么这个时候先用我们这样一个指令,先检查一下语法,语法没问题是吧?
我们用杠sreload指令。好,这个时候我们现在来尝试一下去访问我们用靠指令logohost啊,这样吧,我先去把上面那个给注销啊,因为我们需要这个也会影响我们做测试啊,我们先把这个做注销了,先给先把这个给注释掉。
好,那们直接这样,我们用call命令啊,这个时候我们就是用call命令,这就是我第一次请求,对吧?
我定的一个每分钟能处理两个请求,那一三十秒处理一个。第一次大家看他肯定返回一个NEX我要看朵尼x。
这个时候假如我请求第二次的时候,大家看它会直接给我返回一个五零四,也就是限速行为。
这个时候已经发生了,这时候候下我再请求,他肯定还是没有返回结果的,再请求,再请求还是不行,直到等到距离。
第一次你请求返回结果之后,距离三十秒之内三十秒后才会再给你返回结果啊。
大家看那么三十秒到了,它会又返回一个结果。那这就是我们这个啊那这样吧,我们把它给定义小一点吧。
比如说我们把它定义成一个,为了大家去看这个效果,我们把它定义成每分钟处理十二个吧,每分钟处理十二个请求,也就意味着我五秒钟处理一个啊,这样我们演示起来会好好看一些时间间隔短一点。
比如说我们第一次请求大家看返回结果了,对吧?
第二次肯定没有第三秒还没有第四秒也没有第五秒,大家看是不是大概相隔间隔五秒有结果了呀。
book大家看还有没有两秒,三秒四秒五秒。
大家看对不对?先间隔五秒又返回来一个结果。所以说我们在这儿所给大家说的这个结果,它一定是大家一定要能理解这个每分钟处理多少些请求啊,所以说我们的这个limitrequest模块,它本身是限制我们客户端的一个平均处理速率的。
他并不是说比如说我要限制每分钟十二个请求啊。
他并不是说在每分钟之内,你连续给他发十二个请求,他都会给你处理的。
并不是这样的啊,他会严格的去按照给你换算成每秒能处理的一个请求个数来进行一个处理啊。
ok那我们接下来来看一下啊,我们这个来看一下,我们都知道我们使用那个loglevel来记录我们这个日志了。
那这个时候我们来看一下OBE亚的NGX有一个nox的errorlog,大家看在这儿,它是不是?
比如说我这个limitrequest模块限制行为发生的时候,大家看它这里的是不是记了一些notice呀?
哦,大家记住是ever啊,我看看error模块啊,我们来看一下啊,这个时候我们来监控一下这样一个模块,我们再去访问一下这样一个地址ofvingcalllocalhost,对吧?
第一次没关系,第二次肯定哎,我们在这没有记录进去,我们的这个啊嗯不对,这个时候我们在来给它啊,因为我们在这没定义我们的日志啊,我们再来引用一个日志吧。
比如说我们在这引用一个日志啊,比如说我们在这定义一个llogerrolog我们来看一下errorlog的一个用法吧啊,应该是这样写的啊,airlog这样airlog,我们后面直接跟我们具体的日志。
比如说我就写到logs下面的这limit杠IQ杠error二有点老的。好吧,我这个时候比如说我就记录info级别的,因为我们这个notice级别要比这个info级别要高一些。
所以我们一定要记住的低一些,才能够把它给显示出来。
好,那个时候再用OBDNDX视频下的NI杠sreload指令。
好,这个时候我们再来监控一下,我们看OBD下的NDXlogs下的limit,我们来监控一下这样一个日志啊。
这个时候比如说我这个时候开始访问,第一次有结果是吧?
第二次返回错误。第三次也返回收,大家看我这个日志中,大家看他是不是记录了很多行为啊,第一次大家看它是不是limitrequest,对吧?
限制行为发生了是吧?限制是是哪个客户端啊,幺二七零零零点幺,因为我就是本机发起的HTP请求对吧?
大家看它又有这个限制行为发生了限制行为发生了等等。
这个时候你再去请求,如果有的话,他还会继续限制啊,大家看这个日志中都会给大家记录到这样一些限制行为发生OK那这节课呢我们就给大家讲了一个这个我们的limitrequest模块啊,能够限制我们处理客户数据的一个平均速率啊。
ok那我们本节课呢就给大家讲到这里啊。