当前位置: 首页>后端>正文

使用redis的setnx可以非同一线程进行加锁和解锁(附源码)

问题背景

Redisson做分布式锁是目前比较流行的方式,但是在使用的过程中遇到一些坑:

  • Redisson的分布式锁只能通过创建锁的线程进行解锁,正所谓解铃还须系铃人,不是同一个线程解锁会报异常
  • 因为Redisson是为锁而生,所以一开始设计的时候,为了防止死锁,默认锁的过期时间为30S
  • 当时我居然傻到用单元测试来测试Redisson的分布式锁,我太傻了,单元测试之后马上就会结束项目运行,那么就没有线程持有锁了,更别说还需要同线程解锁了

但我的项目中做了分布式任务调度,定时去扫描任务完成没有,完成了再进行解锁,但定时的扫描线程不是之前的加锁线程了,所以使用redisson做分布式锁不太合适,本篇介绍redis的分布式锁的模板使用

注意事项:

  • 代码是从我的这篇文章进行添加修改的
  • 可以自己创建工程,也可以下载源码进行参考
  • 默认已安装redis,可以使用安装包安装看这篇文章,使用docker安装看这篇文章

项目搭建

1 在RedisUtils工具类中添加setNX方法,value值可以设置为globalId,这样具有唯一性,每次解锁还有增加一层value的判断保证更安全

package com.yg.redisson.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;



/**
 * @Author suolong
 * @Date 2022/4/26 13:33
 * @Version 2.0
 */

@Service("redisUtils")
@Slf4j
public class RedisUtils {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
       try {
           return key == null null : redisTemplate.opsForValue().get(key);
       }catch (Exception ex){
           log.error("获取缓存异常", ex);
           return null;
       }
    }

    public boolean del(String key){
        try {
          return Boolean.TRUE.equals(redisTemplate.delete(key));
        }catch (Exception e){
            log.error("删除缓存键异常",e);
            return false;
        }
    }

    public Long increment(String key, Long delta){
        try {
            return redisTemplate.opsForValue().increment(key, delta);
        }catch (Exception ex){
            log.error("操作缓存异常",ex);
            return null;
        }
    }

    public void saveByPipeLine(Map<String, Map<String, String>> dataMap){
        try {
            final RedisSerializer keySerializer = redisTemplate.getKeySerializer();
            final RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
            redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {
               dataMap.forEach((key,value) -> redisConnection.set(Objects.requireNonNull(keySerializer.serialize(key)), Objects.requireNonNull(valueSerializer.serialize(value)), Expiration.seconds(6 * 3600), RedisStringCommands.SetOption.UPSERT));
                return null;
            });
        }catch (Exception ex){
            log.error("pipeline存储异常",ex);
        }
    }


    public void saveByPipeLineMap(Map<String, String> dataMap, String key){
        try {
            final RedisSerializer keySerializer = redisTemplate.getKeySerializer();
            final RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
            redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {
                redisConnection.set(Objects.requireNonNull(keySerializer.serialize(key)), Objects.requireNonNull(valueSerializer.serialize(dataMap)), Expiration.seconds(6 * 3600), RedisStringCommands.SetOption.UPSERT);
                return null;
            });
        }catch (Exception ex){
            log.error("pipeline存储异常",ex);
        }
    }

    public List<Object> getByPipeLine(List<String> keys){
        try {
            return redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {
                for (String key : keys) {
                    redisConnection.get(key.getBytes(StandardCharsets.UTF_8));
                }
                return null;
            });
        }catch (Exception ex){
            log.error("pipeline读取异常",ex);
        }
        return Collections.emptyList();
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("设置缓存异常:{}",e.getLocalizedMessage());
            return false;
        }
    }

    public Long del(Set<String> key) {
        try {
            if (CollectionUtils.isNotEmpty(key)) {
                return redisTemplate.delete(key);
            }
            return null;
        }catch (Exception ex){
            log.error("删除缓存键异常:{}", ex.getLocalizedMessage());
            return null;
        }
    }

    //设置锁名,value值,过期时间
    public boolean setNX(String lockKey, String value, long expiryTime) {
        if (redisTemplate == null) {
            log.info("redisTemplate is null");
            return false;
        }

        try {
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, value, expiryTime, TimeUnit.SECONDS);
            assert flag != null;
            if (!flag) {
                log.info("加锁失败");
                return false;
            }

            log.info("Thread [{}] redisTemplate lock [{}] success", Thread.currentThread().getName(), lockKey);
            // 加锁成功
            return true;
        } catch (Exception e) {
            log.info("加锁失败", e);
            log.error("redisTemplate lock [{}] Exception:", lockKey, e);
            return false;
        }
    }
}

2 setNX测试,先设置setNX,然后进行del,此时两个方法并不是同一个线程

    @Test
    void redisDel() {
        boolean res = redisUtils.del("yuan");
        log.info("res: {}", res);
    }


    @Test
    void redisSetNX() {
        boolean res = redisUtils.setNX("yuan", "123",12*60*60L);
        log.info("res: {}", res);
    }

总结

  • 需要不同线程删除锁,使用这种方式比较合适




作为程序员第 125 篇文章,每次写一句歌词记录一下,看看人生有几首歌的时间,wahahaha ...

使用redis的setnx可以非同一线程进行加锁和解锁(附源码),第1张

Lyric: 那在终点之前


https://www.xamrdz.com/backend/3r71935962.html

相关文章: