0%

从事故到了解 tornado

从robot-engine事故到了解 tornado

[TOC]

robot-engine事故

问题一:tornado 是多进程还是多线程?它能同时支持多少连接

线上事故

作为一个 TB 的产品,我们的很多功能都支持通过导入导入 excel 来让用户批量修改数据,但是在一些情况下,我们的服务却会出现直接拒绝服务的情况

复现

第一个实验, Recv-Q 少于 Send-Q, Recv-Q 的数量逐渐减少

Recv-Q:当前 accept 队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 服务端 
python server.py -s 1
# 启动后查看, 可以看到是多个进程
ps -ef | grep "python server.py"
# If num_processes is ``None`` or <= 0, we detect the number of cores
# available on this machine and fork that number of child processes.


# 客户端请求100次
./req -path /sleep -count 100
|># ss -ntl | grep 5001
LISTEN 92 128 *:5001 *:*
LISTEN 0 128 [::]:5001 [::]:*
|root@zjk-qa-k8s-node010 ~
|># ss -ntl | grep 5001
LISTEN 84 128 *:5001 *:*
LISTEN 0 128 [::]:5001 [::]:*
|root@zjk-qa-k8s-node010 ~
|># ss -ntl | grep 5001
LISTEN 76 128 *:5001 *:*
LISTEN 0 128 [::]:5001 [::]:*
|root@zjk-qa-k8s-node010 ~
|># ss -ntl | grep 5001
LISTEN 68 128 *:5001 *:*
LISTEN 0 128 [::]:5001 [::]:*
|root@zjk-qa-k8s-node010 ~
|># ss -ntl | grep 5001
LISTEN 36 128 *:5001 *:*
LISTEN 0 128 [::]:5001 [::]:*
|root@zjk-qa-k8s-node010 ~
|># ss -ntl | grep 5001
LISTEN 0 128 *:5001 *:*
LISTEN 0 128 [::]:5001 [::]:*

第二个实验, Recv-Q 超过 Send-Q

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
python server.py -s 1

# 客户端请求1000次
./req -path /sleep -count 1000


|># ./req -path /sleep -count 1000
req done

# 服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭
Get Get "http://127.0.0.1:5001/sleep": read tcp 127.0.0.1:40056->127.0.0.1:5001: read: connection reset by peer
Get Get "http://127.0.0.1:5001/sleep": read tcp 127.0.0.1:40058->127.0.0.1:5001: read: connection reset by peer
Get Get "http://127.0.0.1:5001/sleep": read tcp 127.0.0.1:40432->127.0.0.1:5001: read: connection reset by peer
Get Get "http://127.0.0.1:5001/sleep": read tcp 127.0.0.1:40436->127.0.0.1:5001: read: connection reset by peer
Get Get "http://127.0.0.1:5001/sleep": read tcp 127.0.0.1:40060->127.0.0.1:5001: read: connection reset by peer
Get Get "http://127.0.0.1:5001/sleep": read tcp 127.0.0.1:40062->127.0.0.1:5001: read: connection reset by peer
Get Get "http://127.0.0.1:5001/sleep": dial tcp 127.0.0.1:5001: i/o timeout
Get Get "http://127.0.0.1:5001/sleep": dial tcp 127.0.0.1:5001: i/o timeout
Get Get "http://127.0.0.1:5001/sleep": dial tcp 127.0.0.1:5001: i/o timeout
Get Get "http://127.0.0.1:5001/sleep": dial tcp 127.0.0.1:5001: i/o timeout

问题二:如果要使用 tornado,如何处理这个问题?

开放…

谣言

python “有问题”

之前遇到线上的这个问题,不了解 python的同事可能就觉得 python 这个语言有问题,但是实际上这很容易证伪, python 语言下的 Flask 框架就天然没有这个问题。

tornado 是 “异步的”

还有就是很多人认为 tornado 是异步的,实际上理解不完全准确。首先大多数场景下,我们使用 tornado 都是在阻塞的使用,其次官方文档中异步主要还是用于 httpclient中,像我们在 configure-tb 中的使用。

DAc3gU.md.png

相同的模型 – nginx

多进程+事件驱动IO 是一种高效的编程模型,例如 Nginx 就是使用了这种模型。

问题三, 多进程下,一个请求过来,如何决定是哪个进程处理这个请求(惊群)?

惊群现象

惊群通常发生在server 上,当父进程绑定一个端口监听socket,然后fork出多个子进程,子进程们开始循环处理(比如accept)这个socket。每当用户发起一个TCP连接时,多个子进程同时被唤醒,然后其中一个子进程accept新连接成功,余者皆失败,重新休眠。

1
2
3
4
5
6
7
8
worker  3 return from epoll_wait!
worker 2 return from epoll_wait!
worker 1 return from epoll_wait!
worker 0 return from epoll_wait!
worker 3 accept successed!
worker 2 accept failed!
worker 0 accept failed!
worker 1 accept failed!

总结

tornado 作为服务端,其编程模式是多进程+IO多路复用,这种编程模型的好处是能支持10k及以上的并发连接,缺点是一旦一个请求占用时间过长,那么整个系统的可能因为其他请求直接死掉,因为没有其他的线程(worker)来工作了。tornado作为请求的client(tornado也是一个http请求库),他可以实现异步请求。

参考

tornado