小i约球诞生记04:约球小程序没有 MQ,怎么处理异步任务
本文是「小i约球诞生记」系列文章之一。
小i约球是一个微信小程序,主要帮乒乓球、羽毛球、台球、网球爱好者找到附近合适的球友。本文从一个具体技术或产品问题切入,记录约球小程序从 0 到 1 过程中的设计取舍。
小程序:微信搜索「小i约球」
前言
这篇聊一个很常见的取舍:做约球小程序时,没有消息队列,能不能用数据库 + 定时任务先顶一下。
我一开始对这个问题的感觉也很简单。小程序项目嘛,用户量还没起来,很多事情同步做一下就好了。比如用户发布一条乒乓球约球或羽毛球约球信息后,顺手做一点后续处理;有些消息通知,接口里直接发掉;有些统计,写完主数据之后再顺手更新。
真正做起来就会发现,同步链路很容易越长越难受。
一个接口本来只负责“保存用户提交”,后来慢慢挂上通知、审核、统计、附近球友匹配、找搭子推荐、清理、失败重试。每一件事单看都不大,串在一起就麻烦了。接口变慢,失败点变多,用户等得久,开发者也不好排查。
这时候你就会想到 MQ。
但现实是,很多小程序云开发项目并没有开箱即用的 MQ。为了一个早期产品单独引入一套消息系统,又有点重。
于是数据库 + 定时任务,就成了一个很实用的过渡方案。
为什么需要异步
异步不是为了显得架构高级,而是为了把“用户正在等的事”和“系统可以稍后做的事”分开。
用户点击发布,最关心的是内容有没有保存成功。至于后面要不要发通知、要不要做匹配、要不要生成某些派生数据,这些事情通常不需要卡在用户面前。
放到约球场景里也是一样。用户发布约球名片时,最在意的是“我这条约球信息发出去了吗”。至于系统后面怎么找附近球友、怎么提醒可能感兴趣的人、怎么更新一些后台状态,这些更适合放到异步任务里。
如果全部同步做,问题会很明显。
第一,接口耗时会变长。用户体感不好,小程序也更容易遇到超时。
第二,失败会互相影响。主数据已经保存成功了,但后面某个通知接口失败,整个请求到底算成功还是失败?如果返回失败,用户可能重复提交;如果返回成功,后台又留下半截状态。
第三,代码会变脏。一个原本很清楚的业务入口,最后变成一条很长的流水线,什么都塞在里面。
所以我的判断是:只要某件事不是用户当下必须看到的结果,就可以考虑拆出去异步处理。
没有 MQ 时,可以怎么做
最朴素的办法,是在数据库里放一张“任务表”。
业务接口只做两件事:保存主数据,然后写一条待处理任务。定时任务每隔一段时间扫描待处理任务,取出一批来执行。执行成功就标记完成,执行失败就记录失败原因和重试次数。
这个方案听起来土,但很好理解,也好排查。
你能在数据库里直接看到现在有哪些任务还没处理,哪些失败了,失败了几次,最后一次失败原因是什么。对早期项目来说,这种可见性很重要。
它不像真正的 MQ 那样优雅,也没有那么强的吞吐能力,但它解决了一个很实际的问题:先把同步链路拆短。
我现在会把它理解成一种“轻量任务队列”,不是 MQ 的平替。
设计时要注意什么
数据库任务队列最怕的不是功能跑不起来,而是边界没想清楚。
第一,要有状态。
至少要区分待处理、处理中、处理成功、处理失败。否则任务一多,你很难知道系统到底卡在哪里。
第二,要有重试次数。
失败重试是异步任务的常态,但不能无限重试。网络抖动可以重试,参数错误一直重试就没有意义。一般会给一个最大次数,超过后进入失败状态,后面人工看情况处理。
第三,要考虑幂等。
定时任务可能重复扫到同一条任务,也可能执行到一半失败,下次又来一次。如果任务内容是发通知、改状态、生成记录,就要想清楚重复执行会不会造成脏数据。
第四,要控制并发。
如果多个定时任务实例同时跑,或者一次取太多任务,都可能出现重复处理。早期可以做得简单一点,但至少要有“领取任务”的概念,别让所有执行器都去处理同一批数据。
第五,要清理历史数据。
任务表不是日志仓库。成功任务保留一段时间方便排查就够了,长期不清理,查询会越来越慢,数据库也会变得很乱。
这些东西听起来都不复杂,但少一个,后面都会变成坑。
它适合什么场景
数据库 + 定时任务适合早期产品里的轻量异步任务。
比如通知类任务、低频统计、状态同步、延迟清理、简单的批处理。放在约球产品里,像发布约球信息后的提醒、附近球友匹配后的后续处理、找搭子相关的轻量推荐,都比较接近这一类。
这类任务的共同点是:量不大,实时性要求没那么高,失败后可以重试,偶尔延迟几分钟用户也不太敏感。
它不适合高并发、强实时、严格顺序、复杂消费组这类场景。
如果你需要的是秒级消费、削峰填谷、多消费者组、消息堆积监控、死信队列,那就不要硬用数据库模拟了。该上 MQ 就上 MQ。
技术选型最怕的就是把过渡方案当成最终方案。
和真正 MQ 的差距
真正的 MQ 解决的是专业消息问题。
它有更成熟的投递机制、消费确认、重试、死信、堆积监控、消费者扩展能力。数据库任务表能模拟其中一小部分,但很难模拟完整。
数据库方案的优势是简单、便宜、能看见、接入成本低。劣势也明显:吞吐有限,轮询有延迟,状态管理要自己写,失败处理也要自己兜。
所以我觉得更合理的姿势是:早期先用数据库队列把同步链路拆开,等任务量、实时性、稳定性真的逼近边界,再迁移到真正的 MQ。
不要一开始就上重系统,也不要明知道快撑不住了还硬扛。
小结
没有 MQ,不代表所有事情都只能同步做。
数据库 + 定时任务是一个挺实用的中间方案。它不高级,但适合早期小程序项目:先把用户请求变短,把后台任务变得可见,让失败可以重试,让问题有地方查。
不过要记住,它只是过渡方案。
如果任务开始变多,延迟变敏感,失败处理越来越复杂,就该认真考虑真正的消息队列了。
如果你也在找附近球友、乒乓球搭子、羽毛球搭子,或者想发布本地约球信息,可以试试小i约球。
---
本文属于「小i约球诞生记」系列。
项目官网:https://iyueqiu.cn 微信小程序:搜索「小i约球」