文章目录
分布式锁
使用Redis做分布式锁的思路大概是这样的:在redis中设置一个值表示加了锁,然后释放锁的时候就把这个key删除。
setnx lock:key true
del lock:key
若执行到中间出现异常,那么导致del指令调用不到,这样就会产生 死锁 现象
setnx lock:key true
expire lock:key 5
del lock:key
若setnx与expire中间挂了,也会导致同样得问题
使用redis事务解决,但是这里不行,因为expire是依赖setnx的,setnx没抢到锁,expire不应该执行,事务没有if分支逻辑
setnx和expire组合在一起的原子指令
set lock:key true ex 5 nx
del lock:key
不能解决超时问题
若在加锁和释放锁中间的执行逻辑太长,导致超出了锁的expire限制,就会出现线程安全问题。
解决方案:为set指令的value参数设置一个随机数,释放锁的时候先匹配随机数。这是为了保证当前线程占有的锁不会被其他线程释放
但是匹配value和删除value不是一个原子操作,这就需要Lua脚本处理。
但这也不是一个完美方案,若超时发生,当前线程逻辑未执行完,其他线程也会进行抢占的。
集群模式缺陷
在集群模式,客户端在主节点申请的锁还没有来得及同步到从节点,主节点就挂掉了,然后从节点编程了主节点。另一个客户端请求加锁也会成功,最后导致两个客户端同时获得了锁操作资源,引起线程安全问题。
这种不安全只在主从faileover情况发生,持续时间短,业务系统多数可以容忍
若希望绝对高可用,挂一台redis完全不受影响
解决方案:RedLock算法
需要多个redis实例,相互没有主从关系。 通过过半选举机制,加锁时,会向多redis实例发送set(key,value,nx=true,ex=xxx)指令 ,只要过半set成功,那就加锁成功。释放锁时,需要向所有节点发送del指令。
代价:需要多redis实例,性能也下降了,代码需要引入额外library
# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
ZooKeeper实现分布式锁
zookeeper是什么?
ZooKeeper是一个分布式的应用程序协调服务,提供可靠的、可扩展的、分布式的、可配置的协调服务来管理整个集群的状态。
zookeeper提供了什么?
1. 文件系统
四种节点类型:持久有序,持久无需,临时有序,临时无序
2. 通知机制
四种事件类型:节点创建事件,节点删除事件,节点修改事件,数据修改事件
客户端向服务端 注册一个watcher监听,当某事件触发这个监听,就会像客户端发送一个事件通知。
- 客户端向zk注册whatcher的同时,会将watcher对象存储在客户端的whatchManager中
2.当zk触发watcher事件后,会向客户端发送通知
3.客户端线程从管理者取出watcher对象执行回调逻辑
zookeeper的作用有哪些?
- 命名服务:通过路径的唯一性实现命名
- 集群配置服务:将集群节点的配置信息放到zk某节点上,所有节点都监听此节点,一旦收到数据改变通知,则应用新得配置数据。
- 集群管理服务:实现数据库节点出入检测,以及leader选举(创建临时顺序节点)
- 线程同步管理服务:实现分布式锁
- 数据订阅服务:订阅方监听被订阅方节点数据变化的事件
- 临时节点:客户端连接时创建,客户端挂起的时候自动删除。
- 节点类型:。。。。。。。。。。。只有持久化节点才可以创建子节点
zookeeper实现分布式锁,解决线程同步问题
- 首先创建持久化父节点/distribute_lock
- 当Client访问资源时,创建临时顺序节点,然后判断自己创建的子节点是否为当前子节点列表中最小的子节点,如果是则获取锁访问共享资源;否则监听父节点【变更事件】,当收到子节点变更通知后重复判断,直到获取锁。
- 当线程完成业务逻辑后,删除对应子节点释放锁
zookeeper实现分布式锁优化
- 创建临时节点:保证故障情况下也能释放锁,完美解决了redis实现分布式锁的劣势,包括业务执行时间过长超过锁的有效时间,包括集群宕机时发生线程安全问题。
- Client只监听比自己小的节点,以避免发生羊群效应