SpringBoot 整合Redisson

SpringBoot整合Redisson

网站地址,讲解源码的

网站地址,配置文件的

1. 引入依赖
     <!-- redisson config -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.16.8</version>
        </dependency>

1.1 配置redisson配置文件

server:
  port: 8080
spring:
    # Redisson配置 spring.redisson.singleServerConfig
    redisson:
      config:
        singleServerConfig:
          # 连接空闲超时,单位:毫秒
          idleConnectionTimeout: 100000
          # 连接超时,单位:毫秒
          connectTimeout: 10000
          # 命令等待超时,单位:毫秒
          timeout: 3000
          # 命令失败重试次数
          retryAttempts: 3
          # 命令重试发送时间间隔,单位:毫秒
          retryInterval: 1500
          # 密码
          password: ${spring.redis.password}
          # 单个连接最大订阅数量
          subscriptionsPerConnection: 5
          # 客户端名称
          clientName: null
          # 节点地址
          address: redis://${spring.redis.host}:${spring.redis.port}
          # 发布和订阅连接的最小空闲连接数
          subscriptionConnectionMinimumIdleSize: 1
          # 发布和订阅连接池大小
          subscriptionConnectionPoolSize: 50
          # 最小空闲连接数
          connectionMinimumIdleSize: 10
          # 连接池大小
          connectionPoolSize: 64
          # redis数据库编号
          database: ${spring.redis.database}
          # DNS监测时间间隔,单位:毫秒
          dnsMonitoringInterval: 5000
        # 线程池数量
        threads: 0
        # Netty线程池数量
        nettyThreads: 0
        # 编码
        codec:
          class: "org.redisson.codec.JsonJacksonCodec"
        # 传输模式
        transportMode: "NIO"
        # 配置看门狗的默认 超时 时间为30s,这里改为10s
        # 因为看门狗会去修改 锁的超时时间,所以要设置一个链接redis的超时时间
        lockWatchdogTimeout: 10000

1.2 也可以用另一种方式初始化Redisson

@Configuration
public class RedissonConfig {
    
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        Config config = new Config();
		config.useSingleServer().setAddress("redis://127.0.0.1:6379");
		RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

使用


@Service
public class LockTestService{
    @Autowired
    RedissonClient redisson;
    
    public void testLock(){
        RLock lock = redisson.getLock("myLock");
        try {
            lock.lock();
            // 执行需要加锁的代码
        } finally {
            lock.unlock();
        }
    }
}
2. Redisson的使用

通过第一种方式注入到容器当中的使用方式

/**
 *Redisson工具类
 * @date 2023/11/02 10:29
 **/
@Component
public class RedissonUtil {
    @Resource
    private RedissonClient client;

    /**
     * 获取Redis锁
     * 基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。
     */
    public RLock getLock(String key) {
        return client.getLock(key);
    }

    /**
     * 获取Redis公平锁
     * 它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。
     */
    public RLock getFairLock(String key) {
        return client.getFairLock(key);
    }
}
public class TestRedissonController {
    private final RedissonUtil redissonUtil;

    public TestRedissonController(RedissonUtil redissonUtil) {
        this.redissonUtil = redissonUtil;
    }

    /**
     * 分布式锁伪代码
     * redisson在启用的时候,会默认开启看门狗策略,如果超出key过期时间,进程还没结束的话,看门狗会自动续费1/3的时间
     *
     * 看门狗机制 是当锁过期的时候,看门狗会查看执行业务的线程是否还存活,如果存活的话,就会对当前锁续期;
     * 如果业务线程一直存活,那么当前的分布式锁就不会失效
     *
     * 解析源码参考:https://blog.51cto.com/u_16213688/7540380
     */
    @PostMapping("/getLock")
    public void getLock(String key) {
        RLock lock = redissonUtil.getLock(key);
        try {
            log.info("尝试获取redison锁:{}",Thread.currentThread().getName());
            // 尝试加锁,最多等待5秒,上锁以后10秒自动解锁
            // 1.tryLock leaseTime 如果设置了过期时间,那么当超过这个时间的话,就会自动释放锁
            // 2.但是如果 不设置这个值的话默认超时时间是30秒,但是会触发看门狗机制,即:业务没有执行完成之前会一直续费
            boolean isOk = lock.tryLock(2, -1, TimeUnit.SECONDS);
            if (!isOk) {
                // 获取分布式锁失败,可以进行抛出获取锁失败的提示等,根据业务来
                log.info("获取分布式锁失败,线程名称:{}",Thread.currentThread().getName());
                return;
            }
            log.info("业务处理中~~~~,线程名称:{}",Thread.currentThread().getName());
            // 获取锁成功后,开始处理业务逻辑
            Thread.sleep(300000);
            log.info("业务执行完成!!!!");

        } catch (InterruptedException e) {
            // 如果10s没有执行完,锁会自动释放,这里抛出异常
            Thread.currentThread().interrupt();
        } finally {
            log.info("释放分布式锁~~~~,释放线程名称:{}",Thread.currentThread().getName());
            // 如果10s没有执行完,锁会自动释放,这里抛出异常,操作失败,需要对数据回滚
            // 需要判断是不是当前线程是否还持有锁,因为看门狗是可以对当前锁进行续时的,有可能应该不持有锁了
            if (lock.isLocked() && lock.isHeldByCurrentThread())
                lock.unlock();
        }
        System.out.println("eqweqeqweqwewqeqweqeq");
    }


}

注意*

当获取redisson锁的时候如果设置了过期时间(tryLock(long waitTime, long leaseTime, TimeUnit unit) 中的leaseTime 时间是过期时间),那么看门狗就无法续费了

源码地址org.redisson.RedissonLock#tryAcquireAsync

    private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Long> ttlRemainingFuture;
        if (leaseTime != -1) {
            ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
        CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
            // lock acquired
            if (ttlRemaining == null) {
                // 如果,设置了过期时间就不走,schedule 到期续订方法了,所以这块代码已经写死了,就无法续费了
                if (leaseTime != -1) {
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    // schedule 到期续订
                    scheduleExpirationRenewal(threadId);
                }
            }
            return ttlRemaining;
        });
        return new CompletableFutureWrapper<>(f);
    }
// schedule 到期续订    
protected void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
        ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            try {
                // 续订到期 方法
                renewExpiration();
            } finally {
                if (Thread.currentThread().isInterrupted()) {
                    cancelExpirationRenewal(threadId);
                }
            }
        }
    }
// 续订到期 方法
private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                // 刷新等待时间
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.whenComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
                    
                    if (res) {
                        // 重新安排自身,递归调用自身为锁自动续费
                        renewExpiration();
                    } else {
                        cancelExpirationRenewal(null);
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
    }
// 刷新等待时间
// internalLockLeaseTime 这个时间是30s
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
    return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                          "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return 1; " +
                          "end; " +
                          "return 0;",
                          Collections.singletonList(getName()),
                          internalLockLeaseTime, getLockName(threadId));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值