如何使用注解方式实现 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 查看所有的key方式
May 07 Redis
Java Socket实现Redis客户端的详细说明
May 26 Redis
详解Redis集群搭建的三种方式
May 31 Redis
SpringBoot 集成Redis 过程
Jun 02 Redis
浅谈Redis中的RDB快照
Jun 29 Redis
redis中lua脚本使用教程
Nov 01 Redis
Redis 持久化 RDB 与 AOF的执行过程
Nov 07 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
浅谈Redis的事件驱动模型
May 30 Redis
Redis Lua脚本实现ip限流示例
Jul 15 Redis
Redis过期数据是否会被立马删除
Jul 23 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
php编写一个简单的路由类
2011/04/13 PHP
深入理解curl类,可用于模拟get,post和curl下载
2013/06/08 PHP
PHP图片处理之使用imagecopy函数添加图片水印实例
2014/11/19 PHP
分享php代码将360浏览器导出的favdb的sqlite数据库文件转换为html
2015/12/09 PHP
PHP打印输出函数汇总
2016/08/28 PHP
php制作基于xml的RSS订阅源功能示例
2017/02/08 PHP
thinkphp框架无限级栏目的排序功能实现方法示例
2020/03/29 PHP
php自动加载代码实例详解
2021/02/26 PHP
JavaScript 事件查询综合
2009/07/13 Javascript
三种取消选中单选框radio的方法
2014/09/09 Javascript
JS+DIV实现鼠标划过切换层效果的方法
2015/05/25 Javascript
简单三步实现报表页面集成天气
2016/12/15 Javascript
vue.js获取数据库数据实例代码
2017/05/26 Javascript
简单实现js进度条加载效果
2020/03/25 Javascript
Vue-Router进阶之滚动行为详解
2017/09/13 Javascript
JavaScript设计模式之建造者模式实例教程
2018/07/02 Javascript
Vue常用指令详解分析
2018/08/19 Javascript
浅谈Vue数据响应
2018/11/05 Javascript
不刷新网页就能链接新的js文件方法总结
2020/03/01 Javascript
JavaScript缺少insertAfter解决方案
2020/07/03 Javascript
微信小程序学习之自定义滚动弹窗
2020/12/20 Javascript
pycharm修改界面主题颜色的方法
2019/01/17 Python
Django之使用celery和NGINX生成静态页面实现性能优化
2019/10/08 Python
IDLE下Python文件编辑和运行操作
2020/04/25 Python
python批量处理多DNS多域名的nslookup解析实现
2020/06/28 Python
基于django和dropzone.js实现上传文件
2020/11/24 Python
Python 按比例获取样本数据或执行任务的实现代码
2020/12/03 Python
一款html5 canvas实现的图片玻璃碎片特效
2014/09/11 HTML / CSS
品恩科技软件测试面试题
2014/10/26 面试题
八年级音乐教学反思
2014/01/09 职场文书
计算机数据库专业职业生涯规划书
2014/02/08 职场文书
物业保安员岗位职责
2014/03/14 职场文书
校庆接待方案
2014/03/18 职场文书
体育系毕业生求职自荐信
2014/04/16 职场文书
合作经营协议书范本
2014/09/16 职场文书
2014幼儿园家长工作总结
2014/11/10 职场文书