• 技术文章 >数据库 >Redis

    深入解析Redis中的分布式锁

    青灯夜游青灯夜游2021-10-29 10:13:55转载179
    本篇文章给大家主要带大家了解一下Redis中分布式锁的实现和代码解析,希望对大家有所帮助!

    Redis 分布式锁

    大家项目中都会使用到分布式锁把,通常用来做数据的有序操作场景,比如一笔订单退款(如果可以退多次的情况)。或者用户多端下单。【相关推荐:Redis视频教程

    Maven 依赖

    我主要是基于 Spring-Boot 2.1.2 + Jedis 进行实现

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.2.RELEASE</version>
        </parent>
    
        <groupId>cn.edu.cqvie</groupId>
        <artifactId>redis-lock</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <java.version>1.8</java.version>
            <redis.version>2.9.0</redis.version>
            <spring-test.version>5.0.7</spring-test.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>${redis.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>log4j-over-slf4j</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>

    配置文件

    application.properties 配置文件内容如下:

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    spring.redis.timeout=30000
    spring.redis.jedis.pool.max-active=8
    spring.redis.jedis.pool.min-idle=2
    spring.redis.jedis.pool.max-idle=4
    
    
    logging.level.root=INFO

    接口定义

    接口定义,对于锁我们核心其实就连个方法 lockunlock.

    public interface RedisLock {
    
        long TIMEOUT_MILLIS = 30000;
    
        int RETRY_MILLIS = 30000;
    
        long SLEEP_MILLIS = 10;
    
        boolean tryLock(String key);
    
        boolean lock(String key);
    
        boolean lock(String key, long expire);
    
        boolean lock(String key, long expire, long retryTimes);
    
        boolean unlock(String key);
    }

    分布式锁实现

    我的实现方式是通过 setnx 方式实现了,如果存在 tryLock 逻辑的话,会通过 自旋 的方式重试

    // AbstractRedisLock.java 抽象类
    public abstract class AbstractRedisLock implements RedisLock {
    
        @Override
        public boolean lock(String key) {
            return lock(key, TIMEOUT_MILLIS);
        }
    
        @Override
        public boolean lock(String key, long expire) {
            return lock(key, TIMEOUT_MILLIS, RETRY_MILLIS);
        }
    }
    
    // 具体实现
    @Component
    public class RedisLockImpl extends AbstractRedisLock {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
        private ThreadLocal<String> threadLocal = new ThreadLocal<String>();
        private static final String UNLOCK_LUA;
    
        static {
            StringBuilder sb = new StringBuilder();
            sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
            sb.append("then ");
            sb.append("    return redis.call(\"del\",KEYS[1]) ");
            sb.append("else ");
            sb.append("    return 0 ");
            sb.append("end ");
            UNLOCK_LUA = sb.toString();
    
        }
    
        @Override
        public boolean tryLock(String key) {
            return tryLock(key, TIMEOUT_MILLIS);
        }
    
        public boolean tryLock(String key, long expire) {
            try {
                return !StringUtils.isEmpty(redisTemplate.execute((RedisCallback<String>) connection -> {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    threadLocal.set(uuid);
                    return commands.set(key, uuid, "NX", "PX", expire);
                }));
            } catch (Throwable e) {
                logger.error("set redis occurred an exception", e);
            }
            return false;
        }
    
        @Override
        public boolean lock(String key, long expire, long retryTimes) {
            boolean result = tryLock(key, expire);
    
            while (!result && retryTimes-- > 0) {
                try {
                    logger.debug("lock failed, retrying...{}", retryTimes);
                    Thread.sleep(SLEEP_MILLIS);
                } catch (InterruptedException e) {
                    return false;
                }
                result = tryLock(key, expire);
            }
            return result;
        }
    
        @Override
        public boolean unlock(String key) {
            try {
                List<String> keys = Collections.singletonList(key);
                List<String> args = Collections.singletonList(threadLocal.get());
                Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                    Object nativeConnection = connection.getNativeConnection();
    
                    if (nativeConnection instanceof JedisCluster) {
                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
                    if (nativeConnection instanceof Jedis) {
                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
                    return 0L;
                });
                return result != null && result > 0;
            } catch (Throwable e) {
                logger.error("unlock occurred an exception", e);
            }
            return false;
        }
    }

    测试代码

    最后再来看看如何使用吧. (下面是一个模拟秒杀的场景)

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class RedisLockImplTest {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Autowired
        private RedisLock redisLock;
        @Autowired
        private StringRedisTemplate redisTemplate;
        private ExecutorService executors = Executors.newScheduledThreadPool(8);
    
        @Test
        public void lock() {
            // 初始化库存
            redisTemplate.opsForValue().set("goods-seckill", "10");
            List<Future> futureList = new ArrayList<>();
    
            for (int i = 0; i < 100; i++) {
                futureList.add(executors.submit(this::seckill));
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 等待结果,防止主线程退出
            futureList.forEach(action -> {
                try {
                    action.get();
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            });
    
        }
    
        public int seckill() {
            String key = "goods";
            try {
                redisLock.lock(key);
                int num = Integer.valueOf(Objects.requireNonNull(redisTemplate.opsForValue().get("goods-seckill")));
                if (num > 0) {
                    redisTemplate.opsForValue().set("goods-seckill", String.valueOf(--num));
                    logger.info("秒杀成功,剩余库存:{}", num);
                } else {
                    logger.error("秒杀失败,剩余库存:{}", num);
                }
                return num;
            } catch (Throwable e) {
                logger.error("seckill exception", e);
            } finally {
                redisLock.unlock(key);
            }
            return 0;
        }
    }

    总结

    本文是 Redis 锁的一种简单的实现方式,基于 jedis 实现了锁的重试操作。 但是缺点还是有的,不支持锁的自动续期,锁的重入,以及公平性(目前通过自旋的方式实现,相当于是非公平的方式)。

    更多编程相关知识,请访问:编程入门!!

    以上就是深入解析Redis中的分布式锁的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Redis 分布式锁
    上一篇:Redis中的两种持久化方式,为什么需要两种持久化? 下一篇:一起聊聊Redis缓存的淘汰策略

    相关文章推荐

    • 浅谈Redis中布隆过滤器的安装和配置方法• 一文详解Redis中的LRU算法• 深入解析Redis中的Info指令• 带你吃透Redis中的主从复制、Sentinel、集群• 2022年Redis高频面试题分享(附答案分析)• linux下 php怎么安装redis扩展

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网