Gunicorn with gevent:维护每个请求的全局数据
Gunicorn with gevent: maintaining per request global data
我有一个 python 应用程序(基于 MVC 模式构建)由 Gunicorn 服务器使用异步工作器 class(即 gevent)提供服务。这意味着多个客户端请求同时由一个工作进程提供服务。
每个 http 请求都包含一些特定于该请求的数据,例如 'user_id'。假设模型中发生错误,我想记录
user_id 错误。我不想继续将 user_id(以及更多请求特定值)传递给每个 class 或方法。我希望这些值可全局用于为此特定请求执行的任何代码。收到请求的控制器设置这些值,然后为该请求执行的任何代码都可以访问这些值。为多个同时请求执行的代码应该能够访问它们各自的数据值。可能吗?
总体思路是将您的每个请求数据与每个请求唯一的内容相关联。例如,有一个字典,这个唯一标识符作为键,每个请求的数据作为值。
既然你说你在使用 gevent workers,我们可以使用 greenlet.getcurrent()
作为唯一标识符。
这几乎 Flask + Werkzeug 所做的,但他们以比我下面的示例更高效、内存效率更高、线程兼容和最终用户友好的方式来实现。
这里有一个简单的 wsgi 应用程序作为示例。这里 a
是在通过 'globally available' 函数 get_per_greenlet_dict
获取的每个请求字典中设置和获取的。而 b
作为参数传递以验证 a
是否正确。
# wsgi.py
import collections, logging, time, greenlet
logging.basicConfig()
log = logging.getLogger(__name__)
log.level = logging.DEBUG
# used to store per-request data
# keys are greenlets, values are dicts
storage = collections.defaultdict(dict)
# return a dict for this request
# TODO: remove the per-request dict at the end of the request
def get_per_greenlet_dict():
return storage[greenlet.getcurrent()]
def application(env, start_response):
# extract query vars
query_vars = env['QUERY_STRING'].split("&")
a = query_vars[0].split("=")[1]
b = query_vars[1].split("=")[1]
# store 'a' in our per-request dict
get_per_greenlet_dict()['a'] = a
log_a_and_b("Before sleep", b)
time.sleep(1)
log_a_and_b("After sleep", b)
start_response('200 OK', [('Content-Type', 'text/html')])
return [b"OK: "]
def log_a_and_b(prefix, b):
# log both a and b,
# where a is sourced from our per-request dict
# and b is passed as a parameter as a means of verifying a
a = get_per_greenlet_dict()['a']
log.debug(prefix + "; a:%s b:%s", a, b)
运行 带有 gevent worker 的 gunicorn 服务器:
$ gunicorn -k gevent wsgi
运行 多个同时请求,例如:
$ for i in `seq 1 5`; do curl "127.0.0.1:8000?a=$i&b=$i" & done
然后你会看到 gunicorn 的输出如下:
DEBUG:wsgi:Before sleep; a:2 b:2
DEBUG:wsgi:Before sleep; a:5 b:5
DEBUG:wsgi:Before sleep; a:4 b:4
DEBUG:wsgi:Before sleep; a:1 b:1
DEBUG:wsgi:Before sleep; a:3 b:3
DEBUG:wsgi:After sleep; a:2 b:2
DEBUG:wsgi:After sleep; a:5 b:5
DEBUG:wsgi:After sleep; a:4 b:4
DEBUG:wsgi:After sleep; a:1 b:1
DEBUG:wsgi:After sleep; a:3 b:3
我有一个 python 应用程序(基于 MVC 模式构建)由 Gunicorn 服务器使用异步工作器 class(即 gevent)提供服务。这意味着多个客户端请求同时由一个工作进程提供服务。 每个 http 请求都包含一些特定于该请求的数据,例如 'user_id'。假设模型中发生错误,我想记录 user_id 错误。我不想继续将 user_id(以及更多请求特定值)传递给每个 class 或方法。我希望这些值可全局用于为此特定请求执行的任何代码。收到请求的控制器设置这些值,然后为该请求执行的任何代码都可以访问这些值。为多个同时请求执行的代码应该能够访问它们各自的数据值。可能吗?
总体思路是将您的每个请求数据与每个请求唯一的内容相关联。例如,有一个字典,这个唯一标识符作为键,每个请求的数据作为值。
既然你说你在使用 gevent workers,我们可以使用 greenlet.getcurrent()
作为唯一标识符。
这几乎 Flask + Werkzeug 所做的,但他们以比我下面的示例更高效、内存效率更高、线程兼容和最终用户友好的方式来实现。
这里有一个简单的 wsgi 应用程序作为示例。这里 a
是在通过 'globally available' 函数 get_per_greenlet_dict
获取的每个请求字典中设置和获取的。而 b
作为参数传递以验证 a
是否正确。
# wsgi.py
import collections, logging, time, greenlet
logging.basicConfig()
log = logging.getLogger(__name__)
log.level = logging.DEBUG
# used to store per-request data
# keys are greenlets, values are dicts
storage = collections.defaultdict(dict)
# return a dict for this request
# TODO: remove the per-request dict at the end of the request
def get_per_greenlet_dict():
return storage[greenlet.getcurrent()]
def application(env, start_response):
# extract query vars
query_vars = env['QUERY_STRING'].split("&")
a = query_vars[0].split("=")[1]
b = query_vars[1].split("=")[1]
# store 'a' in our per-request dict
get_per_greenlet_dict()['a'] = a
log_a_and_b("Before sleep", b)
time.sleep(1)
log_a_and_b("After sleep", b)
start_response('200 OK', [('Content-Type', 'text/html')])
return [b"OK: "]
def log_a_and_b(prefix, b):
# log both a and b,
# where a is sourced from our per-request dict
# and b is passed as a parameter as a means of verifying a
a = get_per_greenlet_dict()['a']
log.debug(prefix + "; a:%s b:%s", a, b)
运行 带有 gevent worker 的 gunicorn 服务器:
$ gunicorn -k gevent wsgi
运行 多个同时请求,例如:
$ for i in `seq 1 5`; do curl "127.0.0.1:8000?a=$i&b=$i" & done
然后你会看到 gunicorn 的输出如下:
DEBUG:wsgi:Before sleep; a:2 b:2
DEBUG:wsgi:Before sleep; a:5 b:5
DEBUG:wsgi:Before sleep; a:4 b:4
DEBUG:wsgi:Before sleep; a:1 b:1
DEBUG:wsgi:Before sleep; a:3 b:3
DEBUG:wsgi:After sleep; a:2 b:2
DEBUG:wsgi:After sleep; a:5 b:5
DEBUG:wsgi:After sleep; a:4 b:4
DEBUG:wsgi:After sleep; a:1 b:1
DEBUG:wsgi:After sleep; a:3 b:3