异曲同工的租约

问题描述

有这样一个需求:WordPress中有一个叫做wp-cron.php的文件,它负责做一些定时任务,例如定时发送博文,定时清理垃圾回复等。因为WordPress是运行在Web PHP环境下,不借助第三方工具,实现定时任务有一定的困难。它的思路是,每当博客有点击时,就总触发一次cron,为了不阻塞客户的正常访问,用到了fopensocket 发起一个异步cron请求,当cron页面收到这个请求的时候,就开始检查各种执行条件,如果条件满足,则从数据库中获取cron任务并执行。

这里的问题是,如果有两个用户同时点击了页面,同时触发了cron任务,如何保证只起一个cron?如何保证起了一个cron后,它不会退出,常驻后台?如何保证万一cron退出了,会有后备的cron能起来?

基本流程


对于每一个cron请求,按照下面的顺序执行: 
SELECT获取一个任务 
把这个任务从数据表中删除 
检查删除是否成功 
-如果删除成功,则开始执行SELECT到的任务 
-否则直接退出(有另外一个cron请求也在执行这个任务)

存在的问题


这种方法简单粗暴,很能解决问题,但是它有这样几个问题:

-顺序问题:SELECT取任务的顺序必须都一致。cron1取A、B;cron2取B、A,则可能两个cron都会先后主动退出,导致后台没有cron了。 
-开销问题:每次都会尝试起cron,意味着每次都会发起一次内部的http连接

如何解决


  1. 顺序问题:这个保证每次SELECT都是按照主键顺序取即可,或者按照某个行值唯一的列顺序执行即可。
  2. 开销问题:引入lease(租约)机制,每个cron job一旦启动,就会持有一个lease(3秒),每次执行完一个任务,就续一下自己的lease。任何cron希望启动的时候,必须先看一下lease是否过期,如果lease过期,则立即获取lease,并启动自己。
  3. 由于没有加锁,可能两个cron都抢到了lease,没关系,当他们处理任务的时候,会有一个主动放弃(见上面的流程说明)。这种情况比较罕见,不会影响性能。

缺陷


上面的方案,在特殊情况下还是有一些小缺陷: 
1. cron job中途异常退出后的3秒内,新的任务无法被执行。如果cron job异常退出3秒后,不再有新的请求到来,那么任务队列中堆积的任务将无人处理。

如果cron job异常退出的可能性比较低,则这不是一个很大的问题。如果需要确保任务总能被及时执行,可以考虑使用Linux系统自带的crontab,来定时触发PHP的Cron Job。

关于LEASE


分布式系统中,Lease的概念被广泛采用。当我们无法确切了解到彼此的行为时,我们可以依赖一套约定,来规范和预测彼此的行为,以保障系统处于一个一致的状态。所谓“一致的状态”,就是我们觉得正确、可以理解的状态。上文中,两个cron请求无法知道彼此的存在,通过Lease的方式,很好地达成了一致,不会出现两个cron job同时运行的窘境。

补记


PHP脚本的执行时间,是有限制的,即使在脚本执行之初调用了 ignore_user_abort 方法。该方法的语义是设置客户端断开连接时是否中断脚本的执行,并不能改变PHP脚本最长。控制PHP最大执行时长的,需要修改php-fpm、nginx等的配置,详细参考 这里这里 。不过,在脚本中,也是可以改变PHP的最大执行时间的,相关函数请参考set_time_limit()ini_set(“max_execution_time”, “45”),这里还有一篇小结。根据PHP官方文档,希望在脚本中设定最大执行时间的时候,必须保证php.ini配置中safe_mode=Off。一般默认改选项都是Off,所以你可以在脚本中设置一个无限长的脚本运行时间。不过,安全起见,不建议运行无限长的时间,而是应该在ini_get(“max_execution_time”)的基础上减去若干秒来运行,然后主动释放lease。

上一篇:【数据科学老司机在线教学第二期】阿里云大数据生态协同过滤推荐系统实战


下一篇:数据科学老司机在线开车系列: 如何自己训练一个热狗识别模型