微服務-分佈式鎖(三)-Zookeeper方案

1 Apache-Curator

如上藉助於臨時順序節點,可以避免同時多個節點的併發競爭鎖,緩解了服務端壓力。這種實現方式所有加鎖請求都進行排隊加鎖,是公平鎖的具體實現。Apache-Curator中提供的常見鎖有如下:

  • InterProcessMutex:就是公平鎖的實現。可重入、獨佔鎖
  • InterProcessSemaphoreMutex:不可重入、獨佔鎖
  • InterProcessReadWriteLock:讀寫鎖
  • InterProcessSemaphoreV2:共享信號量
  • InterProcessMultiLock:多重共享鎖 (將多個鎖作爲單個實體管理的容器)

2 使用案例

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMultiLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2;
import org.apache.curator.framework.recipes.locks.Lease;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class DistributedLockDemo {

	// ZooKeeper 鎖節點路徑, 分佈式鎖的相關操作都是在這個節點上進行
	private final String lockPath = "/distributed-lock";
	// ZooKeeper 服務地址, 單機格式爲:(127.0.0.1:2181),
	// 集羣格式爲:(127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183)
	private String connectString="127.0.0.1:2181";
	// Curator 客戶端重試策略
	private RetryPolicy retry;
	// Curator 客戶端對象
	private CuratorFramework client1;
	// client2 用戶模擬其他客戶端
	private CuratorFramework client2;

	// 初始化資源
	@Before
	public void init() throws Exception {
		// 重試策略
		// 初始休眠時間爲 1000ms, 最大重試次數爲 3
		retry = new ExponentialBackoffRetry(1000, 3);
		// 創建一個客戶端, 60000(ms)爲 session 超時時間, 15000(ms)爲鏈接超時時間
		client1 = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
		client2 = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
		// 創建會話
		client1.start();
		client2.start();
	}

	// 釋放資源
	@After
	public void close() {
		CloseableUtils.closeQuietly(client1);
	}

	/**
	 * InterProcessMutex:可重入、獨佔鎖
	 */
	@Test
	public void sharedReentrantLock() throws Exception {
		// 創建可重入鎖
		InterProcessMutex lock1 = new InterProcessMutex(client1, lockPath);
		// lock2 用於模擬其他客戶端
		InterProcessMutex lock2 = new InterProcessMutex(client2, lockPath);
		
		// lock1 獲取鎖
		lock1.acquire();
		try {
			// lock1 第2次獲取鎖
			lock1.acquire();
			try {
				// lock2 超時獲取鎖, 因爲鎖已經被 lock1 客戶端佔用, 所以lock2獲取鎖失敗, 需要等 lock1 釋放
				Assert.assertFalse(lock2.acquire(2, TimeUnit.SECONDS));
			} finally {
				lock1.release();
			}
		} finally {
			// 重入鎖獲取與釋放需要一一對應, 如果獲取 2 次, 釋放 1 次, 那麼該鎖依然是被佔用, 
			// 如果將下面這行代碼註釋, 那麼會發現下面的 lock2
			// 獲取鎖失敗
			lock1.release();
		}
		
		// 在 lock1 釋放後, lock2 能夠獲取鎖
		Assert.assertTrue(lock2.acquire(2, TimeUnit.SECONDS));
		lock2.release();
	}
	
	/**
	 * InterProcessSemaphoreMutex: 不可重入、獨佔鎖
	 */
	@Test
	public void sharedLock() throws Exception {
		InterProcessSemaphoreMutex lock1 = new InterProcessSemaphoreMutex(client1, lockPath);
		// lock2 用於模擬其他客戶端
		InterProcessSemaphoreMutex lock2 = new InterProcessSemaphoreMutex(client2, lockPath);

		// 獲取鎖對象
		lock1.acquire();

		// 測試是否可以重入
		// 因爲鎖已經被獲取, 所以返回 false
		Assert.assertFalse(lock1.acquire(2, TimeUnit.SECONDS));// lock1 返回是false
		Assert.assertFalse(lock2.acquire(2, TimeUnit.SECONDS));// lock2 返回是false

		// lock1 釋放鎖
		lock1.release();

		// lock2 嘗試獲取鎖成功, 因爲鎖已經被釋放
		Assert.assertTrue(lock2.acquire(2, TimeUnit.SECONDS));// 返回是true
		lock2.release();
		System.out.println("測試結束");
	}

	/**
	 * InterProcessReadWriteLock:讀寫鎖.
	 * 特點:讀寫鎖、可重入
	 */
	@Test
	public void sharedReentrantReadWriteLock() throws Exception {
		// 創建讀寫鎖對象, Curator 以公平鎖的方式進行實現
		InterProcessReadWriteLock lock1 = new InterProcessReadWriteLock(client1, lockPath);
		// lock2 用於模擬其他客戶端
		InterProcessReadWriteLock lock2 = new InterProcessReadWriteLock(client2, lockPath);
		
		// 使用 lock1 模擬讀操作
		// 使用 lock2 模擬寫操作
		// 獲取讀鎖(使用 InterProcessMutex 實現, 所以是可以重入的)
		final InterProcessLock readLock = lock1.readLock();
		// 獲取寫鎖(使用 InterProcessMutex 實現, 所以是可以重入的)
		final InterProcessLock writeLock = lock2.writeLock();

		/**
		 * 讀寫鎖測試對象
		 */
		class ReadWriteLockTest {
			// 測試數據變更字段
			private Integer testData = 0;
			private Set<Thread> threadSet = new HashSet<>();

			// 寫入數據
			private void write() throws Exception {
				writeLock.acquire();
				try {
					Thread.sleep(10);
					testData++;
					System.out.println("寫入數據 \t" + testData);
				} finally {
					writeLock.release();
				}
			}

			// 讀取數據
			private void read() throws Exception {
				readLock.acquire();
				try {
					Thread.sleep(10);
					System.out.println("讀取數據 \t" + testData);
				} finally {
					readLock.release();
				}
			}

			// 等待線程結束, 防止 test 方法調用完成後, 當前線程直接退出, 導致控制檯無法輸出信息
			public void waitThread() throws InterruptedException {
				for (Thread thread : threadSet) {
					thread.join();
				}
			}

			// 創建線程方法
			private void createThread(final int type) {
				Thread thread = new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							if (type == 1) {
								write();
							} else {
								read();
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				});
				threadSet.add(thread);
				thread.start();
			}

			// 測試方法
			public void test() {
				for (int i = 0; i < 5; i++) {
					createThread(1);
				}
				for (int i = 0; i < 5; i++) {
					createThread(2);
				}
			}
		}

		ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
		readWriteLockTest.test();
		readWriteLockTest.waitThread();
	}

	/**
	 * InterProcessSemaphoreV2 共享信號量
	 */
	@Test
	public void semaphore() throws Exception {
		// 創建一個信號量, Curator 以公平鎖的方式進行實現
		InterProcessSemaphoreV2 semaphore1 = new InterProcessSemaphoreV2(client1, lockPath, 6);
		// semaphore2 用於模擬其他客戶端
		InterProcessSemaphoreV2 semaphore2 = new InterProcessSemaphoreV2(client2, lockPath, 6);

		// 獲取一個許可
		Lease lease1 = semaphore1.acquire();
		Assert.assertNotNull(lease1);
		// semaphore.getParticipantNodes() 會返回當前參與信號量的節點列表, 倆個客戶端所獲取的信息相同
		Assert.assertEquals(semaphore1.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 超時獲取一個許可
		Lease lease2 = semaphore2.acquire(2, TimeUnit.SECONDS);
		Assert.assertNotNull(lease2);
		Assert.assertEquals(semaphore1.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 獲取多個許可, 參數爲許可數量
		Collection<Lease> leases = semaphore1.acquire(2);
		Assert.assertTrue(leases.size() == 2);
		Assert.assertEquals(semaphore1.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 超時獲取多個許可, 第一個參數爲許可數量
		Collection<Lease> leases2 = semaphore2.acquire(2, 2, TimeUnit.SECONDS);
		Assert.assertTrue(leases2.size() == 2);
		Assert.assertEquals(semaphore1.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 目前 semaphore 已經獲取 3 個許可, semaphore2 也獲取 3 個許可, 加起來爲 6 個, 所以他們無法再進行許可獲取
		Assert.assertNull(semaphore1.acquire(2, TimeUnit.SECONDS));
		Assert.assertNull(semaphore2.acquire(2, TimeUnit.SECONDS));

		// 釋放一個許可
		semaphore1.returnLease(lease1);
		semaphore2.returnLease(lease2);
		// 釋放多個許可
		semaphore1.returnAll(leases);
		semaphore2.returnAll(leases2);
	}

	/**
	 * InterProcessMutex :可重入、獨佔鎖
	 * InterProcessSemaphoreMutex : 不可重入、獨佔鎖
	 * InterProcessMultiLock: 多重共享鎖(將多個鎖作爲單個實體管理的容器)
	 */
	@Test
	public void multiLock() throws Exception {
		InterProcessMutex mutex = new InterProcessMutex(client1, lockPath);
		InterProcessSemaphoreMutex semaphoreMutex = new InterProcessSemaphoreMutex(client2, lockPath);
		//將上面的兩種鎖入到其中
		InterProcessMultiLock multiLock = new InterProcessMultiLock(Arrays.asList(mutex, semaphoreMutex));
		// 獲取參數集合中的所有鎖
		multiLock.acquire();
		// 因爲存在一個不可重入鎖, 所以整個 multiLock 不可重入
		Assert.assertFalse(multiLock.acquire(2, TimeUnit.SECONDS));
		// mutex 是可重入鎖, 所以可以繼續獲取鎖
		Assert.assertTrue(mutex.acquire(2, TimeUnit.SECONDS));
		// semaphoreMutex  是不可重入鎖, 所以獲取鎖失敗
		Assert.assertFalse(semaphoreMutex.acquire(2, TimeUnit.SECONDS));
		// 釋放參數集合中的所有鎖
		multiLock.release();
		// interProcessLock2 中的鎖已經釋放, 所以可以獲取
		Assert.assertTrue(semaphoreMutex.acquire(2, TimeUnit.SECONDS));
	}
}

更多JAVA、高併發、微服務、架構、解決方案、中間件的總結在:https://github.com/yu120/lemon-guide

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章