HTTP请求并发和反向代理

时间:2017-03-09 05:50:03

标签: python networking proxy multiprocessing couchdb

道歉,如果标题不具有描述性,我很难找到问题的好标题。

我的问题涉及Python,CouchDB(在较小程度上),多处理和网络。它开始于我试图使用Python的multiprocessing模块调试同事的程序,以使用couchdb-python将请求并行化到CouchDB数据库。我创建了一个最小程序来展示bug并最终解决了这个问题,但是解决方案引出了另一个问题,我无法回答我的最佳知识。我希望有关SO的专家可以帮助我解决这个问题,所以就这样了。

问题的前提非常简单。我们有n个资源,所有资源都可以同时检索。我的同事正在使用n模块并行获取所有multiprocessing资源,而不是发出n个串行请求。这是我写的一个程序来演示这个问题:

脚本(bug.py)

import couchdb
import multiprocessing

server = couchdb.Server(SERVER)

try:
    database = server.create('test')
except:
    server.delete('test')
    database = server.create('test')

database.save({'_id': '1', 'type': 'dog', 'name': 'chase'})
database.save({'_id': '2', 'type': 'dog', 'name': 'rubble'})
database.save({'_id': '3', 'type': 'cat', 'name': 'kali'})

def query_id(id):
    print(dict(database[id]))

def main():
    args = [
        ['dog', 'chase'],
        ['dog', 'rubble'],
        ['cat', 'kali'],
    ]
    print('-' * 80)
    processes = []
    for id_ in ['1', '2', '3']:
        proc = multiprocessing.Process(target=query_id, args=(id_))
        processes.append(proc)
        proc.start()

    for proc in processes:
        proc.join()

if __name__ == '__main__':
    main()

相当无辜的代码,对吧?好吧,在最新的couchdb和couchdb-python上运行它会产生以下错误:

输出

--------------------------------------------------------------------------------
Process Process-2:
Process Process-1:
Traceback (most recent call last):
Traceback (most recent call last):
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 258, in _bootstrap
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
    self.run()
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 114, in run
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 114, in run
    self._target(*self._args, **self._kwargs)
    self._target(*self._args, **self._kwargs)
  File "bug.py", line 25, in query_id
  File "bug.py", line 25, in query_id
    print(dict(database[id]))
  File "/home/kevin/src/couchdb-python/couchdb/client.py", line 418, in __getitem__
    print(dict(database[id]))
  File "/home/kevin/src/couchdb-python/couchdb/client.py", line 418, in __getitem__
    return Document(data)
TypeError: 'ResponseBody' object is not iterable
    return Document(data)
TypeError: 'ResponseBody' object is not iterable

经过一番挖掘,我终于发现了couchdb-python的ConnectionPool实现不是多进程安全的。有关详细信息,请参阅此PR。基本上,所有进程共享相同的ConnectionPool对象,并且被赋予相同的httplib.HTTPConnection对象,并且当它们同时尝试从连接读取时,返回的字符串是乱码,并且随后出现错误。如果将print(os.getpid(), line)放在httplib.HTTPResponse._read_status方法中,您可以看到它的证据。这是添加print语句后的示例输出:

(26490, 'TP1.120 O\r\n')
(26489, 'T/ 0KServer: CouchDB/1.6.1 (Erlang OTP/17)\r\n')
Process Process-2:
Process Process-3:
Traceback (most recent call last):
Traceback (most recent call last):
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 258, in _bootstrap
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 114, in run
    self._target(*self._args, **self._kwargs)
  File "bug.py", line 25, in query_id
    self.run()
  File "/usr/lib64/python2.7/multiprocessing/process.py", line 114, in run
    print(dict(database[id]))
  File "/home/kevin/src/couchdb-python/couchdb/client.py", line 418, in __getitem__
    self._target(*self._args, **self._kwargs)
  File "bug.py", line 25, in query_id
    print(dict(database[id]))
  File "/home/kevin/src/couchdb-python/couchdb/client.py", line 418, in __getitem__
    return Document(data)
TypeError: 'ResponseBody' object is not iterable
    return Document(data)
TypeError: 'ResponseBody' object is not iterable

如此处所示,从子流程读取的第一行仅是部分的,表示此处的竞争条件。如果我进一步检查HTTPConnection对象,我可以看到所有三个进程共享相同的连接对象,服务器的套接字和来自用于读取的套接字的文件描述符。

益智

到目前为止一切顺利。我已经确定了问题的根本原因并整理了一个修复程序。但是,当我将couchdb实例放在反向代理后面时会出现复杂情况。在这种情况下,我正在使用haproxy。这是一个示例配置:

global
    ...
defaults
    ...
listen couchdb
    bind *:9999
    mode http
    stats enable
    option httpclose
    option forwardfor
    server couchdb-1 127.0.0.1:5984 check

并在bug脚本中将couchdb服务器URL指向http://localhost:9999,重新编写脚本,一切都很好!我还检查了连接对象,套接字和文件描述符,并且还在所有进程之间共享。

这让我感到困惑。我提起了mitmproxy并检查了两种情况下发生了什么:有没有haproxy。

没有haproxy

当没有haproxy进行并行请求时,我在mitmproxy详细信息选项卡中观察到(我显示了一个请求,但所有3个并发请求的时序相同):

Without haproxy

此处的事件序列表示阻塞同步请求。

使用haproxy With haproxy

你可以看到这里的序列与没有haproxy的序列不同。如果没有启动服务器连接,则认为请求已完成。

问题

我不习惯在这个低级别工作,所以我知道我在这方面的知识非常缺乏。我想了解将haproxy放在它面前的差异是什么导致了破坏了couchdb-python中的多处理错误? haproxy是基于事件的,所以我怀疑它与它有关,但真的很感谢有人解释这个差异!

提前感谢一大堆!

0 个答案:

没有答案