如何使用注解方式实现 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通过6379端口无法连接服务器(redis-server.exe闪退)
May 08 Redis
redis客户端实现高可用读写分离的方式详解
Jul 04 Redis
浅谈Redis位图(Bitmap)及Redis二进制中的问题
Jul 15 Redis
浅谈redis整数集为什么不能降级
Jul 25 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
Redis入门教程详解
Aug 30 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Mar 16 Redis
Redis监控工具RedisInsight安装与使用
Mar 21 Redis
Redis 异步机制
May 15 Redis
redis lua限流算法实现示例
Jul 15 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
新的一年,新的期待:DC在2020年的四部动画电影
2020/01/01 欧美动漫
php,不用COM,生成excel文件
2006/10/09 PHP
关于Iframe如何跨域访问Cookie和Session的解决方法
2013/04/15 PHP
php通过隐藏表单控件获取到前两个页面的url
2014/09/09 PHP
php读取目录及子目录下所有文件名的方法
2014/10/20 PHP
php实现多维数组排序的方法示例
2017/03/23 PHP
php设计模式之单例模式用法经典示例分析
2019/09/20 PHP
JavaScript ECMA-262-3 深入解析.第三章.this
2011/09/28 Javascript
js关闭当前页面(窗口)的几种方式总结
2013/03/05 Javascript
jquery仿京东导航/仿淘宝商城左侧分类导航下拉菜单效果
2013/04/24 Javascript
Js(JavaScript)中,弹出是或否的选择框示例(confirm用法的实例分析)
2013/07/09 Javascript
Jquery判断radio、selelct、checkbox是否选中及获取选中值方法总结
2015/04/15 Javascript
jQuery插件zepto.js简单实现tab切换
2015/06/16 Javascript
js实现头像图片切割缩放及无刷新上传图片的方法
2015/07/17 Javascript
js print打印网页指定区域内容的简单实例
2016/11/01 Javascript
详解在express站点中使用ejs模板引擎
2017/09/21 Javascript
vue仿淘宝订单状态的tab切换效果
2020/06/23 Javascript
vue父组件给子组件的组件传值provide inject的方法
2019/10/23 Javascript
Vue SSR 即时编译技术的实现
2020/05/06 Javascript
js实现飞机大战小游戏
2020/08/26 Javascript
[00:42]《辉夜杯》—职业组预选赛12月3日15点 正式打响
2015/12/03 DOTA
python将图片文件转换成base64编码的方法
2015/03/14 Python
Python Flask基础教程示例代码
2018/02/07 Python
详解python中的装饰器
2018/07/10 Python
Python简直是万能的,这5大主要用途你一定要知道!(推荐)
2019/04/03 Python
python pymysql链接数据库查询结果转为Dataframe实例
2020/06/05 Python
python3从网络摄像机解析mjpeg http流的示例
2020/11/13 Python
Peter Alexander新西兰站:澳大利亚领先的睡衣设计师品牌
2016/12/10 全球购物
美国转售二手商品的电子商务平台:BLINQ
2018/12/13 全球购物
Eton丹麦官网:精美的男式衬衫
2020/05/27 全球购物
毕业生自荐信
2013/12/14 职场文书
打造完美自荐信
2014/01/24 职场文书
学校开除通知书
2015/04/25 职场文书
2019邀请函格式及范文
2019/05/20 职场文书
家长必看:义务教育,不得以面试 评测等名义选拔学生
2019/07/09 职场文书
2021-4-5课程——SQL Server查询【3】
2021/04/05 SQL Server