「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」
引子
随着后端的架构设计逐渐往微服务架构转型,使得后端开发可以专心的开发业务逻辑(CRUD), 这也导致了部分开发人员对于客户端网络请求的处理方式不甚了解,因为他们只需要关心接口的出入参即可,并与客户端开发人员对接即可。
会注意到这个问题,是在内网排查问题时发现有几十个条接口耗时异常记录,此外还出现了部分接口鉴权失败(超时)的情况,虽然这些异常日志条数不多,但还是引起了我的警觉。一通分析后,发现接口的业务逻辑并不复杂,遂定位设备,发现原来是测试妹子在搞弱联网测试。
鉴权失败的原因是:服务端接收请求所消耗的时间已经耗光了整个接口的时间,到鉴权服务时已经没有多少时间可用了,进而出现了鉴权失败的问题(超时)。
项目中的鉴权服务是独立部署的
什么是弱联网
大多数后端开发人员并不在乎用户的网络情况是怎样的,也不在乎客户端程序员有没有对请求进行排队防抖节流啥的, 只关心怎么处理请求。而实际上在移动的设备普及的今天,大多数用户的网络稳定情况基本上都是处于一个不断变化的状态。
比如,一个人走进了厕所,其手机的网络大概率会发生波动(有些地方甚至会在厕所安装信号屏蔽器)。
更多弱联网的细节,可以参考移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
要理解网络波动会产生什么后果,我们需要来看一道经典的面试题:
一个HTTP请求从发出到接收到响应都会经历哪些阶段?
在没有使用HTTP连接池的情况下:
- 域名解析阶段, 此阶段主要是解析出服务端的IP地址
- 与服务端建立TCP连接, 发送HTTP报文
- 等待服务端响应, 读到HTTP响应报文之后开始渲染页面或者执行其他操作
本文所有的讨论均基于HTTP1.1
更多详细细节, 请阅读一个HTTP请求的曲折经历
以注册接口为例,其请求发送到接收到响应数据所花费的时间将分为以下几段
那么很明显的一个问题就是用户处于弱联网的环境中的话相应的将导致请求数据的发送和响应数据接收的出现不稳定的情况,进而导致而客户端认为HTTP请求失败了。
对于由弱网络导致的请求报文发送失败的问题,服务端无需做任何处理,因为请求还没到达业务端(可能被Nginx或网关以Read timeout
为由拒绝了)。客户端则需要做重试逻辑。
我们要重点关注的是接收响应报文阶段,即:客户都端请求已发出并且被后端处理了,但是接收响应数据阶段失败了。
- 对于只读接口后端通常无需做任何处理,客户端网络请求失败后直接重试即可。
- 对于数据变更接口, 由于在写出响应报文之前服务端已经提交了事务,因此如果客户端进行了重试服务端需要做好请求幂等性处理。(幂等性处理实用性很强但又很八股的知识点了属于是)
SLOW POST
SLOW POST是DDOS攻击的手段之一, 其特征为缓慢的读取数据以及缓慢的发送数据,让服务端长时间去维护一个无意义的TCP连接 从而把服务端的资源压榨得一滴不剩,达到攻击的目的。
之所以将其和弱联网扯到一起,是因为二者都有相似之处即在数据发送和接收方面均表现出了不稳定。区别在于是否主动。
其实现原理很简单, 只需构造出一个合法的HTTP请求报文,然后逐个发送字节或者逐个字节的接收响应报文,这么做的后果就是服务端的操作系统需要在内核中长时间维护恶意用户的TCP连接从而挤压正常用户的资源,最终导致服务崩溃。
具体如何实现可以参考开源项目 slowhttptest
防御手段
防御手段的原理很简单, 就是为每一个HTTP请求设置读/写超时时间(Read/Writetimeout)。
- 在指定时间内请求没有被读完,则返回
Request timeout
错误 - 在指定时间内响应数据没有写完,则关闭连接释放资源
之所以说是体面,因为这些问题开源软件的作者都已经考虑到,并且提供了参数以供控制, 但这些开源软件的默认值大都数都设置了一个比较高的数值或者干脆就没有设置,因此我们需要根据实际情况进行调整。
Niginx
- 通过
client_header_timeout
和client_body_timeout
两个参数分配控制读HTTP请求头(HttpHeader
)和请求内容(Body
)的超时时间 - 通过
send_timeout
来控制发送响应数据的超时时间
详细请参考此文
Tomcat
通过调整server.xml
中Connector
的connectionTimeout
数来控制整个请求的超时时间, 相比Nginx
来说确实没那么细
1 | xml复制代码 <Connector port="8080" protocol="HTTP/1.1" |
Golang
golang的项目可以在初始化http.Server
的时候设置读写超时时间并且可以设置keep-alive
超时时间(多久没活动就关闭连接).
1 | go复制代码 server := http.Server{ |
总结
- 应对弱联网, 保证接口幂等性
- 应对
SLOW POST
攻击, 设置读写超时间 - 能用
Nginx
做代理就别直接暴露服务了
本文转载自: 掘金