转载自:http://www.chengxuyuans.com/Java+/72042.html
共享锁: 全局同步分布式锁, 同一时间两台机器只有一台能获得同一把锁。
共享读写锁: 用于分布式的读写互斥处理, 同时生成两个锁:一个读锁, 一个写锁, 读锁能被多个应用持有, 而写锁只能一个独占, 当写锁未被持有时, 多个读锁持有者可以同时进行读操作。
共享信号量: 在分布式系统中的各个JVM使用同一个zk lock path, 该path将跟一个给定数量的租约(lease)相关联, 然后各个应用根据请求顺序获得对应的lease, 相对来说, 这是最公平的锁服务使用方式。
多共享锁:内部构件多个共享锁(会跟一个znode path关联), 在acquire()过程中, 执行所有共享锁的acquire()方法, 如果中间出现一个失败, 则将释放所有已require的共享锁; 执行release()方法时, 则执行内部多个共享锁的release方法(如果出现失败将忽略)。
如下展示如何设计一个分布式重入锁,其中一个path表示一个锁资源.
所谓"重入",就是同一个线程多次获取锁时,如果此线程已经持有了锁(即创建了zk临时节点),事实上将不会再次创建zk的临时节点,而是直接返回。
因为"重入锁",基于临时节点的特性,因此必须关注client链接重建的问题;粗糙的解决办法,就是每次链接重建(session过期),重新实例化lock对象。
Curator实现Leader选举有两种方式,第一种是LeaderLatch,这种是有阻塞的,所有client一起去争leader,没有选上的client会一直阻塞,等待上一leader退出或则挂掉。一旦leader位置为空了,继续争夺leader。第二种是LeaderSelector监听器实现Leader选举功能。同一时刻,只有一个Listener会进入takeLeadership()方法,说明它是当前的Leader。注意:当Listener从takeLeadership()退出时就说明它放弃了“Leader身份”, 这时Curator会利用Zookeeper再从剩余的Listener中选出一个新的Leader。autoRequeue()方法使放弃 Leadership的Listener有机会重新获得Leadership,如果不设置的话放弃了的Listener是不会再变成Leader的。
LeaderLatch示例:
LeaderSelector示例:
分布式锁Lock
Curator中的支持的锁服务有多种类型,详见http://ifeve.com/zookeeper-lock/。共享锁: 全局同步分布式锁, 同一时间两台机器只有一台能获得同一把锁。
共享读写锁: 用于分布式的读写互斥处理, 同时生成两个锁:一个读锁, 一个写锁, 读锁能被多个应用持有, 而写锁只能一个独占, 当写锁未被持有时, 多个读锁持有者可以同时进行读操作。
共享信号量: 在分布式系统中的各个JVM使用同一个zk lock path, 该path将跟一个给定数量的租约(lease)相关联, 然后各个应用根据请求顺序获得对应的lease, 相对来说, 这是最公平的锁服务使用方式。
多共享锁:内部构件多个共享锁(会跟一个znode path关联), 在acquire()过程中, 执行所有共享锁的acquire()方法, 如果中间出现一个失败, 则将释放所有已require的共享锁; 执行release()方法时, 则执行内部多个共享锁的release方法(如果出现失败将忽略)。
如下展示如何设计一个分布式重入锁,其中一个path表示一个锁资源.
public class DistributedLock{
private InterProcessMutex lock;//重入的,排他的.
private Map<Thread,Boolean> lockedThread = new WeakHashMap<Thread,Boolean>();
private String lockPath;
private ConnectionStateListener stateListener = new StateListener();
private RevocationListener<InterProcessMutex> revocationListener;
public DistributedLock(CuratorFramework client,String path){
lockPath = path;
revocationListener = new RevocationListener<InterProcessMutex>() {
@Override
public void revocationRequested(InterProcessMutex forLock) {
if(!forLock.isAcquiredInThisProcess()){
return;
}
try{
forLock.release();
}catch(Exception e){
e.printStackTrace();
}
}
};
lock = createLock(client);
lock.makeRevocable(revocationListener);
client.getConnectionStateListenable().addListener(stateListener);
}
public boolean lock(){
try{
lock.acquire();
lockedThread.put(Thread.currentThread(),Boolean.TRUE);
} catch (Exception e){
//
}
return false;
}
public void unlock(){
try{
lock.release();
}catch (Exception e){
//
}
}
private InterProcessMutex createLock(CuratorFramework client){
lock = new InterProcessMutex(client,lockPath);
//协同中断,如果其他线程/进程需要此锁中断时,调用此listener.
lock.makeRevocable(revocationListener);
client.getConnectionStateListenable().addListener(stateListener);
return lock;
}
class StateListener implements ConnectionStateListener{
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
if(Boolean.FALSE.equals(lockedThread.get(Thread.currentThread()))){
return;//如果当前lock没有获取锁,则忽略
}
switch (newState){
case LOST:
//一旦丢失链接,就意味着zk server端已经删除了锁数据
lockedThread.clear();
lock = createLock(client);//must be rebuild
break;
default:
System.out.println(newState.toString());
}
}
}
}
底层的机制非常的简单: "获取锁"的操作,就是在zookeeper中创建一个EPHEMERAL_SEQUENTIAL节点,同时对此节点的临近节点注册一个watcher; 当"临近节点"被删除时,表示其他进程已经释放了锁,此watcher将会触发,并唤醒当前线程,然后acquire方法返回.."释放锁"的操作,就是 删除此临时节点.此时临近的下一个节点将获得锁。
所谓"重入",就是同一个线程多次获取锁时,如果此线程已经持有了锁(即创建了zk临时节点),事实上将不会再次创建zk的临时节点,而是直接返回。
因为"重入锁",基于临时节点的特性,因此必须关注client链接重建的问题;粗糙的解决办法,就是每次链接重建(session过期),重新实例化lock对象。
Barrier
栅栏, 可以用来协同分布式环境中的线程.让他们有条件的阻塞,且同时唤醒.DistributedBarrier barrier = new DistributedBarrier(client,"/barrier");
barrier.setBarrier(); //设置barrier
System.out.println("setBarrier...");
barrier.waitOnBarrier();//等待其他进程移除barrier,此后所有的waitOnBarrier进程都将解除阻塞.
//barrier.removeBarrier(); //移除barrier,解除阻塞.
DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(client,"/d-barrier",12);
System.out.println("enter...");
barrier.enter();//阻塞,直到12个成员加入
System.out.println("running...");
barrier.leave();//阻塞,直到12个成员离开
其中DistributedDoubleBarrier为双端栅栏,可以让N个线程(进程)同时开始,并且同时退出。对于DistributedBarrier内部机制非常简单: setBarrier()方法就是创建"栅栏"节点,removeBarrier()方法就是删除此节点;当执行setBarrier之后,所有的 waitOnBarrier()操作都将阻塞,直到删除节点的事件触发。DistributedBarrier 会监控连接状态,当连接断掉时waitOnBarrier()方法会抛出异常。
leader选举
Curator实现Leader选举有两种方式,第一种是LeaderLatch,这种是有阻塞的,所有client一起去争leader,没有选上的client会一直阻塞,等待上一leader退出或则挂掉。一旦leader位置为空了,继续争夺leader。第二种是LeaderSelector监听器实现Leader选举功能。同一时刻,只有一个Listener会进入takeLeadership()方法,说明它是当前的Leader。注意:当Listener从takeLeadership()退出时就说明它放弃了“Leader身份”, 这时Curator会利用Zookeeper再从剩余的Listener中选出一个新的Leader。autoRequeue()方法使放弃 Leadership的Listener有机会重新获得Leadership,如果不设置的话放弃了的Listener是不会再变成Leader的。LeaderLatch示例:
// 选举Leader 启动
LeaderLatch latch = new LeaderLatch(client, "/leader");
latch.addListener(new LeaderLatchListener(){
@Override
public void isLeader() {
System.out.println("I am leader");
}
@Override
public void notLeader() {
System.out.println("I not leader");
}
});
latch.start();
// latch.await();
// System.out.println("I am leader");
System.out.println(latch.hasLeadership());
TimeUnit.SECONDS.sleep(20);
latch.close();
latch会参与竞选leader,如果被选举为leader,会调用isLeader()方法,hasLeadership()会返回true。
LeaderSelector示例:
final LeaderSelector leaderSelector = new LeaderSelector(client, "/leader",
new LeaderSelectorListenerAdapter() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("I am leader, working...");
TimeUnit.SECONDS.sleep(3);
System.out.println("I am leader, end.");
}
});
leaderSelector.autoRequeue();
leaderSelector.start();
TimeUnit.SECONDS.sleep(1000);
除了LeaderSelectorListenerAdapter外,还可以使用LeaderSelectorListener,LeaderSelectorListener将会额外监控stateChanged。
LeaderSelectorListener selectorListener = new LeaderSelectorListener() {
//此方法将会在Selector的线程池中的线程调用
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("I am leader...");
//如果takeLeadership方法被调用,说明此selector实例已经为leader
//此方法需要阻塞,直到selector放弃leader角色
}
//这个方法将会在Zookeeper主线程中调用---watcher响应时
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
System.out.println("Connection state changed...");
//对于LeaderSelector,底层实现为对leaderPath节点使用了"排他锁",
//"排他锁"的本质,就是一个"临时节点"
//如果接收到LOST,说明此selector实例已经丢失了leader信息.
if (newState == ConnectionState.LOST) {
//需要做特殊处理
}
}
};