Redisson之Semaphore使用记录

本文介绍在Spring Boot项目中如何使用Redisson实现分布式限流,包括配置RedissonClient、使用信号量进行限流的具体步骤及源码解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在公司一个项目需要用到限流,就想到concurrent包下的Semaphore(信号量)。不过实际场景中并不能用,一个原因是项目采用的微服务架构,分布式场景并不适用。第二个场景是限流采用令牌桶算法,每秒钟某个接口限流多少,超过丢弃。

 

后面实现采用的redis分布式限流方案,每分钟设置一个计数器,计数器一旦达到限流次数,后面的请求被直接挡回。

 

后面无意中看到有Redisson这个东东,发现能在分布式场景下支持java concurrent下很多工具类(Reentrant Lock、Semaphore、CountDownLatch等 )。

 

就去学习了一波,试用一下Semaphore功能,就记录下使用方法。

在springBoot中引入Redisson

                <dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.4.3</version>
		</dependency>

然后配置RedissonClient的相关信息

通过配置类的方式

package com.sendi.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;

import io.netty.channel.nio.NioEventLoopGroup;

@Configuration
public class RedissonConfig {
	private String address = "redis://localhost:6379";
    private int connectionMinimumIdleSize = 10;
    private int idleConnectionTimeout = 10000;
    private int pingTimeout = 1000;
    private int connectTimeout = 10000;
    private int timeout = 3000;
    private int retryAttempts = 3;
    private int retryInterval = 1500;
    private int reconnectionTimeout = 3000;
    private int failedAttempts = 3;
    private String password = null;
    private int subscriptionsPerConnection = 5;
    private String clientName = null;
    private int subscriptionConnectionMinimumIdleSize = 1;
    private int subscriptionConnectionPoolSize = 50;
    private int connectionPoolSize = 64;
    private int database = 0;
    private boolean dnsMonitoring = false;
    private int dnsMonitoringInterval = 5000;

    private int thread; //当前处理核数量 * 2

    private String codec = "org.redisson.codec.JsonJacksonCodec";

    @Bean(destroyMethod = "shutdown")
    RedissonClient redisson() throws Exception {
        Config config = new Config();
        config.useSingleServer().setAddress(address)
                .setConnectionMinimumIdleSize(connectionMinimumIdleSize)
                .setConnectionPoolSize(connectionPoolSize)
                .setDatabase(database)
                .setDnsMonitoring(dnsMonitoring)
                .setDnsMonitoringInterval(dnsMonitoringInterval)
                .setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
                .setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
                .setSubscriptionsPerConnection(subscriptionsPerConnection)
                .setClientName(clientName)
                .setFailedAttempts(failedAttempts)
                .setRetryAttempts(retryAttempts)
                .setRetryInterval(retryInterval)
                .setReconnectionTimeout(reconnectionTimeout)
                .setTimeout(timeout)
                .setConnectTimeout(connectTimeout)
                .setIdleConnectionTimeout(idleConnectionTimeout)
                .setPingTimeout(pingTimeout)
                .setPassword(password);
        Codec codec = (Codec) ClassUtils.forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance();
        config.setCodec(codec);
        config.setThreads(thread);
        config.setEventLoopGroup(new NioEventLoopGroup());
        config.setUseLinuxNativeEpoll(false);
        return Redisson.create(config);
    }
}

配置完后就可以在java类中使用了

//注入
@Autowired
private RedissonClient redissonClient;




//使用
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
			semaphore.trySetPermits(1);//设置许可
			semaphore.acquire();//获得一个许可
			semaphore.release();//释放一个许可

使用方法可以在githup上面的api里面查看说明  https://github.com/redisson/redisson

和java有一点区别,不过基本使用方法是一样的,猜测实现原理还是根据redis键值对应计数方法实现,就去redis中看了一下

果然在redis中看到了"semaphore"这个键

消耗完许可后

查看源码发现其实现方式还是网上的分布式锁类似

 public void acquire(int permits) throws InterruptedException {
        if (tryAcquire(permits)) {
            return;
        }

        RFuture<RedissonLockEntry> future = subscribe();
        commandExecutor.syncSubscription(future);
        try {
            while (true) {
                if (tryAcquire(permits)) {
                    return;
                }

                getEntry().getLatch().acquire(permits);
            }
        } finally {
            unsubscribe(future);
        }
//        get(acquireAsync(permits));
    }
 @Override
    public RFuture<Boolean> tryAcquireAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits amount can't be negative");
        }

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                  "local value = redis.call('get', KEYS[1]); " +
                  "if (value ~= false and tonumber(value) >= tonumber(ARGV[1])) then " +
                      "local val = redis.call('decrby', KEYS[1], ARGV[1]); " +
                      "return 1; " +
                  "end; " +
                  "return 0;",
                  Collections.<Object>singletonList(getName()), permits);
    }

第一段代码是许可获取的源码,可以看出是通过while循环实现,直到拿到许可这个方法才返回,继续往下面代码执行

第二段代码是通过lua脚本执行原子操作(信号量许可获取和释放)

 

总结:发现Redisson底层实现方案和之前在githup上面看到的一些分布式锁,限流之类的基本大同小异。

 

### Redisson底层源码解析与工作原理详解 Redisson 是基于 Redis 的 Java 客户端,提供了许多高级功能,比如分布式锁、信号量等。以下是对其底层源码的工作原理及实现细节的深入分析。 #### 1. 分布式锁的核心机制 Redisson 实现分布式锁的方式主要依赖于 Redis 的原子操作特性。通过 `SETNX` 和过期时间设置来完成加锁逻辑[^3]。 当客户端尝试获取锁时,会执行如下命令: ```bash SET resource_name any_value NX PX timeout_in_milliseconds ``` 如果返回值为 `true`,表示成功获得锁;否则说明该资源已被其他线程占用。为了防止死锁问题,在设定超时期间后自动释放锁[^2]。 #### 2. Lock对象的具体实现 在 Redisson 中,`RLock` 接口定义了基本的操作方法,而实际的实现类则是 `RedissonRedLock` 或者其他的子类。这些类封装了具体的加解锁流程以及重试策略等功能。 下面是一个典型的加锁过程伪代码展示: ```java public void lock() { while (true) { try { boolean acquired = attemptToAcquireLock(); if (acquired) break; } catch (Exception e) { Thread.sleep(retryInterval); } } } private boolean attemptToAcquireLock() { String scriptText = "if(redis.call('setnx', KEYS[1], ARGV[1]) == 1) then redis.call('pexpire', KEYS[1], ARGV[2]); return 1; else return 0; end"; Object result = connection.eval(scriptText, Collections.singletonList(lockName), Arrays.asList(uuid, expirationTime)); return Boolean.TRUE.equals(result); } ``` 上述代码片段展示了如何利用 Lua 脚本来保证整个加锁动作在一个事务内完成,从而提高效率并减少竞争条件的发生概率。 #### 3. 续命机制(Watchdog) 为了避免因网络分区等原因导致提前释放锁的情况发生,Redisson 提供了一种叫做 Watchdog 的续命机制。一旦某个线程获得了锁,则会在后台启动定时任务定期延长其有效期限直到显式调用 unlock 方法为止。 具体来说,每次成功获取到锁之后都会触发一次延长时间请求,通常默认间隔时间为锁总有效期的一半。 #### 4. PermitExpirableSemaphore 工作方式 对于 `PermitExpirableSemaphore` 来讲,它允许指定数量的任务并发运行,并且可以给每一个许可分配单独的有效期。这样即使某些任务因为异常终止也没有关系,因为他们所持有的许可证将在规定时间内被回收再利用[^1]。 下面是创建一个具有三个可用槽位并且每个槽位持续五分钟的例子: ```java RBucket<Long> bucket = redissonClient.getBucket("semaphore:permits"); bucket.set(3L); // 初始化可发放的数量 RPermitExpirableSemaphore semaphore = redissonClient.getPermitExpirableSemaphore("mySem"); semaphore.acquireAsync(permit -> System.out.println("Got permit with id:" + permit.getId()), TimeUnit.MINUTES.toMillis(5)); ``` 以上代码先初始化了一个存储桶用于记录剩余名额数,接着实例化目标信号灯并通过异步接口申请其中一个配额的同时指定了它的存活周期。 --- ### 总结 综上所述,Redisson 不仅简化了开发者使用复杂数据结构的过程,而且在其背后隐藏着诸多精妙的设计理念和技术手段。无论是普通的同步互斥访问控制还是更复杂的限流场景下都能找到合适的解决方案。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值