分布式锁是多节点部署场景下保证共享资源互斥访问的核心机制。库存扣减、订单防重、定时任务去重等业务场景需要可靠的分布式锁。Redis、etcd、Zookeeper三种方案在实现原理、一致性保证、性能特性上各有侧重,选型时需要根据业务对一致性和吞吐量的要求权衡决策。
分布式锁核心要求与Redis实现
分布式锁需满足四个基本要求:互斥性(同一时刻仅一个客户端持有锁)、避免死锁(锁有超时时间,持有者崩机后自动释放)、可重入(同一客户端可多次获取)、高可用(锁服务自身不能成为单点故障)。
Redis单节点分布式锁使用SET NX PX命令实现原子加锁:
import redis, uuid, time
class RedisLock:
def __init__(self, redis_client, lock_key, expire_ms=10000):
self.redis = redis_client
self.lock_key = lock_key
self.expire_ms = expire_ms
self.lock_value = str(uuid.uuid4())
def acquire(self, retry_count=3, retry_delay=0.2):
for i in range(retry_count):
result = self.redis.set(
self.lock_key, self.lock_value,
nx=True, px=self.expire_ms
)
if result:
return True
time.sleep(retry_delay)
return False
def release(self):
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
self.redis.eval(lua_script, 1, self.lock_key, self.lock_value)
可重入锁实现,使用Hash结构记录重入次数:
class ReentrantRedisLock:
def acquire(self):
lua_acquire = """
if redis.call('exists', KEYS[1]) == 0 then
redis.call('hset', KEYS[1], ARGV[1], 1)
redis.call('pexpire', KEYS[1], ARGV[2])
return 1
end
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then
redis.call('hincrby', KEYS[1], ARGV[1], 1)
redis.call('pexpire', KEYS[1], ARGV[2])
return 1
end
return 0
"""
return self.redis.eval(lua_acquire, 1, self.lock_key,
self.client_id, self.expire_ms)
RedLock算法多节点加锁方案
单Redis实例存在主从切换丢锁风险。RedLock算法在多个独立Redis节点上同时加锁,多数节点加锁成功即视为锁获取成功:
class RedLock:
def __init__(self, redis_nodes, lock_key, expire_ms=10000):
self.nodes = redis_nodes
self.lock_key = lock_key
self.expire_ms = expire_ms
self.lock_value = str(uuid.uuid4())
self.quorum = len(redis_nodes) // 2 + 1
def acquire(self):
start_time = time.time()
success_count = 0
for node in self.nodes:
try:
result = node.set(self.lock_key, self.lock_value,
nx=True, px=self.expire_ms)
if result:
success_count += 1
except Exception:
pass
elapsed_ms = (time.time() - start_time) * 1000
if success_count >= self.quorum and elapsed_ms < self.expire_ms:
return True
else:
self.release()
return False
etcd分布式锁实现
etcd基于Raft一致性算法,提供强一致的租约机制。使用Lease + Compare-And-Swap实现分布式锁:
import etcd3, time, uuid
class EtcdLock:
def __init__(self, endpoints, lock_key, lease_ttl=10):
self.client = etcd3.client(host=endpoints)
self.lock_key = lock_key
self.lease_ttl = lease_ttl
self.lease = None
self.lock_value = str(uuid.uuid4())
def acquire(self, timeout=10):
start = time.time()
while time.time() - start < timeout:
self.lease = self.client.lease(self.lease_ttl)
status, _ = self.client.transaction(
compare=[self.client.transactions.create_revision(self.lock_key) == 0],
success=[self.client.transactions.put(self.lock_key, self.lock_value, lease=self.lease.id)],
failure=[]
)
if status:
return True
self.lease.revoke()
time.sleep(0.1)
return False
def release(self):
if self.lease:
self.lease.revoke()
self.lease = None
三种方案性能与一致性对比
| 维度 | Redis单节点 | RedLock | etcd | Zookeeper |
|---|---|---|---|---|
| 一致性 | AP(异步复制) | CP(多数派) | CP(Raft) | CP(ZAB) |
| 性能 | 10万+ QPS | 1万+ QPS | 数千QPS | 数千QPS |
| 锁续期 | 需额外实现 | 需额外实现 | Lease自动续期 | Session机制 |
| 复杂度 | 低 | 中 | 中 | 高 |
| 故障恢复 | 主从切换可能丢锁 | 多数节点存活即可 | Raft自动选主 | Leader选举 |
选型决策:对性能要求高且能接受极端情况下少量不一致的场景(如缓存预热、限流计数)用Redis单节点。对一致性要求严格的金融场景(如余额扣减、库存锁定)用etcd,Lease机制天然支持锁自动释放和续期。Zookeeper适合已有ZK集群的Dubbo/Kafka等中间件生态。RedLock在理论上有争议(时钟漂移问题),实际生产中使用不如etcd普遍。
分布式锁使用中的注意事项:锁超时时间应大于业务执行时间但不宜过长,避免持有者崩溃后资源长时间被锁。加锁后业务执行需做幂等处理,防止锁自动释放后被其他客户端获取导致重复执行。高并发场景下获取锁失败时应使用指数退避重试,避免大量客户端同时重试造成Redis压力。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-suo-shi-xian-shi-zhan-redis-yu-etcd-yu-zookeeper/