Python中Django的models信号机制是否稳定,是否存在遗漏风险?

我在项目中使用了 receiver 来监测 django models(mysql)的 post_save 信号, 这个 receiver 发送一个异步任务至 celery,异步任务写入数据至 redis,最后由一个脚本取出数据来进行统计任务。

receiver 的写法如下:

@receiver(post_save, sender=test_models)
def record_processor(sender, instance, **kwargs):

tasks.test_task.apply_async(args=[instance.id])

异步任务(celery)如下:

@app.task(bind=True)
def record_test(self, test_models_id):
try:
test = test_models.objects.get(id=test_models_id)
except:
return
date = datetime.now().date()
key2 = “{0}:{1}”.format(date, “test_num”)
pipe = r.pipeline()
pipe.incr(key2)
pipe.expire(key2, expired_time)
pipe.execute()


但在整个项目工作过程中,只有约十分之一的数据发送到了 redis 中——异步任务没有报错提醒。我不确定是哪个环节出了问题(从经验来看似乎 django 中 sql 的 singal 发送会有些不够稳定),想询问一下可能的原因以及解决的方案
Python中Django的models信号机制是否稳定,是否存在遗漏风险?


7 回复

猜想大概有几种可能,可以逐一排除一下:
1. update 不发送 post_save 的 signal。代码里有的地方是直接 update 而不是调用的 save
2. save 放在了一个 transaction 里面。task 的代码是发送到 celery 上执行的,可能这时候 transaction 还没提交,objects.get 会报错,被你 catch,return 掉了
3. redis 里的 key 被其他地方重复使用。这个搜一下代码就知道了
4. 统计脚本有问题


Django的信号机制本身是稳定的,它是框架的核心功能之一,经过长期生产环境验证。但“遗漏风险”确实存在,这通常不是信号本身的bug,而是使用方式导致的。

主要风险点在于信号注册的时机。如果包含@receiver装饰器的模块没有被导入,信号回调就不会被注册。常见情况是:

  1. models.py中直接定义信号接收函数,这通常是安全的,因为Django启动时会导入所有应用的models。
  2. 在独立的signals.py中定义,但忘记在应用的apps.pyready()方法中导入该模块。这是最常导致“信号不响”的原因。

正确做法示例:

# myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import MyModel

@receiver(post_save, sender=MyModel)
def my_handler(sender, instance, created, **kwargs):
    if created:
        print(f"新建实例: {instance.id}")

# myapp/apps.py
from django.apps import AppConfig

class MyappConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myapp'

    def ready(self):
        import myapp.signals  # 关键!确保信号模块被导入

其他注意事项:

  • 事务与异常:信号通常在数据库事务中触发。如果接收函数抛出异常,可能会中断整个事务(取决于ATOMIC_REQUESTS等设置)。
  • 执行顺序:多个接收器对同一信号的执行顺序不固定(虽然通常按注册顺序),不要依赖特定顺序。
  • 测试:在测试中,有时需要临时断开信号,可以使用django.test.override_settings或手动断开。

总结:信号稳定,但需要确保正确注册。

估计是数据还没保存到数据库任务就已经运行了 test_models.objects.get(id=test_models_id) ,然后获取不到数据

receiver 和 task 里打个 log 看看
另外数据库是读写分离的么

已解决
因为使用了读写分离的缘故 导致这个信号的异步任务发起时没有从读库获取对应的信息 现在在异步任务里加一个 delay 应该就可以解决了

是的 应该是读写分离的缘故 读库速度较慢

更好的办法是用 canal 吧?

回到顶部