Master选举
Master选举架构
- 服务注册——各个服务器在启动过程中,首先会到Zookeeper节点下创建一个临时节点,并把自己的基本信息写入到这个临时节点。
- 服务发现——系统中的其他服务可以通过获取servers节点的子节点列表,来了解当前系统哪些服务器可用。
- 争抢Master——尝试创建master节点,谁能创建成功,谁就是master,其他的服务器则为Slave。
- 重新选举——所有服务器必须监听master服务器的删除时间(因为Zookeeper的临时节点hui会随着会话失效而被删除),一旦master节点宕机,其他的节点就可立刻发现,并重新选举。
Master争抢流程
ZkClient实现服务器Master争抢
就Master争抢做一个简易Demo
- 服务器信息
public class ServerData implements Serializable{
private int serverId;
private String serverName;
public int getServerId() {
return serverId;
}
public void setServerId(int serverId) {
this.serverId = serverId;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
}
- Master选举
public class MasterVote {
//zkclient客户端
private ZkClient zkClient;
//争抢的master节点
private static final String MASTER_NODE = "/master";
//连接地址
private static final String CONNECTION_STRING = "xxx.xxx.xxx.xxx:2181";
//超时时间
private static final int SESSION_TIMEOUT=5000;
//争抢master的服务器节点信息
private ServerData serverData;
//争抢到master节点的服务器信息
private ServerData masterData;
//服务器开启状态
private volatile boolean running = false;
//master节点监听事件
IZkDataListener dataListener;
private ScheduledExecutorService scheService = Executors.newScheduledThreadPool(1);
public MasterVote(ZkClient zkclient,ServerData serverData){
this.zkClient = zkclient;
this.serverData = serverData;
dataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
takeMaster();
}
};
}
/**
* 开始争抢master
*/
public void start(){
if(running){
throw new RuntimeException("服务器在启动状态!");
}
running = true;
zkClient.subscribeDataChanges(MASTER_NODE,dataListener);
takeMaster();
}
/**
* 停止争抢master
*/
public void stop(){
if(!running){
throw new RuntimeException("服务器在停止状态");
}
running = false;
zkClient.subscribeDataChanges(MASTER_NODE,dataListener);
releaseMaster();
}
/**
* 争抢master节点的具体实现
*/
private void takeMaster(){
if(!running){
throw new RuntimeException("服务器没有启动");
}
System.out.println(serverData.getServerName()+"开始抢master节点");
try {
//创建master节点成功,该服务器为master,记录master节点信息
zkClient.createEphemeral(MASTER_NODE,serverData);
masterData = serverData;
System.out.println(masterData.getServerName()+"成功抢到master!");
//每五秒释放一次
scheService.schedule(new Runnable() {
@Override
public void run() {
releaseMaster();
}
},5,TimeUnit.SECONDS);
}
//ZkNode节点已经存在,说明master节点已经存在
catch (ZkNodeExistsException e) {
//读取当前master节点的信息
ServerData currentMasterData = zkClient.readData(MASTER_NODE);
//读取的过程中master节点已经被释放
if(currentMasterData==null){
takeMaster();
}
// else {
// masterData = currentMasterData;
// }
}
}
/**
* 释放master节点
*/
private void releaseMaster(){
if(checkMaster()){
zkClient.delete(MASTER_NODE);
System.out.println("释放===================================");
}
}
/**
* 校验当前的服务器是否是master
*/
private boolean checkMaster(){
try {
//读取当前master节点数据
ServerData currentMasterData = zkClient.readData(MASTER_NODE);
masterData = currentMasterData;
//master的服务名等于当前争抢的服务器名
if (masterData.getServerName().equals(masterData.getServerName())) {
return true;
}
else {
return false;
}
}
catch (ZkNoNodeException e) {
return false;
}
//因网络打断出现的异常
catch (ZkInterruptedException e) {
return checkMaster();
}
catch (Exception e) {
return false;
}
}
- 测试
使用信号量模拟多个服务同时开始争抢
public static void main(String[] args){
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(10);
for(int i = 0 ; i < 10 ;i++){
final int idx= i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try{
semaphore.acquire();
//初始化
ZkClient zk = new ZkClient(CONNECTION_STRING,SESSION_TIMEOUT,
SESSION_TIMEOUT,new SerializableSerializer());
//定义争抢的服务器信息
ServerData serverData = new ServerData();
serverData.setServerId(idx);
serverData.setServerName("#server-"+idx);
MasterVote mv = new MasterVote(zk,serverData);
//开始争抢
mv.start();
semaphore.release();
}catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
- 结果
#server-3开始抢master节点
#server-1开始抢master节点
#server-6开始抢master节点
#server-8开始抢master节点
#server-4开始抢master节点
#server-9开始抢master节点
#server-2开始抢master节点
#server-0开始抢master节点
#server-7开始抢master节点
#server-5开始抢master节点
#server-4成功抢到master!
释放===================================
#server-0开始抢master节点
#server-3开始抢master节点
#server-8开始抢master节点
#server-9开始抢master节点
#server-2开始抢master节点
#server-5开始抢master节点
#server-6开始抢master节点
#server-7开始抢master节点
#server-1开始抢master节点
#server-4开始抢master节点
#server-8成功抢到master!
分布式锁
分布式锁包含:共享锁,排他锁。
- 排他锁(Exclusive Locks)——又称为写锁或独占锁,需要等待其它所有的锁全部释放才能争抢,且仅当持有该锁时才能进行写操作。
- 共享锁(Shared Locks) ——又称为读锁,所有操作都可持有读锁,但这些锁是有序的,根据顺序依次进行读操作。
原生API实现共享锁
获取子节点列表,如果自己创建的节点值最小,则持有锁,并进行操作,待操作完成释放锁;否则等待并监听比自己小的节点。
下面使用原生API模拟一次共享锁的竞争过程。
- DistributeSharedLock
public class DistributeSharedLock implements Watcher{
//原生API
ZooKeeper zooKeeper = null;
//定义根节点
private String root = "/locks";
//当前获取节点
private String currentZnode;
//当前等待节点
private String waitZnode;
//发令枪
private CountDownLatch countDownLatch;
//连接地址
private static final String CONNECTION_STRING = "xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2182,xxx.xxx.xxx.xxx:2183";
//超时时间
private static final int SESSION_TIMEOUT=5000;
/**
* 构造函数
* @param config
*/
public DistributeSharedLock(String config){
try {
zooKeeper = new ZooKeeper(config,SESSION_TIMEOUT,this);
//查看root节点是否存在
Stat stat = zooKeeper.exists(root,false);
//不存在则创建根节点
if(stat == null){
zooKeeper.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 获取锁
*/
public void lock(){
if (tryLock()) {
System.out.println("Thread " + Thread.currentThread().getName()+" hold lock!");
return;
}
try {
//等待并获取锁
waitLock(waitZnode,SESSION_TIMEOUT);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 释放锁
*/
public void unLock(){
System.out.println("Thread " + Thread.currentThread().getName()+" un lock!");
try {
zooKeeper.delete(currentZnode,-1);
currentZnode=null;
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
private boolean tryLock(){
// /locks/lock_0000000001
String spliteStr = "lock_";
try {
currentZnode = zooKeeper.create(root+"/"+spliteStr,new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("Thread " + Thread.currentThread().getName()+" create success: " + currentZnode);
//获取根节点下所有子节点
List<String> childNodes = zooKeeper.getChildren(root,false);
//排序
Collections.sort(childNodes);
//如果当前节点的值是最小值,则获得锁
if(currentZnode.equals(root+"/"+childNodes.get(0))){
return true;
}
//否则,监听比自己小的节点
String subNode = currentZnode.substring(currentZnode.lastIndexOf("/")+1);//lock_0000000003
waitZnode = childNodes.get(Collections.binarySearch(childNodes,subNode)-1);//lock_0000000002
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
private boolean waitLock(String lower,long waitTime) throws KeeperException, InterruptedException {
Stat stat = zooKeeper.exists(root + "/" + lower, true);
//存在比自己小的节点,开始等待
if (stat != null) {
System.out.println("Thread " + Thread.currentThread().getName() + " waiting for: " + root + "/" + lower);
this.countDownLatch = new CountDownLatch(1); //实例化计数器,让当前的线程等待
this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
this.countDownLatch = null;
}
return true;
}
/**
* 比当前节点小的临时节点删除时触发,计数器-1
* @param watchedEvent
*/
@Override
public void process(WatchedEvent watchedEvent) {
if(this.countDownLatch!=null){ //如果计数器不为空话话,释放计数器锁
this.countDownLatch.countDown();
}
}
}
- 测试
public static void main(String[] args) {
ExecutorService executorService= Executors.newCachedThreadPool();
final Semaphore semaphore=new Semaphore(10);
for(int i=0;i<10;i++){
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
DistributeSharedLock sharedLock = new DistributeSharedLock(CONNECTION_STRING);
sharedLock.lock();
//业务代码
Thread.sleep(3000);
sharedLock.unLock();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.execute(runnable);
}
executorService.shutdown();
}
- 结果
Thread pool-1-thread-4 create success: /locks/lock_0000000123
Thread pool-1-thread-10 create success: /locks/lock_0000000122
Thread pool-1-thread-7 create success: /locks/lock_0000000121
Thread pool-1-thread-6 create success: /locks/lock_0000000128
Thread pool-1-thread-2 create success: /locks/lock_0000000129
Thread pool-1-thread-5 create success: /locks/lock_0000000130
Thread pool-1-thread-8 create success: /locks/lock_0000000125
Thread pool-1-thread-9 create success: /locks/lock_0000000127
Thread pool-1-thread-3 create success: /locks/lock_0000000126
Thread pool-1-thread-1 create success: /locks/lock_0000000124
Thread pool-1-thread-7 hold lock!
Thread pool-1-thread-4 waiting for: /locks/lock_0000000122
Thread pool-1-thread-6 waiting for: /locks/lock_0000000127
Thread pool-1-thread-5 waiting for: /locks/lock_0000000129
Thread pool-1-thread-8 waiting for: /locks/lock_0000000124
Thread pool-1-thread-2 waiting for: /locks/lock_0000000128
Thread pool-1-thread-9 waiting for: /locks/lock_0000000126
Thread pool-1-thread-10 waiting for: /locks/lock_0000000121
Thread pool-1-thread-1 waiting for: /locks/lock_0000000123
Thread pool-1-thread-3 waiting for: /locks/lock_0000000125