django + mysql + eventlet monkey+patch() 导致报错django.db.utils.DatabaseError

最近在django,在项目中遇到一个奇怪的问题,现写个demo 项目复盘分析一下:

django:1.8.4
eventlet :0.17.4-4
MySQL-python: 1.2.5-1

django项目:

horizon_new/
├── horizon_new
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── settings.py
│   ├── settings.pyc
│   ├── urls.py
│   ├── urls.pyc
│   ├── wsgi.py
│   └── wsgi.pyc
├── manage.py
└── vmware
    ├── admin.py
    ├── admin.pyc
    ├── __init__.py
    ├── __init__.pyc
    ├── migrations
    │   ├── 0001_initial.py
    │   ├── 0001_initial.pyc
    │   ├── __init__.py
    │   └── __init__.pyc
    ├── models.py
    ├── models.pyc
    ├── tests.py
    ├── views.py
    └── views.pyc

其中:

settings.py

配置了mysql数据库链接:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': '******',
        'USER': '*****',
        'PASSWORD': '*******',
        'HOST': '127.0.0.1',
        'PORT': '3306'
    }
}

urls.py

from django.conf.urls import include, url
from django.contrib import admin
from vmware import views

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^vmware$', views.get_vmware),
    
]

vmware.models.py

from django.db import models


class Vmware(models.Model):
    owner_id = models.CharField(max_length=16, default="")
    name = models.CharField(max_length=32)

    def __str__(self):
        return str(self.name)
~        

vmware.views.py

from django.shortcuts import render
from django.http import HttpResponse
from vmware import models


def get_vmware(request):
    queryset = models.Vmware.objects.all()
    print queryset
    return HttpResponse(queryset)

这是通过python manage.py runserver 8001服务正常启动,并且可以正常读写数据库,获取数据。

django + mysql + eventlet monkey+patch() 导致报错django.db.utils.DatabaseError_第1张图片

但是,当在代码中引入eventlet 的并发协程库时,并且进行了monkey_patch()后,修改views.py

from django.shortcuts import render
from django.http import HttpResponse
from vmware import models

import eventlet
eventlet.monkey_patch()

def get_vmware(request):
    queryset = models.Vmware.objects.all()
    print queryset
    return HttpResponse(queryset)

加入了

import eventlet

eventlet.monkey_patch()

 

重新启动服务:

manage.py runserver 8001,再次访问,发现无法正常读写数据库,提示报错

django + mysql + eventlet monkey+patch() 导致报错django.db.utils.DatabaseError_第2张图片

服务端看报错:

django + mysql + eventlet monkey+patch() 导致报错django.db.utils.DatabaseError_第3张图片

具体报错:

Traceback (most recent call last):
  File "/usr/lib64/python2.7/wsgiref/handlers.py", line 85, in run
    self.result = application(self.environ, self.start_response)
  File "/usr/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 177, in __call__
    signals.request_started.send(sender=self.__class__, environ=environ)
  File "/usr/lib/python2.7/site-packages/django/dispatch/dispatcher.py", line 189, in send
    response = receiver(signal=self, sender=sender, **named)
  File "/usr/lib/python2.7/site-packages/django/db/__init__.py", line 64, in close_old_connections
    conn.close_if_unusable_or_obsolete()
  File "/usr/lib/python2.7/site-packages/django/db/backends/base/base.py", line 407, in close_if_unusable_or_obsolete
    self.close()
  File "/usr/lib/python2.7/site-packages/django/db/backends/base/base.py", line 195, in close
    self.validate_thread_sharing()
  File "/usr/lib/python2.7/site-packages/django/db/backends/base/base.py", line 425, in validate_thread_sharing
    % (self.alias, self._thread_ident, thread.get_ident()))
DatabaseError: DatabaseWrapper objects created in a thread can only be used in that same thread. The object with alias 'default' was created in thread id 140107533682432 and this is thread id 65391024.

经过分析django的db模块的代码,发现进行数据库操作关闭时,会对创建这个连接进行验证是否是同 一个thread进行操作,如果不是一个操作,就会报错。

经过排查eventlet的monkey_patch(),发现eventlet在提供绿色协程时,为了性能提升,竟然强悍的部分原生模块(os、socket、thread)进行了修改,并且通过monkey_patch的方式进行替换了。这里受影响的就是eventlet 对原生的thread 进行了补丁操作,对thread的获取线程id的方法get_ident()进行了重写。

def get_ident(gr=None):
    if gr is None:
        return id(greenlet.getcurrent())
    else:
        return id(gr)

这样,就会引起,之前thread.get_ident() 和monkey_patch 后的thread.get_ident() 获取的值不同,monkey_patch() 后获取的是线程中绿色协程的id。

想要解决这个问题,可以在monkey_patch() 时,不对thread进行打补丁,monkey_patch(thread=False),就可以解决。不过,这样不知道会不会影响到eventlet提供的写成并发能力。如果有大牛,可以做进一步补充。

你可能感兴趣的:(django,开发,原创,python)