前段时间遭遇了性能问题,主要是项目应用有在windows下部署的场景,在这样的场景下性能不够优秀。对标linux下Gunicore的waitress并不能解决我们的问题。所以研究了一下Gunicore。发现Gunicore的核心主要有如下几点:
1. 多进程
2. 端口复用(socket.SO_REUSEADDR)
3. Linux内心的select和epoll模型
然后windows下的socket没有SO_REUSEADDR。所以不存在多端口直接复用的可能。猜测这也是Gunicore不支持Windows的原因之一吧。(windows下可以直接多个进程指定同一端口,但是只有最后一个进程是存活状态,其余都被顶掉了。)
于是!作为一个!程序员!我永不磨米的造轮子之心又熊熊燃烧起来了!Gunicore因为不可端口复用导致了不支持Windows,那么我们直接在select模型下进行socket的非阻塞请求轮询,请求分发到不同的端口不就可以解决问题了嘛!
于是,搞出了一个办Gunicore办Nginx的玩意。虽然最后仍然没有解决问题,但是还是蛮开心的。在Linux下可以有不错的性能提升,略低于Gunicore。在windows上没有提升。原因未知...可能与python对windows的多进程支持有关,或者是GIL的问题。还需要继续研究。
贴一点核心代码~
import sys
import socket
import threading
import select
import time
import psutil
from datetime import datetime
from runtogether.process import CBaseProcess
from runtogether.utils import WorkerBaseQueue
PROXY_PORTS = []
def get_port(workers, queue=None):
'''
获取端口
:param workers: 工作进程数
:param queue: 通信管道对象
:return: [(pid, 端口), (pid, 端口)...]
'''
queue = queue if queue else WorkerBaseQueue(workers)
ports = []
# nodes = []
for i in range(workers):
# node = queue.get()
# ports.append(list(node.values())[0])
ports.append(queue.get())
return ports
def proxy_socket(client, addr):
'''
socket代理
:param client:
:param addr:
:return:
'''
inputs = [client]
outputs = []
remote_socket = 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
live = True
while live:
readable, writable, exceptional = select.select(inputs, outputs, inputs, None)
try:
for s in readable:
if s is client:
request_header = s.recv(4096)
if remote_socket is 0:
headers = request_header.decode()
host_addr = headers.split("\r\n")[1].split(":")
interface = headers.split("\r\n")[0]
name, host, port = map(lambda x: x.strip(), host_addr)
port = PROXY_PORTS.pop(0)
PROXY_PORTS.append(port)
sock.connect((host, port))
remote_socket = sock
inputs.append(sock)
print('%s server port: %s-->%s : %s' % (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), str(port), "client connent:{0}:{1}".format(addr[0], addr[1]), interface))
remote_socket.sendall(request_header)
else:
while True:
resp = s.recv(512)
if len(resp):
client.sendall(resp)
else:
live = False
break
except Exception as e:
print("http socket error {0}".format(e))
class Referee(object):
def __init__(self, app, workers=1, host='0.0.0.0', port=8899):
'''
:param app: application 对象.
:param workers: 工作进程数
:param host: 主进程运行host
:param port: 主进程运行端口
:param process: 工作进程对象列表
:param p: 工作进程元组列表
'''
self.app = app
self.workers = workers
self.host = host
self.port = port
self.process = []
self.p = []
def add_process(self):
'''
添加工作进程
:return:
'''
p = CBaseProcess(self.app, self.workers)
self.process.append(p)
p.start()
def handle_process(self):
'''
控制工作进程
:return:
'''
for i in range(self.workers):
self.add_process()
def handle_check(self):
'''
子进程健康检查及重建对应端口的进程
:return:
'''
time.sleep(10)
while True:
# print('health checking!')
pop_idx = []
# print(self.p)
for i, p in enumerate(self.p):
if not psutil.pid_exists(p[0]) or psutil.Process(p[0]).status() not in ['running', 'sleeping']:
print(psutil.Process(p[0]).status())
print('pid:[%s] is dead!' % str(p[0]))
pop_idx.append(i)
np = CBaseProcess(self.app, 1, port=p[1])
np.start()
print(np.pid)
self.p.append((np.pid, np.port))
print('new process[%s] is start!' % str(np.pid))
[self.p.pop(i) for i in pop_idx]
time.sleep(5)
def run(self):
'''
主方法
:return:
'''
self.handle_process()
self.p = get_port(self.workers)
[PROXY_PORTS.append(p[1]) for p in self.p]
threading.Thread(target=self.handle_check, args=()).start()
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# print(type(self.port))
http_server.bind((self.host, self.port))
except Exception as e:
sys.exit("python proxy bind error [%s]" % str(e))
print("python proxy start")
print("* Server Running on http://{}:{}. Proxy Ports {}".format(self.host, str(self.port), str(PROXY_PORTS)))
http_server.listen(1024)
while True:
conn, addr = http_server.accept()
http_thread = threading.Thread(target=proxy_socket, args=(conn, addr))
http_thread.start()
完整版代码在https://github.com/csy5621080/runtogether上。
此外还过了把发布工具包的瘾。直接安装命令
pip install runtogether