如何在Flask中异步获取多个资源?
我有一个用flask做的应用,它通过uwsgi在nginx上运行。当用户发起请求时,我需要同时向不同的服务发出多个API调用。为了让用户体验更好,我想同时进行这些调用,因为它们之间没有依赖关系。我还想把这些响应缓存起来,以便进一步提高效率。我正在尝试用gevent来实现这个,但在传递请求上下文给子进程时遇到了麻烦。我得到了一个错误:“TypeError: cannot concatenate 'str' and 'NoneType' objects”。
from flask import Flask, request
from flask_cache import Cache
import requests
app = Flask(__name__)
cache = Cache(app=app, config={'CACHE_TYPE': 'filesystem',
'CACHE_DIR': '/path/to/cache',})
@app.route("/")
def hello():
def get_external(i, request):
with app.app_context():
with app.test_request_context():
if i == 'first':
return first_request()
elif i == 'second':
return second_request()
threads = [gevent.spawn(get_external, i, request) for i in ['first', 'second']]
gevent.joinall(threads)
first = threads[0].get(block=False)
second = threads[1].get(block=False)
return render_template('index.html', first=first, second=second)
@cache.cached(timeout=10)
def first_request():
r = requests.get('http://api.example1.com?' + request.args.get('query'))
my_list = []
for row in r.json():
d = {}
d['article_id'] = row['article_id']
my_list.append(d)
return my_list
@cache.cached(timeout=10000)
def second_request():
r = requests.get('http://api.example2.com?' + request.args.get('query'))
my_list = []
for row in r.json():
d = {}
d['id'] = row['user_id']
my_list.append(d)
return my_list
if __name__ == "__main__":
app.run(debug=True)
另外,如果gevent不适合这个工作,请告诉我。我对python并不是很精通,也从来没有用过gevent,但我觉得应该有更简单的方法来实现这个,不是吗?
补充:我尝试过用grequests模块和requests-cache,但根据这个链接,它的效果不是很好,因为它使用sqlite作为后端(而我需要用文件作为后端)。
1 个回答
gevent的joinall()方法会一直等到所有线程都执行完吗?如果不是的话,我觉得这里好像缺少了什么。如果你异步启动了几个线程,那么你需要定期检查这些线程是否都已经完成,当它们完成后,再根据返回的结果调用render_template。我可能在这里理解错了,因为我还没用过gevent。
另外,你构建线程的方式让我觉得有点奇怪……使用列表推导式可能不是个好主意,因为对于每个i,你都需要在get_external()里明确检查,这样看起来有点乱。与其这样,不如为每个线程定义具体的函数,然后一个一个地把新线程添加到threads列表里。这样你之后可以更清楚地引用它们。
缓存响应其实很简单。对于每个函数,记录上次返回的值和返回的时间。如果当前时间减去上次检查的时间大于某个阈值,那么就再检查一次并更新缓存的值和检查时间,否则就返回缓存的值。如果你想要更稳妥的方案,可以考虑使用redis或memcache。