道歉,如果标题不具有描述性,我很难找到问题的好标题。
我的问题涉及Python,CouchDB(在较小程度上),多处理和网络。它开始于我试图使用Python的multiprocessing
模块调试同事的程序,以使用couchdb-python将请求并行化到CouchDB数据库。我创建了一个最小程序来展示bug并最终解决了这个问题,但是解决方案引出了另一个问题,我无法回答我的最佳知识。我希望有关SO的专家可以帮助我解决这个问题,所以就这样了。
问题的前提非常简单。我们有n
个资源,所有资源都可以同时检索。我的同事正在使用n
模块并行获取所有multiprocessing
资源,而不是发出n
个串行请求。这是我写的一个程序来演示这个问题:
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进行并行请求时,我在mitmproxy详细信息选项卡中观察到(我显示了一个请求,但所有3个并发请求的时序相同):
此处的事件序列表示阻塞同步请求。
你可以看到这里的序列与没有haproxy的序列不同。如果没有启动服务器连接,则认为请求已完成。
我不习惯在这个低级别工作,所以我知道我在这方面的知识非常缺乏。我想了解将haproxy放在它面前的差异是什么导致了破坏了couchdb-python中的多处理错误? haproxy是基于事件的,所以我怀疑它与它有关,但真的很感谢有人解释这个差异!
提前感谢一大堆!