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

Redis分布式锁

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

Redis分布式锁

一、Redis锁
  • 多个客户端,通过watch一个键-值,然后开启事务

  • 如果在开启事务的期间,watch的值没有被其他客户端修改过,则执行成功

  • 如果在开启事务的期间,watch的值被其他客户端修改了,则执行失败

    set name erick
    set age 10

    watch name age # 监控对应的属性
    multi
    set address shanxi
    set year 2022
    exec

    如果事务在执行期间,没有其他客户端去操作被watch的值,则A客户端的事物可以执行成功 如果想解除监控,unwatch watch必须在开启事务之前
二、分布式锁
  • 在集群模式下,synchronized只能保证单个JVM内部的线程互斥,不能保证跨JVM的互斥
1. 单个JVM

2. 多个JVM

3. 分布式锁
  • 满足分布式系统或集群模式下多进程可见并互斥的锁

    分布式锁特点
    1. 多进程可见: 必须多个jvm都能去访问到该锁资源
    2. 互斥: 锁资源必须是互斥
    3. 高可用: 锁的稳定性要得到保证
    4. 高性能: 加锁本来就会降低系统性能,如何保证
    5. 安全性: 锁假如无法释放怎么办
三、Redis分布式锁 1. 基础版本
  • 单线程保证一定只有一个线程来获取锁

    场景一: 假如锁匙放失败怎么半?
    1. 获取: SETNX k v
    2. 执行业务
    3. 释放锁 DEL k
    场景二:
    1. 获取锁,并添加过期时间 SET K V EX 10 NX
    2. 执行业务
    3. 释放锁

package com.erick.redis;

import redis.clients.jedis.Jedis;

public class Demo01 {

    public static final String LOCK_NAME = "LOCK";

    public static final String LOCK_VALUE = "ERICK";

    public static final int EXPIRE_SECS = 5;

    private static Jedis getJedis() {
        return new Jedis("60.205.229.31", 6381);
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> secondLock()).start();
        new Thread(() -> secondLock()).start();
    }

    
    public static void firstLock() {
        //1.上锁
        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");
        }
    }

    
    public static void secondLock() {
        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");
        }
    }

    private static void executeBusiness() {
        System.out.println("Business execution.....");
    }
}
2. 增强版本
  • 上面分布式锁存在问题: 误删,删已失效

    解决方法一:设置超时时间远大于业务执行时间,但是会带来性能问题

    解决方法二:删除锁的时候要判断,是不是自己的,如果是再删除 UUID

    1. 其中key可以用业务名称来表示
    2. value用uuid来表示
      2.1 删除锁时,先通过value来判断锁是不是自己线程的
      2.2 如果是,则删除,如果不是,就不要删除

    package com.erick.redis;

    import redis.clients.jedis.Jedis;

    import java.util.UUID;

    public class Demo11 {

    private static Jedis getJedis() {
        return new Jedis("60.205.229.31", 6381);
    }
    
    private static String getLockValue() {
        return UUID.randomUUID().toString();
    }
    
    private static final String LOCK_KEY = "LOCK";
    
    private static final int EXPIRE_SECS = 5;
    
    public static void main(String[] args) {
        new Thread(() -> firstMethod(LOCK_KEY, getLockValue())).start();
    }
    
    private static void firstMethod(String lockKey, String lockValue) {
        Jedis redis = getJedis();
        String lockResult = redis.set(lockKey, lockValue, "NX", "EX", EXPIRE_SECS);
        if ("OK".equalsIgnoreCase(lockResult)) {
            executeBusiness();
            String presentValue = redis.get(lockKey);
            
            
            if (lockValue.equalsIgnoreCase(presentValue)) {
                redis.del(lockKey);
                System.out.println("lock deleted");
            }
        } else {
            System.out.println("Can not get lock");
        }
    }
    
    private static void executeBusiness() {
        System.out.println("Business execution.....");
    }
    

    }

3. Lua脚本 3.1 存在的问题
  • 判断锁是否能释放,和锁真正释放的代码中间,假如存在full gc,那么就会依然出现问题
  • 判断锁是否该释放锁和释放锁,应该做成一个原子性的动作
  • 但是redis的事务机制不是强一致性
3.2 Lua脚本
  • Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令的原子性

    1. Redis内部函数

    redis.call(‘命令名称’,‘key’,‘其他参数’, …)

    2. 无参数, 0代表key的参数

    EVAL “return redis.call(‘set’,‘name’,‘erick’)” 0

    3. 带参数

    EVAL “return redis.call(‘set’,KEYS[1],ARGV[1])” 1 age 20
    KEYS[1]: redis的key值个数
    ARGV[1]: redis的value的值个数
    1: 具体包含几个key
    age: 实际传递的key值
    20: 实际传递的value值

  • 获取流程

    – 获取锁中的线程标示,动态传递参数
    local keyName = redis.call(‘get’,KEYS[1])

    – 比较线程标示与锁中的是否一直
    if (ARGV[1] == keyName) then
    – 释放锁
    redis.call(‘del’,KEYS[1])
    return 1
    – 如果不一致,则返回结果为0
    else
    return 0
    end

    private static boolean deleteLockIfMy(Jedis redis, String lockKey, String lockValue) {
    /用lua脚本来保证/
    String luaScript = "-- 获取锁中的线程标示,动态传递参数
    " +
    "local keyName = redis.call(‘get’,KEYS[1])
    " +
    "
    " +
    "-- 比较线程标示与锁中的是否一直
    " +
    "if (keyName == ARGV[1]) then
    " +
    " – 释放锁
    " +
    " redis.call(‘del’,KEYS[1])
    " +
    " return 1
    " +
    " – 如果不一致,则返回结果为0
    " +
    "else
    " +
    " return 0
    " +
    “end”;

        
        String script = redis.scriptLoad(luaScript);
        
        Object delResult = redis.evalsha(script, Arrays.asList(lockKey), Arrays.asList(lockValue));
        
        if (delResult.equals(1L)) {
            return true;
        } else {
            return false;
        }
    }
    
4. 存在的问题
  • 某些业务场景,需要对锁有更高的要求
  • 极端情况下出现的问题

四、Redisson
  • 一个用来进行分布式锁的工具类
  • org.redisson:redisson:3.16.8
0. 入门案例
package com.erick.redis;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

public class Demo03 {

    private static final String LOCK_KEY = "COMMERCE-BUSINESS";

    
    private static RedissonClient redissonClient() {
        Config config = new Config();
        
        config.useSingleServer().setAddress("redis://60.205.229.31:6381");
        return Redisson.create(config);
    }

    private static void executeBusiness() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Business executing.....");
    }

    public static void main(String[] args) {
        new Thread(() -> lockMethodWithRetry()).start();
        new Thread(() -> lockMethodWithRetry()).start();
    }

    
    private static void lockMethod() {
        RedissonClient redissonClient = redissonClient();
        
        RLock lock = redissonClient.getLock(LOCK_KEY);

        
        if (lock.tryLock()) {
            try {
                executeBusiness();
            } catch (Exception e) {
                e.printStackTrace();

            } finally {
                lock.unlock();
                System.out.println("Lock Released");
            }
        } else {
            System.out.println("Can not get lock");
        }
    }

    
    private static void lockMethodWithRetry() {
        RedissonClient redissonClient = redissonClient();
        
        RLock lock = redissonClient.getLock(LOCK_KEY);

        // 内部包含 重试机制,通过Redis的发布订阅者模式来实现
        
        boolean hasLok = false;
        try {
            hasLok = lock.tryLock(6, 20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (hasLok) {
            try {
                executeBusiness();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("Lock Released");
            }
        } else {
            System.out.println("Can not get lock");
        }
    }
}
1. 可重入 1.1 不可重入锁

1.2 可重入锁
  • 存储的键值对用Hash结构来保存
  • 为了保证多条命令的原子性,必须采取lua脚本来做

1.3 Lua脚本


2. 重试机制
  • 通过等待时间结合,发布以及订阅模式来实现
  • 不会立即触发重试机制,而是订阅当前锁的使用者发布的消息
3. 锁超时释放
  • 业务执行期间,不断有定时任务去更新过期时间
  • 业务执行完毕后,取消定时任务

4 . 主从一致性 4.1 主节点宕机

4.2 联锁
  • 设立多个redis作为主节点

  • 只有每个都获取成功的时候,才会去执行

    package com.erick.redis;

    import org.redisson.Redisson;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;

    import java.util.concurrent.TimeUnit;

    public class Test04 {
    public static void main(String[] args) {
    businessWithLock();
    }

    private static void businessWithLock() {
        String lockKey = "BUSINESS";
        RedissonClient firstClient = redissonClient01();
        RedissonClient secondClient = redissonClient02();
        RedissonClient thirdClient = redissonClient03();
    
        RLock firstLock = firstClient.getLock(lockKey);
        RLock secondLock = secondClient.getLock(lockKey);
        RLock thirdLock = thirdClient.getLock(lockKey);
    
        
        RLock multiLock = firstClient.getMultiLock(firstLock, secondLock, thirdLock);
    
        boolean hasLock = multiLock.tryLock();
        try{
            if (hasLock) {
                business();
            } else {
                System.out.println("未获取到锁,业务没有执行");
            }
        }finally {
            multiLock.unlock();
        }
    }
    
    private static void business() {
        System.out.println("执行业务");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    
    private static RedissonClient redissonClient01() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://60.205.229.31:6379");
        return Redisson.create(config);
    }
    
    private static RedissonClient redissonClient02() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://60.205.229.31:6380");
        return Redisson.create(config);
    }
    
    private static RedissonClient redissonClient03() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://60.205.229.31:6381");
        return Redisson.create(config);
    }
    

    }

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

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

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

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