栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

# redis相关

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

# redis相关

https://zhuanlan.zhihu.com/p/135864820

实现分布式锁

  • 在集群模式下,synchronized只能保证单个JVM内部的线程互斥,不能保证跨JVM的互斥

1、redisTemplate是基于某个具体实现的再封装,比如说springBoot1.x时,具体实现是jedis;而到了springBoot2.x时,具体实现变成了lettuce。封装的好处就是隐藏了具体的实现,使调用更简单,但是有人测试过jedis效率要10-30倍的高于redisTemplate的执行效率,所以单从执行效率上来讲,jedis完爆redisTemplate。redisTemplate的好处就是基于springBoot自动装配的原理,使得整合redis时比较简单。

2、jedis作为老牌的redis客户端,采用同步阻塞式IO,采用线程池时是线程安全的。优点是简单、灵活、api全面,缺点是某些redis高级功能需要自己封装。

3、lettuce作为新式的redis客户端,基于netty采用异步非阻塞式IO,是线程安全的,优点是提供了很多redis高级功能,例如集群、哨兵、管道等,缺点是api抽象,学习成本高。lettuce好是好,但是jedis比他生得早。

4、redission作为redis的分布式客户端,同样基于netty采用异步非阻塞式IO,是线程安全的,优点是提供了很多redis的分布式操作和高级功能,缺点

是api抽象,学习成本高。

Jedissetnx

 

jedis是redis的java客户端,通过它可以对redis进行操作。与之功能相似的还包括:Lettuce等

spring-boot-data-redis 内部实现了对Lettuce和jedis两个客户端的封装,默认使用的是Lettuce

lock_name 使用线程id或者一个唯一id

Jedis redis = getJedis();        
Long lockResult = redis.setnx(LOCK_NAME, LOCK_VALUE);
 if (1 == lockResult) {
            // 2. 执行业务
            executeBusiness();
            // 3. 释放锁
            redis.del(LOCK_NAME);
  } else {
            // 获取锁失败
            System.out.println("Can not get lock");
        }      



Jedis redis = getJedis();
String lockResult = redis.set(LOCK_NAME, LOCK_VALUE, "NX", "EX", EXPIRE_SECS);
if ("OK".equalsIgnoreCase(lockResult)) {
            executeBusiness();
            redis.del(LOCK_NAME);
} else {
            System.out.println("Can not get lock");
        }        
Redisson(Lua脚本)
KEYS[1]   key
ARGV[1]   key 的默认生存时间
ARGV[2]   加锁的客户端的 ID


if (redis.call('exists', KEYS[1]) == 0) then " +              //值过来先判断是否存在
   "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +           //命令设置一个 hash 结构
   "redis.call('pexpire', KEYS[1], ARGV[1]); " +              //设置存活时间
   "return nil; " +
   "end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +          //myLockkey的hash 数据结构中是否包含客户端2的ID
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
    "return nil; " +
    "end; " +
"return redis.call('pttl', KEYS[1]);"  //返回myLock 这个锁key 的剩余生存时间

续期机制

Redisson 提供了一个续期机制, 只要客户端 1 一旦加锁成功,就会启动一个 Watch Dog。

private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
    if (leaseTime != -1) {
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e != null) {
            return;
        }

        // lock acquired
        if (ttlRemaining == null) {
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

 

Watch Dog 机制必须使用默认的加锁时间为 30s。(`leaseTime` 为 -1 开启 Watch Dog 机制,)
自定义时间,超过这个时间,锁就会自定释放,并不会延长。

Watch Dog 机制是一个后台定时任务线程
获取锁成功之后,会将持有锁的线程放入到一个 `RedissonLock.EXPIRATION_RENEWAL_MAP`里面
每隔 10 秒 检查一下,如果客户端 1 还持有锁 key(判断客户端是否还持有 key,其实就是遍历 `EXPIRATION_RENEWAL_MAP` 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的生存时间。

 

Lettuce
public class RedisLockUtil {

    
    public static boolean lock(RedisTemplate redisTemplate, String key, String value, int timeout, int number, int interval) {
        //加锁
        for (int i = 0; i < number; i++) {
            //尝试获取锁,成功则返回不成功则重试
            if (redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(timeout))) {
                return true;
            }
            //暂停
            try {
                TimeUnit.MILLISECONDS.sleep(interval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //最终获取不到锁返回失败
        return false;
    }

    public static boolean lock(RedisTemplate redisTemplate, String key, String value) {
        return lock(redisTemplate, key, value, 30, 3, 1000);
    }

    
    private static String UN_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    
    public static void unLock(RedisTemplate redisTemplate, String key, String value) {
        //解锁
        redisTemplate.execute(new DefaultRedisScript<>(UN_LOCK_SCRIPT, Long.class), Collections.singletonList(key), value);
    }

    
    public static String getMethodPath() {
        StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];
        //获取当前类名
        String className = stackTraceElement.getClassName();
        //获取当前方法名
        String methodName = stackTraceElement.getMethodName();
        return className + "#" + methodName;
    }

    
    public static String getRandom() {
        return Objects.toString(ThreadLocalRandom.current().nextInt(0, 100000));
    }
}

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/826844.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号