zoukankan      html  css  js  c++  java
  • redis 分布式锁-简易版与 redisson 实验

    1

    在原来的redsi测试服务上,继续来做实验。点击这里

    在原有的IRedisService接口上新增两个 分布式的获取锁与释放锁。

    /**
         * 获取锁
         * @param lockKey
         * @param requestId
         * @param expireTimeSeconds
         * @return
         */
        boolean getLock(String lockKey, String requestId, long expireTimeSeconds);
    
        /**
         * 释放锁
         * @param lockKey
         * @param requestId
         * @return
         */
        boolean releaseLock(String lockKey, String requestId);

    2 RedisServiceImpl 实现

    @Override
        public boolean getLock(String lockKey, String requestId, long expireTimeSeconds) {
            return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTimeSeconds, TimeUnit.SECONDS);
        }
    
        @Override
        public boolean releaseLock(String lockKey, String requestId) {
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            return (Boolean) this.redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Collections.singletonList(lockKey), new Object[]{requestId});
    
        }

    getLock

    在获得锁的时候,opsForValue().setIfAbsent 用到了redis的 setnx特性,这是分布式锁的关键一步,

    为了保证锁不被一直占用,要有时间的限制,获取锁同时给了失效时间,保证了原子性。

    releaseLock

    释放的同时,也要保证原子性,这里用了lua脚本。

    3 redis 锁工具类

     因为锁是一个经常用到的东西,所以为了方便他人的使用,需要改造成工具类。

     我们的redis 是一个service     tools是一个class  这时不能直接注入。

    需要用springUtil 

    
    
    package com.zhouqiang.demo.tools;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    /**
     * @author :zhouqiang
     * @date :2021/8/11 17:32
     * @description:
     * @version: $
     */
    @Component
    public class SpringUtil implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        public SpringUtil() {
        }
    
        @Override
        public void setApplicationContext(ApplicationContext arg0) throws BeansException {
            applicationContext = arg0;
        }
    
        public static <T> T getBean(String id, Class<T> type) {
            return applicationContext.getBean(id, type);
        }
    
        public static <T> T getBean(String id, Object params) {
            return (T) applicationContext.getBean(id, new Object[]{params});
        }
    
        public static <T> T getBean(Class<T> type) {
            return applicationContext.getBean(type);
        }
    
        public static <T> T getBeanByParams(Class<T> type, Object... objects) {
            return applicationContext.getBean(type, objects);
        }
    }
    
    

    这时候,就可以写出完整的  Locl 锁工具类,放在公共的地方,方便其他同事调用。

    
    
    package com.zhouqiang.demo.tools;
    
    import com.zhouqiang.demo.enmu.RedisEnum;
    import com.zhouqiang.demo.service.redis.IRedisService;
    import lombok.Getter;
    
    import javax.annotation.Resource;
    import java.util.UUID;
    
    /**
     * @author :zhouqiang
     * @date :2021/8/11 15:58
     * @description:redsi缓存锁
     * @version: $
     */
    public class Lock {
    
    
        private String key;
        private String requestId;
        private long second;
    
        private Lock(String key, String requestId, long second) {
    
            if (second <= 0) {
                throw new RuntimeException("超时时间必须大于0");
            }
    
            this.key = key;
            this.requestId = requestId;
            this.second = second;
        }
    
    
        private IRedisService getRedisLockService() {
            return SpringUtil.getBean(IRedisService.class);
        }
    
        /**
         * 写一个单例
         *
         * @param redisEnum
         * @param keys
         * @return
         */
        public static Lock getInstance(RedisEnum redisEnum, Object... keys) {
    
            return new Lock(redisEnum.getKey(), UUID.randomUUID().toString(), redisEnum.getExpiredTime());
        }
    
    
        /**
         * 得到锁
         */
        public boolean getLock() {
            return getRedisLockService().getLock(key, requestId, second);
        }
    
        /**
         * 释放锁
         *
         * @return
         */
        public boolean releaseLock() {
            return getRedisLockService().releaseLock(key, requestId);
        }
    
    }
    
    
    
    既然写了工具类,方便调用。缓存的KEY,也需要规范使用。

    还要redis枚举。

    
    
    package com.zhouqiang.demo.enmu;
    
    import lombok.Getter;
    
    /**
     * @author :zhouqiang
     * @date :2021/8/11 16:27
     * @description:缓存枚举
     * @version: $
     */
    @Getter
    public enum  RedisEnum {
        /**
         * 商品锁
         */
        SHOP_CLOCK("SHOP_CLOCK", 10L,"商品锁");
    
    
        /**
         * redis的key
         */
        private final String key;
    
        /**
         * 键的过期时间,单位为秒,有效期默认为20秒
         */
        private final Long expiredTime;
    
        /**
         * key的描述
         */
        private final String desc;
    
        RedisEnum(String key, Long expiredTime, String desc) {
            this.key = key;
            this.desc = desc;
            this.expiredTime = expiredTime;
        }
    
    
        public static RedisEnum getByKey(String key) {
            for (RedisEnum value : values()) {
                if (value.getKey() == key) {
                    return value;
                }
            }
            return null;
        }
    }
    
    
    

    这样一套下来,基本上redis锁是完成了。

    4 业务

    
    

     随便写一个业务接口。

    然后写实现类。

    package com.zhouqiang.demo.service.redisDemo.impl;
    
    import com.zhouqiang.demo.enmu.RedisEnum;
    import com.zhouqiang.demo.enmu.ResultCodeEnum;
    import com.zhouqiang.demo.entity.ResultCode;
    import com.zhouqiang.demo.service.redis.IRedisService;
    import com.zhouqiang.demo.service.redisDemo.RedisDemoService;
    import com.zhouqiang.demo.tools.Lock;
    import org.redisson.Redisson;
    
    import javax.annotation.Resource;
    import java.util.UUID;
    
    /**
     * @author :zhouqiang
     * @date :2021/8/12 10:51
     * @description:
     * @version: $
     */
    public class RedisDemoServiceImpl implements RedisDemoService {
    
        @Resource
        private IRedisService iRedisService;
    
        @Resource
        private Redisson redisson;
    
        @Override
        public String redisDemoTest() {
    
            String id = UUID.randomUUID().toString();
            //防止操作的事务发生异常造成堵塞  最后都会释放锁 不影响别人操作
            Object redis = null;
            try {
                //得到锁 5秒失效期
                boolean lock = Lock.getInstance(RedisEnum.SHOP_CLOCK, id, 5).getLock();
                if (lock == false) {
                    return ResultCodeEnum.LOCK_FAIL.getMsg();
                }
                //要操作的事务(举例)
                iRedisService.setValue("redis", id);
                redis = iRedisService.getValue("redis");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //释放锁  万一超时 锁自己没了  不能误删别人的正在进行的锁
                Lock.getInstance(RedisEnum.SHOP_CLOCK, id).releaseLock();
            }
    
            if(redis==null){
                return ResultCodeEnum.MESSAGE_FAIL.getMsg();
            }
            return redis.toString();
        }
    }

    这里对分布式锁的考虑是比较多的,需要注意很多事项。

    1 异常的捕捉

    2 缓存的时间

    3 锁的释放

    这里我还加了返回的枚举,ResultCodeEnum 也做了标准化。

    package com.zhouqiang.demo.enmu;
    
    import com.zhouqiang.demo.entity.Result;
    import lombok.Getter;
    
    import javax.ws.rs.GET;
    
    /**
     * @author :zhouqiang
     * @date :2021/8/12 10:34
     * @description:
     * @version: $
     */
    @Getter
    public enum ResultCodeEnum  {
        LOCK_SUCCESS(0, "获取锁成功!"),
        LOCK_FAIL(-1, "获取锁失败!"),
        MESSAGE_FAIL(-1, "消息失败!"),
        ;
        private int code;
        private String msg;
    
         ResultCodeEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    }

    5  测试

     最后写一个接口测试。这里的返回体用了result封装。

    result 封装在这里。

    封装了这些,是为了标准化一点。也为了以后好维护。


    到此,Redis 分布式锁的就这样了。

    BUT redsi 自己封装的一个redisson,更加的好用。

    6   redission分布式锁

    Redisson 支持单点模式、主从模式、哨兵模式、集群模式 非常的强大啊。

    redisson这个框架重度依赖了Lua脚本和Netty,代码很牛逼,各种Future及FutureListener的异步、同步操作转换

    实现起来也是非常的容易。

    引依赖

    这里还需要配置,选择模式等等,下次在做展示。

     <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.4.3</version>
            </dependency>

    在业务层只需要几步,

     这样就省略了我们1-4 所有的操作。简直就是泪目。

    至于redison 有空再去看看源码。到此结束!

  • 相关阅读:
    玩转Android之手摸手教你DIY一个抢红包神器!
    NetWork——关于TCP协议的三次握手和四次挥手
    请保持心情快乐,请保持情绪稳定
    第八节:Task的各类Task<TResult>返回值以及通用线程的异常处理方案。
    第七节:利用CancellationTokenSource实现任务取消和利用CancellationToken类检测取消异常。
    第六节:深入研究Task实例方法ContinueWith的参数TaskContinuationOptions
    第五节:Task构造函数之TaskCreationOptions枚举处理父子线程之间的关系。
    第四节:Task的启动的四种方式以及Task、TaskFactory的线程等待和线程延续的解决方案
    第三节:ThreadPool的线程开启、线程等待、线程池的设置、定时功能
    第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。
  • 原文地址:https://www.cnblogs.com/zq1003/p/15132127.html
Copyright © 2011-2022 走看看