springboot项目redis分布式锁实现(基于luttuce)

问题背景

对于大部分的后端服务,服务很多都是多实例部署的,而在我们的工程中,比如说代码中定义的定时任务需要从数据库中捞数据,那么多机部署上的每个实例都会执行,那么就会存在数据重复上报,那么就不可避免得出现脏数据,影响数据的准确性。

问题分析

解决这个问题最直接的思路就是,当多机部署中无论哪个实例捞到了某条数据,那么其他的实例就不能再次捞取。针对这个思路,我们大致可以有以下两个解决方案:

1. 在数据库层解决。给访问的数据表中,增一个字段flag,标识是否已经上报过,每个实例上报一条,就更新一下这个状态,后面的实例再读到这一条时,发现上报过了,就不处理了。这就要改一下现在的读取方式,需要从数据库中逐条读取,避免脏数据。
2. 引入分布式锁,最简单的,就用redis实现,每个实例开始定时任务前,先尝试在redis中获取锁,如果获取得到,这个实例的定时任务就执行,否则就跳过。

对于第一个解决方案的实现本文不予说明,这种实现方式虽然可行,但是对于频繁更新数据库的操作我个人是非常不推荐的,性能方面可能会受到影响。本文主要想介绍一下方案2的实现。

方案实现

对于多机部署的问题,自然而然就可以想到分布式锁来保证任务执行的准确性。在Springboot项目中,最直接就是通过redis实现,我们知道,从Springboot 2.X开始,底层的redis客户端从Jedis换成了luttuce,对于低版本的Jedis的一些API可能无法兼容了,建议直接基于luttuce进行开发。

1、首先要做的就是需要在pom文件中加入你所需要的redis依赖,注意,在springboot 2.x之前,一般依赖的是spring-boot-starter-redis,在2.x,建议依赖spring-boot-starter-data-redis。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

pom文件中的依赖搞完后,还需要在application.properties中配置redis相关配置,如host,port,password等信息。这里我就不展开说了。

2、创建分布式锁

/**
 * redis分布式锁
 *
 */
@Slf4j
@Component
public class RedisDistributedLock {
    /**
     * 锁定时长(单位:秒)
     */
    public static final long LOCK_TIME = 60 * 60L;

    /**
     * 释放锁脚本
     */
    private static final String UNLOCK_LUA;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    /**
     * redis set操作
     *
     * @param key
     * @param expire
     * @return 返回结果
     */
    private boolean setRedis(String key, long expire) {
        RedisCallback<Boolean> callback = connection -> connection.set(key.getBytes(StandardCharsets.UTF_8),
                key.getBytes(StandardCharsets.UTF_8), Expiration.seconds(expire),
                    RedisStringCommands.SetOption.SET_IF_ABSENT);
        return redisTemplate.execute(callback);
    }

    /**
     * redis加锁操作
     *
     * @param key key
     * @param expire 过期时间
     * @return 返回结果
     */
    public boolean lock(String key, long expire) {
        log.info("Get lock '{}'.", key);
        return this.setRedis(key, expire);
    }

    /**
     * redis释放锁
     *
     * @param lockKey key
     * @return 返回结果
     */
    public boolean releaseLock(String lockKey) {
        log.info("release lock '{}'", lockKey);
        RedisCallback<Boolean> callback = connection -> connection.eval(UNLOCK_LUA.getBytes(StandardCharsets.UTF_8),
                ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8),
                lockKey.getBytes(StandardCharsets.UTF_8));
        return redisTemplate.execute(callback);
    }
}

3、为了信息搜集更为方便,可以创建分布式锁的注解,并创建对应的切面

Lock注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {
    /**
     * redis库中标识符key
     *
     * @return value
     */
    String value() default "";
}

Lock信息解析切面Aspect,在创建切面之前需在pom中加入aop的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Slf4j
@Aspect
@Component
public class LockParseAspect {
    @Autowired
    private RedisDistributedLock redisLock;

    /**
     * 切面拦截@Lock注解并进行加锁
     *
     * @param joinPoint 切入点
     * @param lock lock注解
     * @return 返回结果
     * @throws Throwable 异常
     */
    @Around(value = "@annotation(com.xxx.model.Lock) && @annotation(lock))",argNames = "joinPoint, lock")
    public Object proceed(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
        Object result = null;
        if (StringUtils.hasText(lock.value())) {
            if (redisLock.lock(lock.value(), RedisDistributedLock.LOCK_TIME)) {
                try {
                    result = joinPoint.proceed();
                } finally {
                    redisLock.releaseLock(lock.value());
                }
            } else {
                log.info("Lock failed, maybe another instance is calculating data.");
            }
        }
        return result;
    }
}

说明:第3个步骤是可选的,只是为了在加锁和释放锁的时候信息能搜集得更多。

分布式锁的使用

上面准备工作做完了,分布式锁即可用起来了,使用的方法很简单,只需要在需要进行加锁的方法上加上Lock的注解,例如@Lock(key)。

测试的方法也很简单,在你IDE中或者机器上把工程启动2次(在不同的端口启动,例如8080和8081),将你想测试的方法做定时执行处理(cron表达式设置到你测试的时间即可),观察IDE的打印台上log打印,加锁和解锁正常的情况下,会打印一个端口的服务获取到锁并执行方法内的代码逻辑并在执行完后释放锁,而另一个端口会打印获取锁失败“Lock failed, maybe another instance is calculating data.”日志。说明一个获取到锁,而另一个并未获取到。也就达到了分布式锁预期的效果。

 

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
回答: 在Spring Boot中集成Redis分布式锁可以使用Redisson来实现Redisson是一个基于Redis的分布式对象和服务框架,内部已经实现Redis分布式锁,使用起来更加方便和稳定。通过Redisson,可以使用RedLock算法来实现分布式锁,确保锁的正确性和可靠性。在Redis集群环境下,可能存在锁失效或死锁的问题,但使用Redisson的分布式锁可以解决这个问题。此外,分布式锁是为了解决分布式系统中控制共享资源访问的问题,因为在分布式系统中,多线程、多进程分布在不同机器上,传统的并发控制锁策略失效。因此,通过集成Redis分布式锁,可以有效地控制共享资源的访问。 #### 引用[.reference_title] - *1* *2* [springboot集成redis 分布式锁(redistemplate,lua,redisson)](https://blog.csdn.net/jun2571/article/details/130382023)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Springboot集成Redis——实现分布式锁](https://blog.csdn.net/tang_seven/article/details/126769580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值