我有以下场景:
我有一个web服务,根据单个用户请求聚合来自某些第三方服务器的数据。对第三方的请求可以是带有XML数据的SOAP或普通urllib2请求,每个请求都在单独的线程中完成。
这是我正在做的事情的总体情况:
ThirdParty1(Thread):
def run(self):
try:
result = SOAPProxy('http://thirdparty.com', timeout=2).method(params)
dostuff_and_save(result) # save results on database
except Exception:
log.warn('Ooops')
ThirdParty2(Thread): ...
def myview(params):
thread = [ThirdParty1(), ThirdParty2()]
for t in thread: t.start()
for t in thread: t.join(timeout=2)
return result # this is actually just a token, that I use to retrieve the data saved by the threads
我目前的问题是可靠地返回响应我的用户的请求时,任何第三方服务器挂在他们的一边。我尝试在线程连接上设置超时,在SOAPProxy对象上设置超时,并执行socket.setdefaulttimeout
。所有的超时都不受尊重。
我设法挖掘了SOAPProxy问题,发现它使用了httplib,而httplib深入使用了socket.makefile(),文档说:
插座。makefile([模式[,bufsize]])
返回与套接字关联的文件对象。(文件对象在File>对象)。文件对象引用了一个dup()ped版本的套接字文件描述符,因此>文件对象和套接字对象可以分别被关闭或被垃圾收集。套接字必须处于阻塞模式(不能有超时)。可选的mode和bufsize参数的解释方式与内置的file()函数相同。
我找到的其他SOAP库,无论如何,也都使用了httplib。更复杂的是,我可能需要从请求线程访问数据库,我不完全理解用这种策略杀死线程的后果是什么,我正在考虑从线程外部做数据库的事情,如果可能的话。
我的问题是:
我的web服务如何在适当的时间响应用户,并优雅地处理行为恶劣的第三方服务器,当超时不受尊重?
事实上,HTTPResponse使用makefile可能没有我想象的那么糟糕,事实证明,makefile
是真正的非缓冲默认情况下,它可以引发超时异常,这是我所尝试的:
在一个控制台上我打开netcat -l -p 8181 '0.0.0.0'
在另一个我打开python2.7
并运行:
>>> import socket
>>> af, socktype, proto, canoname, sa = socket.getaddrinfo('0.0.0.0', 8181, 0, socket.SOCK_STREAM)[0]
>>> s=socket.socket(af, socktype, proto)
>>> s.settimeout(.5)
>>> s.connect(sa)
>>> f=s.makefile('rb', 0)
>>> f.readline()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/socket.py", line 430, in readline
data = recv(1)
socket.timeout: timed out
但是我的问题是如何做可靠的第三方请求仍然存在。
我想我找到了一个可行的解决方案。
我要做的第一件事是启动线程,它将请求所需的任何第三方服务器。这工作得很好,因为GIL在线程执行阻塞操作(socket.recv())时不会保持,这允许我的服务器在处理请求时做自己的事情。
我从线程中删除了所有的副作用,不再与数据库交谈,如果一个请求需要比预期更多的响应,我不需要杀死它,只是离开它并忽略它。
当第一个线程启动时,计时器启动,在我的服务器完成它的事情之后,它绝对需要第三方的结果,它检查每个线程,看看它们是否完成,当它们全部完成或超时时,它得到每个完成线程的结果,它看起来像这样:
start, data = time(), []
threads = launch_threads()
# ... do my thing
for t in threads: # wait up to TIMEOUT
timeout = TIMEOUT - (time() - start)
t.join(t)
for t in threads:
if not t.isAlive(): # should not have a race
data.append(t.getdata())