背景
产品:VIP 用户的任务优先执行,能做不?
开发:嗯嗯嗯,我不会。
产品:???要你何用,拿你去祭天吧!!!
需要 VIP 用户优先的,一般都是执行时间比较长的任务。一般这种任务都是吃资源,且耗时长。比如第三方的 QPS 限额,比如算法侧的并发限制。
这类需求,产品可能会要求实现以下 2 个需求
- VIP 用户任务优先执行
- 展示当前任务在队列中的位置
那么要实现这样的需求,我们该如何设计呢?接下来就要好好看看了,以后碰到了,就不会被拿去祭天了。 先抽根华子,好好思考下!
需求分析
上来先对需求一顿嘎嘎分析🤡。找出需求的实现关键点。
从需求中,我们得出以下几个关键点:
- 执行耗时长,要考虑异步。
- 资源有限,VIP 优先级要更高。
- 用户需要知道自己在队列的位置。
关键点,都梳理出来了。让我们开搞!开搞!开搞!
整体流程图
不想看细节的,看该流程图即可。
异步化
首先,我们要通过异步化方式,让用户提交任务时,不必阻塞。 用户提交任务时,将任务写入数据库,并返回任务ID 给用户。
流程如下
用户拿到任务ID 就可以通过轮询 Server 的方式,拿到结果。
VIP 队列和普通队列
任务被写入数据库后,因为要 VIP 优先。因此需要有 2 个队列区分普通用户和 VIP 用户。
这里的队列,我们可以考虑使用 Zset。因为 Zset 可以获取元素在集合中的位置。
ZADD vip_queue 1714558972 taskId
vip_queue
: 队列名称
1714558972
: 任务的时间戳
taskId
: 任务ID
随后,我们可以借助 Job 将任务放入队列。
- Job 每隔 3s 执行一次。
- 从任务表中取出 1000 条未放入队列的任务。
- 判断任务是 VIP 还是普通用户,丢入对应队列。
- 修改任务状态为已放入队列。
流程图如下:
这里之所以借助 Job 将任务放入队列,而不是在任务入库的时候,就把任务放入队列是因为:有可能在把任务放入队列后,程序出现 crash,接口告诉用户任务提交失败,但是 Server 在后台却把任务处理完成了。如果不介意这种情况,可以选择在任务入库后,将任务放入队列。
任务拉取
将任务放入队列后,就需要从队列中获取任务执行了。 要从队列获取任务,有如下 2 个选择可以考虑:
- 服务启动后,本地启动 2个线程。1 个线程处理普通队列,1 个线程处理 VIP 队列。
- 借助 Job 每秒调度一次。同理,需要 2 个Job。
本地启动线程的方式,相对来说比较轻量,但是要自己做好线程的监控。 这里,我们使用本地启动线程的方式来实现。
程序实现
ApplicationRunner
接口,并在 run()
中启动 2 个线程。
VIP 优先级更高
OK,干活(拉任务)的驴(线程)有了🤡。现在我们还要考虑一件事情。我们需要让 VIP 用户优先级更高,因此我们要分别控制普通队列、VIP 队列拉取任务的 QPS。
常见的限流算法有:
- 漏桶算法
- 令牌桶算法
- 时间滑动窗口算法
- 固定窗口算法
比较适合的是时间滑动窗口算法,要是觉得复杂,也可以使用简单的令牌桶或漏桶算法。
这里可以选择接入 Sentinel
,利用 Sentinel
来做限流。
如果不想接入 Sentinel
, 可以考虑使用 Redis + Lua
实现时间滑动窗口算法。
接入限流后,我们就能控制每个队列的 QPS,这样就实现了 VIP 优先的需求了。
基本流程如下
结语
嗯嗯嗯...