走过路过不要错过,VIP优先功能看一看哟

124 阅读4分钟

背景

产品:VIP 用户的任务优先执行,能做不?
开发:嗯嗯嗯,我不会。
产品:???要你何用,拿你去祭天吧!!!

需要 VIP 用户优先的,一般都是执行时间比较长的任务。一般这种任务都是吃资源,且耗时长。比如第三方的 QPS 限额,比如算法侧的并发限制。

这类需求,产品可能会要求实现以下 2 个需求

  1. VIP 用户任务优先执行
  2. 展示当前任务在队列中的位置

那么要实现这样的需求,我们该如何设计呢?接下来就要好好看看了,以后碰到了,就不会被拿去祭天了。 先抽根华子,好好思考下!

需求分析

上来先对需求一顿嘎嘎分析🤡。找出需求的实现关键点。

从需求中,我们得出以下几个关键点:

  1. 执行耗时长,要考虑异步。
  2. 资源有限,VIP 优先级要更高。
  3. 用户需要知道自己在队列的位置。

关键点,都梳理出来了。让我们开搞!开搞!开搞!

整体流程图

不想看细节的,看该流程图即可。

异步化

首先,我们要通过异步化方式,让用户提交任务时,不必阻塞。 用户提交任务时,将任务写入数据库,并返回任务ID 给用户。

流程如下

1715000445768.jpg 用户拿到任务ID 就可以通过轮询 Server 的方式,拿到结果。

VIP 队列和普通队列

任务被写入数据库后,因为要 VIP 优先。因此需要有 2 个队列区分普通用户和 VIP 用户。
这里的队列,我们可以考虑使用 Zset。因为 Zset 可以获取元素在集合中的位置。
ZADD vip_queue 1714558972 taskId
vip_queue: 队列名称
1714558972: 任务的时间戳
taskId: 任务ID

随后,我们可以借助 Job 将任务放入队列。

  1. Job 每隔 3s 执行一次。
  2. 从任务表中取出 1000 条未放入队列的任务。
  3. 判断任务是 VIP 还是普通用户,丢入对应队列。
  4. 修改任务状态为已放入队列。

流程图如下:

1715000473205.jpg

这里之所以借助 Job 将任务放入队列,而不是在任务入库的时候,就把任务放入队列是因为:有可能在把任务放入队列后,程序出现 crash,接口告诉用户任务提交失败,但是 Server 在后台却把任务处理完成了。如果不介意这种情况,可以选择在任务入库后,将任务放入队列。

任务拉取

将任务放入队列后,就需要从队列中获取任务执行了。 要从队列获取任务,有如下 2 个选择可以考虑:

  1. 服务启动后,本地启动 2个线程。1 个线程处理普通队列,1 个线程处理 VIP 队列。
  2. 借助 Job 每秒调度一次。同理,需要 2 个Job。

本地启动线程的方式,相对来说比较轻量,但是要自己做好线程的监控。 这里,我们使用本地启动线程的方式来实现。

1715000500837.jpg 程序实现 ApplicationRunner 接口,并在 run() 中启动 2 个线程。

VIP 优先级更高

OK,干活(拉任务)的驴(线程)有了🤡。现在我们还要考虑一件事情。我们需要让 VIP 用户优先级更高,因此我们要分别控制普通队列、VIP 队列拉取任务的 QPS。

常见的限流算法有:

  1. 漏桶算法
  2. 令牌桶算法
  3. 时间滑动窗口算法
  4. 固定窗口算法

比较适合的是时间滑动窗口算法,要是觉得复杂,也可以使用简单的令牌桶或漏桶算法。 这里可以选择接入 Sentinel,利用 Sentinel 来做限流。 如果不想接入 Sentinel, 可以考虑使用 Redis + Lua 实现时间滑动窗口算法。

接入限流后,我们就能控制每个队列的 QPS,这样就实现了 VIP 优先的需求了。

基本流程如下

1715000546381.jpg

结语

嗯嗯嗯...