Redis实现分布式锁,通过注解进行加锁

2020-06-11  本文已影响0人  闫文江i

项目介绍:为公司项目进行封装common包,需要分布式锁功能的实现,决定写一个比较简易拿来就用的包。

分布式锁实现方式通常分为3种:

1.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">
    <modelVersion>4.0.0</modelVersion>

    <!--spring boot 版本-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
    </parent>
    <groupId>com.ict.common</groupId>
    <artifactId>Redis-Distribute-Lock</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--Springboot与redis起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--AOP切面依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--redisTemplate中jackson序列化依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <!--jedis客户端-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <type>jar</type>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>none</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2.RedisConfig配置类

package com.ict.common.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.*;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

/**
 * @author: DevWenjiang
 * Description: Redis配置类
 * @date : 2020-06-10 17:34
 */
@Configuration
@EnableAutoConfiguration
public class RedisConfig {

    @Value("${spring.redis.defaultExpiration:3600}")
    private Long defaultExpiration;
    /**
     * 创建JedisPoolConfig对象
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.redis")
    public JedisPoolConfig jedisPoolConfig(){
        return new JedisPoolConfig();
    }

    /**
     * 创建JedisConnectionFactory对象
     */
    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.redis")
    public JedisConnectionFactory jedisConnectionFactory(){
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        JedisPoolConfig config = jedisPoolConfig();
        jedisConnectionFactory.setPoolConfig(config);
        return jedisConnectionFactory;
    }




    @Bean
    public RedisTemplate<String,Object> jdkRedisTemplate(){
        RedisTemplate<String,Object> jdkRedisTemplate = new RedisTemplate<>();
        jdkRedisTemplate.setConnectionFactory(jedisConnectionFactory());
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        jdkRedisTemplate.setHashKeySerializer(stringRedisSerializer);
        jdkRedisTemplate.setKeySerializer(stringRedisSerializer);
        jdkRedisTemplate.afterPropertiesSet();
        return jdkRedisTemplate;
    }

    /**
     * 创建以jackson序列化方式redisTemplate
     * @return
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        //设置对象mapper
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //设置key与value序列化方式
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 创建stringRedisTemplate
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(){
        return new StringRedisTemplate(jedisConnectionFactory());
    }

    /**
     * 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //设置CacheManager的值序列化方式为json序列化
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
                .fromSerializer(jsonSerializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(pair);
        //设置默认超过期时间是30秒
        defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
        //初始化RedisCacheManager
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }

}

3.LockStrategy所类型枚举

package com.ict.common.enums;

/**
 * @author: DevWenjiang
 * Description:
 * @date : 2020-06-10 17:29
 */
public enum LockStrategy {
    //失败重试
    WAIT_RETRY,
    //忽略
    IGNORE,
    //抛出异常
    THROWABLE
}

4.RedisDistributeLockAnnotion分布式锁注解

package com.ict.common.annotion;

import com.ict.common.enums.LockStrategy;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author: DevWenjiang
 * Description: redis分布式锁注解(需要加锁的方法加入注解)
 * @date : 2020-06-10 17:23
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisDistributeLockAnnotion {
    @AliasFor("value")
    String key() default "";
    @AliasFor("key")
    String value() default "";
    //锁策略:失败忽略|重试|抛出异常
    LockStrategy LOCK_STRATEGY() default LockStrategy.IGNORE;
    /**
     * 获锁最长时间
     */
    long maxLockTime() default 30000l;
    /**
     * 等待重试时间
     */
    long retryTime() default 500l;
    /**
     * 最大重试次数
     */
    int maxRetryTimes() default 10;
}

5.RedisClient封装的Redis客户端操作

package com.ict.common.lock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author : DevWenjiang
 * Description: Redis客户端类,与redis服务器进行数据交互类
 * @date : 2020/6/10-20:13
 */
@Component
public class RedisClient {

    @Autowired()
    @Qualifier("redisTemplate")
    private RedisTemplate<String, Object> redisTemplate;


    @Autowired()
    @Qualifier("stringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate;

    /**
     * string字符串set方法
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * string字符串set方法,带有过期时间
     *
     * @param key
     * @param value
     * @param expireTime
     * @param timeUnit
     */
    public void set(String key, String value, Long expireTime, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
    }

    /**
     * Object对象set方法
     *
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * Object对象set方法,带有过期时间
     *
     * @param key
     * @param value
     * @param expireTime
     * @param timeUnit
     */
    public void set(String key, Object value, Long expireTime, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
    }

    /**
     * setNX方法
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean setNX(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * setNX,带有过期时间
     * @param key
     * @param value
     * @param expireTime
     * @param timeUnit
     * @return
     */
    public Boolean setNX(String key, Object value, Long expireTime, TimeUnit timeUnit) {
        return redisTemplate.execute(new SessionCallback<Boolean>() {
            @Override
            public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
                //开启事务
                redisOperations.multi();
                redisOperations.opsForValue().setIfAbsent(key, value);
                redisOperations.expire(key, expireTime, timeUnit);
                List exec = redisOperations.exec();
                if (exec == null || exec.isEmpty()) {
                    return false;
                }
                return (Boolean) exec.get(0);
            }
        });
    }

    /**
     *  自增
     * @param key
     * @param i
     * @return
     */
    public Long increment(String key, int i) {

        return stringRedisTemplate.boundValueOps(key).increment(i);
    }

    /**
     * 获取key对应值
     * @param key
     * @return
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * Object的
     * @param key
     * @return
     */
    public Object getObj(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 模糊查询满足要求的key
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern){
        return redisTemplate.keys(pattern);
    }

    /**
     * 获得过期时间(以秒为单位)
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 设置过期时间
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean expire(String key,Long timeout,TimeUnit unit){
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 删除key
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 删除多个key
     * @param keys
     */
    public void delete(Set<String> keys){
        redisTemplate.delete(keys);
    }

    /**
     * 对key进行hash
     * @param key
     * @return
     */
    public boolean haskey(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 右进
     * @param key
     * @param value
     * @return
     */
    public Long rightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 左出
     * @param key
     * @return
     */
    public Object leftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 获取list集合
     * @param key
     * @return
     */
    public List<Object> getList(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
}

6.RedisDistributeLock分布式具体实现类

package com.ict.common.lock;


import org.springframework.util.Assert;

import java.util.concurrent.TimeUnit;

/**
 * @author : DevWenjiang
 * Description: redis分布式锁实现类
 * @date : 2020/6/10-20:38
 */
public class RedisDistributeLock {
    //默认最大线程获取锁时间
    private static final Long DEFAULT_MAX_LOCK_TIME = 300000l;//30s

    //redis客户端
    private RedisClient redisClient;

    //key
    private String key;

    //最大锁时间
    private Long maxLockTime;

    //持有锁状态
    private Boolean isLock;


    private RedisDistributeLock(RedisClient redisClient, String key, Long maxLockTime) {
        Assert.notNull(redisClient, "redisClient不能为空");
        Assert.hasText(key, "key不能为空");
        this.redisClient = redisClient;
        this.key = key;
        this.maxLockTime = maxLockTime;
    }

    /**
     * 建造者模式
     * @param redisClient
     * @param key
     * @param maxLockTime
     * @return
     */
    public static RedisDistributeLock build(RedisClient redisClient,String key,Long maxLockTime){
        return new RedisDistributeLock(redisClient,key,maxLockTime);
    }

    public static RedisDistributeLock build(RedisClient redisClient,String key){
        return new RedisDistributeLock(redisClient,key,DEFAULT_MAX_LOCK_TIME);
    }

    /**
     * 获取锁
     * @return
     */
    public Boolean lock(){
        Boolean aBoolean = redisClient.setNX(key, "1", maxLockTime, TimeUnit.MILLISECONDS);
        if (aBoolean){
            isLock = true;
            return true;
        }
        isLock = false;
        return false;

    }

    /**
     * 释放锁
     */
    public void unlock(){
        if (isLock){
            redisClient.delete(key);
            isLock = false;
        }
    }
}

7.RedisDistributeLockAspect分布式锁切面增强类

package com.ict.common.aop;

import com.ict.common.annotion.RedisDistributeLockAnnotion;
import com.ict.common.exception.RedisDistributeLockException;
import com.ict.common.lock.RedisClient;
import com.ict.common.lock.RedisDistributeLock;
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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;


/**
 * @author: DevWenjiang
 * Description: Redis分布式锁切面类,加入注解开启分布式锁
 * @date : 2020-06-11 09:36
 */
@Component
@Aspect
public class RedisDistributeLockAspect {
    //日志对象
    private static final Logger log = LoggerFactory.getLogger(RedisDistributeLockAspect.class);

    //注入redisClient
    @Autowired
    private RedisClient redisClient;


    //存放重试次数的ThreadLocal
    private static ThreadLocal<Integer> retryTimes = new ThreadLocal<>();

    //注解切面,只要方法上加该注解就会进行切面方法增强
    @Pointcut("@annotation(com.ict.common.annotion.RedisDistributeLockAnnotion)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object aroundMethod(ProceedingJoinPoint pj) throws RedisDistributeLockException {
        //获取方法参数
        Object[] args = pj.getArgs();
        //获取方法签名,方法名称
        MethodSignature signature = (MethodSignature) pj.getSignature();
        //获取参数名
        String[] parameterNames = signature.getParameterNames();
        //存放参数名:参数值
        HashMap<String, Object> params = new HashMap<>();
        if (null != parameterNames && parameterNames.length > 0 && null != args && args.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                params.put(parameterNames[i], args[i]);
            }
        }
        //MethodSignature signature = (MethodSignature) pj.getSignature();
        //获取方法注解
        RedisDistributeLockAnnotion redisDistributeLockAnnotion = signature.getMethod().getAnnotation(RedisDistributeLockAnnotion.class);
        //获取key
        String key = redisDistributeLockAnnotion.key();
        if (null == key || key.trim().equals("")) {
            key = redisDistributeLockAnnotion.value();
        }
        if (null == key || key.trim().equals("")) {

            log.error("key值不能为空");
            throw new RedisDistributeLockException("key值不能为空");
        }
        //创建redis分布式锁对象
        RedisDistributeLock redisDistributeLock = RedisDistributeLock.build(redisClient, key, redisDistributeLockAnnotion.maxLockTime());
        try {
            Boolean lock = redisDistributeLock.lock();
            if (lock){
                try {
                    //获取锁成功,执行方法
                    System.out.println(Thread.currentThread().getName()+"获取到锁"+key);
                    Object proceed = pj.proceed(args);
                    //移除ThreadLocal中的重试次数
                    retryTimes.remove();
                    //返回执行结果
                    return proceed;
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }else {
                System.out.println(Thread.currentThread().getName()+"没有获取到锁"+key);
            }

            switch (redisDistributeLockAnnotion.LOCK_STRATEGY()){
                case IGNORE:
                    break;
                case THROWABLE:
                    throw new RedisDistributeLockException("该"+key+"已经被获取");
                case WAIT_RETRY:
                    //获取
                    Integer retryTime = retryTimes.get();
                    if (retryTime != null &&retryTime >= redisDistributeLockAnnotion.maxRetryTimes()){
                        retryTimes.remove();
                        throw new RedisDistributeLockException(Thread.currentThread().getId()+"尝试获取锁失败超过了最大重试次数,key="+key);
                    }
                    if (retryTime == null){
                        retryTime = 1;
                    }else {
                        retryTime++;
                    }
                    retryTimes.set(retryTime);
                    try{
                        Thread.sleep(redisDistributeLockAnnotion.retryTime());
                    }catch (Exception e){
                        log.error(e.getMessage(),e);
                    }
                    aroundMethod(pj);
                    break;
                default:break;
            }
            return null;
        }finally {
            redisDistributeLock.unlock();
        }
    }
}

8.RedisDistributeLockException自定义异常类

package com.ict.common.exception;

/**
 * @author : DevWenjiang
 * Description: redis分布式锁异常类
 * @date : 2020/6/10-20:10
 */
public class RedisDistributeLockException extends Exception {
    public RedisDistributeLockException() {
    }
    public RedisDistributeLockException(String message) {
        super(message);
    }
    public RedisDistributeLockException(String message, Throwable cause) {
        super(message, cause);
    }
    public RedisDistributeLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

9.application.yml配置文件(此项目无需定义,只需要调用此服务的非服务上有即可)

server:
  port: 8800
  tomcat:
    uri-encoding: UTF-8
  servlet:
    context-path: /
spring:
  redis:
    database: 2
    hostName: 119.3.182.194
    port: 6386
    password: ict2020.
    pool:
      maxActive: 50
      maxWait: 3000
      maxIdle: 30
      minIdle: 10
      timeout: 1000
    defaultExpiration: 3600

10.多线程运行结果图,测试时依据商品库存所做,库存不会出现超买现象


运行效果图

项目中用到的类就这些,可以进行一些扩展等等;也在尝试中。
项目github地址:https://github.com/yanwenjiang01/Redis_Distribute_Lock.git

上一篇 下一篇

猜你喜欢

热点阅读