覆盖Django mysql model中save方法时碰到的一个数据库更新延迟问题


最近有一个需求,通过django的admin后台,可以人工配置5张表的数据,这些数据进行一些业务规则处理后会统一成一份数据缓存在一个cache之中供服务端业务访问,因而任何一张表的数据更新(增、删、改),都要需要重新根据规则计算数据结果,并更新cache。

首先想到的方法就是覆盖每个表model子类中的save方法,在其中先调用父类的save方法走原有保存逻辑更新数据到数据库后,之后再单独调用一次cache的更新逻辑,这样每张表的任意数据被用户更新后,都将先触发model的数据库更新、而后执行cache的数据更新,其中Application表model的代码如下所示:

 1 class Application(models.Model):
 2     name = models.CharField(max_length=128, blank=False, verbose_name=u'应用名')
 3     description = models.TextField(blank=False, verbose_name=u'应用描述')
 4     status = models.IntegerField(verbose_name=u'状态', choices=APPLICATION_STATUS)
 5     mtime = models.DateTimeField(blank=False, verbose_name=u'修改日期', auto_now=True)
 6     ctime = models.DateTimeField(blank=False, verbose_name=u'创建日期', auto_now_add=True)
 7     class Meta:
 8         db_table = 'application'
 9         verbose_name_plural = u'应用'
10 
11     def save(self, *args, **kargs):
12         super(Application, self).save(*args, **kargs)
13         # 更新memcached逻辑实现函数,该函数为通用函数一部分,会单独建立mysql连接,查询数据库数据,并更新到memcached
14         update_memcached_from_mysql()

这样每次在web上新增或者修改数据表记录时,都会先执行父类save操作,save完成后,又会执行update_memcached_from_mysql函数,从mysql查询到最新数据,而后更新到cache之中了。

然而实际测试的时候,发现每次修改数据时,更新到cache的并不是最新数据,而是未修改前的旧数据,比如当前name="test0",修改为name="test1"点击保存后,更新到cache之中的确还是test0,再次修改为name="test2",更新到cache之中的确实test1。

百思不得其解~怀疑是model执行save时,本地有cache会延迟更新,于是在super.save和update_memcached_from_mysql之间增加了time.sleep(10),并多次调用update_memcached_from_mysql函数,可是依然是每次修改保存时,更新到cache的数据都是修改前的取值:

1     def save(self, *args, **kargs):
2         super(Application, self).save(*args, **kargs)
3         # 更新memcached逻辑实现函数,该函数为通用函数一部分,会单独建立mysql连接,查询数据库数据,并更新到memcached
4         update_memcached_from_mysql()
5         time.sleep(10)
6         update_memcached_from_mysql()
7         time.sleep(10)
8         update_memcached_from_mysql()

想不出好的解决方案,猜测model真正将数据更新到数据的时机是在save整个函数执行结束后,臆测了如下更新逻辑:

1 子类save执行前
2 父类save执行
3 更新memcached
4 子类save执行结束
5 真正更新到数据库

于是必须想办法将第3步的cache更新逻辑挪到save执行结束后,然后要保证每次执行save操作时更新cache,这个位置又不能动~~

于是考虑通过开启独立线程异步执行的方式实现,改写update_memcached_from_mysql,在其中开启独立线程执行一个delay版本的更新函数,线程start后会先休眠n秒钟(n为可控参数,下例中为2),而后才执行从数据库读取数据并更新到cache的逻辑,改完后手动更新数据多次,验证已经能拉取到最新数据。

1 def update_memcached_from_mysql():
2     """ 
3     猜测由于model的缓存机制,save函数执行完成前,新的数据可能未及时更新到数据库,
4     此处开启独立线程执行memcache更新操作,线程中会休眠数秒再从数据库拉取最新数据更新
5     """
6     td = threading.Thread(target=update_memcached_from_mysql_delay, args=(2, ))
7     td.start()

然而之前的更新流程还仅仅是猜测而已,虽然采用线程异步延迟更新cache的方法后,多次修改验证避开了取不到新数据的问题,并不就说明猜测一定是正确的,而且即便猜测是正确的,如果save函数执行完后,model的数据更新没有在线程延迟时间结束前完成,理论上还是会有问题,考虑可以通过设置一个定时任务,比如每隔10分钟定时执行cache更新逻辑,来保证新数据最多延迟10分钟也能生效。

本来想深入探究model save更新机制~然而最近太忙了~~blog都两周没更新了,初步尝试了一下也还没有研究清楚这一块save逻辑的源码,这个数据修改平台也仅供内部使用~~暂时先这么修补一下~~以后有时间再深究这一块的问题~~加入TODO list。