• 分布式锁的实现方式和优缺点&Java代码实现


    以下实现仅仅是大概思路,是非公平锁,也是独享锁,而且没有考虑阻塞。

    1、基于数据库

      它的实现方式很简单,就是在数据库中创建一个lock表,申请锁就是向表中插入一行唯一关键字,数据库能够保证只有一个请求执行成功,也就是说这个请求申请到了锁,其他请求会报错说明没有申请到锁。释放锁就是在数据库中删除这一行数据。

      分部式锁接口定义:

    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代码实现:

    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;
            }
        }
    }

    2、基于缓存(redis)

      使用redis做分布式锁,主要是因为redis作为高速缓存,它的存储速度非常快。另外redis可对记录设置过期时间,防止系统崩溃锁无法自动释放。

      Java实现:

    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();
            }
        }
    }

    3、基于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;
        }
    }

    4、锁测试

    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) {
            }
        }
    }
  • 相关阅读:
    多态性的理解
    类(三)——继承与多态
    类(二)——拷贝控制(浅拷贝,深拷贝,浅赋值,深赋值)
    类 (一) ——基本概念
    STL容器底层数据结构的实现
    异常处理
    C++实现单例模式
    类的成员函数的连续调用与返回值问题
    拷贝构造函数的参数为什么必须使用引用类型?拷贝赋值运算符的参数为什么也是引用类型?
    U盘装机教程
  • 原文地址:https://www.cnblogs.com/zhi-leaf/p/12821202.html
Copyright © 2020-2023  润新知