文章目录
一、前言二、从锁到分布式锁2.1 分布式技术四个分支2.2 从锁到分布式锁(为什么redis需要分布式锁)2.3 zookeeper五个用途 + zookeeper存储 + zookeeper四种节点创建2.4 zookeeper四种节点
三、进程内无锁--进程内加锁--zookeeper分布式锁--zookeeper分布式锁3.1 进程内不是锁,造成线程安全问题3.2 进程内使用锁(synchronized / lock)3.3 zookeeper分布式锁(加锁+解锁+监听)3.4 zookeeper分布式锁(加锁+解锁+监听+解决羊群效应)
四、面试金手指4.1 分布式 + 从锁到分布式锁 + zookeeper五个用途 + zookeeper存储 + zookeeper四种节点创建4.1.1 分布式技术四个分支4.1.2 从锁到分布式锁(为什么redis需要分布式锁)4.1.3 zookeeper五个用途 + zookeeper存储 + zookeeper四种节点创建
4.2 zookeeper是如何基于节点去实现各种分布式锁的?
五、小结
一、前言
二、从锁到分布式锁
2.1 分布式技术四个分支
分布式相关技术:分布式部署导致分布式锁、分布式事务、分布式登录(单点登录)、分布式存储HDFS四个技术的出现 1、后端服务分布式部署后,进程内Java并发多线程锁(阻塞锁+CAS)不适用了,出现了分布式锁(三种实现方式:直接在Mysql表上实现分布式锁、引入一个中间件zookeeper实现各服务之间分布式锁、引入一个中间件redis实现各服务之间分布式锁) 2、mysql分布式部署后,事务不适用了,出现了分布式事务(七种实现方式:两阶段、三阶段、XA、最大努力、ebay本地消息表、TCC编程模式、半消息/最终一致性) 3、后端服务分布式部署后,进程内登录模块,服务端session不适用了,出现了分布式登录,即单点登录 4、分布式存储HDFS
2.2 从锁到分布式锁(为什么redis需要分布式锁)
单个Java进程内的锁: 问题:在Java并发多线程环境下,由于上下文的切换,数据可能出现不一致的情况或者数据被污染。 期待:当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。 实现两种方式:Java中实现锁有两种方式,阻塞锁(synchronized lock)或 CAS硬件层面锁。
进程内的多线程锁 互斥:互斥的机制,保证同一时间只有一个线程可以操作共享资源 synchronized,Lock等。 临界值:让多线程串行话去访问资源 事件通知:通过事件的通知去保证大家都有序访问共享资源 信号量:多个任务同时访问,同时限制数量,比如发令枪CDL(即CountDownLatch),Semaphore等
问题:为什么Redis是单线程,但是需要分布式锁? 标准答案:Redis是单线程,所以单个redis内部不需要进程内锁机制,这是可以确定的; 如果后端采用单体架构,一个Redis对应一个服务,不需要在redis中实现分布式锁; 如果后端采用微服务架构,一个Redis对应多个SOA服务,需要在redis中实现分布式锁。 解释:如果后端采用微服务架构,不使用任何分布式锁机制(mysql、zookeeper、redis) 电商网站中的秒杀,拿到库存判断,然后在减库存,双11之前,为了减少DB的压力,挡住第一批最大秒杀,把库存预热到了KV,现在KV的库存是1。服务A去Redis查询到库存发现是1,那说明我能抢到这个商品对不对,那我就准备减一了,但是还没减。同时服务B也去拿发现也是1,那我也抢到了呀,那我也减。C同理。等所有的服务都判断完了,你发现诶,怎么变成-2了,超卖了呀,这下完了。
2.3 zookeeper五个用途 + zookeeper存储 + zookeeper四种节点创建
zookeeper开发中的使用场景/zookeeper有什么用: 开发用途1:统一配置管理,服务注册与订阅(共用节点,类似于eureka,作为注册中心) 开发用途1:统一配置管理,数据订阅与发布(使用 watcher 实现) 开发用途2:统一命名管理,服务命名(使用 znode特性 实现) 开发用途3:分布式锁(使用 临时顺序节点 实现,三种分布式锁:mysql zookeeper ) 开发用途4:集群管理,分布式通知(使用 监听znode 实现)
金手指:zookeeper存储 问题1:zookeeper是什么? 回答1:zookeeper是个数据库,文件存储系统,并且有监听通知机制(观察者模式)zookeeper作为一个类Unix文件系统,zookeeper通过节点来存储数据。 问题2:接上面,zookeeper节点? 回答2:zk的节点类型有4大类:持久化节点(zk断开节点还在)、持久化顺序编号目录节点、临时目录节点(客户端断开后节点就删除了)、临时目录编号目录节点。 注意:节点名称都是唯一的,这是zookeeper实现分布式锁互斥的基石。
2.4 zookeeper四种节点
使用层面:zookeeper节点怎么创建(linux操作)? 在Zookeeper的数据模型中,Znode分为四种:持久(persistent), 临时(ephemeral),持久序列(persistent_sequential), and 临时序列(ephemeral_sequential)。 从zkCli创建zookeeper znode的创建语法如下 create [-s] [-e] path data // path 表示新节点的路径 data表示节点名 -s 表示有序 -e 表示临时 create /test laogong // 创建永久节点 create -e /test laogong // 创建临时节点 create -s /test // 创建顺序节点 create -e -s /test // 创建临时顺序节点 临时节点与永久节点:退出后,重新连接,上一次创建的所有临时节点都没了,但是永久节点还有。
1、持久节点 持久模式的znode,即使创建该znode的client断开了连接,znode依然存在。当重启zookeeper之后,持久模式的znode也会重新被加载到内存中; 持久节点的znode可以有child node;
2、临时节点 临时模式的znode,其生命周期与创建其的client相同,当创建其的client与zookeeper服务断开连接时,该临时节点就自动删除。 临时节点znode不能有child node
3、持久有序节点 使用持久序列模式创建一个znode时,znode会自动增加一个编号; 持久模式的znode可以有child node;
4、临时有序节点 在临时模式的基础上,增加了一个自动编号的功能。 临时znode不能有child node,一般当做其他Node的子Node。
小结1(使用层面:创建节点):create [-s] [-e] path data // path 表示新节点的路径 data表示节点名 -s 表示有序 -e 表示临时 小结2(使用层面:子节点):持久节点的znode可以有child node,临时节点的znode可以有child node
三、进程内无锁–进程内加锁–zookeeper分布式锁–zookeeper分布式锁
3.1 进程内不是锁,造成线程安全问题
public class Test implements Runnable
{
static int inventory
= 1
; // 要在main函数中用,设置为static
private static final int NUM
= 10
; // 要在main函数中用,设置为static
private static CountDownLatch countDownLatch
= new CountDownLatch
(NUM
); // 要在main函数中用,设置为static
public static void main
(String
[] args
) {
Test
test = new Test
(); // 10个线程都是用同一个test对象
for (int i
= 1
; i
<= NUM
; i++
) {
new Thread
(test
).start
(); // 新建线程并启动
countDownLatch.countDown
(); // 减1
}
}
@Override
public void run
() { // 线程运行的具体逻辑在run
()里面
try
{
countDownLatch.await
(); // 一定到countDownLatch
==0才可以通过阻塞 第一,为什么要使用发令枪?不用发令枪也可以,使用发令枪只是让10个线程启动更加同时,不会先创建的线程先启动
if (inventory
> 0
) { // 就是inventory
==1 可以进入
Thread.sleep
(1
); // 第二,为什么加上Thread.sleep
() 在
if (inventory
> 0
)和 inventory --;中间加上Thread.sleep
(); 让线程安全问题暴露的更明显
inventory--
; // 在inventory--,前面休息5ms 让线程安全问题暴露的更明显
}
System.out.println
(inventory
);
} catch
(Exception e
) {
e.printStackTrace
();
}
}
}
运行结果:
-1
-1
-2
-3
-3
-3
-3
-3
-3
-3
解释代码: 第一,为什么要使用发令枪?不用发令枪也可以,使用发令枪只是让10个线程启动更加同时,不会先创建的线程先启动 第二,为什么加上Thread.sleep()? 在 if (inventory > 0)和 inventory --;中间加上Thread.sleep(); 让线程安全问题暴露的更明显
3.2 进程内使用锁(synchronized / lock)
public class Test1 implements Runnable
{
private final Lock _lock
= new ReentrantLock
();
static int inventory
= 1
; // 要在main函数中用,设置为static
private static final int NUM
= 10
; // 要在main函数中用,设置为static
private static CountDownLatch countDownLatch
= new CountDownLatch
(NUM
); // 要在main函数中用,设置为static
public static void main
(String
[] args
) {
Test1 test1
=new Test1
();
for (int i
= 1
; i
<= NUM
; i++
) {
new Thread
(test1
).start
(); // 新建线程并启动 第三,这个10个线程,一定要传入同一个对象test1,否则锁不起作用
countDownLatch.countDown
(); // 减1
}
}
@Override
public void run
() { // 线程运行的具体逻辑在run
()里面
_lock.lock
();
try
{
countDownLatch.await
(); // 一定到countDownLatch
==0才可以通过阻塞
if (inventory
> 0
) { // 就是inventory
==1 可以进入
Thread.sleep
(5
); // 休息5ms 让线程安全问题暴露的更明显
inventory--
; // 在inventory--,前面休息5ms 让线程安全问题暴露的更明显
}
System.out.println
(inventory
);
} catch
(Exception e
) {
e.printStackTrace
();
} finally
{
_lock.unlock
();
}
// try
{
// countDownLatch.await
(); // 一定到countDownLatch
==0才可以通过阻塞
// synchronized
(this
){
//
if (inventory
> 0
) { // 就是inventory
==1 可以进入
// Thread.sleep
(5
); // 休息5ms 让线程安全问题暴露的更明显
// inventory--
; // 在inventory--,前面休息5ms 让线程安全问题暴露的更明显
//
}
// System.out.println
(inventory
);
//
}
//
//
} catch
(Exception e
) {
// e.printStackTrace
();
//
}
}
}
解释代码: 第一,为什么要使用发令枪?不用发令枪也可以,使用发令枪只是让10个线程启动更加同时,不会先创建的线程先启动 第二,为什么加上Thread.sleep()? 在 if (inventory > 0)和 inventory --;中间加上Thread.sleep(); 让线程安全问题暴露的更明显 第三,这个10个线程,一定要传入同一个对象test1,否则锁不起作用
3.3 zookeeper分布式锁(加锁+解锁+监听)
public class Test2 implements Runnable
{
private static final String IP_PORT
= "127.0.0.1:2181";
private static final String Z_NODE
= "/LOCK";
private static final int NUM
= 10
; // 要在main函数中用,设置为static
private static CountDownLatch countDownLatch
= new CountDownLatch
(NUM
); // 要在main函数中用,设置为static
private static ZkClient zkClient
= new ZkClient
(IP_PORT
);
static int inventory
= 1
; // 要在main函数中用,设置为static
private static final int NUM
= 10
; // 要在main函数中用,设置为static
public static void main
(String
[] args
) {
Test2 test2
= new Test2
(); // 10个线程都是用同一个test对象
for (int i
= 1
; i
<= NUM
; i++
) {
new Thread
(test2
).start
(); // 新建线程并启动 分布式锁3:原来是一个进程中10个线程,用发令枪控制10个线程同时跑
// 现在,一个后端节点一个线程,所以10个后端节点10个线程,不需要发令枪了,它只能控制同一进程里面的线程一起跑
}
}
@Override
public void run
() { // 线程运行的具体逻辑在run
()里面
try
{
new Test2
().lock
();
zkClient.createPersisent
(Z_NODE
); // 分布式锁1:新建一个节点 节点是唯一的,已经有了,再次新建就会报错,用新建节点这个特性来实现分布式锁,相当于synchronized
| lock
if (inventory
> 0
) { // 就是inventory
==1 可以进入
Thread.sleep
(1
); // 第二,为什么加上Thread.sleep
() 在
if (inventory
> 0
)和 inventory --;中间加上Thread.sleep
(); 让线程安全问题暴露的更明显
inventory--
; // 在inventory--,前面休息5ms 让线程安全问题暴露的更明显
}
System.out.println
(inventory
);
return;
} finally
{ // 分布式锁4:去掉catch块
new Test2
().unlock
();
// 分布式锁2:在finally块,释放节点,类似lock.unlock 即,最开始新建节点类似lock.lock
() finally块释放节点类似 lock.unlock
();
System.out.println
("释放锁");
}
}
public void lock
() {
if (tryLock
()) { // 加锁成功,直接return
return;
}
waitForLock
(); // 加锁失败,然后等待,突破发令枪阻塞再开始
lock
(); // 突破发令枪阻塞,再次尝试加锁
}
public void waitForLock
() { // 监听
System.out.println
("加锁失败");
IZkDataListener listener
= new IZkDataListener
() {
@Override
public void handleDataChange
(String arg0, Object arg1
) throws Exception
{ // 修改
}
@Override
public void handleDataDeleted
(String arg0
) throws Exception
{
System.out.println
("监听到配置文件被删除,唤醒"); // config删除,监听打印
}
};
zkClient.subscribeDataChanges
(Z_NODE, listener
);
if (zkClient.exists
(Z_NODE
)) {
try
{
countDownLatch.await
(); // 发令枪阻塞,从这里开始
} catch
(Exception e
) {
e.printStackTrace
();
}
}
zkClient.unsubscribeDataChanges
(Z_NODE, listener
);
}
}
3.4 zookeeper分布式锁(加锁+解锁+监听+解决羊群效应)
public class Test3 implements Runnable
{
private static final String IP_PORT
= "127.0.0.1:2181";
private static final String Z_NODE
= "/LOCK";
private static final int NUM
= 10
; // 要在main函数中用,设置为static
private static CountDownLatch countDownLatch
= new CountDownLatch
(NUM
); // 要在main函数中用,设置为static
private static ZkClient zkClient
= new ZkClient
(IP_PORT
);
static int inventory
= 1
; // 要在main函数中用,设置为static
private static final int NUM
= 10
; // 要在main函数中用,设置为static
public static void main
(String
[] args
) {
Test3 test3
= new Test3
(); // 10个线程都是用同一个test对象
for (int i
= 1
; i
<= NUM
; i++
) {
new Thread
(test3
).start
(); // 新建线程并启动 分布式锁3:原来是一个进程中10个线程,用发令枪控制10个线程同时跑
// 现在,一个后端节点一个线程,所以10个后端节点10个线程,不需要发令枪了,它只能控制同一进程里面的线程一起跑
}
}
@Override
public void run
() { // 线程运行的具体逻辑在run
()里面
try
{
new Test2
().lock
();
zkClient.createPersisent
(Z_NODE
); // 分布式锁1:新建一个节点 节点是唯一的,已经有了,再次新建就会报错,用新建节点这个特性来实现分布式锁,相当于synchronized
| lock
if (inventory
> 0
) { // 就是inventory
==1 可以进入
Thread.sleep
(1
); // 第二,为什么加上Thread.sleep
() 在
if (inventory
> 0
)和 inventory --;中间加上Thread.sleep
(); 让线程安全问题暴露的更明显
inventory--
; // 在inventory--,前面休息5ms 让线程安全问题暴露的更明显
}
System.out.println
(inventory
);
return;
} finally
{ // 分布式锁4:去掉catch块
new Test2
().unlock
();
// 分布式锁2:在finally块,释放节点,类似lock.unlock 即,最开始新建节点类似lock.lock
() finally块释放节点类似 lock.unlock
();
System.out.println
("释放锁");
}
}
public void lock
() {
if (tryLock
()) { // 加锁成功,直接return
System.out.println
("获得锁");
} else {
waitForLock
(); // 加锁失败,然后等待,突破发令枪阻塞再开始
lock
(); // 突破发令枪阻塞,再次尝试加锁
}
}
public synchronized boolean tryLock
() {
if (StringUtils.isBlank
(path
))
path
= zkClient.createEphemeralSequential
(Z_NODE +
"/",
"lock");
List
<String
> children
= zkClient.getClildren
(Z_NODE
);
Collections.sort
(children
);
if (path.equals
(Z_NODE +
"/" + children.get
(0
))){
System.out.println
("I am true");
return true;
}else
{
int i
=Collections.binarySearch
(children,path.substring
(Z_NODE.length
() + 1
));
beforePath
= Z_NODE +
"/"+children.get
(i-1
);
}
return false;
}
public void waitForLock
() {
System.out.println
("加锁失败");
IZkDataListener listener
= new IZkDataListener
() {
@Override
public void handleDataChange
(String arg0, Object arg1
) throws Exception
{ // 修改
}
@Override
public void handleDataDeleted
(String arg0
) throws Exception
{
System.out.println
("监听到配置文件被删除,唤醒"); // config删除,监听打印
}
};
zkClient.subscribeDataChanges
(beforePath, listener
);
if (zkClient.exists
(Z_NODE
)) {
try
{
countDownLatch.await
(); // 发令枪阻塞,从这里开始
} catch
(Exception e
) {
e.printStackTrace
();
}
}
zkClient.unsubscribeDataChanges
(beforePath, listener
);
}
}
四、面试金手指
4.1 分布式 + 从锁到分布式锁 + zookeeper五个用途 + zookeeper存储 + zookeeper四种节点创建
4.1.1 分布式技术四个分支
分布式相关技术:分布式部署导致分布式锁、分布式事务、分布式登录(单点登录)、分布式存储HDFS四个技术的出现 1、后端服务分布式部署后,进程内Java并发多线程锁(阻塞锁+CAS)不适用了,出现了分布式锁(三种实现方式:直接在Mysql表上实现分布式锁、引入一个中间件zookeeper实现各服务之间分布式锁、引入一个中间件redis实现各服务之间分布式锁) 2、mysql分布式部署后,事务不适用了,出现了分布式事务(七种实现方式:两阶段、三阶段、XA、最大努力、ebay本地消息表、TCC编程模式、半消息/最终一致性) 3、后端服务分布式部署后,进程内登录模块,服务端session不适用了,出现了分布式登录,即单点登录 4、分布式存储HDFS
4.1.2 从锁到分布式锁(为什么redis需要分布式锁)
单个Java进程内的锁: 问题:在Java并发多线程环境下,由于上下文的切换,数据可能出现不一致的情况或者数据被污染。 期待:当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。 实现两种方式:Java中实现锁有两种方式,阻塞锁(synchronized lock)或 CAS硬件层面锁。
进程内的多线程锁 互斥:互斥的机制,保证同一时间只有一个线程可以操作共享资源 synchronized,Lock等。 临界值:让多线程串行话去访问资源 事件通知:通过事件的通知去保证大家都有序访问共享资源 信号量:多个任务同时访问,同时限制数量,比如发令枪CDL(即CountDownLatch),Semaphore等
问题:为什么Redis是单线程,但是需要分布式锁? 标准答案:Redis是单线程,所以单个redis内部不需要进程内锁机制,这是可以确定的; 如果后端采用单体架构,一个Redis对应一个服务,不需要在redis中实现分布式锁; 如果后端采用微服务架构,一个Redis对应多个SOA服务,需要在redis中实现分布式锁。 解释:如果后端采用微服务架构,不使用任何分布式锁机制(mysql、zookeeper、redis) 电商网站中的秒杀,拿到库存判断,然后在减库存,双11之前,为了减少DB的压力,挡住第一批最大秒杀,把库存预热到了KV,现在KV的库存是1。服务A去Redis查询到库存发现是1,那说明我能抢到这个商品对不对,那我就准备减一了,但是还没减。同时服务B也去拿发现也是1,那我也抢到了呀,那我也减。C同理。等所有的服务都判断完了,你发现诶,怎么变成-2了,超卖了呀,这下完了。
4.1.3 zookeeper五个用途 + zookeeper存储 + zookeeper四种节点创建
zookeeper开发中的使用场景/zookeeper有什么用: 开发用途1:服务注册与订阅(共用节点,类似于eureka,作为注册中心) 开发用途2:分布式通知(使用 监听znode 实现) 开发用途3:服务命名(使用 znode特性 实现) 开发用途4:数据订阅、发布(使用 watcher 实现) 开发用途5:分布式锁(使用 临时顺序节点 实现,三种分布式锁:mysql zookeeper )
金手指:zookeeper存储 问题1:zookeeper是什么? 回答1:zookeeper是个数据库,文件存储系统,并且有监听通知机制(观察者模式) 问题2:接上面,zookeeper作为一个类Unix文件系统,它是如何存储数据的? 回答2:zookeeper通过节点来存储数据。 问题3:接上面,zookeeper节点? 回答3:zk的节点类型有4大类:持久化节点(zk断开节点还在)、持久化顺序编号目录节点、临时目录节点(客户端断开后节点就删除了)、临时目录编号目录节点。 注意:节点名称都是唯一的,这是zookeeper实现分布式锁互斥的基石。
使用层面:zookeeper节点怎么创建(linux操作)? create /test laogong // 创建永久节点 create -e /test laogong // 创建临时节点 create -s /test // 创建顺序节点 create -e -s /test // 创建临时顺序节点 临时节点与永久节点:退出后,重新连接,上一次创建的所有临时节点都没了,但是永久节点还有。
4.2 zookeeper是如何基于节点去实现各种分布式锁的?
总问题:zookeeper是如何基于节点去实现各种分布式锁的? 问题1:节点,zookeeper如何实现分布式锁? 回答1:和lock一样的,在子节点逻辑之前创建节点,在finally块中释放节点,两行代码实现分布式锁。 解释1: (1)在zookeeper中,节点名为唯一的,给定节点名创建一个后就不能再创建一个,会报错,创建指定节点名的节点,类似于获得到了锁; (2)与lock不同的是,lock是一个进程内,对所有线程可见,而节点名是对 分布式部署 中所有的节点可见; (3)正常情况下,会执行finally的语句,即表示节点执行完逻辑后,正常释放节点。
问题1.1 :zookeeper如何实现分布式锁的互斥? 回答1.1 :zk节点有个唯一的特性,就是我们创建过这个节点了,你再创建zk是会报错的,那我们就利用一下他的唯一性去实现一下。我们全部去创建,创建成功的第一个返回true他就可以继续下面的扣减库存操作,后续的节点访问就会全部报错,扣减失败,我们把它们丢一个队列去排队。 问题1.2 :zookeeper实现的分布式锁,客户端如何释放锁? 回答1.2 :删除节点,Lock在finally里面unLock,现在我们在finally删除节点,删了再通知其他的客户端来争夺锁。 问题1.3 :怎么通知其他的客户端来争夺锁? 回答1.3 :监听节点的删除事件,知道指定节点名的节点被删除了,然后监听函数中就开始争夺 创建指定节点名节点。
问题2:临时节点,接上面,上面说的是成功创建指定节点名的节点,在正常执行完逻辑后,正常执行完逻辑后,在finally中释放节点,但是,如果占有锁的节点宕机之后,造成的死锁的问题,如何处理? 回答2:临时节点,zookeeper有四种节点,永久节点在java客户端程序断开连接后会保留,临时节点在java客户端程序断开连接后会销毁,创建临时节点,这样,一旦java后端创建节点的SOA服务,这个客户端获取到锁之后突然挂掉(Session连接断开),这个临时节点会被销毁,其他客户端自动获取锁。
问题3:临时顺序节点,客户端的监听机制(监听指定节点名的节点的 操作和删除)是所有服务都去监听一个节点的,节点的释放也会通知所有的服务器,如果是900个服务器呢?这对服务器是很大的一个挑战,一个释放的消息,就好像一个牧羊犬进入了羊群,大家都四散而开,随时可能干掉机器,会占用服务资源,网络带宽等等。造成羊群效应,如何处理羊群效应? 回答3:临时顺序节点,可以顺利解决这个问题。其他所有节点都去监听一个节点问题很大,但是监听自己的前一个节点,因为是顺序的,很容易找到自己的前后。因为每个节点只监听了自己的前一个节点,释放当然也是一个个释放下去,就不会出现羊群效应了。
问题4:阻塞效果,加锁创建节点,但是你得实现一个阻塞的效果呀,那咋搞? 回答4:死循环,递归不断去尝试,直到成功,一个伪装的阻塞效果。zk通过节点排队监听的机制,实现了阻塞的原理,其实就是个递归在那无限等待最小节点释放的过程。
问题5:可重入锁,zookeeper使用指定节点名的唯一性,如何实现分布式锁的可重入? 回答5:基于zookeeper的分布式锁实现可重入锁,可以带上线程信息,或者机器信息这样的唯一标识(只要是可以唯一表时间客户端连接的就好),获取的时候判断一下,如果是以获取锁的客户端,就不用再获取了,如果不是以获取锁的客户端,还需要重新获取。
问题6:高可用,zookeeper集群实现的分布式锁,是否高可用? 回答6:zookeeper是一个分布式集群架构的,是高可用的,对于写操作,follower ACK只要半数以上,就成功了。
问题7:用zookeeper来实现分布式锁的缺点? 回答7: 缺点1:zookeeper性能上可能并没有缓存服务redis那么高(创建和删除节点,比较重)。 解释:因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。这是非常重量的,ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。 缺点2:使用Zookeeper实现分布式锁,可能带来并发问题,只是并不常见而已。 解释:由于网络抖动,客户端可zookeeper集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题了,这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。
五、小结
zookeeper分布式锁,完成了。
天天打码,天天进步!!!