三次握手是否会成为服务端的并发瓶颈?
服务端采用多线程模型:
- master thread 负责 accept,创建和客户端的 socket 连接,并把 clientsocket 投递到线程池中
- 线程池中的 work thread 负责读写 clientsocket,先读后写,然后关闭 clientsocket
问题在这个 master thread
,假设三次握手的时间是 1ms
,那不是 RPS
最多只能 1000 RPS
了吗?
但是像 Nginx
号称可以处理百万级别的连接,总不能被三次握手给卡住吧!
指的是 socket 的 accept
Nginx
是如何处理这个问题的呢?
虽然 Nginx 是多进程模型,但是先不管这个
我的逻辑,用下面的 python 代码可以大概描述:
import socketimport sys
import time
import threading
from loguru import logger
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures._base import Future
default_encoding: str = 'utf-8'
pool = ThreadPoolExecutor(
max_workers=20,
thread_name_prefix='simple-work-thread-pool'
)
def init_serversocket() -> socket.socket:
serversocket = socket.socket(
family=socket.AF_INET,
type=socket.SOCK_STREAM
)
# 获取本地主机名
host = socket.gethostname()
logger.debug(f'host {host}')
port = 9999
# 绑定端口号
serversocket.bind(('0.0.0.0', port))
# 设置最大连接数,超过后排队
serversocket.listen(5)
return serversocket
def send_response(clientsocket: socket.socket, addr: tuple, response_body: bytes) -> int:
send_len: int = clientsocket.send(response_body)
clientsocket.close()
return send_len
def start_request(clientsocket: socket.socket, addr: tuple) -> int:
logger.debug(f'get message from {addr}')
request_body: bytes = clientsocket.recv(2048)
request_text: str = request_body.decode(encoding=default_encoding)
response_text: str = f'server get message: {request_text}'
response_body: bytes = response_text.encode(default_encoding)
time.sleep(1)
return send_response(clientsocket=clientsocket, addr=addr, response_body=response_body)
def start_request_callback(future: Future) -> None:
send_len: int = future.result()
logger.debug(
f'{threading.current_thread().name}, send payload len is {send_len}')
serversocket = init_serversocket()
while True:
clientsocket, addr = serversocket.accept()
clientsocket: socket.socket
addr: tuple
future: Future = pool.submit(start_request, clientsocket, addr)
future.add_done_callback(start_request_callback)
回答:
服务端在listen监听时,其实就已经可以建立连接了,所以这上面有个误区,accept并不是在进行三次握手,accept只是在已建立连接队列获取套接字来处理,所以accept操作不存在瓶颈
而Nginx能处理百万级别连接,主要原因是其使用多进程+IO多路复用模型,IO多路复用与传统的一个线程处理一个连接不同,它可以在单个线程里通过操作系统提供的select/poll/epoll等系统调用来同时管理多个连接,这才是高并发的关键所在
回答:
当然会成为瓶颈,不然 SYN FLOOD 这种 DDos 攻击手段是咋来的 —— 不就是用大量的慢速握手来占满你服务器的资源么。
至于 nginx,它又不负责握手。TCP 握手阶段的处理都是交给 Linux 内核处理的(Windows 不确定,但 Socket 这种底层的协议栈一般都是交给操作系统维护的)。Linux 内核中会维护两个队列,一个是“半连接队列”(即已经一次握手的)、一个是“全连接队列”(即已经两次握手的),任何一个队列打满、后面再有新的 TCP 握手请求进来就会被直接丢弃。nginx 拿到的已经都是握完手的了。
这也是为啥你看 nginx 的日志的话,会发现它的 request_time 里并不包含握手时间的原因。
P.S.1 注意 Linux 2.2 之前只有一个队列,2.2 开始才拆成两个了。所以你要看到有些资料说只有一个队列时要注意看一下内容的时效性。
P.S.2 nginx 里确实也有一个跟队列有关的参数 —— backlog
,满了就不再接收新的请求了,但并不是在握手阶段。还是那句话,握手是内核负责的事儿,nginx 拿到的已经是握完手的了。
回答:
在 work thread
中建立连接,master thread
不要处理任何网络请求,只负责本地程序调度
TCP 的握手不是在 accept
才开始的,而是系统底层在处理
参考这篇文章:TCP半连接队列和全连接队列
在 TCP 进行三次握手时,Linux 会为其维护两个队列:
- 半连接队列,也叫 syn 队列
- 全连接队列,也叫 accept 队列
全连接即已经完成握手的连接,调用 accept
只是从全连接队列中取出来
按底层的这个算法,多个 TCP 连接之间是交错进行的,如果同时发起两个连接,服务器的处理顺序不一定是
1:syn -> 1:syn+ack -> 1:ack -> 2:syn -> 2:syn+ack -> 2:ack
而是
1:syn -> 1:syn+ack -> 2:syn -> 2:syn+ack -> 1:ack -> 2:ack
其中 syn
和 ack
是指服务器接收到该报文,syn+ack
是指服务器发送该报文
服务器的握手耗时主要是从发送 syn+ack
到接收到 ack
所等待的时间,第一种顺序时间是相加的,连接越多越耗时,但第二种顺序不会,发送 syn+ack
不需要等待上一个接收到 ack
所以你的程序模型是正确的,不会被握手卡住
以上是 三次握手是否会成为服务端的并发瓶颈? 的全部内容, 来源链接: utcz.com/p/938480.html