分布式锁应用场景:
1. 防止缓存击穿:在高并发场景下,如果缓存失效或者未命中,会导致大量的请求直接访问数据库,导致数据库宕机或者性能下降。使用分布式锁可以保证只有一个请求去访问数据库,其他请求等待,从而避免了缓存击穿。
2. 防止超卖和重复下单:在电商系统中,如果多个用户同时下单同一个商品,会导致超卖和重复下单。使用分布式锁可以保证只有一个用户下单成功。另外,在秒杀和抢购场景中,使用分布式锁可以保证商品的数量不会超卖。
3. 防止任务重复执行:在分布式任务调度场景中,可能会出现多个节点同时调度同一个任务的情况,导致任务重复执行。使用分布式锁可以保证只有一个节点去调度任务,避免任务重复执行。
4. 保证数据一致性:在分布式事务场景中,需要保证多个节点同时访问同一个数据时的数据一致性。使用分布式锁可以保证在一个节点进行修改时,其他节点不能同时进行修改,保证了数据的一致性。
5. 分布式限流:在高并发场景下,使用分布式锁可以实现分布式限流,保证系统不会因为请求过多而宕机。例如,可以使用 Redis 分布式锁实现分布式限流。
实现Redis高并发分布式锁的步骤如下:
-
使用Redis的setnx命令来尝试获取锁(利用Redis的原子性保证互斥)
-
如果获取锁成功,设置锁的过期时间,避免死锁
-
如果获取锁失败,等待一段时间后重试或者放弃
-
在锁的过期时间到达之前,执行完业务逻辑后释放锁
分布式环境防止任务重复执行的代码示例
具体Java代码实现如下:
package com.soilsurvey.webapi.common.core.redis;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Created with 跳跳公司.
*
* @Author: 小皮球
* @Date: 2023/08/29/15:47
* @Description:
*/
public class DistributedLock implements AutoCloseable{
private RedisTemplate<String, String> redisTemplate;
private String lockKey;
private String requestId;
public DistributedLock(RedisTemplate<String, String> redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey;
this.requestId = UUID.randomUUID().toString();
}
/**
* 获取锁
* @param expireTime
* @return
*/
public boolean acquireLock(long expireTime) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
Boolean acquired = valueOperations.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MINUTES);
return acquired != null && acquired;
}
/**
* 释放锁
*/
public void releaseLock() {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
}
@Override
public void close() {
releaseLock();
}
}
使用方式如下:
@Scheduled(cron = "0 1 0 * * ?")
public void executeEveryDay() {
try (DistributedLock lock = new DistributedLock(redisTemplate, LOCK_KEY_EVERY_DAY)) {
// 获取锁
if (lock.acquireLock(10)) {
log.info("开始更新每日任务");
// 这里放业务代码
log.info("更新每日任务完成");
}
} catch (Exception e) {
e.printStackTrace();
}
}