基於ZooKeeper實現——分佈式鎖與實現

ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要組件。它是一個爲分佈式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分佈式同步、組服務等。

ZooKeeper的架構通過冗餘服務實現高可用性。因此,如果第一次無應答,客戶端就可以詢問另一臺ZooKeeper主機。ZooKeeper節點將它們的數據存儲於一個分層的命名空間,非常類似於一個文件系統或一個前綴樹結構。客戶端可以在節點讀寫,從而以這種方式擁有一個共享的配置服務。更新是全序的。

基於ZooKeeper分佈式鎖的流程

  • 在zookeeper指定節點(locks)下創建臨時順序節點node_n
  • 獲取locks下所有子節點children
  • 對子節點按節點自增序號從小到大排序
  • 判斷本節點是不是第一個子節點,若是,則獲取鎖;若不是,則監聽比該節點小的那個節點的刪除事件
  • 若監聽事件生效,則回到第二步重新進行判斷,直到獲取到鎖

具體實現

下面就具體使用java和zookeeper實現分佈式鎖,操作zookeeper使用的是apache提供的zookeeper的包。

  • 通過實現Watch接口,實現process(WatchedEvent event)方法來實施監控,使CountDownLatch來完成監控,在等待鎖的時候使用CountDownLatch來計數,等到後進行countDown,停止等待,繼續運行。
  • 以下整體流程基本與上述描述流程一致,只是在監聽的時候使用的是CountDownLatch來監聽前一個節點。

分佈式鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
 
/**
 * Created by liuyang on 2017/4/20.
 */
public class DistributedLock implements Lock, Watcher {
    private ZooKeeper zk = null;
    // 根節點
    private String ROOT_LOCK = "/locks";
    // 競爭的資源
    private String lockName;
    // 等待的前一個鎖
    private String WAIT_LOCK;
    // 當前鎖
    private String CURRENT_LOCK;
    // 計數器
    private CountDownLatch countDownLatch;
    private int sessionTimeout = 30000;
    private List<Exception> exceptionList = new ArrayList<Exception>();
 
    /**
     * 配置分佈式鎖
     * @param config 連接的url
     * @param lockName 競爭資源
     */
    public DistributedLock(String config, String lockName) {
        this.lockName = lockName;
        try {
            // 連接zookeeper
            zk = new ZooKeeper(config, sessionTimeout, this);
            Stat stat = zk.exists(ROOT_LOCK, false);
            if (stat == null) {
                // 如果根節點不存在,則創建根節點
                zk.create(ROOT_LOCK, 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 process(WatchedEvent event) {
        if (this.countDownLatch != null) {
            this.countDownLatch.countDown();
        }
    }
 
    public void lock() {
        if (exceptionList.size() > 0) {
            throw new LockException(exceptionList.get(0));
        }
        try {
            if (this.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " " + lockName + "獲得了鎖");
                return;
            else {
                // 等待鎖
                waitForLock(WAIT_LOCK, sessionTimeout);
            }
        catch (InterruptedException e) {
            e.printStackTrace();
        catch (KeeperException e) {
            e.printStackTrace();
        }
    }
 
    public boolean tryLock() {
        try {
            String splitStr = "_lock_";
            if (lockName.contains(splitStr)) {
                throw new LockException("鎖名有誤");
            }
            // 創建臨時有序節點
            CURRENT_LOCK = zk.create(ROOT_LOCK + "/" + lockName + splitStr, new byte[0],
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(CURRENT_LOCK + " 已經創建");
            // 取所有子節點
            List<String> subNodes = zk.getChildren(ROOT_LOCK, false);
            // 取出所有lockName的鎖
            List<String> lockObjects = new ArrayList<String>();
            for (String node : subNodes) {
                String _node = node.split(splitStr)[0];
                if (_node.equals(lockName)) {
                    lockObjects.add(node);
                }
            }
            Collections.sort(lockObjects);
            System.out.println(Thread.currentThread().getName() + " 的鎖是 " + CURRENT_LOCK);
            // 若當前節點爲最小節點,則獲取鎖成功
            if (CURRENT_LOCK.equals(ROOT_LOCK + "/" + lockObjects.get(0))) {
                return true;
            }
 
            // 若不是最小節點,則找到自己的前一個節點
            String prevNode = CURRENT_LOCK.substring(CURRENT_LOCK.lastIndexOf("/") + 1);
            WAIT_LOCK = lockObjects.get(Collections.binarySearch(lockObjects, prevNode) - 1);
        catch (InterruptedException e) {
            e.printStackTrace();
        catch (KeeperException e) {
            e.printStackTrace();
        }
        return false;
    }
 
    public boolean tryLock(long timeout, TimeUnit unit) {
        try {
            if (this.tryLock()) {
                return true;
            }
            return waitForLock(WAIT_LOCK, timeout);
        catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
 
    // 等待鎖
    private boolean waitForLock(String prev, long waitTime) throws KeeperException, InterruptedException {
        Stat stat = zk.exists(ROOT_LOCK + "/" + prev, true);
 
        if (stat != null) {
            System.out.println(Thread.currentThread().getName() + "等待鎖 " + ROOT_LOCK + "/" + prev);
            this.countDownLatch = new CountDownLatch(1);
            // 計數等待,若等到前一個節點消失,則precess中進行countDown,停止等待,獲取鎖
            this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
            this.countDownLatch = null;
            System.out.println(Thread.currentThread().getName() + " 等到了鎖");
        }
        return true;
    }
 
    public void unlock() {
        try {
            System.out.println("釋放鎖 " + CURRENT_LOCK);
            zk.delete(CURRENT_LOCK, -1);
            CURRENT_LOCK = null;
            zk.close();
        catch (InterruptedException e) {
            e.printStackTrace();
        catch (KeeperException e) {
            e.printStackTrace();
        }
    }
 
    public Condition newCondition() {
        return null;
    }
 
    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }
 
 
    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
    }
}

  測試代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test {
    static int n = 500;
 
    public static void secskill() {
        System.out.println(--n);
    }
 
    public static void main(String[] args) {
         
        Runnable runnable = new Runnable() {
            public void run() {
                DistributedLock lock null;
                try {
                    lock new DistributedLock("127.0.0.1:2181""test1");
                    lock.lock();
                    secskill();
                    System.out.println(Thread.currentThread().getName() + "正在運行");
                finally {
                    if (lock != null) {
                        lock.unlock();
                    }
                }
            }
        };
 
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章