如何使用注解方式实现 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数据结构之链表与字典的使用
May 11 Redis
深入浅析Redis 集群伸缩原理
May 15 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
Redis可视化客户端小结
Jun 10 Redis
厉害!这是Redis可视化工具最全的横向评测
Jul 15 Redis
基于Redis的List实现特价商品列表功能
Aug 30 Redis
Redis字典实现、Hash键冲突及渐进式rehash详解
Sep 04 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
解决Redis启动警告问题
Feb 24 Redis
Redis批量生成数据的实现
Jun 05 Redis
使用Redis实现分布式锁的方法
Jun 16 Redis
Redis+AOP+自定义注解实现限流
Jun 28 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
VOLVO车载收音机
2021/03/02 无线电
PHP strtok()函数的优点分析
2010/03/02 PHP
ThinkPHP验证码使用简明教程
2014/03/05 PHP
PHP获取远程图片并保存到本地的方法
2015/05/12 PHP
详解WordPress中用于更新和获取用户选项数据的PHP函数
2016/03/08 PHP
手把手编写PHP框架 深入了解MVC运行流程
2016/09/19 PHP
Laravel5中防止XSS跨站攻击的方法
2016/10/10 PHP
laravel解决迁移文件一次删除创建字段报错的问题
2019/10/24 PHP
laravel框架select2多选插件初始化默认选中项操作示例
2020/02/18 PHP
window.dialogArguments 使用说明
2011/04/11 Javascript
javascript将url中的参数加密解密代码
2014/11/17 Javascript
分享28款免费实用的 JQuery 图片和内容滑块插件
2014/12/15 Javascript
jQuery常用数据处理方法小结
2015/02/20 Javascript
JS实现的N多简单无缝滚动代码(包含图文效果)
2015/11/06 Javascript
详解vue表单验证组件 v-verify-plugin
2017/04/19 Javascript
原生js FileReader对象实现图片上传本地预览效果
2020/03/27 Javascript
对Vue2 自定义全局指令Vue.directive和指令的生命周期介绍
2018/08/30 Javascript
Django渲染Markdown文章目录的方法示例
2019/01/02 Python
python hashlib加密实现代码
2019/10/17 Python
Python使用QQ邮箱发送邮件报错smtplib.SMTPAuthenticationError
2019/12/20 Python
python matplotlib中的subplot函数使用详解
2020/01/19 Python
python的reverse函数翻转结果为None的问题
2020/05/11 Python
Restful_framework视图组件代码实例解析
2020/11/17 Python
详解HTML5 data-* 自定义属性
2018/01/24 HTML / CSS
威尔逊皮革:Wilsons Leather
2018/12/07 全球购物
预订旅游活动、景点和旅游:GetYourGuide
2019/09/29 全球购物
教师专业自荐书范文
2014/02/10 职场文书
师德师风个人反思
2014/04/28 职场文书
社区清明节活动总结
2014/07/04 职场文书
2014年教师节国旗下讲话稿
2014/09/10 职场文书
个人纪律作风整改措施思想汇报
2014/10/12 职场文书
保研专家推荐信范文
2015/03/25 职场文书
2015年质量月活动总结报告
2015/03/27 职场文书
十一月早安语录:把心放轻,人生就是一朵自在的云
2019/11/04 职场文书
win11无法登录onedrive错误代码0x8004def7怎么办 ?
2022/04/05 数码科技
SpringBoot详解执行过程
2022/07/15 Java/Android