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