分布式锁(2) ----- 基于redis的分布式锁

Redis单机版实现

set和lua实现

获取锁

SET resource_name my_random_value NX PX 30000

NX key不存在时才set
PX 设置过期时间
my_random_value 要保证每台客户端的每个锁请求唯一,可以使用UUID+ThreadID
该命令在Redis 2.6.12才有,网上有基于setnx、epire的实现和基于setnx、get、getset的实现,这些多多少少都有点瑕疵,大概率是旧版本的redis实现,建议高版本的redis还是使用这个实现。

释放锁

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

通过上述lua脚本实现,先判断锁是否该请求拥有,防止误解锁。

java代码

public boolean tryLock(long waitTime, long leaseTime) {
    long end = Calendar.getInstance().getTimeInMillis() + waitTime;
    Jedis jedis = jedisPool.getResource();
    try {
        do {
            String result = jedis.set(lockName, getClientId(), "NX", "EX", leaseTime);
            if ("OK".equals(result)) {
                return true;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return false;
            }
        } while (Calendar.getInstance().getTimeInMillis() < end);
    }finally {
        if(jedis != null) {
            jedis.close();
        }
    }
    return false;
}

public boolean unlock() {
    String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
    Jedis jedis = jedisPool.getResource();
    try {
        Object obj = jedis.eval(lua, Collections.singletonList(lockName), Collections.singletonList(getClientId()));
        if (obj.equals(1)) {
            return true;
        }
    }finally {
        if(jedis != null){
            jedis.close();
        }
    }
    return false;
}

测试代码

public void lockTest() {
    //用线程模拟进程
    ExecutorService threadPool = Executors.newFixedThreadPool(20);
    CyclicBarrier barrier = new CyclicBarrier(20);
    for (int i = 0; i < 20; i++) {
        threadPool.execute(new RedisLockTest(barrier));
    }
    threadPool.shutdown();
    while (!threadPool.isTerminated()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
private static class RedisLockTest implements Runnable{

    private CyclicBarrier barrier;
    RedisLockTest(CyclicBarrier barrier){
        this.barrier = barrier;
    }

    @Override
    public void run() {
        //模拟一台客户端一个jedisPool
        JedisPool jedisPool = new JedisPool("192.168.9.150", 6379);
        try {
            DistributeLock lock = new SingletonRedisLock(jedisPool, "lock");
            barrier.await();
            boolean flag = lock.tryLock(Integer.MAX_VALUE, 300);
            try {
                System.out.println(Thread.currentThread().getId() + "get lock:flag=" + flag);
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getId() + "get unlock");
            } finally {
                lock.unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedisPool.close();
        }
    }
}

存在的问题

该实现简单,但存在以下问题:
1.没有实现可重入
2.没有锁续租,如果代码在锁的租期内没有执行完成,那么锁过期会导致另一个客户端获取锁

Redisson实现

private static class RedissonLockTest implements Runnable{

    private CyclicBarrier barrier;
    RedissonLockTest(CyclicBarrier barrier){
        this.barrier = barrier;
    }

    @Override
    public void run() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.9.150:6379");
        RedissonClient client = Redisson.create(config);
        try {
            RLock lock = client.getLock("lock");
            barrier.await();
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "get lock");
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "get unlock");
            } finally {
                lock.unlock();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            client.shutdown();
        }
    }
}

Redisson的分布式锁实现可重入,同时有LockWatchDogTimeout来实现锁续约
github:https://github.com/redisson/redisson

Redis多实例版实现

单机实现在多实例下问题

如果Redis实现了集群,由于主从之间时通过异步复制的,假设客户端A在主机上获得锁,这时在未将锁数据复制到从机时,主机挂了,从机切换为主机,那么从机没有这条数据,客户端B同样可以获得锁。

RedLock算法

使用多个独立(非集群)的实例来实现分布式锁,由于实例独立不需复制同步,所以没有上述问题;而保证可用性的是靠数据冗余,将数据多存放几份在不同的实例上。算法如下:

  1. 使用相同的key和value从N个实例上获取锁;
  2. 当从大于(N/2+1)个实例获取锁的时间<锁的过期时间,才获取锁成功
  3. 如果获取失败,解锁所有实例

Redisson实现

public void redLockTest() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.9.150:7000");
    RedissonClient client = Redisson.create(config);
    Config config1 = new Config();
    config1.useSingleServer().setAddress("redis://192.168.9.150:7001");
    RedissonClient client1 = Redisson.create(config1);
    Config config2 = new Config();
    config2.useSingleServer().setAddress("redis://192.168.9.150:7002");
    RedissonClient client2 = Redisson.create(config2);
    try{
        RLock lock = client.getLock("lock");
        RLock lock1 = client1.getLock("lock");
        RLock lock2 = client2.getLock("lock");
        RedissonRedLock redLock = new RedissonRedLock(lock, lock1, lock2);
        redLock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "get lock");
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName() + "get unlock");
        } finally {
            redLock.unlock();
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        client.shutdown();
        client1.shutdown();
        client2.shutdown();
    }
}

参考资料

https://redis.io/topics/distlock
https://github.com/redisson/redisson/wiki

原文地址:https://www.cnblogs.com/wuweishuo/p/10618618.html

时间: 2024-10-29 05:46:21

分布式锁(2) ----- 基于redis的分布式锁的相关文章

基于redis的分布式锁实现

关于分布式锁 很久之前有讲过并发编程中的锁并发编程的锁机制:synchronized和lock.在单进程的系统中,当存在多个线程可以同时改变某个变量时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量.而同步的本质是通过锁来实现的.为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记.

基于Redis的分布式锁到底安全吗(上)?

网上有关Redis分布式锁的文章可谓多如牛毛了,不信的话你可以拿关键词"Redis 分布式锁"随便到哪个搜索引擎上去搜索一下就知道了.这些文章的思路大体相近,给出的实现算法也看似合乎逻辑,但当我们着手去实现它们的时候,却发现如果你越是仔细推敲,疑虑也就越来越多. 实际上,大概在一年以前,关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间就发生过一场争论.由于对这个问题一直以来比较关注,所以我前些日子仔细阅读了与这场争

基于redis的分布式锁

<?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * https://gist.github.com/nickyleach/3694555 */ pc_base::load_sys_class('cache_redis', '', 0); class dist_key_redis { //锁的超时时间 const TIMEOUT = 20; const SL

转载:基于Redis实现分布式锁

转载:基于Redis实现分布式锁  ,出处: http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系.其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制. Redis命令介绍使用Redis实现分

【redis】基于redis实现分布式并发锁

基于redis实现分布式并发锁(注解实现) 说明 前提, 应用服务是分布式或多服务, 而这些"多"有共同的"redis"; GitHub: https://github.com/vergilyn/SpringBootDemo 代码结构: 一.分布式并发锁的几种可行方案 (具体实现思路参考: 分布式锁的实现.如何用消息系统避免分布式事务?) 1.基于数据库 可以用数据库的行锁for update, 或专门新建一张锁控制表来实现. 过于依赖数据库, 且健壮性也不是特别好

基于redis的分布式锁(不适合用于生产环境)

基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境,另一个是集群环境下的Redis锁实现.在介绍分布式锁的实现之前,先来了解下分布式锁的一些信息. 2 分布式锁 2.1 什么是分布式锁? 分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥

[Redis] 基于redis的分布式锁

前言分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁. 可靠性首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性.在任意时刻,只有一个客户端能持有锁.不会发生死锁.即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁.具有容错性.只要大部分的Redis节点正常运行,客户端就可以加锁和解锁.解铃还须系铃人.加锁和解锁必须

python基于redis实现分布式锁

阅读目录 什么事分布式锁 基于redis实现分布式锁 一.什么是分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的锁进行处理,并且可以完美的运行,毫无Bug! 注意这是单机应用,后来业务发展,需要做集群,一个应用需要部署到几台机器上然后做负载均衡,大致如下图: 上图可以看到,变量A存在三个服务器内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),如果不加任何控制的话,变量A同时都会在分配一块内存,三个请求发过来同时对这个变

基于redis的分布式锁的分析与实践

转:https://my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于线程之间是否相互阻塞. 那么,本文主要来讨论基于redis的分布式锁算法问题. 从2.6.12版本开始,redis为SET命令增加了一系列选项(SET key value [EX seconds] [PX

基于Redis的分布式锁和Redlock算法

1 前言 前面写了4篇Redis底层实现和工程架构相关文章,感兴趣的读者可以回顾一下: Redis面试热点之底层实现篇-1 Redis面试热点之底层实现篇-2 Redis面试热点之工程架构篇-1 Redis面试热点之工程架构篇-2 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用. 结合笔者的日常工作,