基于Redission高级应用6-RSemaphoreRPermitExpirableSemaphore实战应用
RSemaphore 实现原理
RSemaphore
是 Redisson 提供的一个分布式信号量(Semaphore),它是基于 Redis 的 SETNX
、LPUSH
、LPOP
等原子操作实现的。信号量用于控制对有限资源的访问,确保同一时间只有有限数量的进程或线程可以访问。
工作原理如下:
- 在 Redis 中,使用一个键来表示信号量,该键关联的值表示剩余的许可数。
- 当一个进程或线程尝试获取许可时,Redisson 会通过 Redis 的原子减操作(如
DECRBY
)尝试减少一个许可。 - 如果减操作后的许可数不小于 0,获取许可成功;如果小于 0,获取许可失败,并且进程或线程可能会被阻塞,直到其他进程或线程释放许可。
- 当释放许可时,Redisson 会通过 Redis 的原子加操作(如
INCRBY
)来增加许可数。
RPermitExpirableSemaphore 实现原理
RPermitExpirableSemaphore
是 Redisson 提供的一个分布式可过期许可信号量。与 RSemaphore
类似,但是每个许可都有一个过期时间,在这个时间后许可会自动释放。
工作原理如下:
- 每个许可都是通过一个唯一的 ID 标识的,这个 ID 与一个 Redis 键关联,该键的生存时间(TTL)就是许可的过期时间。
- 当一个进程或线程尝试获取许可时,如果信号量的计数器大于 0,Redisson 会创建一个新的 Redis 键,并设置其 TTL。
- 许可的 ID 被返回给调用者,用来标识持有的许可。
- 如果持有许可的进程或线程完成了任务,它可以通过提供许可的 ID 来释放许可,这将删除对应的 Redis 键。
- 如果许可未被显式释放,它将在到达 TTL 后自动释放。
优点
- 分布式协调:可以在多个进程或服务之间协调对共享资源的访问。
- 高可用性:由于基于 Redis,这些信号量具有 Redis 的高可用性和持久性特性。
- 可过期许可:
RPermitExpirableSemaphore
支持许可的自动释放,避免了死锁的问题。 - 公平性:Redisson 可以配置信号量来确保公平的获取许可,按请求的顺序授予许可。
缺点
- 性能开销:与本地信号量相比,分布式信号量因为网络通信和 Redis 操作会有更高的性能开销。
- 网络依赖:信号量的操作依赖于网络和 Redis 服务器的稳定性,网络问题可能导致许可获取失败或延迟。
- 时钟同步:在使用可过期许可时,需要确保系统时钟同步,否则可能会导致许可提前过期或过期时间不一致。
- 资源浪费:如果许可频繁地被获取和释放,可能会导致 Redis 的资源浪费和性能下降。
在使用 RSemaphore
或 RPermitExpirableSemaphore
时,需要考虑到实际的业务场景和系统架构,以及它们的优缺点,来决定是否适合在您的应用中使用这些分布式信号量。
实战示例:
Redisson 提供了 RSemaphore
和 RPermitExpirableSemaphore
,这两种信号量可以帮助你在分布式系统中实现限流和资源控制。以下是这些信号量在实战应用中的一些例子:
RSemaphore 实战应用示例
分布式限流
假设你正在构建一个微服务架构的系统,你需要确保一个特定的服务在任何给定时间内不会被过多的并发请求所压垮。
@Autowired
private RedissonClient redissonClient;
public void initSemaphore(int permits) {
RSemaphore semaphore = redissonClient.getSemaphore("serviceLimitSemaphore");
semaphore.trySetPermits(permits);
}
public boolean acquire() {
RSemaphore semaphore = redissonClient.getSemaphore("serviceLimitSemaphore");
return semaphore.tryAcquire();
}
public void release() {
RSemaphore semaphore = redissonClient.getSemaphore("serviceLimitSemaphore");
semaphore.release();
}
在这个例子中,你首先设置了信号量的许可数,这个数目代表了你的服务可以同时处理的最大请求数。然后,每当有新的请求进来时,你会尝试获取一个许可。如果成功,服务将处理请求,处理完成后释放许可。如果无法获取许可,说明服务已达到最大负载,请求可以被拒绝或排队。
RPermitExpirableSemaphore 实战应用示例
分布式定时任务锁
在分布式系统中,可能需要确保定时任务在同一时间只能在一个节点上执行。RPermitExpirableSemaphore
可以发放一个可过期的许可,确保任务不会在多个节点上同时执行。
@Autowired
private RedissonClient redissonClient;
public String acquireJobPermit(long leaseTime, TimeUnit timeUnit) {
RPermitExpirableSemaphore semaphore = redissonClient.getPermitExpirableSemaphore("jobLockSemaphormitId);
}
} else {
// 获取许可失败,另一个节点可能正在执行任务
}
}
在这个例子中,每次尝试执行定时任务之前,我们尝试获取一个带有租约时间的许可。如果获取成功,当前节点将执行任务,并在完成后释放许可。如果许可无法获取,表示有其他节点正在执行任务,当前节点可以跳过任务执行。租约时间确保即使在节点失败的情况下,许可也会在一定时间后自动释放,防止死锁。
注意事项
RSemaphore
和RPermitExpirableSemaphore
在分布式环境中非常有用,但需要注意网络延迟和时钟同步问题。- 在使用可过期许可时,确保租约时间足够长,以覆盖任务的执行时间,防止在任务执行过程中许可过期。
- 释放许可时,确保正确处理异常,并在必要时释放许可,避免资源泄露。
- 在高并发场景下,信号量的操作可能会成为瓶颈,需要合理设置许可数量并监控系统性能。
这些实战应用示例展示了如何使用 Redisson 的信号量来控制分布式系统中的资源访问和任务执行,从而实现限流和同步。在实际应用中,开发者需要根据具体的业务场景和系统架构来选择合适的信号量类型和策略。
RSemaphore
和 RPermitExpirableSemaphore
是 Redisson 提供的两种分布式信号量实现,它们允许在分布式环境中限制对共享资源的访问。以下是这两种信号量的高级用法和相应的实战示例。
RSemaphore 高级用法
公平信号量
RSemaphore
可以配置为公平模式,这意味着 Redisson 会按照请求许可的顺序来分配许可,确保先到先得。
@Autowired
private RedissonClient redissonClient;
public RSemaphore initFairSemaphore(String semaphoreName, int permits) {
RSemaphore semaphore = redissonClient.getFairSemaphore(semaphoreName);
semaphore.trySetPermits(permits);
return semaphore;
}
public void performActionWithFairSemaphore(String semaphoreName) {
RSemaphore semaphore = redissonClient.getFairSemaphore(semaphoreName);
try {
semaphore.acquire();
// 执行受限制的操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
在这个示例中,初始化信号量时,我们使用 getFairSemaphore
方法获取一个公平的信号量。这适用于那些需要保证处理顺序的场景,例如打印任务队列。
RPermitExpirableSemaphore 高级用法
可过期许可
RPermitExpirableSemaphore
允许发放带有过期时间的许可。如果持有许可的服务因任何原因未能释放许可,许可将在指定时间后自动释放。
@Autowired
private RedissonClient redissonClient;
public String acquireExpirablePermit(String semaphoreName, long leaseTime, TimeUnit timeUnit) {
RPermitExpirableSemaphore semaphore = redissonClient.getPermitExpirableSemaphore(semaphoreName);
try {
return semaphore.acquire(leaseTime, timeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
public void performActionWithExpirablePermit(String semaphoreName, long leaseTime, TimeUnit timeUnit) {
String permitId = acquireExpirablePermit(semaphoreName, leaseTime, timeUnit);
if (permitId != null) {
try {
// 执行受限制的操作
} finally {
semaphore.release(permitId);
}
}
}
在这个示例中,我们获取了一个带有过期时间的许可,这适用于那些需要在特定时间内完成操作,或者需要在操作未能正常完成时自动放弃许可的场景。
扩展实战示例
分布式任务调度
在分布式系统中,我们可能需要确保定时任务或后台作业在任何时刻只能在一个节点上运行。
@Autowired
private RedissonClient redissonClient;
public void scheduleJob(String jobName) {
RPermitExpirableSemaphore semaphore = redissonClient.getPermitExpirableSemaphore("jobSemaphore:" + jobName);
String permitId = null;
try {
// 尝试获取许可,设置合适的过期时间
permitId = semaphore.acquire(5, TimeUnit.MINUTES);
if (permitId != null) {
// 执行任务
executeJob(jobName);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (permitId != null) {
// 释放许可
semaphore.release(permitId);
}
}
}
private void executeJob(String jobName) {
// 实际的作业逻辑
}
在这个实战示例中,我们使用 RPermitExpirableSemaphore
来确保定时任务不会在多个节点上同时执行。每个任务都尝试获取一个许可,并在执行完成后释放许可。如果许可无法获取,任务将被跳过,以防止在另一个节点上重复执行。
注意事项
- 在使用这些信号量时,确保所有节点的系统时间同步,避免因时间偏差导致的问题。
- 使用
RPermitExpirableSemaphore
时,应该设置合理的许可过期时间,以便在节点故障时能够自动释放许可。 - 需要处理好中断异常,确保在获取许可失败时,线程的中断状态得到正确处理。
- 释放许可时,确保传递正确的许可 ID,避免错误地释放其他节点的许可。
这些高级用法和实战示例展示了如何在分布式环境中使用 Redisson 的信号量来控制对共享资源的并发访问,以及如何实现任务调度的同步。
转载自:https://juejin.cn/post/7362078214381731878