uWSGI + nginx for django app 避免pylibmc多线程并发问题?
uWSGI + nginx for django app avoids pylibmc multi-thread concurrency issue?
简介
我这周遇到了这个非常有趣的问题,最好从一些事实开始:
pylibmc
是 not thread safe,当用作 django memcached 后端时,直接在 shell 中启动多个 django 实例会在遇到并发请求时崩溃。
- 如果使用 nginx + uWSGI 部署,pylibmc 的这个问题会神奇地消失。
- 如果你将 django 缓存后端切换到
python-memcached
,它也会解决这个问题,但这个问题不是关于那个的。
详细说明
从第一个事实开始,这就是我重现 pylibmc
问题的方式:
失败pylibmc
我有一个 django 应用程序,它执行大量 memcached 读取和写入,并且有这个部署策略,我在 shell 中启动多个 django 进程,绑定到不同的端口(8001、8002),并且使用nginx做平衡。
我使用 locust
针对这两个 django 实例启动了两个单独的负载测试,结果是这样的:
在上面的屏幕截图中,它们都崩溃并报告了完全相同的问题,如下所示:
Assertion "ptr->query_id == query_id +1" failed for function "memcached_get_by_key" likely for "Programmer error, the query_id was not incremented.", at libmemcached/get.cc:107
uWSGI 来拯救
所以在上面的例子中,我们了解到通过 pylibmc
对 memcached 的多线程并发请求可能会导致问题,这在某种程度上不会影响 uWSGI
多个工作进程。
为了证明这一点,我开始 uWSGI
并包含以下设置:
master = true
processes = 2
这告诉 uWSGI 启动两个工作进程,然后我告诉 nginx 为任何 django 静态文件提供服务器,并将非静态请求路由到 uWSGI
,看看会发生什么。服务器启动后,我在 localhost 中针对 django 启动相同的 locust 测试,并确保每秒有足够的请求来引起对 memcached 的并发请求,结果如下:
在uWSGI
控制台中,没有死掉的worker进程的迹象,也没有worker被重新生成,但是看截图的上半部分,确实有并发请求(5.6 req/s).
问题
我非常好奇 uWSGI
是如何解决这个问题的,我无法从他们的文档中了解到这一点,总而言之,问题是:
uWSGI是如何管理worker进程的,多线程memcached请求不会导致django崩溃?
事实上,我什至不确定是 uWSGI
管理工作进程的方式避免了这个问题,还是 uWSGI
附带的一些其他魔法在起作用,我已经在他们的文档中看到一个叫做 memcached 路由器的东西,我不太明白,这有关系吗?
难道不是因为你实际上有两个独立的进程由 uWSGI 管理吗?由于您设置的是进程选项而不是工人选项,所以您实际上应该有多个 uWSGI 进程(由于您使用的配置,我假设一个主进程 + 两个工人)。这些进程中的每一个都会有它自己加载的 pylibmc,所以线程之间没有状态共享(毕竟你还没有在 uWSGI 上配置线程)。
简介
我这周遇到了这个非常有趣的问题,最好从一些事实开始:
pylibmc
是 not thread safe,当用作 django memcached 后端时,直接在 shell 中启动多个 django 实例会在遇到并发请求时崩溃。- 如果使用 nginx + uWSGI 部署,pylibmc 的这个问题会神奇地消失。
- 如果你将 django 缓存后端切换到
python-memcached
,它也会解决这个问题,但这个问题不是关于那个的。
详细说明
从第一个事实开始,这就是我重现 pylibmc
问题的方式:
失败pylibmc
我有一个 django 应用程序,它执行大量 memcached 读取和写入,并且有这个部署策略,我在 shell 中启动多个 django 进程,绑定到不同的端口(8001、8002),并且使用nginx做平衡。
我使用 locust
针对这两个 django 实例启动了两个单独的负载测试,结果是这样的:
在上面的屏幕截图中,它们都崩溃并报告了完全相同的问题,如下所示:
Assertion "ptr->query_id == query_id +1" failed for function "memcached_get_by_key" likely for "Programmer error, the query_id was not incremented.", at libmemcached/get.cc:107
uWSGI 来拯救
所以在上面的例子中,我们了解到通过 pylibmc
对 memcached 的多线程并发请求可能会导致问题,这在某种程度上不会影响 uWSGI
多个工作进程。
为了证明这一点,我开始 uWSGI
并包含以下设置:
master = true
processes = 2
这告诉 uWSGI 启动两个工作进程,然后我告诉 nginx 为任何 django 静态文件提供服务器,并将非静态请求路由到 uWSGI
,看看会发生什么。服务器启动后,我在 localhost 中针对 django 启动相同的 locust 测试,并确保每秒有足够的请求来引起对 memcached 的并发请求,结果如下:
在uWSGI
控制台中,没有死掉的worker进程的迹象,也没有worker被重新生成,但是看截图的上半部分,确实有并发请求(5.6 req/s).
问题
我非常好奇 uWSGI
是如何解决这个问题的,我无法从他们的文档中了解到这一点,总而言之,问题是:
uWSGI是如何管理worker进程的,多线程memcached请求不会导致django崩溃?
事实上,我什至不确定是 uWSGI
管理工作进程的方式避免了这个问题,还是 uWSGI
附带的一些其他魔法在起作用,我已经在他们的文档中看到一个叫做 memcached 路由器的东西,我不太明白,这有关系吗?
难道不是因为你实际上有两个独立的进程由 uWSGI 管理吗?由于您设置的是进程选项而不是工人选项,所以您实际上应该有多个 uWSGI 进程(由于您使用的配置,我假设一个主进程 + 两个工人)。这些进程中的每一个都会有它自己加载的 pylibmc,所以线程之间没有状态共享(毕竟你还没有在 uWSGI 上配置线程)。