如何使用注解方式实现 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 相关文章推荐
在K8s上部署Redis集群的方法步骤
Apr 27 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 Redis
Java Socket实现Redis客户端的详细说明
May 26 Redis
5分钟教你docker安装启动redis全教程(全新方式)
May 29 Redis
比较几种Redis集群方案
Jun 21 Redis
你真的了解redis为什么要提供pipeline功能
Jun 22 Redis
k8s部署redis cluster集群的实现
Jun 24 Redis
浅谈Redis中的RDB快照
Jun 29 Redis
基于Redis结合SpringBoot的秒杀案例详解
Oct 05 Redis
Redis之RedisTemplate配置方式(序列和反序列化)
Mar 13 Redis
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
Mar 16 Redis
基于Redis6.2.6版本部署Redis Cluster集群的问题
Apr 01 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
肝肠寸断了解下!盘点史上最伤心的十大动漫
2020/03/04 日漫
收集的DedeCMS一些使用经验
2007/03/17 PHP
PHP获取MAC地址的具体实例
2013/12/13 PHP
PHP中静态变量的使用方法实例分析
2016/12/01 PHP
Laravel Eloquent分表方法并使用模型关联的实现
2019/11/25 PHP
Javascript 按位与赋值运算符 (&amp;=)使用介绍
2014/02/04 Javascript
js定时调用方法成功后并停止调用示例
2014/04/08 Javascript
jQuery事件用法实例汇总
2014/08/29 Javascript
dedecms页面如何获取会员状态的实例代码
2016/03/15 Javascript
jQuery 移动端拖拽(模块化开发,触摸事件,webpack)
2016/10/28 Javascript
Vue.js -- 过滤器使用总结
2017/02/18 Javascript
ES6中Generator与异步操作实例分析
2017/03/31 Javascript
vue-resource调用promise取数据方式详解
2017/07/21 Javascript
基于vue组件实现猜数字游戏
2020/05/28 Javascript
AngularJS集合数据遍历显示的实例
2017/12/27 Javascript
基于express中路由规则及获取请求参数的方法
2018/03/12 Javascript
[58:42]DOTA2上海特级锦标赛C组败者赛 Newbee VS Archon第一局
2016/02/27 DOTA
[52:41]OG vs IG 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/20 DOTA
解决pycharm运行程序出现卡住scanning files to index索引的问题
2019/06/27 Python
python对XML文件的操作实现代码
2020/03/27 Python
如何使用python写截屏小工具
2020/09/29 Python
用HTML5制作一个简单的桌球游戏的教程
2015/05/12 HTML / CSS
来自世界各地的饮料:Flavourly
2019/05/06 全球购物
PHP使用Redis队列执行定时任务实例讲解
2021/03/24 PHP
一名女生的自荐信
2013/12/08 职场文书
《逃家小兔》教学反思
2014/02/23 职场文书
促销活动总结报告
2014/04/26 职场文书
一份没有按时交货失信于客户的检讨书
2014/09/19 职场文书
检讨书怎么写
2015/05/07 职场文书
计划生育目标责任书
2015/05/09 职场文书
二十年同学聚会致辞
2015/07/28 职场文书
安全教育主题班会总结
2015/08/14 职场文书
经典人生语录分享:不畏将来,不念过去,笑对当下
2019/12/12 职场文书
MySQL CHAR和VARCHAR该如何选择
2021/05/31 MySQL
Python内置的数据类型及使用方法
2022/04/13 Python
mysql 排序失效
2022/05/20 MySQL