最近在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服务正常启动,并且可以正常读写数据库,获取数据。
但是,当在代码中引入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,再次访问,发现无法正常读写数据库,提示报错
服务端看报错:
具体报错:
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提供的写成并发能力。如果有大牛,可以做进一步补充。