如何在Django中监听缓存条目过期并触发回调逻辑
Django缓存订单过期触发库存回退的实现方案
Django自带的缓存系统本身没有内置"过期信号"机制,不过可以通过以下几种方式实现你要的逻辑:
1. 基于Redis键过期通知(推荐,若用Redis作为缓存后端)
如果你的Django项目用Redis做缓存,可以利用Redis的键空间通知功能来监听缓存条目过期事件:
步骤:
先修改Redis配置(
redis.conf),开启键过期通知:notify-keyspace-events Ex或者临时通过Redis命令开启:
CONFIG SET notify-keyspace-events Ex在Django项目中编写一个监听进程,订阅Redis的过期事件频道,当订单相关的键过期时,触发库存回退逻辑:
import redis from django.core.cache import cache from myapp.models import Order, Product def listen_cache_expiry(): # 连接Redis(和Django缓存用同一个实例) r = redis.Redis.from_url(cache._backend.client.connection_pool.connection_kwargs['url']) # 订阅过期事件频道,0是数据库编号,根据你的Redis配置调整 pubsub = r.pubsub() pubsub.subscribe('__keyevent@0__:expired') for message in pubsub.listen(): if message['type'] == 'message': expired_key = message['data'].decode('utf-8') # 判断是否是订单缓存键,比如你的订单键格式是"order:{order_id}" if expired_key.startswith('order:'): order_id = expired_key.split(':')[1] try: # 这里写库存回退逻辑 order = Order.objects.get(id=order_id) for item in order.items.all(): product = Product.objects.get(id=item.product_id) product.stock += item.quantity product.save() # 标记订单为已过期取消 order.status = 'cancelled' order.save() except Order.DoesNotExist: continue把这个监听进程作为独立服务运行,或者用supervisor管理,确保它一直在线。
2. 定时扫描过期订单(通用方案,兼容所有缓存后端)
如果不想依赖Redis的特性,可以用定时任务定期扫描待处理的订单,检查缓存是否已过期:
步骤:
- 用Celery Beat或者Django Q这类定时任务工具,每隔一段时间(比如5分钟)执行一次扫描任务:
from celery import shared_task from django.core.cache import cache from myapp.models import Order, Product @shared_task def check_expired_orders(): # 假设你把待结算的订单ID存在一个集合缓存里,或者从数据库查状态为"pending"的订单 pending_order_ids = cache.get('pending_orders', []) for order_id in pending_order_ids: # 检查订单缓存是否存在,不存在说明已过期 if not cache.get(f'order:{order_id}'): try: order = Order.objects.get(id=order_id) # 库存回退逻辑 for item in order.items.all(): product = Product.objects.get(id=item.product_id) product.stock += item.quantity product.save() order.status = 'cancelled' order.save() # 从pending集合中移除 pending_order_ids.remove(order_id) cache.set('pending_orders', pending_order_ids) except Order.DoesNotExist: continue - 配置定时任务,比如每5分钟执行一次
check_expired_orders。
3. 自定义缓存逻辑,记录过期时间到数据库
另一种思路是在缓存订单的时候,同时把过期时间存入数据库,然后用定时任务对比当前时间和过期时间,处理过期订单:
步骤:
- 缓存订单时,额外记录过期时间(需要先在
Order模型中添加expires_at字段):from django.core.cache import cache from datetime import datetime, timedelta from myapp.models import Order def cache_order(order): expiry_minutes = 30 # 30分钟过期 cache.set(f'order:{order.id}', order.to_dict(), timeout=expiry_minutes*60) # 更新订单的过期时间字段 order.expires_at = datetime.now() + timedelta(minutes=expiry_minutes) order.status = 'pending' order.save() - 定时任务扫描
expires_at小于当前时间且状态为pending的订单:from celery import shared_task from datetime import datetime from myapp.models import Order, Product @shared_task def process_expired_orders(): expired_orders = Order.objects.filter( status='pending', expires_at__lt=datetime.now() ) for order in expired_orders: # 验证缓存是否真的过期(防止手动刷新缓存的情况) if not cache.get(f'order:{order.id}'): # 库存回退 for item in order.items.all(): product = Product.objects.get(id=item.product_id) product.stock += item.quantity product.save() order.status = 'cancelled' order.save()
注意事项:
- 所有方案都要处理并发问题,比如用数据库事务或者锁,避免重复回退库存。
- 第一种Redis监听的方法要注意进程稳定性,可结合第二种定时扫描作为兜底,防止监听进程挂掉后漏掉事件。
内容的提问来源于stack exchange,提问作者Precious112
相关产品推荐
相关产品推荐

