面试题答案
一键面试1. 基于Redis的分布式锁设计
- 加锁操作
- 实现方式:使用
SETNX
(SET if Not eXists)命令。该命令在键不存在时,为键设置指定的值。示例代码(以Python和Redis - Py为例):
- 实现方式:使用
import redis
redis_client = redis.StrictRedis(host='localhost', port=6379, db = 0)
lock_key = "my_distributed_lock"
lock_value = "unique_client_identifier"
expire_time = 10 # 锁的过期时间,单位秒
is_locked = redis_client.set(lock_key, lock_value, ex = expire_time, nx = True)
- **原子性保障**:`SET`命令在Redis中是原子操作,`nx = True`确保只有在锁键不存在时才会设置成功,从而保证了加锁的原子性。
2. 解锁操作 - 实现方式:先判断锁是否由当前客户端持有,然后删除锁键。示例代码:
if redis_client.get(lock_key) == lock_value:
redis_client.delete(lock_key)
- **原子性保障**:可以使用Lua脚本来确保判断和删除操作的原子性。在Lua脚本中,Redis会以原子方式执行脚本中的所有命令。示例Lua脚本如下:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
在Python中执行Lua脚本的代码:
unlock_script = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
"""
unlock_result = redis_client.eval(unlock_script, 1, lock_key, lock_value)
- 锁续约操作
- 实现方式:先判断锁是否由当前客户端持有,然后使用
EXPIRE
命令延长锁的过期时间。示例代码:
- 实现方式:先判断锁是否由当前客户端持有,然后使用
if redis_client.get(lock_key) == lock_value:
redis_client.expire(lock_key, expire_time)
- **原子性保障**:同样可以使用Lua脚本来保证原子性。示例Lua脚本:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("EXPIRE", KEYS[1], ARGV[2])
else
return 0
end
在Python中执行Lua脚本的代码:
renew_script = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("EXPIRE", KEYS[1], ARGV[2])
else
return 0
end
"""
renew_result = redis_client.eval(renew_script, 1, lock_key, lock_value, expire_time)
- 判断锁是否由当前客户端持有
- 实现方式:使用
GET
命令获取锁的值,并与当前客户端的标识符进行比较。示例代码:
- 实现方式:使用
is_held = redis_client.get(lock_key) == lock_value
- **原子性保障**:`GET`命令本身是原子操作,因此此判断操作是原子的。
2. 可能遇到的问题及解决策略
- 竞态条件
- 问题描述:在高并发场景下,可能出现多个客户端同时尝试加锁、解锁或续约操作,导致数据不一致。
- 解决策略:如上述实现中,使用原子操作命令(如
SETNX
、DEL
、EXPIRE
)以及Lua脚本,确保多个操作在Redis端以原子方式执行,避免竞态条件。
- 死锁
- 问题描述:如果某个客户端获取锁后,由于程序崩溃或网络问题等原因未能正常解锁,其他客户端将永远无法获取该锁,形成死锁。
- 解决策略:为锁设置合理的过期时间,如在加锁时通过
ex
参数指定过期时间。这样即使客户端出现异常,锁也会在一定时间后自动释放。
- 锁误删
- 问题描述:当一个客户端持有锁并延长了锁的过期时间,在延长操作执行前,锁可能因为过期而被其他客户端获取,然后原客户端执行解锁操作时,可能会误删其他客户端的锁。
- 解决策略:在解锁和续约操作中,通过客户端唯一标识符(如
lock_value
)进行判断,只有当锁的值与当前客户端标识符一致时才执行操作,并且使用Lua脚本来保证判断和操作的原子性。
- Redis节点故障
- 问题描述:如果使用单节点Redis,当Redis节点发生故障时,分布式锁服务将不可用。
- 解决策略:可以采用Redis集群方案,如Redis Sentinel或Redis Cluster。Redis Sentinel可以监控Redis主节点的状态,当主节点故障时自动进行故障转移;Redis Cluster则提供了分布式存储和高可用性。