Django ASGIHandler 源码 - DB连接部分

本文详细解析了Django中ASGI入口点、ASGIHandler的实现,以及请求处理过程。着重分析了请求开始时如何通过request_started信号重置数据库查询和关闭旧连接。Django通过`django.db.utils.ConnectionHandler`管理数据库连接,使用`BaseDatabaseWrapper`类建立与数据库的链接。ORM操作时,QuerySet在实际使用时才会执行数据库查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无论是asgi还是wsgi协议,均已application为调用入口点

1. asgi入口点

def get_asgi_application():
    """
    The public interface to Django's ASGI support. Return an ASGI 3 callable.

    Avoids making django.core.handlers.ASGIHandler a public API, in case the
    internal implementation changes or moves in the future.
    """
    django.setup(set_prefix=False)
    return ASGIHandler()

2. ASGIHandler实现

请求到达django的过程大致是下面一段代码

class ASGIHandler(base.BaseHandler):
    """Handler for ASGI requests."""
    request_class = ASGIRequest
    # 切割响应体.
    chunk_size = 2 ** 16

    def __init__(self):
        super().__init__()
        # 加载中间件并传入异步参数
        self.load_middleware(is_async=True)

    async def __call__(self, scope, receive, send):
        """  每次请求到达时将从这里开始被处理
        Async 入口点 - 解析请求,然后传递给get_response
        """
        # django 目前3.1版本均只处理http协议的请求
        # 如果处理websocket等请求可以用channel项目 11月发布版本已支持asgi3协议
        # FIXME: 允许覆盖.
        if scope['type'] != 'http':
            raise ValueError(
                'Django can only handle ASGI/HTTP connections, not %s.'
                % scope['type']
            )
        # 以流对象的形式接收HTTP请求体
        try:
            body_file = await self.read_body(receive)
        except RequestAborted:*
*            return
        # 请求接收完成,可以被处理,设置线程头.
        set_script_prefix(self.get_script_prefix(scope))
        # 触发信号
        # django 目前直到3.1版本信号机制均是同步,所以用sync_to_async包装
        await sync_to_async(signals.request_started.send, thread_sensitive=True)(sender=self.__class__, scope=scope)
        # 获取请求并检查基本问题.
        request, error_response = self.create_request(scope, body_file)  # 调用ASGIrequest实例化
        if request is None:
            await self.send_response(error_response, send)
            return
        # 获取响应,使用BaseHandler的async模式.
        response = await self.get_response_async(request)
        response._handler_class = self.__class__
        # 增加文件响应的分块大小(ASGI服务器处理低级分块)。.
        if isinstance(response, FileResponse):
            response.block_size = self.chunk_size
        # 发送响应.
        await self.send_response(response, send)
    
    ...... 

3. DB连接处理

处理DB的方式在哪呢

这里与WSGIHandler一致,在call中,有对request_started信号的异步触发

await sync_to_async(
    signals.request_started.send, thread_sensitive=True
)(sender=self.__class__, scope=scope)

进入request_started信号,我们可以看到

from django.dispatch import Signal

request_started = Signal()
request_finished = Signal()
got_request_exception = Signal()
setting_changed = Signal()

emmmmmmmmm… 原来这四个信号都是信号基类Signal 的实例化,sync_to_async包装中传递的thread_sensitive参数为了线程安全,将该信号与该request请求处理逻辑运行在统一线程

那么在请求结束时,在django.http.response中,我们一定能找到可以找到

class HttpResponseBase:
    ......
    def close(self):
        ......
        # 结束处理发送结束信号
        signals.request_finished.send(sender=self._handler_class) 

这里的request_startedrequest_finished信号仅仅实例化,一定有具体实现注册信号的地方,查询搜索Django源码,找到在

我这里算是比较笨的办法了,如果有好的办法请告诉我一下,我如果发现了更好的办法会更新

  • django/db/init.py注册了两个request_started信号
# 当Django请求启动时,注册一个事件来重置保存的查询.
def reset_queries(**kwargs):
    for conn in connections.all():
        conn.queries_log.clear()


signals.request_started.connect(reset_queries)


# 注册一个事件来重置事务状态,并关闭超过其生命周期的连接。
def close_old_connections(**kwargs):
    for conn in connections.all():
        conn.close_if_unusable_or_obsolete()


signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)
  • django/core/cache/init.pydjango/db/init.py注册了request_finished信号

我们先看request_started 信号,从注释可以看出

接下来上Debug大法

我们可以使用uvicorn启动asgi服务器

3.1 调试

从异步信号触发开始

1

下一步,从send开始

在这里插入图片描述

receiver触发信号

django/db/__init__.py中,找到信号实现,链接建立初始化

在这里插入图片描述

当Django请求启动时,注册一个事件来重置保存的查询,在这里connections.all() 获取所有数据库链接,即在settings中配置的那些,在调试模式下或显示启用时查询日志,并调用clear方法

注意这里的信号,是第一次建立链接时触发,在同一条链接多次复用时,触发下面一个信号

在这里插入图片描述

注册一个事件来重置事务状态,并关闭超过其生命周期的连接(不可用或超时的链接)

可以看到这里request_started触发调用了close_old_connections,而close_old_connections里调用了connections(即ConnetionHandler实例)的all方法,其实它返回的是一个列表,里面的元素为DatabaseWrapper类,其实就是循环setting配置的DATABASES,实例化各ENGINE指定的db后端。

3.2 分析

connections 对应的类是ConnectionHandler() ,其主要实现如下:

# django/db/utils.py

class ConnectionHandler:
    def __init__(self, databases=None):
        """
        databases 是一个可选的数据库定义字典(结构类似 settings.DATABASES)
        """
        self._databases = databases
        # Connections仍然需要是一个实际的本地线程,因为它是真正的关键型线程。
        # 数据库后端应该使用@async_unsafe来保护他们的代码不受异步上下文的影响,
        # 但这将给这些上下文提供单独的连接,以防万一也需要。
        # 不过在异步上下文之后没有清理,所以如果可以的话,我们不允许这样做。
        self._connections = Local(thread_critical=True)
        
    @cached_property
    def databases(self):  # 2.2 
        if self._databases is None:
            self._databases = settings.DATABASES  # 2.2.1 获取一个数据库配置
        if self._databases == {}:
            self._databases = {
                DEFAULT_DB_ALIAS: {
                    'ENGINE': 'django.db.backends.dummy',
                },
            }
        if DEFAULT_DB_ALIAS not in self._databases:  # 判断是否配置了数据库
            raise ImproperlyConfigured("You must define a '%s' database." % DEFAULT_DB_ALIAS)
        if self._databases[DEFAULT_DB_ALIAS] == {}:
            self._databases[DEFAULT_DB_ALIAS]['ENGINE'] = 'django.db.backends.dummy'
        return self._databases
        
    def __getitem__(self, alias):
        # 2.1 关键点,如果local内有的话直接返回,调用方式 connections['db_name']
        if hasattr(self._connections, alias):
            return getattr(self._connections, alias)

        self.ensure_defaults(alias)
        self.prepare_test_settings(alias)
        db = self.databases[alias]  # 2.2 获取配置文件中db信息
        backend = load_backend(db['ENGINE'])  # 2.3 加载对应数据库后端
        # 2.5 上面load的base.py文件里都有DatabaseWrapper类,这里实例化这个类。
        # 它主要负责对应db后端的连接和关闭
        conn = backend.DatabaseWrapper(db, alias)
        setattr(self._connections, alias, conn)  # 2.6 连接放到local里,以接下来的复用
        return conn
        
    def __iter__(self):  # 3. self.databases即为setting配置文件里的DATABASES
        return iter(self.databases)
    
    def all(self):  # 1. connections.all()调用这里
        # 2. self可迭代,alias即为self.databases。而self[alias]即调用的__getitem__的实现
        # self[alias]就是各db实现的后端
        return [self[alias] for alias in self]

其中backend = load_backend(db['ENGINE']) 对应方法实现为

def load_backend(backend_name):
    """
    返回一个数据库后端的 "基础 "模块,
    给定一个完全限定的数据库后端名称,如果它不存在,则引发一个错误.
    """
    # This backend was renamed in Django 1.9.
    if backend_name == 'django.db.backends.postgresql_psycopg2':
        backend_name = 'django.db.backends.postgresql'

    try:
        # 2.4 加载对应的数据库处理类,实际就是django.db.backends.mysql.base类
        return import_module('%s.base' % backend_name)
    except ImportError as e_user:
        # 找不到数据库后台。
        # 显示一个有用的错误信息,列出所有内置的数据库后端。
        backend_dir = str(Path(__file__).parent / 'backends')
        builtin_backends = [
            name for _, name, ispkg in pkgutil.iter_modules([backend_dir])
            if ispkg and name not in {'base', 'dummy', 'postgresql_psycopg2'}
        ]
        if backend_name not in ['django.db.backends.%s' % b for b in builtin_backends]:
            backend_reprs = map(repr, sorted(builtin_backends))
            raise ImproperlyConfigured(
                "%r isn't an available database backend.\n"
                "Try using 'django.db.backends.XXX', where XXX is one of:\n"
                "    %s" % (backend_name, ", ".join(backend_reprs))
            ) from e_user
        else:
            # 如果有其他错误,这一定是Django的错误。
            raise

上面可以看到connections.all()里就是DatabaseWrapper类,每个都继承于BaseDatabaseWrapper,提供了基本的connect函数用于数据库连接,且赋值于属性self.connection(self.connection = self.get_new_connection(conn_params))


再看看DatabaseWrapper 的基类BaseDatabaseWrapper 主要实现,

# django/db/backends/base/base.py

class BaseDatabaseWrapper:
    """表示一个数据库链接."""
    # 将Field对象映射到其列类型.
    data_types = {}
    # 将Field对象映射到其SQL后缀,例如AUTOINCREMENT.
    data_types_suffix = {}
    # 将字段对象映射到其SQL以进行CHECK约束.
    data_type_check_constraints = {}
    ops = None
    vendor = 'unknown'
    display_name = 'unknown'
    SchemaEditorClass = None
    # 在__init__()中实例化的类.
    client_class = None
    creation_class = None
    features_class = None
    introspection_class = None
    ops_class = None
    validation_class = BaseDatabaseValidation

    queries_limit = 9000 
    ......
    
    def get_new_connection(self, conn_params):
        """打开与数据库的链接."""
        raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_new_connection() method')
    
    ......
    @async_unsafe
    def connect(self):
        """链接到数据库. 假定链接关闭."""
        # 检查配置是否有效.
        self.check_settings()
        # 如果之前的链接是在事务中关闭的
        self.in_atomic_block = False
        self.savepoint_ids = []
        self.needs_rollback = False
        # 重置参数定义何时关闭链接
        max_age = self.settings_dict['CONN_MAX_AGE']
        self.close_at = None if max_age is None else time.monotonic() + max_age
        self.closed_in_transaction = False
        self.errors_occurred = False
        # 建立链接
        conn_params = self.get_connection_params()
        self.connection = self.get_new_connection(conn_params)
        self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
        self.init_connection_state()
        connection_created.send(sender=self.__class__, connection=self)

        self.run_on_commit = [] 
    ......
    @async_unsafe
    def ensure_connection(self):  # 主要调这里建立连接
        """确保建立与数据库的连接."""
        if self.connection is None:
            with self.wrap_database_errors:
                self.connect() 
    ......
    def _close(self):
        if self.connection is not None:
            with self.wrap_database_errors:
                return self.connection.close()
    ......
    @async_unsafe
    def close(self):
        """关闭数据库链接."""
        self.validate_thread_sharing()
        self.run_on_commit = []

        # 不要调用validate_no_atomic_block(),以免在无效状态下难以摆脱连接。
        # 反正下一个connect()会重置事务状态。
        if self.closed_in_transaction or self.connection is None:
            return
        try:
            self._close()
        finally:
            if self.in_atomic_block:
                self.closed_in_transaction = True
                self.needs_rollback = True
            else:
                self.connection = None 

这里通过self.get_new_connection 与对应数据库后端建立连接,以mysql为例,对应的mysql后端中通过MySQLdb建立新的链接

# django/db/backends/mysql/base.py

try:
    import MySQLdb as Database
except ImportError as err:
    raise ImproperlyConfigured(
        'Error loading MySQLdb module.\n'
        'Did you install mysqlclient?'
    ) from err
......

class DatabaseWrapper(BaseDatabaseWrapper):
    ......
    @async_unsafe
    def get_new_connection(self, conn_params):
        return Database.connect(**conn_params)  # 通过MySQLdb新建db连接 

顺便一提,django默认使用与mysql创建链接的包是mysqlclientMySQLdbmysqlclient的部分了

到此,一个request请求到来后,检查数据库链接配置并实例化DatabaseWrapper类并关闭超时长链接部分已经完成,执行ORM是另一部分,我们再看。

4. ORM链接 —— 执行DB持久化

在此,我们首先要了解的是,QuerySet是惰性的,只有在我们真正需要对ORM操作取出的数据进行操作时,django才会去执行数据库查询,调用数据的情况包括迭代、序列化、与if合用

例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集books

books = BookInfo.objects.all()

继续执行遍历迭代操作后,才真正的进行了数据库的查询

for book in books:
	print(book.name)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值