首页 > 技术文章 > SpringBoot项目中利用Redis实现分布式锁

liqingjiang 2020-06-04 22:03 原文

实现步骤

1. 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
    <version>2.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

2. 依赖说明

spring-boot-starter-data-redis:
在低版本中默认依赖 Jedis
在高版本中默认依赖 lettuce,若要使用Jedis 则需要修改依赖(如上所示)

3.SpringBoot配置Redis

application.properties 或者 application.yml 都可以(只是有格式的区别,作用并无差别)

spring.redis.host=127.0.0.1
spring.redis.database=0
spring.redis.port=6379
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.password=redis123456

4. 代码实现

package com.uzhizhe.user.cache;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 * @Desc Redis service
 * @Author uzhizhe
 */
@Service
@Slf4j
public class RedisService {


    /**
     * 操作成功
     */
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 操作成功
     */
    private static final String LOCK_SUCCESS = "OK";

    /**
     * 不存在才set
     */
    private static final String SET_IF_NOT_EXIST = "NX";

    /**
     * 毫秒单位
     */
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 释放分布式锁的Lua 脚本
     */
    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 Integer DEFALUT_EXPIRE_TIME = 5000;
 
    /**
     * redisTemplate
     */
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 获取分布式锁
     *
     * @param lockKey    加锁键
     * @param requestId  加锁客户端唯一标识(采用UUID)
     * @param expireTime 锁过期时间, 单位:毫秒
     * @return 获取锁成功或者失败
     */
    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            return LOCK_SUCCESS.equals(result);
        });
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   lockKey
     * @param requestId requestId 加锁时的唯一ID
     * @return 释放锁成功或者失败
     */
    public boolean releaseLock(String lockKey, String requestId) {
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(requestId));
            return RELEASE_SUCCESS.equals(result);
        });
    }
}

5. 最后几点说明

利用redis 的set方法实现加锁
1. set 的5个参数,能够很好的保证加锁的正确性
2. 过期时间:保证不会出现死锁,在没有主动释放时,会因过期释放
3. 设置Value值,保证自己的锁不会被别人释放(所谓:解铃还须系铃人)
4. Redis 命令的原子性,保证了锁的互斥性

推荐阅读