面试题答案
一键面试Redis有序集合可能面临的数据一致性问题
- 竞争条件:在高并发环境下,多个用户同时提交成绩更新排名,可能会出现对同一个有序集合进行并发写操作。例如,两个用户几乎同时提交成绩,由于操作的先后顺序不确定,可能导致最终排名与预期不符。
- 部分更新失败:如果在更新排名过程中,Redis发生故障或者网络问题,可能导致部分成绩更新成功,部分失败,从而破坏数据一致性。
通过合理设计和相关机制保证数据一致性
- 使用事务
- 原理:Redis事务可以将多个命令打包,保证这些命令要么全部执行成功,要么全部不执行。在更新排名场景中,可以将获取当前排名、更新成绩、重新计算排名等相关命令放在一个事务中。
- 示例:
MULTI
ZREM rank_key user1 # 先移除旧成绩
ZADD rank_key new_score user1 # 添加新成绩
EXEC
- 局限性:虽然事务能保证一组命令的原子性执行,但不具备隔离性。在事务执行期间,其他客户端仍然可以对有序集合进行操作,可能导致数据不一致。
- Lua脚本
- 原理:Lua脚本在Redis中是原子性执行的,且执行期间不会被其他命令打断。可以将复杂的排名更新逻辑封装在Lua脚本中,确保数据一致性。
- 示例:
-- 获取当前成绩
local old_score = redis.call('ZSCORE', 'rank_key', KEYS[1])
-- 如果旧成绩存在,移除旧成绩
if old_score then
redis.call('ZREM', 'rank_key', KEYS[1])
end
-- 添加新成绩
redis.call('ZADD', 'rank_key', ARGV[1], KEYS[1])
return 'Update success'
在客户端使用时,通过EVAL
命令执行该脚本:
EVAL "脚本内容" 1 user1 new_score
这里1
表示后面跟着1个键(即user1
),new_score
是传递给脚本的参数。
3. 分布式锁
- 原理:可以使用Redis的
SETNX
(SET if Not eXists)命令实现分布式锁。在更新排名前,先获取锁,更新完成后释放锁,确保同一时间只有一个客户端能进行排名更新操作。 - 示例:
-- 获取锁
SETNX rank_lock 1
-- 如果获取锁成功,进行排名更新操作
if (获取锁成功) {
MULTI
ZREM rank_key user1
ZADD rank_key new_score user1
EXEC
-- 释放锁
DEL rank_lock
}
- 局限性:需要额外的锁管理逻辑,且可能出现死锁等问题,例如获取锁的客户端在更新排名过程中崩溃,未能及时释放锁。