DEV Community

TianZhen
TianZhen

Posted on • Originally published at youth2009.org on

2

Job queue rollback

背景

我们的异步队列是Tasktiger,一个背后有商业公司支持的开源项目。

之前是Pyres,一个Resqueçš„Pythonå®žçŽ°ï¼Œå› ä¸ºGithub在使用Resque,Instagram在使用Pyres,我们应该可以用一段时间。

后来Pyres长久不再维护,积累了一些bug以及性能问题让我们不得不选择更换一个异步队列系统,期初考虑过Celery,那是一个很成熟很强大的队列系统,以至于我很难一下子搞清它。

问题

在使用Tasktiger后有时会遇到已经处理完毕的task又重新被enqueue的情况,如果所有task都是幂等的(Idempotent),那这也不是问题,第二次执行taskä¼šå› ä¸ºä¹‹å‰å¤„ç†è¿‡è€Œä¸è‡³äºŽå†åšä¸€æ¬¡ã€‚ç„¶è€Œå¹¶ä¸æ˜¯æ‰€æœ‰taskéƒ½å®Œæˆäº†å¹‚ç­‰æ”¹é€ ï¼Œæ‰€ä»¥è¿™å¯¹äºŽæˆ‘ä»¬æ¥è¯´æ˜¯ä¸ªä¸¥é‡é—®é¢˜ï¼Œä½†å½“æ—¶å¹¶ä¸èƒ½ç¨³å®šé‡çŽ°ã€‚

探索

怀疑过许多地方,后来发现在queue redis上执行BGREWRITEAOF就能避免这个问题,于是把原先一个月执行一次的AOFé‡å†™æ“ä½œå˜æˆäº†æ¯å¤©æ‰§è¡Œä¸€æ¬¡ï¼Œå¤§æ¦‚åœ¨å¤‡ä»½æ•°æ®ä¹‹å‰ï¼Œå› ä¸ºå¤‡ä»½éœ€è¦åœæœºï¼Œå¦‚æžœä¸åœ¨è¿™ä¹‹å‰åšï¼Œå¯èƒ½å¤‡ä»½å¯¼è‡´é‡å¯æœåŠ¡å°±ä¼šè®©å¾ˆå¤šå·²ç»å¤„ç†è¿‡çš„task重复处理,例如可能会给会员发多条短信和微信提醒。


å°±åœ¨æ˜¨å¤©ï¼Œå› ä¸ºé‡å¯queue redis导致很多task重复处理,这个问题终究没有被解决,于是花了大约2å¤©æ—¶é—´ï¼Œæ‰¾åˆ°äº†å¯¼è‡´é—®é¢˜çš„æ ¹æºã€‚

之前猜测是不是Tasktiger哪里没写好导致,毕竟这个项目在Github上只有不到500个star,而redisæœ‰å‡ ä¸‡ï¼Œå¹¿å¤§ç”¨æˆ·åŸºç¡€æ›´å®¹æ˜“å‘çŽ°å¥‡æ€ªbug,而在Tasktiger上遇到某个bugï¼Œä¹Ÿè®¸ä½ å°±æ˜¯ä¸–ç•Œä¸Šç¬¬ä¸€ä¸ªé‡åˆ°çš„ã€‚åŸºäºŽè¿™ä¸ªå‡è®¾æˆ‘çœ‹äº†å¾ˆå¤šæºä»£ç ï¼Œæ²¡æœ‰æ‰¾åˆ°å¯é è¯æ®ï¼Œè€Œä¸”åŸºäºŽä¸€ä¸ªç®€å•äº‹å®žï¼šä¸€ä¸ªtask从最开始不存在,到出现在Tasktigerä¸­ï¼Œåˆ°å¤„ç†å®Œè¢«åˆ é™¤ï¼Œå¦‚æžœæ˜¯Tasktigerçš„é—®é¢˜ï¼Œé‚£å®ƒä»Žå“ªé‡Œå¾—åˆ°æ•°æ®æž„é€ å‡ºä¸€ä¸ªtask呢?难道是redis的问题吗?

queue redis一般都会启用AOF,所有修改数据的命令和值被记录到AOF中,等redis重启时,会执行AOFæ–‡ä»¶ä¸­çš„å‘½ä»¤ï¼Œè¿™æ ·rediså°±æœ‰äº†å’Œé‡å¯å‰ä¸€æ¨¡ä¸€æ ·çš„æ•°æ®ã€‚åœ¨AOF中记录着某个task从出现到消失的全过程,难道是redis没完成loadå°±æŽ¥å—æœåŠ¡äº†ï¼Ÿä»Žæ—¥å¿—ä¸Šçœ‹å¹¶ä¸æ˜¯è¿™æ ·ï¼Œåœ¨æ²¡load完执行操作会得到异常。

看了下生产环境的queue状态,基本没有正在处理的task,停掉queue redis,下载AOF文件,在本地让redis启动并load这份AOF,不启动Tasktiger的worker,只静静地看queue的状态,发现load完后有很多task被重新放回了queue,此时如果有worker,一定会重复执行。

到这里难道能确定是redis的问题吗?redisçš„AOF并不能让rediså›žåˆ°ä¹‹å‰çš„çŠ¶æ€ï¼Ÿå¦‚æžœæ˜¯è¿™æ ·çš„è¯redis怎么会被广泛应用?

于是找了一个task,从AOF中看看Tasktiger是如何处理它的。

处理过程没发现异常,只是Tasktiger用了很多lua脚本操作数据,例如有个脚本是:当一个keyä¸åœ¨ç»™å®šçš„æŸå‡ ä¸ªzsetä¸­æ—¶åˆ é™¤è¿™ä¸ªkeyã€‚è¿™ä¸ªæ“ä½œä¸å¤æ‚ï¼Œä½†ä½ æ— æ³•åªç”¨rediså®Œæˆï¼Œå› ä¸ºredisåªæ˜¯ä¸€ä¸ªæ•°æ®å­˜å‚¨ï¼Œæ— æ³•å¤„ç†å¤æ‚é€»è¾‘ï¼Œæ‰€ä»¥åªèƒ½ä½¿ç”¨lua完成,当然很多语都可以完成这个逻辑,只不过redis支持执行lua脚本,算作是对redis操作的补充。

å› ä¸ºæœ‰äº†ä¸€ä¸ªå…·ä½“task,查看和它相关的所有操作后,发现除非是在某一步lua脚本未执行时才会导致这个task被放在active queue中而不是被移除。

äºŽæ˜¯æˆ‘æž„é€ äº†ä¸€ä»½AOFï¼Œæœ€å¼€å§‹çš„éƒ¨åˆ†æ˜¯ä¸€å †SCRIPT LOAD指令,把Tasktiger需要的脚本都load到redisä¸­ï¼Œè¿™æ ·åŽç»­æ“ä½œæ‰å¯ä»¥ä½¿ç”¨EVALSHAï¼Œè¿™ä»½æž„é€ å¥½çš„AOF也通过了redis-check-aof检查,被load后再次查看queue状态,竟然正常了,和停redis之前一致!看来问题出在执行这些脚本的地方。

在redis中执行lua脚本有两种做法,第一种是使用EVALï¼Œè¿™ç§åšæ³•æ˜¯æŠŠè„šæœ¬å†…å®¹ä¸€å¹¶è´´ä¸Šï¼Œæ¯æ¬¡æ‰§è¡Œéƒ½éœ€è¦è¿™æ ·ï¼Œå¦‚æžœæ€»æ˜¯é‡å¤æ‰§è¡ŒæŸä¸ªç‰¹å®šè„šæœ¬å°±ä¼šå› ä¸ºä¼ è¾“äº†å¤§é‡è„šæœ¬å†…å®¹è€Œä½¿å¾—æ•ˆçŽ‡ä¸å¤Ÿé«˜ï¼ŒäºŽæ˜¯æœ‰äº†ç¬¬äºŒç§æ‰§è¡Œæ–¹æ³•––EVALSHA,使用这种需要脚本的SHA1,当执行SCRIPT LOAD后,redis返回的内容就是脚本的SHA1,load过的脚本在手工flush前或者redis重启前都是有效的。SCRIPT LOAD在BGREWRITEAOFåŽå¹¶ä¸ä¼šå­˜åœ¨ï¼Œè™½ç„¶ä½ è¿˜æ˜¯å¯ä»¥é€šè¿‡EVALSHA调用脚本。

那么这两种执行方法对于AOF文件记录内容有差别吗?没有,执行EVALSHA也会被记录成EVALï¼Œå¦‚æžœä½ åœ¨AOF中发现有EVALSHA那就奇怪了。在生产环境下载的AOF中,有不少EVALSHA,为何没有被记录成EVALï¼Ÿå› ä¸ºè¿™äº›EVALSHA并不是redis执行的,而是Tasktigerçš„pipeline

å¯¼è‡´å‡ºçŽ°å›žæ»šé—®é¢˜çš„åŽŸå› æ˜¯ï¼šBGREWRITEAOF后,由于SCRIPT LOAD被去掉,redis重启后如果有使用EVALSHAè°ƒç”¨è„šæœ¬çš„åœ°æ–¹ä¼šå‡ºé”™ï¼Œå› ä¸ºé‚£ä¸ªè„šæœ¬å¹¶æ²¡æœ‰è¢«redis load

不是说EVALSHA会被翻译成EVAL放在AOF中吗?是的,不过Tasktiger自己实现了一个redis pipeline,这也是个lua脚本,在这个lua脚本中是需要调用其他lua脚本的。调用方法包含两种,大多数都是EVALSHA, 由于redis禁止在lua中使用redis.call执行EVALSHA调用另一个脚本(并不是所有redis命令都可以在redis.call使用) ,Tasktiger使用了其他方法来调用,算是一种hack。

由于redisçš„é™åˆ¶åŠ ä¸ŠTasktigerçš„hack,导致 在BGREWRITEAOF后,如果有pipeline脚本的调用发生,下次重启redis会使得之前做过的task被重新放回queueï¼Œå› ä¸ºç§»é™¤æ“ä½œçš„è„šæœ¬è¿˜æ²¡æœ‰load,所以移除失败了,看起来就像是task被重新放回queue

目前我们针对这个问题的解决方法是:勤快点BGREWRITEAOF,每小时一次,重启redis之前再做一次

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay