美文网首页
Spring Boot 整合 redisson 来实现分布式锁

Spring Boot 整合 redisson 来实现分布式锁

作者: 枫叶_Maple | 来源:发表于2020-05-24 16:30 被阅读0次
    面试时经常会问到有没有用过分布式锁、redis 锁,很多人平时很少接触到。所以只能很无奈的回答 “没有”。本文通过 Spring Boot 整合 redisson 来实现分布式锁,并结合 demo 测试结果。本来内容来自公众号“Java团长”,本人参考文章本地创建springboot项目demo进行测试,文章结尾附测试结果截图。
    
    微信图片_20200525161825.png
    <!--redis-->
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--redisson-->
    <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.10.6</version>
    </dependency>
    

    配置信息

    server.port:8080
    # redis
    spring.redis.host:118.126.66.195
    spring.redis.port:6379
    spring.redis.password:c428cb38d5f260853678922e
    # 连接池最大连接数(使用负值表示没有限制)
    spring.redis.jedis.pool.max-active:100
    # 连接池中的最小空闲连接
    spring.redis.jedis.pool.max-idle:10
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.jedis.pool.max-wait:-1
    # 连接超时时间(毫秒)
    spring.redis.jedis.pool.timeout:5000
    #默认是索引为0的数据库
    spring.redis.jedis.pool.database:0
    

    配置类

    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.util.StringUtils;
    
    /**
     * @Created by zl
     * @Date 2020/5/25 11:40
     * @Description
     */
    @Configuration
    public class RedissonConfig {
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private String port;
        @Value("${spring.redis.password}")
        private String password;
    
        @Bean
        public RedissonClient redissonClient() {
            Config config = new Config();
            //单节点
            config.useSingleServer().setAddress("redis://" + host + ":" + port);
            if (StringUtils.isEmpty(password)) {
                config.useSingleServer().setPassword(null);
            } else {
                config.useSingleServer().setPassword(password);
            }
            //添加主从配置
            // config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
            // 集群模式配置 setScanInterval()扫描间隔时间,单位是毫秒, //可以用"rediss://"来启用SSL连接
            // config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
            return Redisson.create(config);
        }
    }
    

    Redisson 工具类

    package com.redis.lock.demo.util;
    
    import com.redis.lock.demo.redis.DistributedLocker;
    import org.redisson.api.RCountDownLatch;
    import org.redisson.api.RLock;
    import org.redisson.api.RSemaphore;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Created by zl
     * @Date 2020/5/25 11:52
     * @Description
     */
    public class RedisLockUtil {
        private static DistributedLocker distributedLocker = SpringContextHolder.getBean(DistributedLocker.class);
    
        /**
         * 加锁
         *
         * @param lockKey
         * @return
         */
        public static RLock lock(String lockKey) {
            return distributedLocker.lock(lockKey);
        }
    
        /**
         * 释放锁
         *
         * @param lockKey
         */
        public static void unlock(String lockKey) {
            distributedLocker.unlock(lockKey);
        }
    
        /**
         * 释放锁
         *
         * @param lock
         */
        public static void unlock(RLock lock) {
            distributedLocker.unlock(lock);
        }
    
        /**
         * 带超时的锁
         *
         * @param lockKey
         * @param timeout 超时时间   单位:秒
         */
        public static RLock lock(String lockKey, int timeout) {
            return distributedLocker.lock(lockKey, timeout);
        }
    
        /**
         * 带超时的锁
         *
         * @param lockKey
         * @param unit    时间单位
         * @param timeout 超时时间
         */
        public static RLock lock(String lockKey, int timeout, TimeUnit unit) {
            return distributedLocker.lock(lockKey, unit, timeout);
        }
    
        /**
         * 尝试获取锁
         *
         * @param lockKey
         * @param waitTime  最多等待时间
         * @param leaseTime 上锁后自动释放锁时间
         * @return
         */
        public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
            return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
        }
    
        /**
         * 尝试获取锁
         *
         * @param lockKey
         * @param unit      时间单位
         * @param waitTime  最多等待时间
         * @param leaseTime 上锁后自动释放锁时间
         * @return
         */
        public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
            return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
        }
    
        /**
         * 获取计数器
         *
         * @param name
         * @return
         */
    //    public static RCountDownLatch getCountDownLatch(String name) {
    //        return distributedLocker.getCountDownLatch(name);
    //    }
    //
    //    /**
    //     * 获取信号量
    //     *
    //     * @param name
    //     * @return
    //     */
    //    public static RSemaphore getSemaphore(String name) {
    //        return distributedLocker.getSemaphore(name);
    //    }
    }
    

    底层封装

    package com.redis.lock.demo.redis;
    
    import org.redisson.api.RLock;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Created by zl
     * @Date 2020/5/25 11:53
     * @Description TODO
     */
    public interface DistributedLocker {
        RLock lock(String lockKey);
    
        RLock lock(String lockKey, int timeout);
    
        RLock lock(String lockKey, TimeUnit unit, int timeout);
    
        boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
    
        void unlock(String lockKey);
    
        void unlock(RLock lock);
    }
    
    

    实现类

    package com.redis.lock.demo.redis.impl;
    
    import com.redis.lock.demo.redis.DistributedLocker;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Created by zl
     * @Date 2020/5/25 11:56
     * @Description TODO
     */
    @Component
    public class RedisDistributedLocker implements DistributedLocker {
    
        @Autowired
        private
        RedissonClient
                redissonClient;
    
        @Override
        public RLock
        lock(String lockKey) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
            return lock;
        }
    
        @Override
        public RLock lock(String lockKey, int leaseTime) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock(leaseTime, TimeUnit.SECONDS);
            return lock;
        }
    
        @Override
        public RLock
        lock(String lockKey, TimeUnit unit, int timeout) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock(timeout, unit);
            return lock;
        }
    
        @Override
        public boolean
        tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
            RLock lock = redissonClient.getLock(lockKey);
            try {
                return lock.tryLock(waitTime, leaseTime, unit);
            } catch (InterruptedException e) {
                return false;
            }
        }
    
        @Override
        public void
        unlock(String lockKey) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.unlock();
        }
    
        @Override
        public void unlock(RLock lock) {
            lock.unlock();
        }
    }
    
    

    SpringContextHolder类

    package com.redis.lock.demo.util;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    /**
     * Spring的ApplicationContext的持有者,可以用静态方法的方式获取spring容器中的bean
     *
     * @author zl
     * @date 2020年05月25日 下午14:05:11
     */
    @Component
    public class SpringContextHolder implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            SpringContextHolder.applicationContext = applicationContext;
        }
    
        public static ApplicationContext getApplicationContext() {
            assertApplicationContext();
            return applicationContext;
        }
    
        @SuppressWarnings("unchecked")
        public static <T> T getBean(String beanName) {
            assertApplicationContext();
            return (T) applicationContext.getBean(beanName);
        }
    
        public static <T> T getBean(Class<T> requiredType) {
            assertApplicationContext();
            return applicationContext.getBean(requiredType);
        }
    
        private static void assertApplicationContext() {
            if (SpringContextHolder.applicationContext == null) {
                throw new RuntimeException("applicaitonContext属性为null,请检查是否注入了SpringContextHolder!");
            }
        }
    
    }
    
    

    controller测试类

    package com.redis.lock.demo.controller;
    
    import com.redis.lock.demo.util.RedisLockUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Created by zl
     * @Date 2020/5/25 13:45
     * @Description
     */
    @RestController
    @RequestMapping("/redisson")
    @Slf4j
    public class RedissonLockController {
        /**
         * 锁测试共享变量
         */
        private
        Integer lockCount = 30;
    
        /**
         * 无锁测试共享变量
         */
        private Integer count = 30;
    
        /**
         * 模拟线程数
         */
        private static int threadNum = 30;
    
        /**
         * 模拟并发测试加锁和不加锁
         *
         * @return
         */
        @GetMapping("/test")
        public void lock() {
            // 计数器
            final CountDownLatch countDownLatch = new CountDownLatch(1);
            for (int i = 0; i < threadNum; i++) {
                MyRunnable myRunnable = new MyRunnable(countDownLatch);
                Thread myThread = new Thread(myRunnable);
                myThread.start();
            }
            // 释放所有线程
            countDownLatch.countDown();
        }
    
        /**
         * 加锁测试
         */
        private void testLockCount() {
            String lockKey = "lock-test";
            try {
                // 加锁,设置超时时间2s
                RedisLockUtil.lock(lockKey, 2, TimeUnit.SECONDS);
                lockCount--;
                log.info("lockCount值:" + lockCount);
            } catch
            (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                // 释放锁
                RedisLockUtil.unlock(lockKey);
            }
        }
    
        /**
         * 无锁测试
         */
        private void testCount() {
            count--;
            log.info("count值:" + count);
        }
    
    
        public class MyRunnable implements Runnable {
            /**
             * 计数器
             */
            final CountDownLatch countDownLatch;
    
            public MyRunnable(CountDownLatch countDownLatch) {
                this.countDownLatch = countDownLatch;
            }
    
            @Override
            public void run() {
                try {
                    // 阻塞当前线程,直到计时器的值为0
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    log.error(e.getMessage(), e);
                }
                // 无锁操作
                testCount();
                // 加锁操作
                testLockCount();
            }
    
        }
    }
    
    

    结果示例图


    image.png

    测试结果
    根据打印结果可以明显看到,未加锁的 count-- 后值是乱序的,而加锁后的结果是按照顺序排序的和我们预期的一样。

    相关文章

      网友评论

          本文标题:Spring Boot 整合 redisson 来实现分布式锁

          本文链接:https://www.haomeiwen.com/subject/lnbyahtx.html