1. 程式人生 > 資料庫 >基於redis方式實現分散式鎖

基於redis方式實現分散式鎖

目錄


一、傳統單jvm實現方式:synchronized

Test001.java
package com.itmayiedu;

public class Test001 {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(threadDemo);
            thread.start();
        }
    }
}

//執行緒安全問題,在同一個jvm中,多個執行緒共享同一個全域性變數做寫的操作的時候,可能會受到其他執行緒的干擾。
class ThreadDemo implements Runnable {
    // synchronized 至適合於單個jvm
    private static int count;

    // 重寫Runnable執行緒執行方法run
    @Override
    public synchronized void run() {
        count();
    }

    private synchronized void count() {
        try {
            Thread.sleep(15);
        } catch (Exception e) {
            e.printStackTrace();
        }
        count++;
        System.out.println(Thread.currentThread().getName() + ",count:" + count);
    }
}

執行結果(部分):

 

二、基於redis方式實現分散式鎖

LockRedis.java
package com.itmayiedu;

import java.util.UUID;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class LockRedis {

    // redis執行緒池
    private JedisPool jedisPool;

    // 同時在redis上建立相同的一個key 相同key名稱
    private String redislockKey = "redis_lock";

    public LockRedis(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 分散式鎖最終核心思路:保證只能有一個jvm進行操作
     *
     * 基於Redis實現分散式鎖原理:用setnx命令
     * setnx命令可以做寫入key操作,可以獲取返回結果
     * 該返回如果是為1,表示key不存在,寫入成功
     * 該返回如果是位0,表示key已存在,寫入失敗
     *
     *
     * 兩個超時時間含義:
     * 1.在獲取鎖之前的超時時間----在嘗試獲取鎖的時候,如果在規定的時間內還沒有獲取鎖,直接放棄。
     * 2.在獲取鎖之後的超時時間---當獲取鎖成功之後,對應的key有對應有效期,對應的key在規定時間內進行失效
     */

    /**
     * @param acquireTimeout 在獲取鎖之前的超時時間
     * @param timeOut        在獲取鎖之後的超時時間
     * @return
     */
    public String getRedisLock(Long acquireTimeout, Long timeOut) {
        Jedis conn = null;

        try {
            //1.建立redis連線
            conn = jedisPool.getResource();

            //2.定義redis對應key的value值(uuid),隨機生成不重複的value,相當於鎖的id
            String identifierValue = UUID.randomUUID().toString();

            //3.定義在獲取鎖之後的超時時間(相當於value有效期)
            int expireLock = (int) (timeOut / 1000);// 以秒為單位

            //4.定義在獲取鎖之前的超時時間
            Long endTime = System.currentTimeMillis() + acquireTimeout;

            //5.使用迴圈機制,要在規定acquireTimeout時間保證重複進行嘗試獲取鎖(樂觀鎖)
            while (System.currentTimeMillis() < endTime) {

                //6.嘗試獲取鎖,使用setnx命令插入對應的redislockKey,如果返回為1,成功獲取鎖
                if (conn.setnx(redislockKey, identifierValue) == 1) {
                    conn.expire(redislockKey, expireLock); //設定鎖的key的有效期
                    return identifierValue; //返回鎖的id
                }

            }
            /*
             * 問:為什麼獲取鎖之後,還要設定鎖的超時時間?
             * 答:目的是為了防止死鎖
             *
             * 問:zookeeper實現分散式鎖通過什麼方式防止死鎖?
             * 答:設定session有效期
             *
             */
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return null;
    }

    //PS:如果直接使用conn.del(redislockKey);保證對應是自己的建立redislockKey 刪除對應自己的。
    /**
     * 釋放redis鎖
     * 釋放鎖有兩種:
     * 1.key自動有有效期
     * 2.整個程式執行完畢情況下,刪除對應key
     *
     * @param identifierValue 佔有鎖的執行緒的鎖的key對應的value值,相當於鎖的id
     */
    public void unRedisLock(String identifierValue) {
        Jedis conn = null;

        // 1.建立redis連線
        conn = jedisPool.getResource();

        try {
            // 如果redis鎖的id等於identifierValue,表示是同一把鎖情況,才可以刪除
            if (conn.get(redislockKey).equals(identifierValue)) {
                System.out.println("執行緒" + Thread.currentThread().getName() + "釋放鎖..." + ",鎖的id(identifierValue)為:" + identifierValue);
                conn.del(redislockKey); //刪除key
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }
}
LockService.java
package com.itmayiedu;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class LockService {

    private static JedisPool pool = null;

    static {
        JedisPoolConfig config = new JedisPoolConfig();

        // 設定最大連線數
        config.setMaxTotal(200);

        // 設定最大空閒數
        config.setMaxIdle(8);

        // 設定最大等待時間 100s
        config.setMaxWaitMillis(1000 * 100);

        // 在borrow一個jedis例項時,是否需要驗證,若為true,則所有jedis例項均是可用的
        config.setTestOnBorrow(true);

        // 自己redis伺服器的埠,自行設定。
        // 分別表示:redis執行緒池配置物件、redis伺服器IP、redis服務埠、超時時間、redis服務密碼(沒有就不寫)
        pool = new JedisPool(config, "192.168.3.51", 6379, 2000, "123456");
    }

    private LockRedis lockRedis = new LockRedis(pool);


    // 演示redis實現分散式鎖
    public void seckill() {
        // 1.獲取鎖
        String identifierValue = lockRedis.getRedisLock(2000l, 2000l); //獲取鎖前等待2s,獲取鎖後有2s有效期

        if (identifierValue == null) {
            System.out.println("執行緒" + Thread.currentThread().getName() + ",獲取鎖失敗,因為獲取鎖時間超時...");
            return;
        }
        System.out.println("執行緒" + Thread.currentThread().getName() + ",獲取鎖成功,鎖的id為:" + identifierValue + ",正常執行業務邏輯");

        // 2.釋放鎖
        lockRedis.unRedisLock(identifierValue);
    }
}
ThreadRedis.java
package com.itmayiedu;

public class ThreadRedis extends Thread {
	private LockService lockService;

	public ThreadRedis(LockService lockService) {
		this.lockService = lockService;
	}

	@Override
	public void run() {
		lockService.seckill();
	}

}
Test002.java
package com.itmayiedu;

public class Test002 {

	public static void main(String[] args) {
		LockService lockService = new LockService();
		for (int i = 0; i < 50; i++) {
			new ThreadRedis(lockService).start();
		}
	}

}

執行結果(部分):