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

168 lines
7.7 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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": []
}
]
}