如何在Django Rest Framework中处理并行请求避免重复数据?
解决DRF中同一IP并行请求导致的重复数据问题
针对你遇到的同一IP并行提交反馈导致重复数据的问题,之前用BooleanField的方案失效是因为并行请求存在竞态条件——多个请求同时读取到can_submit_feedback=True,然后都执行了保存操作。既然你倾向于在请求层面处理,下面提供几种可行的实现方式:
方法一:基于缓存的请求互斥锁
利用Django的缓存框架(生产环境推荐用Redis作为后端,性能更好),给每个请求的IP地址+API端点生成唯一锁键,请求到达时先尝试获取锁,成功则处理请求,失败则直接返回"请求处理中"的提示,避免并行执行。
实现步骤:
- 确保你的Django配置了分布式缓存(比如Redis),默认的内存缓存在多进程部署时会失效,无法实现跨进程的锁同步。
- 编写自定义装饰器,用于视图函数/类视图:
from django.core.cache import cache from rest_framework.response import Response from rest_framework import status import hashlib def prevent_parallel_requests(view_func): def _wrapped_view(request, *args, **kwargs): # 生成唯一锁键:IP地址 + 请求路径,确保同一IP同一端点的请求共享锁 ip_address = request.META.get('REMOTE_ADDR', 'unknown') endpoint = request.path lock_key = hashlib.md5(f"{ip_address}:{endpoint}".encode()).hexdigest() # cache.add是原子操作:只有键不存在时才会添加成功,避免竞态 if cache.add(lock_key, 'locked', timeout=5): try: # 执行原视图的业务逻辑 return view_func(request, *args, **kwargs) finally: # 请求处理完成后主动释放锁 cache.delete(lock_key) else: # 已有并行请求在处理,返回提示 return Response( {"detail": "请求正在处理中,请稍后再试"}, status=status.HTTP_429_TOO_MANY_REQUESTS ) return _wrapped_view
- 在你的API视图上应用装饰器:
from rest_framework.views import APIView class FeedbackSubmitView(APIView): @prevent_parallel_requests def post(self, request): # 你的反馈保存逻辑 user_id = request.data.get('user_id') message = request.data.get('message') student = Student.objects.get(id=user_id) Feedback.objects.create(user=student, message=message) return Response({"detail": "反馈提交成功"}, status=status.HTTP_201_CREATED)
这种方法的核心是cache.add()的原子性——多个并行请求同时调用add时,只有第一个能成功添加锁键,其他都会失败,完美规避竞态条件。
方法二:自定义中间件全局处理
如果多个API端点都需要防并行处理,可以写一个中间件,对指定端点做统一拦截:
from django.core.cache import cache from rest_framework.response import Response from rest_framework import status import hashlib class ParallelRequestBlockerMiddleware: def __init__(self, get_response): self.get_response = get_response # 定义需要拦截的端点列表 self.protected_endpoints = ['/api/submit-feedback/'] def __call__(self, request): # 只处理POST请求和指定端点 if request.method == 'POST' and request.path in self.protected_endpoints: ip_address = request.META.get('REMOTE_ADDR', 'unknown') lock_key = hashlib.md5(f"{ip_address}:{request.path}".encode()).hexdigest() if not cache.add(lock_key, 'locked', timeout=5): return Response( {"detail": "请求正在处理中,请稍后再试"}, status=status.HTTP_429_TOO_MANY_REQUESTS ) try: response = self.get_response(request) finally: cache.delete(lock_key) return response return self.get_response(request)
然后把这个中间件添加到settings.py的MIDDLEWARE列表中即可。
注意事项
- 缓存后端选择:生产环境不要用默认的
LocMemCache,多进程/多服务器部署时缓存不共享,锁会失效,推荐用Redis或Memcached。 - 锁超时时间:要根据API的实际处理时间合理设置,太短可能导致正常请求未完成锁就释放,太长可能导致异常情况下锁长期存在。
- IP局限性:如果多个用户共享同一IP(比如公司网络、公共WiFi),这种方法会误拦截不同用户的请求。若API支持用户认证,可以把
用户ID+IP作为锁键,实现更精准的互斥。
内容的提问来源于stack exchange,提问作者SHIVAM JINDAL
相关产品推荐
相关产品推荐

