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信号机制是否稳定,是否存在遗漏风险?
猜想大概有几种可能,可以逐一排除一下:
1. update 不发送 post_save 的 signal。代码里有的地方是直接 update 而不是调用的 save
2. save 放在了一个 transaction 里面。task 的代码是发送到 celery 上执行的,可能这时候 transaction 还没提交,objects.get 会报错,被你 catch,return 掉了
3. redis 里的 key 被其他地方重复使用。这个搜一下代码就知道了
4. 统计脚本有问题
Django的信号机制本身是稳定的,它是框架的核心功能之一,经过长期生产环境验证。但“遗漏风险”确实存在,这通常不是信号本身的bug,而是使用方式导致的。
主要风险点在于信号注册的时机。如果包含@receiver装饰器的模块没有被导入,信号回调就不会被注册。常见情况是:
- 在
models.py中直接定义信号接收函数,这通常是安全的,因为Django启动时会导入所有应用的models。 - 在独立的
signals.py中定义,但忘记在应用的apps.py的ready()方法中导入该模块。这是最常导致“信号不响”的原因。
正确做法示例:
# 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 吧?

