Redis分布式锁,基于StringRedisTemplate和基于Lettuce实现setNx

Stella981
• 阅读 1138

使用redis分布式锁,来确保多个服务对共享数据操作的唯一性
一般来说有StringRedisTemplate和RedisTemplate两种redis操作模板。

根据key-value的类型决定使用哪种模板,如果k-v均是String类型,则使用StringRedisTemplate,否则使用RedisTemplate

redis加锁操作
必须遵循原子性操作,保证加锁的唯一性
核心方法
set(lockKey,value,"NXXX","EXPX",expireTime)
NXXX:只能取NX或者XX,NX-key不存在时进行保存,XX-key存在时才进行保存
EXPX:过期时间单位 (EX,PX),EX-秒,PX-毫秒

使用StringRedisTemplate实现加锁

public class StringRedisTemplateImplClient {
    // NX,XX
    //NX-key不存在则保存,XX-key存在则保存
    private static final String STNX= "NX";

    //EX,PX
    //EX-秒,PX-毫秒
    private static final String SET_EXPIRE_TIME = "PX";

    private RedisTemplate redisTemplate;
    private StringRedisTemplate stringRedisTemplate;
    

    public StringRedisTemplateImplClient(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = new StringRedisTemplate();
        this.stringRedisTemplate.setConnectionFactory(redisTemplate.getConnectionFactory());
        this.stringRedisTemplate.afterPropertiesSet();
    }

    public StringRedisTemplateImplClient(StringRedisTemplate redisTemplate){
        this.stringRedisTemplate = redisTemplate;
    }
    
    public boolean addRedisLock(String lockKey,String requestId,long expireTime){
        boolean result = stringRedisTemplate.opsForValue()
            .setIfAbsent(lockKey,lockKey,expireTime, TimeUnit.SECONDS);
        return result;
    }
}

下面简要分析下这个方法的一致性,查看setIfAbsent的源码

setIfAbsent这个方法是spring-data-redis提供的,分析其源码,实现类为org.springframework.data.redis.core.DefaultValueOperations
方法如下:
key-需要加锁的key
value-需要加锁的value
timeout-失效的时间
timeunit-常量,指定失效的时间单位

connection默认为lettuce驱动连接(springboot 2.0以后的版本)

public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {
    byte[] rawKey = this.rawKey(key);
    byte[] rawValue = this.rawValue(value);
    Expiration expiration = Expiration.from(timeout, unit);
    return (Boolean)this.execute((connection) -> {
        return connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent());
    }, true);
}

其中SetOption为指定NX/XX类型

public static enum SetOption {
    UPSERT,
    SET_IF_ABSENT,
    SET_IF_PRESENT;

    private SetOption() {
    }

    public static RedisStringCommands.SetOption upsert() {
        return UPSERT;
    }

    public static RedisStringCommands.SetOption ifPresent() {
        return SET_IF_PRESENT;
    }

    public static RedisStringCommands.SetOption ifAbsent() {
        return SET_IF_ABSENT;
    }
}

查询spring官网查询这三个属性

SET_IF_ABSENT--->NX
SET_IF_PRESENT--->XX

Redis分布式锁,基于StringRedisTemplate和基于Lettuce实现setNx

自定义实现SetNx

setNx+expireTime实现加锁

 核心代码

String status = stringRedisTemplate.execute(new RedisCallback<String>() {
  @Override
  public String doInRedis(RedisConnection connection) throws DataAccessException {
    Object nativeConnection = connection.getNativeConnection();
    String status = null;
    RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) stringRedisTemplate.getKeySerializer();

    byte[] keyByte = stringRedisSerializer.serialize(key);
    //springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包

    //lettuce连接包,集群模式,ex为秒,px为毫秒
    if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
      logger.debug("lettuce Cluster:---setKey:"+setKey+"---value"+value+"---maxTimes:"+expireSeconds);
      status = ((RedisAdvancedClusterAsyncCommands) nativeConnection)
          .getStatefulConnection().sync()
          .set(keyByte,keyByte,SetArgs.Builder.nx().ex(30));
      logger.debug("lettuce Cluster:---status:"+status);
    }
    //lettuce连接包,单机模式,ex为秒,px为毫秒
    if (nativeConnection instanceof RedisAsyncCommands) {
      logger.debug("lettuce single:---setKey:"+setKey+"---value"+value+"---maxTimes:"+expireSeconds);
      status = ((RedisAsyncCommands ) nativeConnection)
      .getStatefulConnection().sync()
      .set(keyByte,keyByte, SetArgs.Builder.nx().ex(30));
      logger.debug("lettuce single:---status:"+status);
    }
    return status;
  }
});
logger.debug("getLock:---status:"+status);//执行正确status="OK"
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
Nginx + lua +[memcached,redis]
精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用
Stella981 Stella981
3年前
SpringBoot使用RedisTemplate操作Redis时,key值出现 -xac-xed-x00-x05t-x00-tb
原因分析原因与RedisTemplate源码中的默认序列化方式有关defaultSerializernewJdkSerializationRedisSerializer(classLoader!null?classLoader:this.getClass().getClassLoader()
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Easter79 Easter79
3年前
SpringBoot使用RedisTemplate操作Redis时,key值出现 -xac-xed-x00-x05t-x00-tb
原因分析原因与RedisTemplate源码中的默认序列化方式有关defaultSerializernewJdkSerializationRedisSerializer(classLoader!null?classLoader:this.getClass().getClassLoader()
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这