redis 實現分佈式鎖

1.初始化

public class RedisLock {	
    static Jedis jedis = new Jedis("127.0.0.1", 6379);
	static String kill_key = "goods-1-1-1-num";
	static String lock_key = "lock-goods-1-1-1";

    public static void init() {
		jedis.set(kill_key, "500");
		String num = jedis.get(kill_key);
		System.out.println(num);
	}
}

2.setnx 和expire

  • 通過 jedis.setnx(lock_key, "lock") 的返回值來判斷是否獲取鎖(即能不能寫入值),返回1即獲取鎖,返回0則失敗
  • 爲了避免宕機而沒有釋放鎖,給鎖設置存活時間
  • 下面代碼中 System.exit(10); 模擬了宕機,finally 裏的釋放鎖代碼沒有執行,可通過註釋並執行兩次main 函數來驗證
public class RedisLock {
	static Jedis jedis = new Jedis("127.0.0.1", 6379);
	static String kill_key = "goods-1-1-1-num";
	static String lock_key = "lock-goods-1-1-1";
	public static void main(String[] args) throws Exception {
		jedis.del(lock_key);
		demo1();

        //第二次註釋掉上面兩句代碼,打印下面代碼得到0,說明不能獲取鎖
		//System.out.println(jedis.setnx(lock_key, "lock"));
	}
	
	public static void demo1() {
		if (jedis.setnx(lock_key, "lock") == 1) {
			System.exit(10); // 退出虛擬機模擬宕機。。
			jedis.expire(lock_key, 5);
			try {
				// do something
			} finally {
				jedis.del(lock_key);
			}
		}
	}
}

3.通過 jedis.set(lock_key, "lock", "nx", "ex", 5) 保證原子性來解決2 中的問題

  • 第三個參數:只能填 nx(不存在則賦值) 或 xx(存在則賦值)
  • 第四個參數:ex(秒)或 px(毫秒)
        if ("ok".equals(jedis.set(lock_key, "lock", "nx", "ex", 5))) {
			try {
				// do something
			} finally {
				jedis.del(lock_key);
			}
		}

4.時間問題

  • 規定時間內線程 A 的任務還沒完成,而鎖過期則自動釋放了
  • 此時線程 B 則獲取了鎖,此時兩個線程同時執行任務
  • 線程 A 完成任務,執行finally 把 B 的鎖給釋放了

用Lua 腳本。。。暫時不會。。。

 

5.使用守護線程

  • 守護線程中設置一定時間後執行判斷 key 的剩餘的存活時間,若能執行判斷說明當前的執行業務的線程還沒結束,而且 key 的存活時間剩餘不多了,可以在此時爲key “續命”,當然可以隔一段時間判斷。
  • 下面例子設置 key 的生存時間爲15秒,若過了10秒剩餘5秒還沒完成的時候,守護線程會爲 key 續命爲10秒。
public class RedisLock {
	Jedis jedis = new Jedis("127.0.0.1", 6379);
	String kill_key = "goods-1-1-1-num";
	String lock_key = "lock-goods-1-1-1";

	@Test
	public void main() {

		doSomething();
	}
	@Test
	public void test() {
		Long ttl = jedis.ttl(lock_key);
		System.out.println(ttl);
	}

	public void doSomething() {
		String isOk = jedis.set(lock_key, "lock", "nx", "ex", 15);
		System.out.println(isOk);
		// 給key設置了15秒的超時時間,而線程20秒才執行完
        if ("OK".equals(isOk)) {
			try {
				createDaemon().start();
				// do something over time
				Thread.sleep(20000); // 假設超過15秒還沒執行完
			} catch (Exception e) {
				e.printStackTrace();
			} 
			finally {
				jedis.del(lock_key);
			}
		}
	}

	public Thread createDaemon() {
		Thread daemon = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println("開始執行");
					// 延遲10秒,若還能有判斷,則程序還沒執行完,finally 還沒運行
					Thread.sleep(10000);
					System.out.println("睡完");
					Long ttl = jedis.ttl(lock_key);
					System.out.println(ttl);
					if ( ttl <= 5 && ttl > 0) {
						jedis.expire(lock_key, 10);						
						System.out.println("續命成功");
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		daemon.setDaemon(true);
		return daemon;
	}
}

 

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