You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 06:45:14