详细摘要 摘要
生成:2025-06-21 19:03摘要详情
- 音频文件
- 2019-10-19 | DjangoCon 2019 | Django REST Framework: Taking your API to the next level by Carlos Martinez
- 摘要类型
- 详细摘要
- LLM 提供商
- openai
- LLM 模型
- gemini-2.5-pro
- 温度
- 0.3
- 已创建
- 2025-06-21 19:03:27
摘要内容
概览/核心摘要 (Executive Summary)
本内容总结了 Carlos Martinez 在 DjangoCon 2019 上关于提升 Django REST Framework (DRF) API 性能与功能的演讲。演讲的核心在于,一个初始简单的 DRF API 会随着数据量的增长而面临严重的性能瓶颈,主要原因是未经优化的数据库查询(N+1问题)。Carlos 通过一个实例展示了如何将一个响应时间从超过2分钟的 API 优化到仅需124毫秒。
为实现这一目标,他系统性地介绍了六个关键技术和实践:
1. 动态序列化器 (Dynamic Serializers): 通过自定义基类,根据不同上下文(如嵌套对象)动态调整序列化字段,以重用代码并减少不必要的数据传输。
2. 查询优化: 使用 select_related 和 prefetch_related 从根本上解决 N+1 查询问题,并通过 Prefetch 对象进行更精细的数据获取控制。
3. 高级过滤: 借助 django-url-filter 等库,实现复杂的、基于 URL 的数据筛选功能。
4. 多层缓存: 结合使用 Django 内置的页面缓存和 django-cacheops 库对 ORM 查询结果进行缓存,大幅提升响应速度。
5. 精细化权限管理: 采用 dry-rest-permissions 将权限逻辑直接定义在模型(Model)中,实现全局、对象级、字段级的精细化权限控制,使代码更清晰。
6. 自定义渲染器: 扩展 API 的输出格式,如通过 drf-renderer-xlsx 等工具轻松支持 Excel 文件导出。
演讲强调,综合运用这些策略,可以构建出既可扩展、高性能,又易于维护的健壮 API。
背景:API性能瓶颈问题
演讲者首先构建了一个包含用户、事件和票务的简单 API。在数据量较少时,API 表现良好。但随着数据增长,问题开始显现。
- 初始状态: API 端点返回嵌套对象(例如,票务信息中包含完整的事件和用户信息)。
- 问题暴露:
- 当演讲者向数据库中添加约5000条新记录后,API 的响应变得极其缓慢。
- 根本原因在于 N+1 查询问题:DRF 在序列化嵌套对象时,每处理一个主对象,就会为每个关联的嵌套对象发起一次独立的数据库查询,导致查询数量呈爆炸式增长。
- 量化影响:
- 优化前: 获取一个包含大量数据的列表,响应时间长达 “2分5秒” (two minutes and 5s)。
- 优化后: 经过一系列优化,同样的请求(分页后)响应时间缩短至 124毫秒。
解决方案一:动态序列化器 (Dynamic Serializers)
为了在不同场景下(如列表视图 vs. 嵌套表示)返回不同的字段,同时避免代码重复,演讲者推荐使用动态序列化器。
- 目的: 根据上下文动态选择序列化器应包含的字段。
- 实现方式:
- 创建一个继承自
ModelSerializer的自定义基类,如DynamicSerializer。 - 在该基类中,通过覆盖
__init__方法或使用特定属性来接收和处理需要动态显示的字段列表。 - 在具体的序列化器中,定义多个静态方法(
staticmethod),每个方法返回一个特定场景下所需的字段元组(tuple)。例如,可以创建一个
get_list_fields()方法用于列表视图,创建一个get_nested_fields()方法用于嵌套表示。
- 创建一个继承自
- 优点:
- 代码复用: 将所有与同一模型相关的序列化逻辑集中在一个类中。
- 减少冗余: 无需为每个视图或嵌套级别创建新的序列化器类。
- 灵活性: 轻松控制 API 在不同端点返回的数据结构。
解决方案二:查询性能优化
这是解决性能瓶颈的核心步骤,旨在通过减少数据库查询次数来提升效率。
-
select_related:- 适用场景: 用于优化外键(
ForeignKey)和一对一(OneToOneField)关系。 - 工作原理: 通过 SQL
JOIN操作,在一次数据库查询中获取主对象及其关联对象。 - 效果: 将多次单独查询合并为一次包含连接的查询,显著减少数据库交互。
- 适用场景: 用于优化外键(
-
prefetch_related:- 适用场景: 用于优化多对多(
ManyToManyField)和反向外键关系。 - 工作原理: 它不使用
JOIN,而是执行两次独立的查询。第一次查询主对象,第二次使用主对象的 ID 列表,通过WHERE ... IN (...)子句一次性获取所有相关的子对象。 - 效果: 虽然是两次查询,但远比 N+1 次查询高效。
- 适用场景: 用于优化多对多(
-
Prefetch对象:- 目的: 对
prefetch_related进行更精细的控制。 - 实现方式: 将
Prefetch对象实例传递给prefetch_related,可以在该对象中指定一个自定义的QuerySet。 - 高级用法: 可以在自定义
QuerySet中使用.only('id', 'name')等方法,仅预取所需字段,进一步减少数据传输量和内存消耗。queryset.prefetch_related(Prefetch('related_field', queryset=RelatedModel.objects.only('id', 'name')))
- 目的: 对
解决方案三:高级过滤
为了让 API 用户能更灵活地获取所需数据,演讲者推荐使用 django-url-filter 库。
- 功能: 允许通过 URL 参数进行复杂的、跨关联模型的查询。
- 使用示例:
- 筛选多个ID:
?id__in=1,2,3 - 模糊匹配名称:
?name__contains=some_text - 跨关系筛选:
?promoter__name__icontains=promoter_name
- 筛选多个ID:
- 集成:
- 在视图集(ViewSet)的
filter_backends中添加URLFilterBackend。 - 通过
filterset_fields指定允许过滤的字段,或创建可复用的ModelFilterSet类进行更复杂的定义。
- 在视图集(ViewSet)的
解决方案四:多层缓存策略
缓存是提升高频读取 API 性能的有效手段。
-
Django 内置缓存:
- 设置: 在
MIDDLEWARE中添加UpdateCacheMiddleware和FetchFromCacheMiddleware。 - 使用: 在视图方法上使用
@cache_page装饰器来缓存整个页面的响应。 - 关键警告: “请不要忘记以某种方式使缓存失效” (Please do not forget to invalidate cash in some way)。当数据发生变化时,必须有相应的机制来清除旧的缓存,否则用户会看到过时的数据。
- 设置: 在
-
django-cacheops库:- 依赖: 需要使用 Redis 作为缓存后端。
- 功能: 能够自动缓存 ORM 查询结果。
- 配置: 可以在
settings.py中精细配置哪些应用或模型的哪些操作(如get,fetch)默认被缓存及其超时时间。 - 手动使用: 可以通过在查询集末尾附加
.cache()方法来手动触发对该查询结果的缓存。MyModel.objects.filter(...).cache()
解决方案五:精细化权限管理
为了使权限逻辑更清晰、更集中,演讲者推荐了 dry-rest-permissions 库。
- 核心思想: 将权限检查的逻辑从视图(View)转移到模型(Model)中。
- 实现方式:
- 在视图集的
permission_classes中添加DRYPermissions。 - 直接在模型类中定义特定的权限方法。
- 在视图集的
- 权限类型:
- 全局权限:
has_read_permission(request)、has_write_permission(request) - 对象级权限:
has_object_read_permission(request, obj),可以访问对象实例obj。 - 字段级权限:
has_my_field_write_permission(request, obj),可以保护特定字段的读写。 - 动作级权限: 可以为自定义的 ViewSet action 定义权限。
- 全局权限:
- 注意事项: 当在非
ModelViewSet(如APIView)中使用时,必须在创建序列化器时手动传入request上下文,否则模型中的权限方法将无法获取请求信息。
>serializer = MySerializer(data=..., context={'request': request})
解决方案六:自定义渲染器
为了满足多样化的客户端需求,可以为 API 添加不同的输出格式。
- 示例: 生成 Excel 文件。
- 工具: 使用
drf-renderer-xlsx等库(演讲中提及XLSXFileMixin和XLSXRenderer)。 - 实现:
- 在视图集中继承相应的 Mixin。
- 在
renderer_classes列表中添加新的渲染器,如XLSXRenderer。
- 重要提示: 如果希望保留 DRF 默认的浏览器友好界面,不要忘记在
renderer_classes中同时保留BrowsableAPIRenderer。
结论与成果
通过综合应用上述六种技术,演讲者成功将一个存在严重性能问题的 API 转变为一个高效、健壮且功能丰富的服务。
- 性能飞跃: 响应时间从 2分5秒 降至 124毫秒,直观展示了优化的巨大价值。
- 查询效率: 优化后的 API 数据库查询次数显著减少,日志记录显示查询语句变得极为简洁。
- 可维护性: 将序列化、权限等逻辑进行集中管理,提高了代码的可读性和可维护性。
- 最终建议: 演讲者强调,分页(Pagination)对于处理大规模数据集仍然是必不可少的,即使经过了多重优化。