使用Redis实现分布式锁

本文介绍了在分布式环境中,为何需要使用分布式锁以解决多实例并发问题,如防止重复发送报表。文章主要聚焦于基于Redis的分布式锁实现,包括添加Redis依赖、初始化RedisTemplate配置以及创建加锁和释放锁的工具类,适用于Java开发。

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

 

1.为什么要使用分布式锁

      传统项目中,大部分项目都是部署一个实例就行了,这个时候使用JDK自带的锁基本就可以解决并发问题。但是,一旦项目部署了多个实例,那么使用Java自带的锁,就没有用了;举个栗子,项目A部署了三个实例,有一段代码是通过定时任务定时触发的,比如发送报表,时间是每天早上9点,这个时候,你会发现每天早上9点,同一个人会收到3份一样的报表。但是这并不是我们想要的,正常业务里面是一个人收到一份报告就足够了。那么,我怎么确定让那个实例来定时发送报告呢?换句话说,我怎么在9点的时候,在3个实例中选一个实例来发送报告呢?是不是觉得,原生的Java的锁就做不到了;但是,使用分布式锁就可以解决这个问题,只需要在9点的时候,让三个实例去竞争获取锁,只有持有锁的实例,才可以发送报表。

 

2.如何实现分布式锁

目前主流分布式锁的实现有三种:

a. 基于redis实现(本文介绍的就是这个)

b.基于zookeeper实现(本文暂不介绍)

c.基于数据库实现(平安面试时,架构师说的,但是实际开发我没有使用过)

 

3.如何使用Redis实现分布式锁(基于Java实现)

3.1 pom文件添加redis依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <!-- 1.5的版本默认采用的连接池技术是jedis,2.0以上版本默认连接池是lettuce, 因为此次是采用jedis,所以需要排除lettuce的jar-->
        <exclusion>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

注意:jedis版本不要随便更新,更换了可能导致方法不支持

 

3.2 初始化RedisTemplate配置

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(new StringRedisSerializer());
    RedisSerializer<Object> serializer = new  JdkSerializationRedisSerializer(getClass().getClassLoader());
    template.setValueSerializer(serializer);
    template.afterPropertiesSet();
    return template;
}

 

3.3 redis加锁释放锁工具类

public class RedisLock {

    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    /**
     * 锁的后缀
     */
    private static final String LOCK_SUFFIX = "_redis_lock";
    /**
     * 锁超时时间,防止线程在入锁以后,防止阻塞后面的线程无法获取锁
     */
    private int expireMsecs = 60 * 1000;

    private final RedisTemplate redisTemplate;

    /**
     * 加锁键
     */
    private final String lockKey;

    /**
     * 加锁客户端唯一标识(采用UUID)
     */
    private final String clientId;

    public RedisLock(RedisTemplate redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey + LOCK_SUFFIX;
        this.clientId = UUID.randomUUID().toString();
    }

    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     * <p>
     * 支持重复,线程安全
     */
    public boolean tryLock() {
        return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireMsecs);
            if (LOCK_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

    /**
     * 与 tryLock 相对应,用作释放锁
     */
    public boolean releaseLock() {
        return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(clientId));
            if (RELEASE_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }
}

 

4. 工具类使用示例

@EnableScheduling
@Component
public class SmsReportTask {

    private Logger logger = LoggerFactory.getLogger(SmsReportTask.class);

    @Value("${report.yunpian.enabled}")
    private boolean enabled;

    private final String ALI_LOCK_KEY = "notify_sync_ali";

    private final String YUNPIAN_LOCK_KEY = "notify_sync_yunpian";

    private final SmsTemplateLogMapper smsTemplateLogMapper;

    private final AccountService accountService;

    private final IAcsClient iAcsClient;

    private final RedisTemplate redisTemplate;

    @Autowired
    public SmsReportTask(SmsTemplateLogMapper smsTemplateLogMapper, AccountService accountService,
                         IAcsClient iAcsClient, RedisTemplate redisTemplate) {
        this.smsTemplateLogMapper = smsTemplateLogMapper;
        this.accountService = accountService;
        this.iAcsClient = iAcsClient;
        this.redisTemplate = redisTemplate;
    }

    @Scheduled(cron = "${report.trigger.aliyun}")
    private void refreshAliReport() {
        RedisLock lock = new RedisLock(redisTemplate, ALI_LOCK_KEY);
        if (lock.tryLock()) {
            try {
                updateAliReport();
            } finally {
                lock.releaseLock();
            }
        }
    }

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值