小i约球诞生记04:约球小程序没有 MQ,怎么处理异步任务

本文是「小i约球诞生记」系列文章之一。

小i约球是一个微信小程序,主要帮乒乓球、羽毛球、台球、网球爱好者找到附近合适的球友。本文从一个具体技术或产品问题切入,记录约球小程序从 0 到 1 过程中的设计取舍。

官网:https://iyueqiu.cn

小程序:微信搜索「小i约球」

前言

这篇聊一个很常见的取舍:做约球小程序时,没有消息队列,能不能用数据库 + 定时任务先顶一下。

我一开始对这个问题的感觉也很简单。小程序项目嘛,用户量还没起来,很多事情同步做一下就好了。比如用户发布一条乒乓球约球或羽毛球约球信息后,顺手做一点后续处理;有些消息通知,接口里直接发掉;有些统计,写完主数据之后再顺手更新。

真正做起来就会发现,同步链路很容易越长越难受。

一个接口本来只负责“保存用户提交”,后来慢慢挂上通知、审核、统计、附近球友匹配、找搭子推荐、清理、失败重试。每一件事单看都不大,串在一起就麻烦了。接口变慢,失败点变多,用户等得久,开发者也不好排查。

这时候你就会想到 MQ。

但现实是,很多小程序云开发项目并没有开箱即用的 MQ。为了一个早期产品单独引入一套消息系统,又有点重。

于是数据库 + 定时任务,就成了一个很实用的过渡方案。

为什么需要异步

异步不是为了显得架构高级,而是为了把“用户正在等的事”和“系统可以稍后做的事”分开。

用户点击发布,最关心的是内容有没有保存成功。至于后面要不要发通知、要不要做匹配、要不要生成某些派生数据,这些事情通常不需要卡在用户面前。

放到约球场景里也是一样。用户发布约球名片时,最在意的是“我这条约球信息发出去了吗”。至于系统后面怎么找附近球友、怎么提醒可能感兴趣的人、怎么更新一些后台状态,这些更适合放到异步任务里。

如果全部同步做,问题会很明显。

第一,接口耗时会变长。用户体感不好,小程序也更容易遇到超时。

第二,失败会互相影响。主数据已经保存成功了,但后面某个通知接口失败,整个请求到底算成功还是失败?如果返回失败,用户可能重复提交;如果返回成功,后台又留下半截状态。

第三,代码会变脏。一个原本很清楚的业务入口,最后变成一条很长的流水线,什么都塞在里面。

所以我的判断是:只要某件事不是用户当下必须看到的结果,就可以考虑拆出去异步处理。

没有 MQ 时,可以怎么做

最朴素的办法,是在数据库里放一张“任务表”。

业务接口只做两件事:保存主数据,然后写一条待处理任务。定时任务每隔一段时间扫描待处理任务,取出一批来执行。执行成功就标记完成,执行失败就记录失败原因和重试次数。

这个方案听起来土,但很好理解,也好排查。

你能在数据库里直接看到现在有哪些任务还没处理,哪些失败了,失败了几次,最后一次失败原因是什么。对早期项目来说,这种可见性很重要。

它不像真正的 MQ 那样优雅,也没有那么强的吞吐能力,但它解决了一个很实际的问题:先把同步链路拆短。

我现在会把它理解成一种“轻量任务队列”,不是 MQ 的平替。

设计时要注意什么

数据库任务队列最怕的不是功能跑不起来,而是边界没想清楚。

第一,要有状态。

至少要区分待处理、处理中、处理成功、处理失败。否则任务一多,你很难知道系统到底卡在哪里。

第二,要有重试次数。

失败重试是异步任务的常态,但不能无限重试。网络抖动可以重试,参数错误一直重试就没有意义。一般会给一个最大次数,超过后进入失败状态,后面人工看情况处理。

第三,要考虑幂等。

定时任务可能重复扫到同一条任务,也可能执行到一半失败,下次又来一次。如果任务内容是发通知、改状态、生成记录,就要想清楚重复执行会不会造成脏数据。

第四,要控制并发。

如果多个定时任务实例同时跑,或者一次取太多任务,都可能出现重复处理。早期可以做得简单一点,但至少要有“领取任务”的概念,别让所有执行器都去处理同一批数据。

第五,要清理历史数据。

任务表不是日志仓库。成功任务保留一段时间方便排查就够了,长期不清理,查询会越来越慢,数据库也会变得很乱。

这些东西听起来都不复杂,但少一个,后面都会变成坑。

它适合什么场景

数据库 + 定时任务适合早期产品里的轻量异步任务。

比如通知类任务、低频统计、状态同步、延迟清理、简单的批处理。放在约球产品里,像发布约球信息后的提醒、附近球友匹配后的后续处理、找搭子相关的轻量推荐,都比较接近这一类。

这类任务的共同点是:量不大,实时性要求没那么高,失败后可以重试,偶尔延迟几分钟用户也不太敏感。

它不适合高并发、强实时、严格顺序、复杂消费组这类场景。

如果你需要的是秒级消费、削峰填谷、多消费者组、消息堆积监控、死信队列,那就不要硬用数据库模拟了。该上 MQ 就上 MQ。

技术选型最怕的就是把过渡方案当成最终方案。

和真正 MQ 的差距

真正的 MQ 解决的是专业消息问题。

它有更成熟的投递机制、消费确认、重试、死信、堆积监控、消费者扩展能力。数据库任务表能模拟其中一小部分,但很难模拟完整。

数据库方案的优势是简单、便宜、能看见、接入成本低。劣势也明显:吞吐有限,轮询有延迟,状态管理要自己写,失败处理也要自己兜。

所以我觉得更合理的姿势是:早期先用数据库队列把同步链路拆开,等任务量、实时性、稳定性真的逼近边界,再迁移到真正的 MQ。

不要一开始就上重系统,也不要明知道快撑不住了还硬扛。

小结

没有 MQ,不代表所有事情都只能同步做。

数据库 + 定时任务是一个挺实用的中间方案。它不高级,但适合早期小程序项目:先把用户请求变短,把后台任务变得可见,让失败可以重试,让问题有地方查。

不过要记住,它只是过渡方案。

如果任务开始变多,延迟变敏感,失败处理越来越复杂,就该认真考虑真正的消息队列了。

如果你也在找附近球友、乒乓球搭子、羽毛球搭子,或者想发布本地约球信息,可以试试小i约球。

---

本文属于「小i约球诞生记」系列。

项目官网:https://iyueqiu.cn 微信小程序:搜索「小i约球」