redis实现分布式锁系列之一:基于读取properties文件的分布式锁

这个文章分为3篇:一、实现一个redis的分布式锁;二、基于分布式锁实现一个spring-boot-starter系列;三、spring-boot-starter的例子

1.新建工程redis-distributed-lock-core

此工程是基于单机模式的分布式锁,主要是基于redis的setnx命令实现的。
在这里插入图片描述
pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>redis-distributed-lock</artifactId>
        <groupId>com.snowalker</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.snowalker</groupId>
    <artifactId>redis-distributed-lock-core</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>redis-distributed-lock-core</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <aspectjweaver>1.8.10</aspectjweaver>
        <aspecjrt>1.8.10</aspecjrt>
        <apache-common-lang3>3.4</apache-common-lang3>
        <google-guava>20.0</google-guava>
        <jedis>2.9.0</jedis>
        <slf4j-api>1.7.25</slf4j-api>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- aspectjrt -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspecjrt}</version>
        </dependency>
        <!-- aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${apache-common-lang3}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${google-guava}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j-api}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.18.RELEASE</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.18.RELEASE</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.5.4</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.20.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

工具类PropertiesUtil.java

package com.snowalker.util;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author snowalker
 * @date 2017-9-21
 * @describe 配置文件读取
 */
public class PropertiesUtil {
	
	private static final Logger log = LoggerFactory.getLogger(PropertiesUtil.class);

	private static Properties props;

    static {
        String fileName = "redis.properties";
        props = new Properties();
        try {
            props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
        } catch (IOException e) {
            log.error("配置文件读取异常",e);
        }
    }

    public static String getProperty(String key){
        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
            return null;
        }
        return value.trim();
    }

    public static String getProperty(String key,String defaultValue){

        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
            value = defaultValue;
        }
        return value.trim();
    }

}

2.主要功能如下:

RedisLock类是一个注解类,主要是想将来基于注解去调用这个分布式锁,比较方便。

package com.snowalker.annotation;

import java.lang.annotation.*;

/**
 * @author snowalker
 * @date 2018-7-9
 * @desc redisLock自定义注解
 */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RedisLock {

    String value() default "redis-lock";
}

RedisPool类主要是基于JedisPool实现的连接池,主要是配置一些属性,比如最大连接数,最大空闲连接数等。

package com.snowalker.config;

import com.snowalker.util.PropertiesUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author snowalker
 * @date 2018-7-9
 * @desc Redis单机模式连接池
 */
public class RedisPool {

    /**jedis连接池*/
    private static JedisPool pool;
    /**最大连接数*/
    private static Integer maxTotal;
    /**最大空闲连接数,在jedispool中最大的idle状态(空闲的)的jedis实例的个数*/
    private static Integer maxIdle;
    /**最小空闲连接数,在jedispool中最小的idle状态(空闲的)的jedis实例的个数*/
    private static Integer minIdle;
    /**在取连接时测试连接的可用性,在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。*/
    private static Boolean testOnBorrow;
    /**再还连接时不测试连接的可用性,在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。*/
    private static Boolean testOnReturn;
    /**redis服务端ip*/
    private static String redisIp;
    /**redis服务端port*/
    private static Integer redisPort;
    /**redis连接超时时间*/
    private static Integer redisTimeout;

    static {
        init();
    }

    private static void init() {
        maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total", "20"));
        maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));
        minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","10"));
        testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));
        testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));
        redisTimeout = Integer.valueOf(PropertiesUtil.getProperty("redis.server.timeout", "3000"));

        redisIp = PropertiesUtil.getProperty("redis.ip");
        if (redisIp == null) {
            throw new RuntimeException("请检查redis服务端ip配置项redis.ip是否配置");
        }
        redisPort = Integer.parseInt(PropertiesUtil.getProperty("redis.port"));
        if (redisPort == null) {
            throw new RuntimeException("请检查redis服务端port配置项redis.port是否配置");
        }

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        /**连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true*/
        config.setBlockWhenExhausted(true);

        pool = new JedisPool(config, redisIp, redisPort, redisTimeout);
    }

    public static Jedis getJedis(){
        return pool.getResource();
    }

    public static void close(Jedis jedis){
        jedis.close();
    }

    public static void returnBrokenResource(Jedis jedis){
        pool.returnBrokenResource(jedis);
    }

    public static void returnResource(Jedis jedis){
        pool.returnResource(jedis);
    }

}

RedisPoolUtil类主要实现对RedisPool类的操作,比如设置,删除等操作。

package com.snowalker.config;

import redis.clients.jedis.Jedis;

/**
 * @author snowalker
 * @date 2018-7-9
 * @desc 封装单机版Jedis工具类
 */
public class RedisPoolUtil {

    private RedisPoolUtil(){}

    private static RedisPool redisPool;

    public static String get(String key){
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.get(key);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long setnx(String key, String value){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.setnx(key, value);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static String getSet(String key, String value){
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.getSet(key, value);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long expire(String key, int seconds){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.expire(key, seconds);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }

    public static Long del(String key){
        Jedis jedis = null;
        Long result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.del(key);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            return result;
        }
    }
}

lock包:主要是基于jedis和redisson实现的分布式锁,这里主要以jedis实现为例,具体的可以参考代码
DistributedLock接口主要定义了加锁,释放锁的方法,实现交给了RedisDistributedLock子类。

package com.snowalker.lock;

/**
 * @author snowalker
 * @date 2018-7-9
 * @desc Redis分布式锁接口声明
 */
public interface DistributedLock {

    /**
     * 加锁
     * @param lockName
     * @return
     */
    boolean lock(String lockName);

    /**
     * 解锁
     * @param lockName
     */
    boolean release(String lockName);
}

RedisDistributedLock具体实现如下:

package com.snowalker.lock;

import com.snowalker.config.RedisPoolUtil;
import com.snowalker.util.PropertiesUtil;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import com.sun.org.apache.regexp.internal.RE;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author snowalker
 * @date 2018-7-9
 * @desc redis分布式锁核心实现
 */
public class RedisDistributedLock implements DistributedLock {

    /**默认锁超时时间为10S*/
    private static final int EXPIRE_SECONDS = 50;
    private static final Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);

    private RedisDistributedLock() {
    }

    private volatile static RedisDistributedLock redisDistributedLock;

    // 使用了懒汉式单例模式
    public static RedisDistributedLock getInstance() {
        if (redisDistributedLock == null) {
            synchronized (RedisDistributedLock.class) {
                redisDistributedLock = new RedisDistributedLock();
            }
        }
        return redisDistributedLock;
    }

    /**
     * 加锁
     *
     * @param lockName
     * @return 返回true表示加锁成功,执行业务逻辑,执行完毕需要主动释放锁,否则就需要等待锁超时重新争抢
     * 返回false标识加锁失败,阻塞并继续尝试获取锁
     */
    @Override
    public boolean lock(String lockName) {
        // 1.使用setNx开始加锁
        log.info("开始获取Redis分布式锁流程,lockName={},CurrentThreadName={}", lockName, Thread.currentThread().getName());

        long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("redis.lock.timeout", "5"));
        // redis中锁的值为:当前时间+超时时间
        Long lockResult = RedisPoolUtil.setnx(lockName, String.valueOf(System.currentTimeMillis() + lockTimeout));
        if (lockResult != null && lockResult.intValue() == 1){
            log.info("setNx获取分布式锁[成功],threadName={}", Thread.currentThread().getName());

            RedisPoolUtil.expire(lockName,EXPIRE_SECONDS);
            return true;
        }else {
            log.info("setNx获取分布式锁[失败],threadName={}", Thread.currentThread().getName());

            return false;
        }
    }

    private boolean tryLock(String lockName, long lockTimeout) {

        /**
         * 2.加锁失败后再次尝试
         * 2.1 获取锁失败,继续判断,判断时间戳,看是否可以重置并获取锁
         *      setNx 结果小于当前时间,表明锁已过期,可以再次尝试加锁
         */
        String lockValueStr = RedisPoolUtil.get(lockName);
        Long lockValueATime = Long.parseLong(lockValueStr);
        log.info("lockValueATime为:" + lockValueATime);

        if (lockValueStr != null && lockValueATime < System.currentTimeMillis()){
            /**
             * 2.2 再次用当前时间戳getset--->将给定 key 的值设为 value, 并返回 key 的旧值(old value)
             * 通过getset 重设锁对应的值:新的当前时间+超时时间,并返回旧的锁对应值
             */
            String getSetResult = RedisPoolUtil.getSet(lockName, String.valueOf(System.currentTimeMillis() + lockTimeout));
            log.info("lockValueBTime为:" + Long.parseLong(getSetResult));
            if (getSetResult == null || (getSetResult != null && StringUtils.equals(lockValueStr, getSetResult))) {

                /**
                 * 2.3 旧值判断,是否可以获取锁
                 * 当key没有旧值时,即key不存在时,返回nil -> 获取锁,设置锁过期时间
                 */
                log.info("获取Redis分布式锁[成功],lockName={},CurrentThreadName={}",
                        lockName, Thread.currentThread().getName());

                RedisPoolUtil.expire(lockName, EXPIRE_SECONDS);
                return true;
            }else{
                log.info("获取锁失败,lockName={},CurrentThreadName={}",
                        lockName, Thread.currentThread().getName());
                return false;
            }
        }else {
            /**3.锁未超时,获取锁失败*/
            log.info("当前锁未失效!!!!,竞争失败,继续持有之前的锁,lockName={},CurrentThreadName={}",
                    lockName, Thread.currentThread().getName());
            return false;
        }
    }

    /**
     * 解锁
     *
     * @param lockName
     */
    @Override
    public boolean release(String lockName) {

        Long result = RedisPoolUtil.del(lockName);
        if (result != null && result.intValue() == 1) {
            log.info("删除Redis分布式锁成功,锁已释放, key= :{}", lockName);
            return true;
        }
        log.info("删除Redis分布式锁失败,锁未释放, key= :{}", lockName);
        return false;

    }
}

3.特色:主要是基于AOP编程实现了一个解析Handler

RedisLockHandler类使用环绕切的切面方式。

package com.snowalker.handler;

import com.snowalker.annotation.RedisLock;
import com.snowalker.lock.RedisDistributedLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author snowalker
 * @date 2018-7-9
 * @desc Redis分布式锁注解解析器
 */
@Aspect
@Component
public class  RedisLockHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockHandler.class);
	// 切点就是注解类
    @Pointcut("@annotation(com.snowalker.annotation.RedisLock)")
    public void redisLock(){}


    @Around("@annotation(redisLock)")
    public void around(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
        LOGGER.info("[开始]执行RedisLock环绕通知,获取Redis分布式锁开始");

        String lockName = redisLock.value();
        RedisDistributedLock redisDistributedLock = RedisDistributedLock.getInstance();
        if (redisDistributedLock.lock(lockName)){
            try {
                LOGGER.info("获取Redis分布式锁[成功]");
                joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            LOGGER.info("释放Redis分布式锁[成功]");
            redisDistributedLock.release(lockName);
        }else{
            LOGGER.error("获取Redis分布式锁[失败]");
        }
        LOGGER.error("[结束]执行RedisLock环绕通知");

    }
}

这一步就算完成了,只是简单的实现了功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值