分佈式鎖的實現方式和優缺點&Java代碼實現

如下實現僅僅是大概思路,是非公平鎖,也是獨享鎖,並且沒有考慮阻塞。java

一、基於數據庫mysql

  它的實現方式很簡單,就是在數據庫中建立一個lock表,申請鎖就是向表中插入一行惟一關鍵字,數據庫可以保證只有一個請求執行成功,也就是說這個請求申請到了鎖,其餘請求會報錯說明沒有申請到鎖。釋放鎖就是在數據庫中刪除這一行數據。redis

  分部式鎖接口定義:sql

package com.zhi.util;

/**
 * 分佈式鎖接口
 * 
 * @author 張遠志
 * @since 2020年5月3日11:38:27
 *
 */
public interface DistributeLock {
    /**
     * 申請鎖
     * 
     * @param key       鎖關鍵字
     * @param requestId 請求ID,主要用於校驗,防止鎖被誤釋放
     * @return 是否成功申請鎖
     */
    boolean lock(String key, String requestId);

    /**
     * 申請鎖
     * 
     * @param key       鎖關鍵字
     * @param requestId 請求ID,主要用於校驗,防止鎖被誤釋放
     * @param second    自動釋放時間,防止系統崩潰後鎖一直出於沒法釋放的狀態
     * @return 是否成功申請鎖
     */
    boolean lock(String key, String requestId, Integer second);

    /**
     * 釋放鎖
     * 
     * @param key       鎖關鍵字
     * @param requestId 請求ID
     * @return
     */
    boolean unlock(String key, String requestId);
}

   建表SQL:數據庫

CREATE TABLE `dis_lock`  (
  `id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `content` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '分佈式鎖' ROW_FORMAT = Dynamic;

  Java代碼實現:apache

package com.zhi.util;

import java.sql.Connection;
import java.sql.PreparedStatement;

import javax.sql.DataSource;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 數據庫實現分佈式鎖,數據庫先建立好dis_lock表
 * 
 * @author 張遠志
 * @since 2020年5月3日12:05:22
 *
 */
public class DatabaseLock implements DistributeLock {
    private static Logger logger = LogManager.getLogger(DatabaseLock.class);
    private final static String INSET = "insert dis_lock(id, content) values (?, ?)";
    private final static String DELETE = "delete from dis_lock where id=? and content=?";
    private DataSource dataSource;

    public DatabaseLock(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public boolean lock(String key, String requestId) {
        try (Connection connection = dataSource.getConnection();
                PreparedStatement statement = connection.prepareStatement(INSET);) {
            statement.setString(1, key);
            statement.setString(2, requestId);
            statement.execute();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean lock(String key, String requestId, Integer second) {
        if (second != null) {
            throw new RuntimeException("not suport expire time set");
        }
        return lock(key, requestId);
    }

    @Override
    public boolean unlock(String key, String requestId) {
        try (Connection connection = dataSource.getConnection();
                PreparedStatement statement = connection.prepareStatement(DELETE);) {
            statement.setString(1, key);
            statement.setString(2, requestId);
            statement.execute();
            return statement.getUpdateCount() > 0;
        } catch (Exception e) {
            logger.error("釋放鎖出錯", e);
            return false;
        }
    }
}

二、基於緩存(redis)api

  使用redis作分佈式鎖,主要是由於redis做爲高速緩存,它的存儲速度很是快。另外redis可對記錄設置過時時間,防止系統崩潰鎖沒法自動釋放。緩存

  Java實現:dom

package com.zhi.util;

import java.util.Collections;

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

/**
 * Redis分佈式鎖實現
 * 
 * @author 張遠志
 * @since 2020年5月3日11:45:56
 *
 */
public class RedisLock implements DistributeLock {
    private JedisPool jedisPool;
    /**
     * 加鎖成功標示
     */
    private static final String LOCK_SUCCESS = "OK";
    /**
     * 解鎖成功標示
     */
    private static final Long RELEASE_SUCCESS = 1L;

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

    @Override
    public boolean lock(String key, String requestId) {
        return lock(key, requestId, null);
    }

    @Override
    public boolean lock(String key, String requestId, Integer second) {
        try (Jedis jedis = jedisPool.getResource()) {
            String back = null;
            SetParams params = new SetParams();
            if (second != null) {
                params.ex(second); // 設置超時時間,單位秒
            }
            back = jedis.setnx(key, requestId, params); // 若鎖不存在才進行寫操做
            return LOCK_SUCCESS.equals(back);
        }
    }

    @Override
    public boolean unlock(String key, String requestId) {
        try (Jedis jedis = jedisPool.getResource();) {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));
            return RELEASE_SUCCESS.equals(result);
        }
    }
}

  redisson中已經實現了redis分佈式鎖,使用起來很是方便,這裏給出簡單示例,你們能夠詳細百度:分佈式

public void test() throws InterruptedException {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.59.134:6379"); // 使用單例模式
    config.setLockWatchdogTimeout(5000); // 設置鎖的看門狗監測時間爲5秒,默認30秒,用於鎖續期
    RedissonClient redisson = Redisson.create(config);

    RLock locker = redisson.getFairLock("myLock"); // 獲取公平鎖
    if (locker.tryLock(30, TimeUnit.SECONDS)) { // 沒有設置過時時間,過時時間爲lockWatchdogTimeout,而且每過1/3的時間會自動續期
        try {
            ...
        } finally {
            locker.unlock();
        }
    }
}

三、基於zookeeper

package com.zhi.util;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.ZooKeeper;

/**
 * Zookeeper實現分佈式鎖
 * 
 * @author 張遠志
 * @since 2020年5月3日12:04:33
 *
 */
public class ZookeeperLock implements DistributeLock {
    private ZooKeeper zooKeeper;
    /**
     * 通常鎖單獨放一個目錄中,我這裏簡單加一個前綴
     */
    private static final String prefix = "/lock_";

    public ZookeeperLock(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    @Override
    public boolean lock(String key, String requestId) {
        try {
            String path = getPath(key);
            Stat stat = zooKeeper.exists(path, false);
            if (stat != null) { // 節點已存在,鎖已佔用
                return false;
            }
            // CreateMode.PERSISTENT表示數據將被持久化保存
            zooKeeper.create(path, requestId.getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean lock(String key, String requestId, Integer second) {
        if (second != null) {
            throw new RuntimeException("not suport expire time set");
        }
        return lock(key, requestId);
    }

    @Override
    public boolean unlock(String key, String requestId) {
        try {
            String path = getPath(key);
            Stat stat = new Stat();
            byte[] bits = zooKeeper.getData(path, false, stat);
            String oKey = new String(bits, "UTF-8");
            if (requestId.equals(oKey)) { // 比較擁有者是否一致
                zooKeeper.delete(path, stat.getVersion()); // 刪除指定版本數據
            } else {
                throw new RuntimeException("不容許釋放擁有者不是本身的鎖");
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private String getPath(String key) {
        return prefix + key;
    }
}

四、鎖測試

package com.zhi.test;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;

import com.zhi.util.DatabaseLock;
import com.zhi.util.DistributeLock;
import com.zhi.util.RedisLock;
import com.zhi.util.ZookeeperLock;

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

/**
 * 分佈式鎖測試
 * 
 * @author 張遠志
 * @since 2020年5月3日13:10:43
 *
 */
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LockTest {
    private final Logger logger = LogManager.getLogger(this.getClass());
    private static final String key = "test";

    @Order(1)
    @Test
    public void test1() {
        logger.info("開始測試數據庫(MySQL)分佈式鎖-----------------------------------------------");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("abc123");
        dataSource.setUrl("jdbc:mysql://192.168.59.131:3306/zhi_test");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

        doJob(new DatabaseLock(dataSource));
    }

    @Order(2)
    @Test
    public void test2() {
        logger.info("開始測試Redis分佈式鎖-----------------------------------------------");
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(5);
        config.setTestOnBorrow(false);
        JedisPool jedisPool = new JedisPool(config, "192.168.59.131", 6379);

        doJob(new RedisLock(jedisPool));
    }

    @Order(3)
    @Test
    public void test3() {
        logger.info("開始測試Zookeeper分佈式鎖-----------------------------------------------");
        ZooKeeper zooKeeper = null;
        try {
            zooKeeper = new ZooKeeper("192.168.59.131:2181", 60000, new Watcher() {
                public void process(WatchedEvent event) {
                    logger.info("事件類型:{},路徑:{}", event.getType(), event.getPath());
                }
            });

            doJob(new ZookeeperLock(zooKeeper));
        } catch (Exception e) {
        } finally {
            try {
                zooKeeper.close();
            } catch (Exception e) {
            }
        }
    }

    private void doJob(DistributeLock locker) {
        int jobCount = 100;
        CountDownLatch latch = new CountDownLatch(jobCount);
        for (int i = 0; i < jobCount; i++) {
            new Worker(i, locker, latch).start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
        }
    }

    class Worker extends Thread {
        private DistributeLock locker;
        private CountDownLatch latch;

        public Worker(int tNo, DistributeLock locker, CountDownLatch latch) {
            super("線程" + StringUtils.leftPad(String.valueOf(tNo), 3, "0"));
            this.locker = locker;
            this.latch = latch;
        }

        @Override
        public void run() {
            LockTest.doSleep(new Random().nextInt(20) * 1000);
            long startTime = System.currentTimeMillis();
            if (locker.lock(key, getName())) {
                logger.info("{}申請到鎖,申請動做耗時{}毫秒", getName(), System.currentTimeMillis() - startTime);
                LockTest.doSleep(1000);
                boolean flag = locker.unlock(key, getName());
                logger.info("{}釋放鎖{}", getName(), flag ? "成功" : "失敗");
            }
            latch.countDown();
        }
    }

    public static void doSleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (Exception e) {
        }
    }
}
相關文章
相關標籤/搜索