如下實現僅僅是大概思路,是非公平鎖,也是獨享鎖,並且沒有考慮阻塞。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) { } } }