面试题答案
一键面试Redis分布式锁方案设计
- 加锁
- 使用
SETNX
(SET if Not eXists)命令,示例如下:
import redis r = redis.Redis(host='localhost', port=6379, db = 0) lock_key = "your_lock_key" lock_value = "unique_value_for_lock" lock_acquired = r.setnx(lock_key, lock_value) if lock_acquired: # 加锁成功,执行业务逻辑 pass else: # 加锁失败,处理等待或重试逻辑 pass
- 为了保证锁的可重入性,当一个线程再次获取它已经持有的锁时,不应该被视为新的锁获取。可以在
lock_value
中加入线程标识等信息,每次获取锁时检查是否是自己已经持有锁,如果是则增加持有计数。例如:
if lock_acquired: r.hset(lock_key, "holder_thread", threading.current_thread().ident) r.hset(lock_key, "hold_count", 1) else: current_holder = r.hget(lock_key, "holder_thread") if current_holder == threading.current_thread().ident: current_count = r.hget(lock_key, "hold_count") r.hset(lock_key, "hold_count", int(current_count) + 1) lock_acquired = True
- 使用
- 解锁
- 对于普通解锁,使用
DEL
命令删除锁键。但在可重入锁场景下,需要先检查持有计数。
if lock_acquired: hold_count = r.hget(lock_key, "hold_count") if int(hold_count) == 1: r.delete(lock_key) else: r.hset(lock_key, "hold_count", int(hold_count) - 1)
- 对于普通解锁,使用
性能优化
- 减少网络开销
- 批量操作:尽量将多个 Redis 操作合并为一个批量操作,例如使用
pipeline
。在Python中:
pipe = r.pipeline() pipe.setnx(lock_key, lock_value) pipe.hset(lock_key, "holder_thread", threading.current_thread().ident) pipe.hset(lock_key, "hold_count", 1) results = pipe.execute()
- 长连接:使用连接池来复用 Redis 连接,减少连接创建和销毁的开销。在Python中,
redis - py
库默认使用连接池。
- 批量操作:尽量将多个 Redis 操作合并为一个批量操作,例如使用
- 优化锁的粒度
- 细分锁:根据业务逻辑,将大的锁细分为多个小的锁,减少锁的竞争。例如,在一个电商系统中,如果有商品库存扣减和订单创建两个操作,可以为商品库存扣减设置一个锁,为订单创建设置另一个锁。
可靠性保障
- 锁超时
- 设置合理的超时时间:在加锁时使用
SET key value ex seconds
形式,既设置锁的值又设置超时时间,防止死锁。例如:
lock_acquired = r.set(lock_key, lock_value, ex = 10)
- 自动续期:对于业务执行时间可能较长的情况,可以使用
Redisson
等框架,它提供了锁自动续期功能。在业务执行过程中,当锁快过期时,框架会自动延长锁的有效期。
- 设置合理的超时时间:在加锁时使用
- 故障处理
- 主从复制:部署 Redis 主从集群,当主节点发生故障时,从节点可以晋升为主节点继续提供服务。但要注意主从复制可能存在数据同步延迟问题,可能导致短暂的锁一致性问题。
- Redlock 算法:使用 Redlock 算法,通过向多个独立的 Redis 实例获取锁,当大多数实例都获取到锁时,才认为锁获取成功。这样即使某个 Redis 实例发生故障,也能保证锁的可靠性。在Python中,可以使用
redlock - py
库实现 Redlock。例如:
from redlock import Redlock # 创建 Redlock 实例 redlock = Redlock( resource="your_resource_name", connection_details=[ {"host": "localhost", "port": 6379, "db": 0}, {"host": "localhost", "port": 6380, "db": 0}, {"host": "localhost", "port": 6381, "db": 0} ] ) # 获取锁 lock_acquired = redlock.lock() if lock_acquired: try: # 执行业务逻辑 pass finally: redlock.unlock()