如何使用注解方式实现 Redis 分布式锁


Posted in Redis onJuly 23, 2022

引入 Redisson

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.14.1</version>
</dependency>

初始化 Redisson

@Configuration
public class RedissonConfiguration {
  
    // 此处更换自己的 Redis 地址即可
    @Value("${redis.addr}")
    private String addr;

    @Bean
    public RedissonClient redisson() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress(String.format("%s%s", "redis://", addr))
                .setConnectionPoolSize(64)              // 连接池大小
                .setConnectionMinimumIdleSize(8)        // 保持最小连接数
                .setConnectTimeout(1500)                // 建立连接超时时间
                .setTimeout(2000)                       // 执行命令的超时时间, 从命令发送成功时开始计时
                .setRetryAttempts(2)                    // 命令执行失败重试次数
                .setRetryInterval(1000);                // 命令重试发送时间间隔

        return Redisson.create(config);
    }
}

这样我们就可以在项目里面使用 Redisson 了。

编写 Redisson 分布式锁工具类

Redis 分布式锁的工具类,主要是调用 Redisson 客户端实现,做了轻微的封装。

@Service
@Slf4j
public class LockManager {
    /**
     * 最小锁等待时间
     */
    private static final int MIN_WAIT_TIME = 10;

    @Resource
    private RedissonClient redisson;

    /**
     * 加锁,加锁失败抛默认异常 - 操作频繁, 请稍后再试
     *
     * @param key        加锁唯一key
     * @param expireTime 锁超时时间 毫秒
     * @param waitTime   加锁最长等待时间 毫秒
     * @return LockResult  加锁结果
     */
    public LockResult lock(String key, long expireTime, long waitTime) {
        return lock(key, expireTime, waitTime, () -> new BizException(ResponseEnum.COMMON_FREQUENT_OPERATION_ERROR));
    }
    /**
     * 加锁,加锁失败抛异常 - 自定义异常
     *
     * @param key               加锁唯一key
     * @param expireTime        锁超时时间 毫秒
     * @param waitTime          加锁最长等待时间 毫秒
     * @param exceptionSupplier 加锁失败时抛该异常,传null时加锁失败不抛异常
     * @return LockResult  加锁结果
     */
    private LockResult lock(String key, long expireTime, long waitTime, Supplier<BizException> exceptionSupplier) {
        if (waitTime < MIN_WAIT_TIME) {
            waitTime = MIN_WAIT_TIME;
        }
        LockResult result = new LockResult();
        try {
            RLock rLock = redisson.getLock(key);
            try {
                if (rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS)) {
                    result.setLockResultStatus(LockResultStatus.SUCCESS);
                    result.setRLock(rLock);
                } else {
                    result.setLockResultStatus(LockResultStatus.FAILURE);
                }
            } catch (InterruptedException e) {
                log.error("Redis 获取分布式锁失败, key: {}, e: {}", key, e.getMessage());
                result.setLockResultStatus(LockResultStatus.EXCEPTION);
                rLock.unlock();
            }
        } catch (Exception e) {
            log.error("Redis 获取分布式锁失败, key: {}, e: {}", key, e.getMessage());
            result.setLockResultStatus(LockResultStatus.EXCEPTION);
        }

        if (exceptionSupplier != null && LockResultStatus.FAILURE.equals(result.getLockResultStatus())) {
            log.warn("Redis 加锁失败, key: {}", key);
            throw exceptionSupplier.get();
        }

        log.info("Redis 加锁结果:{}, key: {}", result.getLockResultStatus(), key);

        return result;
    }
    /**
     * 解锁
     */
    public void unlock(RLock rLock) {
        try {
            rLock.unlock();
        } catch (Exception e) {
            log.warn("Redis 解锁失败", e);
        }
    }
}

加锁结果状态枚举类。

public enum LockResultStatus {
    /**
     * 通信正常,并且加锁成功
     */
    SUCCESS,
    /**
     * 通信正常,但获取锁失败
     */
    FAILURE,
    /**
     * 通信异常和内部异常,锁状态未知
     */
    EXCEPTION;
}

加锁结果类封装了加锁状态和RLock。

@Setter
@Getter
public class LockResult {

    private LockResultStatus lockResultStatus;

    private RLock rLock;
}

自此我们就可以使用分布式锁了,使用方式:

@Service
@Slf4j
public class TestService {

    @Resource
    private LockManager lockManager;

    public String test(String userId) {
        // 锁:userId, 锁超时时间:5s, 锁等待时间:50ms
        LockResult lockResult = lockManager.lock(userId, 5000, 50);

        try {
            //  业务代码
        } finally {
            lockManager.unlock(lockResult.getRLock());
        }

        return "";
    }
}

为了防止程序发生异常,所以每次我们都需要在finally代码块里手动释放锁。为了更方便优雅的使用 Redis 分布式锁,我们使用注解方式实现下。

声明注解 @Lock

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
    /**
     * lock key
     */
    String value();

    /**
     * 锁超时时间,默认5000ms
     */
    long expireTime() default 5000L;

    /**
     * 锁等待时间,默认50ms
     */
    long waitTime() default 50L;

}

注解解析类

@Aspect
@Component
@Slf4j
public class LockAnnotationParser {

    @Resource
    private LockManager lockManager;

    /**
     * 定义切点
     */
    @Pointcut(value = "@annotation(Lock)")
    private void cutMethod() {
    }
		
    /**
     * 切点逻辑具体实现
     */
    @Around(value = "cutMethod() && @annotation(lock)")
    public Object parser(ProceedingJoinPoint point, Lock lock) throws Throwable {
        String value = lock.value();
        if (isEl(value)) {
            value = getByEl(value, point);
        }
        LockResult lockResult = lockManager.lock(getRealLockKey(value), lock.expireTime(), lock.waitTime());
        try {
            return point.proceed();
        } finally {
            lockManager.unlock(lockResult.getRLock());
        }
    }

    /**
     * 解析 SpEL 表达式并返回其值
     */
    private String getByEl(String el, ProceedingJoinPoint point) {
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        String[] paramNames = getParameterNames(method);
        Object[] arguments = point.getArgs();
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(el);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < arguments.length; i++) {
            context.setVariable(paramNames[i], arguments[i]);
        }
        return expression.getValue(context, String.class);
    }
    /**
     * 获取方法参数名列表
     */
    private String[] getParameterNames(Method method) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        return u.getParameterNames(method);
    }
    private boolean isEl(String str) {
        return str.contains("#");
    }
    /**
     * 锁键值
     */
    private String getRealLockKey(String value) {
        return String.format("lock:%s", value);
    }
}

下面使用注解方式使用分布式锁:

@Service
@Slf4j
public class TestService {
    @Lock("'test_'+#user.userId")
    public String test(User user) {
        // 业务代码
        return "";
    }
}

当然也可以自定义锁的超时时间和等待时间

@Service
@Slf4j
public class TestService {
    @Lock(value = "'test_'+#user.userId", expireTime = 3000, waitTime = 30)
    public String test(User user) {
        // 业务代码
        return "";
    }
}

到此这篇关于如何使用注解方式实现 Redis 分布式锁的文章就介绍到这了,更多相关Redis 分布式锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Redis 相关文章推荐
浅谈redis五大数据结构和使用场景
Apr 12 Redis
Redis缓存-序列化对象存储乱码问题的解决
Jun 21 Redis
使用Redis实现实时排行榜功能
Jul 02 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
Redis字典实现、Hash键冲突及渐进式rehash详解
Sep 04 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
Redis高可用集群redis-cluster详解
Mar 20 Redis
 Redis 串行生成顺序编码的方法实现
Apr 03 Redis
Redis 异步机制
May 15 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
Redis唯一ID生成器的实现
Jul 07 Redis
redis lua限流算法实现示例
Redis Lua脚本实现ip限流示例
Jul 15 #Redis
redis protocol通信协议及使用详解
Jul 15 #Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 #Redis
Redis唯一ID生成器的实现
Jul 07 #Redis
Redis+AOP+自定义注解实现限流
Jun 28 #Redis
利用Redis实现点赞功能的示例代码
Jun 28 #Redis
You might like
JS JavaScript获取Url参数,src属性参数
2021/03/09 Javascript
jQuery lazyload 的重复加载错误以及修复方法
2010/11/19 Javascript
Extjs中TabPane如何嵌套在其他网页中实现思路及代码
2013/01/27 Javascript
js播放wav文件(源码)
2013/04/22 Javascript
jquery给图片添加鼠标经过时的边框效果
2013/11/12 Javascript
基于jQuery实现仿淘宝套餐选择插件
2015/03/04 Javascript
JQuery标签页效果实例详解
2015/12/24 Javascript
jQuery form 表单验证插件(fieldValue)校验表单
2016/01/24 Javascript
JavaScript数组去重的两种方法推荐
2016/04/05 Javascript
深入浅析JavaScript中的constructor
2016/04/19 Javascript
AngularJS基础 ng-hide 指令用法及示例代码
2016/08/01 Javascript
BootStrap无限级分类(无限极分类封装版)
2016/08/26 Javascript
jQuery弹出遮罩层效果完整示例
2016/09/13 Javascript
AngularJS页面带参跳转及参数解析操作示例
2017/06/28 Javascript
加载 vue 远程代码的组件实例详解
2017/11/20 Javascript
vue计算属性和监听器实例解析
2018/05/10 Javascript
解决vue attr取不到属性值的问题
2018/09/18 Javascript
解决vue脚手架项目打包后路由视图不显示的问题
2018/09/20 Javascript
vue 解决路由只变化参数页面组件不更新问题
2019/11/05 Javascript
微信小程序云开发获取文件夹下所有文件(推荐)
2019/11/14 Javascript
封装Vue Element的table表格组件的示例详解
2020/08/19 Javascript
Python中音频处理库pydub的使用教程
2017/06/07 Python
python使用PyCharm进行远程开发和调试
2017/11/02 Python
pandas删除行删除列增加行增加列的实现
2019/07/06 Python
python画双y轴图像的示例代码
2019/07/07 Python
pycharm设置当前工作目录的操作(working directory)
2020/02/14 Python
美国最大的农村生活方式零售店:Tractor Supply Company(TSC)
2017/05/15 全球购物
澳大利亚领先的女帽及配饰公司:Morgan&Taylor
2019/12/01 全球购物
求∏的近似值,直到最后一项的绝对值小于指定的数
2016/02/12 面试题
浅谈react路由传参的几种方式
2021/03/23 Javascript
历史学专业推荐信
2013/11/06 职场文书
受欢迎的大学生自我评价
2013/12/05 职场文书
高中生家长会演讲稿
2014/01/14 职场文书
我的中国梦演讲稿500字
2014/08/19 职场文书
公司演讲稿开场白
2014/08/25 职场文书
普通党员个人对照检查材料
2014/09/18 职场文书