Home  >  Article  >  Java  >  How to use and implement the function of Java-Redis-Redisson distributed lock

How to use and implement the function of Java-Redis-Redisson distributed lock

PHPz
PHPzforward
2023-05-14 21:55:04621browse

Preface

We have modified the Java-Redis-Redisson configuration to make the use of locks more convenient

Infrastructure

RedissonLock

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLock {
    int lockTime() default 3; //加锁的时间默认3秒,  如果任务在3秒内执行完毕那么自动释放锁,如果任务3秒内没有执行完毕也会释放锁, 所以内容执行时间过长适当加大锁的时间
    String key() default "" ;  //唯一标识,如果没有那么默认为token->sessionId
    String doc() default "重复提交请求,请稍后再试";
    boolean repeatLock() default false; //可重复加锁直到加锁成功,默认为false不能重复加锁
    int repeatLockCount() default -1; //可重复加锁限制加锁的次数, 默认-1直到成功,设置10那么加锁10次都没成功就直接返回
    int lockWaitTimeMs() default 100; //重复加锁默认的阻塞时间100毫秒,可以自己定义
}

RepeatSubmitAspect

import com.application.Result;
import com.commonutils.NullUtils;
import com.redis.utils.DistributedRedisLock;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
public class RepeatSubmitAspect {
    @Value("${spring.redis.redisson.tokenName}")
    private  String tokenName;
    @Autowired
    private DistributedRedisLock redisLock;
    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(RedissonLock noRepeatSubmit) {
    }
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes ra= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return ra.getRequest();
    }
    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, RedissonLock noRepeatSubmit) throws Throwable {
        int lockSeconds = noRepeatSubmit.lockTime();
        String doc = noRepeatSubmit.doc();
        String keyName = noRepeatSubmit.key();
        boolean b = noRepeatSubmit.repeatLock();
        int repeatLockCount = noRepeatSubmit.repeatLockCount();
        int lockWaitTimeMs = noRepeatSubmit.lockWaitTimeMs();
        HttpServletRequest request = getRequest();
        Assert.notNull(request, "request can not null");
        //如果没有唯一表示那么就使用token或者sessionID来唯一表示
        if(!NullUtils.notEmpty(keyName)){
            String token = request.getHeader(tokenName);
            if(NullUtils.notEmpty(token)){
                keyName=token;
            }else{
                //使用sessionID (注意保证分布式session共享)
                keyName = request.getSession().getId();
            }
            System.out.println("tokenName:"+keyName);
        }
        String path = request.getServletPath();
        String key = getKey(keyName, path);
        //加锁
        boolean isSuccess = redisLock.acquire(key, lockSeconds,b,repeatLockCount,lockWaitTimeMs);
        if (isSuccess) {
            // 获取锁成功
            Object result;
            try {
                // 执行
                result = pjp.proceed();
            } finally {
                // 解锁
                redisLock.release(key);
            }
            return result;
        } else {
            // 获取锁失败,认为是重复提交的请求
            return Result.Error(doc);
        }
    }
    private String getKey(String token, String path) {
        return token + path;
    }
}

DistributedRedisLock

/**
 * 简要描述
 *
 * @Author: huanmin
 * @Date: 2022/8/1 17:39
 * @Version: 1.0
 * @Description: 文件作用详细描述....
 */

import com.multithreading.utils.SleepTools;
import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class DistributedRedisLock {
    private static final Logger logger = LoggerFactory.getLogger(DistributedRedisLock.class);
    //从配置类中获取redisson对象
    @Autowired
    private RedissonClient redissonClient;
    private final String LOCK_TITLE = "redisLock_";
    private final ThreadLocal count = new ThreadLocal<>();//计数

    //加锁 , 线程加锁的时候发现,锁有人用了 ,那么就会进入自旋等待
    @SneakyThrows
    public boolean acquire(String lockKey, long seconds, boolean repeatLock, int repeatLockCount, int lockWaitTimeMs) {
        count.set(0); //初始化值
        //获取锁对象
        RLock mylock = redissonClient.getLock((LOCK_TITLE + lockKey));
        do {
            //尝试加锁
            boolean b = mylock.tryLock(0, seconds, TimeUnit.SECONDS);;
            if (b) {
                logger.info("获取锁成功");
                return true;
            }
            logger.info("尝试获取锁" + Thread.currentThread().getName());
            //获取加锁的次数,如果是-1那么持续加锁,如果满足加锁次数那么结束加锁
            if (repeatLockCount != -1 && count.get().equals(repeatLockCount)) {
                logger.warn(Thread.currentThread().getName() + "尝试加锁失败以尝试了:" + count.get() + "次");
                return false;
            }
            SleepTools.ms(lockWaitTimeMs);
            //加锁次数增加
            count.set(count.get() + 1);
        } while (repeatLock);
        logger.warn("重复提交");
        return false;
    }

    //手动锁的释放
    public void release(String lockKey) {
        //获取所对象
        RLock mylock = redissonClient.getLock((LOCK_TITLE + lockKey));
        // 这里判断下当前key是否上锁,不然业务执行时间大于锁自动释放时间后,解锁报异常
        if (mylock.isLocked()&&mylock.isHeldByCurrentThread()) { // 是否还是锁定状态并且锁是当前线程的
                mylock.unlock(); // 释放锁
                logger.info("解锁:" + lockKey);
        }

    }
}

Function usage and introduction

  • Support configuration lock time

  • Support configuration lock key (requests for the same key will be locked)

  • Support repeated addition Lock

  • The number of times supported for repeated locking

  • The interval time supported for repeated locking

Through the combination of the above functions, idempotence, distributed pessimistic locking, and timeout discarding can be achieved. They are not only used in the Controller, but are supported by any Bean managed based on the Spring container. Of course, we can use it directly in special scenarios. DistributedRedisLock class, pay attention to release the lock at any time

    /***
     *默认加锁3秒
     * 加锁的key为token->sessionId
     * 加锁失败不可重复加锁
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock
    @GetMapping(value = "/updateAgeAsyncLock" )
    public Result updateAgeAsyncLock() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }

    /***
     * 默认加锁3秒
     * 加锁失败不可重复加锁
     * 加锁的key为token->sessionId
     * 提示:  加锁失败
     */
    @RedissonLock(doc = "加锁失败")
    @GetMapping(value = "/updateAgeAsyncLockDoc" )
    public Result updateAgeAsyncLockDoc() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }
    /***
     * 默认加锁15秒
     * 加锁失败不可重复加锁
     * 加锁的key为token->sessionId
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock(lockTime = 15)
    @GetMapping(value = "/updateAgeAsyncLock0" )
    public Result updateAgeAsyncLock0() {
         userService.updateAgeAsyncLock();
         return Result.Ok();
    }

    /***
     * 默认加锁15秒
     * 加锁失败不可重复加锁
     * 加锁的key为updateAgeAsyncLock
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock(lockTime = 15,key = "updateAgeAsyncLock")
    @GetMapping(value = "/updateAgeAsyncLock1" )
    public Result updateAgeAsyncLock1() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }
    /***
     * 默认加锁15秒
     * 加锁失败可重复加锁,直到加锁成功
     * 加锁的key为updateAgeAsyncLock
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock(lockTime = 15,key = "updateAgeAsyncLock",repeatLock = true)
    @GetMapping(value = "/updateAgeAsyncLock2" )
    public Result updateAgeAsyncLock2() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }

    /***
     * 默认加锁15秒
     * 加锁失败可重复加锁10次,每次默认间隔100毫秒
     * 加锁的key为updateAgeAsyncLock
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock(lockTime = 15,key = "updateAgeAsyncLock",repeatLock = true,repeatLockCount = 10)
    @GetMapping(value = "/updateAgeAsyncLock3" )
    public Result updateAgeAsyncLock3() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }
    /***
     * 默认加锁15秒
     * 加锁失败可重复加锁10次,每次默认间隔500毫秒
     * 加锁的key为updateAgeAsyncLock
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock(lockTime = 15,key = "updateAgeAsyncLock",repeatLock = true,repeatLockCount = 10,lockWaitTimeMs = 500)
    @GetMapping(value = "/updateAgeAsyncLock4" )
    public Result updateAgeAsyncLock4() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }
    /***
     * 默认加锁15秒
     * 加锁失败可重复加锁,直到成功,每次尝试间隔500毫秒
     * 加锁的key为updateAgeAsyncLock
     * 默认提示:  重复提交请求,请稍后再试
     */
    @RedissonLock(lockTime = 15,key = "updateAgeAsyncLock",repeatLock = true,lockWaitTimeMs = 500)
    @GetMapping(value = "/updateAgeAsyncLock4" )
    public Result updateAgeAsyncLock4() {
        userService.updateAgeAsyncLock();
        return Result.Ok();
    }

Verification method: Use jmeter multi-thread 10000 requests, be careful not to use the default lock key method when using jmeter, because jmeter requests a different session ID every time , to verify the effect we need to manually add key or use token

Other pessimistic lock implementations

public class DistributedRedisLock {
    //从配置类中获取redisson对象
    @Autowired
    private RedissonClient redissonClient;
    private  final String LOCK_TITLE = "redisLock_";
    //加锁 , 线程加锁的时候发现,锁有人用了 ,那么就会进入自旋等待
    public  boolean acquire(String lockName){
        //声明key对象
        String key = LOCK_TITLE + lockName;

        //获取锁对象
        RLock mylock = redissonClient.getLock(key);
        //一直等待直到加锁成功后,并且设置锁过期时间,防止死锁的产生
        mylock.lock(10, TimeUnit.SECONDS);
        System.err.println("======lock======"+Thread.currentThread().getName());
        //加锁成功
        return  true;
    }
    //锁的释放
    public    void release(String lockName){
        //必须是和加锁时的同一个key
        String key = LOCK_TITLE + lockName;
        //获取所对象
        RLock mylock = redissonClient.getLock(key);
     // 这里判断下当前key是否上锁,不然业务执行时间大于锁自动释放时间后,解锁报异常
        if(mylock.isLocked()){ // 是否还是锁定状态
            if(mylock.isHeldByCurrentThread()){ // 时候是当前执行线程的锁
                mylock.unlock(); // 释放锁
                System.err.println("======unlock======"+Thread.currentThread().getName());
            }
        }
    }
}

The above is the detailed content of How to use and implement the function of Java-Redis-Redisson distributed lock. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete