Files
examination/topics/thumbup/lua-bucketing-sync/single_choice.json
T

168 lines
7.7 KiB
JSON
Raw Normal View History

{
"topic": "lua-bucketing-sync",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "2026-09-09T21:42:00+08:00",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 1,
"tags": [
"lua-bucketing-sync",
"lua"
],
"question": "在点赞 Lua 脚本中,当用户已经对某博客点过赞时,脚本返回什么值?",
"options": {
"A": "0",
"B": "1",
"C": "-1",
"D": "2"
},
"answer": "C",
"explanation": "点赞 Lua 脚本开头通过 HEXISTS 检查 userThumbKey 中是否已存在该 blogId 的记录,如果已存在(即用户已点赞),则直接 return -1,不再执行后续的计数累加操作。这保证了幂等性——重复调用点赞不会重复计数。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 2,
"tags": [
"lua-bucketing-sync",
"lua"
],
"question": "点赞 Lua 脚本中使用了哪两个 Redis 数据结构来存储临时计数和去重状态?",
"options": {
"A": "String 和 List",
"B": "Hash 和 String",
"C": "Hash 和 Hash",
"D": "Sorted Set 和 Hash"
},
"answer": "C",
"explanation": "脚本中 tempThumbKey(KEYS[1])使用 HSET 存储 'userId:blogId' 到点赞次数的映射,是一个 Hash;userThumbKey(KEYS[2])使用 HEXISTS/HSET/HDEL 存储 blogId 到 1 的映射,也是 Hash。两个都是 Hash 结构,分别用于临时计数和去重标记。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 2,
"tags": [
"lua-bucketing-sync",
"lua"
],
"question": "为什么点赞和取消点赞的操作必须使用 Lua 脚本而不是分步执行多条 Redis 命令?",
"options": {
"A": "为了减少网络往返次数,提升吞吐量",
"B": "Redis 单线程执行 Lua 脚本时保证原子性,避免并发下计数不一致",
"C": "Redis 不支持事务中的 HEXISTS 操作",
"D": "Lua 脚本比MULTI/EXEC事务的性能更好"
},
"answer": "B",
"explanation": "Redis 保证 Lua 脚本的原子执行——脚本执行期间不会有其他命令插入。点赞操作需要先检查是否已赞(HEXISTS),再累加计数(HGET + HSET),再设置去重标记(HSET),这三步必须作为一个原子操作。如果分步执行,两个并发请求可能同时通过 HEXISTS 检查,导致重复计数。MULTI/EXEC 事务虽然也保证原子性,但 Lua 脚本支持条件分支逻辑,更适合这类复合操作。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 3,
"tags": [
"lua-bucketing-sync",
"time-bucket"
],
"question": "getTimeSlice() 方法将时间按每 10 秒分桶,当当前时间为 14:35:47 时,生成的时间片字符串是什么?",
"options": {
"A": "14:35:40",
"B": "14:35:50",
"C": "14:35:47",
"D": "14:35:00"
},
"answer": "A",
"explanation": "代码逻辑为 `(DateUtil.second(nowDate) / 10) * 10`,整数除法 47/10=4,再乘以 10 得到 40。拼接格式为 'HH:mm:' + 秒数,即 '14:35:40'。这是向下取整到最近的 10 秒边界。例如 14:35:40 ~ 14:35:49 之间的请求都会归入同一个桶 '14:35:40'。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 3,
"tags": [
"lua-bucketing-sync",
"sync-job"
],
"question": "SyncThumb2DBJob 每隔 10 秒执行一次,为什么它计算的是上一个时间片而非当前时间片?",
"options": {
"A": "为了与数据库的写入延迟保持一致",
"B": "因为当前时间片的请求还在进行中,写入会导致数据丢失",
"C": "因为上一个时间片的 Redis Key 已自动过期",
"D": "为了在整点触发补偿任务"
},
"answer": "B",
"explanation": "定时任务取 (second / 10 - 1) * 10,即上一个完整 10 秒窗口。当前时间片可能仍有进行中的请求,如果立即写入 DB 再删除 Key,会导致那些未完成的请求数据丢失。取上一个时间片保证该窗口内的所有写入已经结束,是一个已完结的时间段,可以安全持久化。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 4,
"tags": [
"lua-bucketing-sync",
"sync-job"
],
"question": "SyncThumb2DBJob 在跨分钟边界时(second 计算结果为 -10),代码做了什么处理?",
"options": {
"A": "跳过本次执行,等待下一个调度周期",
"B": "将 second 设为 50,并将 nowDate 前推一分钟",
"C": "将 second 设为 0,直接处理当前小时的 00 秒片",
"D": "抛出异常并回滚事务"
},
"answer": "B",
"explanation": "当秒数在 0~9 之间时,(second/10 - 1) = -1,乘以 10 得到 -10。此时需要处理的是上一分钟的 50 秒桶。代码将 second 设为 50,并用 DateUtil.offsetMinute(nowDate, -1) 将时间前推一分钟,这样拼出的 Key 例如 '14:35:50' 就是上一分钟的最后一个桶。这是时间片分桶边界条件的典型处理。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 3,
"tags": [
"lua-bucketing-sync",
"sync-job"
],
"question": "syncThumb2DBByDate 方法在同步完成后,使用什么方式删除 Redis 中的临时 Key?",
"options": {
"A": "直接在当前线程执行 redisTemplate.delete()",
"B": "启动虚拟线程异步删除",
"C": "依赖 Redis Key 的 TTL 过期自动清理",
"D": "通过 MQ 消息通知清理服务"
},
"answer": "B",
"explanation": "代码使用 Thread.startVirtualThread(() -> redisTemplate.delete(tempThumbKey)) 启动一个虚拟线程来异步删除。这样做是因为删除操作不在核心业务逻辑中,将其异步化可以避免阻塞主流程,减少落库任务的执行时间。这也说明落库时使用的是 copy 模式——先 HGETALL 读取所有数据到 Java 内存,再遍历写 DB,最后异步清理 Redis。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 4,
"tags": [
"lua-bucketing-sync",
"sync-job"
],
"question": "SyncThumb2DBCompensatoryJob 的补偿任务触发条件是什么?它解决了什么问题?",
"options": {
"A": "每 60 秒执行一次,补偿数据库写入失败的数据",
"B": "每天凌晨 2 点执行,扫描残留的时间片 Key 并重新同步,防止落库遗漏",
"C": "在每次点赞操作后触发,实时补偿 Redis 与 DB 的不一致",
"D": "当 Redis 内存超过阈值时触发,强制清理过期数据"
},
"answer": "B",
"explanation": "cron 表达式 '0 0 2 * * *' 表示每天凌晨 2:00 执行。它用 KEYS 命令扫描所有 tempThumbKey 前缀匹配的残留 Key,对每个未被正常同步的 Key 重新调用 syncThumb2DBByDate。这解决的问题是:定时同步任务可能因宕机、重启或网络异常等原因漏掉某些时间片的 Key,补偿任务作为兜底机制确保所有数据最终都能落库,实现了最终一致性。",
"source": null,
"related": []
}
]
}