面试题答案
一键面试可能出现的一致性问题场景
- 数据插入时:当多个请求并发插入数据,同时有分页查询请求。例如,一个分页查询请求获取第1页数据,在获取过程中,其他请求插入新数据到第一页的位置,导致查询到的数据并非预期的第1页数据。
- 数据删除时:若有分页查询正在获取某页数据,同时其他请求删除了该页中的某些数据,会使查询结果包含已删除的数据(若删除操作在查询之后才生效),或者遗漏原本应在该页的数据(若删除操作在查询之前生效,但查询逻辑未感知到)。
设计思路
- 使用Redis事务或Lua脚本:通过Redis事务(MULTI - EXEC)或Lua脚本,将分页查询操作和可能影响分页结果的插入、删除操作进行原子化处理,保证在同一时间内,只有一个操作能对分页数据相关部分进行修改和查询。
- 基于版本号控制:为数据添加版本号字段。每次插入、删除操作成功后,版本号递增。分页查询时,不仅查询数据,同时获取当前版本号。若查询过程中版本号发生变化,说明数据有变动,重新查询。
- 使用Redis的发布订阅机制:对于插入、删除操作,发布相应的消息。分页查询操作监听这些消息,若接收到消息,重新查询分页数据。
涉及到的Redis命令及原理
- Redis事务(MULTI - EXEC):
- 原理:MULTI命令开启一个事务,之后的命令会进入队列,直到EXEC命令执行,队列中的所有命令会原子性地执行。
- 示例:
MULTI
// 例如插入数据
SET key:1 value1
// 假设这里进行分页查询相关操作,如ZRANGEBYSCORE page:1 0 10
EXEC
- Lua脚本:
- 原理:Redis执行Lua脚本时,会将脚本作为一个整体执行,期间不会被其他命令打断,从而保证原子性。
- 示例:假设使用Lua脚本实现分页查询并处理可能的并发修改。
-- 获取分页数据的Lua脚本示例
local start = ARGV[1]
local endIndex = ARGV[2]
local result = redis.call('ZRANGEBYSCORE', KEYS[1], start, endIndex)
return result
在客户端通过EVAL
命令执行该脚本:
EVAL "脚本内容" 1 key:1 0 10
- 基于版本号控制:
- 命令:
- INCR:用于递增版本号,如每次数据插入或删除成功后执行
INCR version_key
。 - GET:查询数据时获取版本号,如
GET version_key
。
- INCR:用于递增版本号,如每次数据插入或删除成功后执行
- 原理:通过比较查询前后版本号,判断数据是否发生变化。
- 命令:
- 发布订阅机制:
- 命令:
- PUBLISH:发布消息,如插入或删除操作成功后
PUBLISH page_change_channel "data changed"
。 - SUBSCRIBE:分页查询操作订阅频道,如
SUBSCRIBE page_change_channel
。
- PUBLISH:发布消息,如插入或删除操作成功后
- 原理:发布者发布消息,订阅者接收到消息后触发相应操作(如重新查询分页数据)。
- 命令: