文档章节

redis分布式锁

明舞
 明舞
发布于 2015/10/16 14:05
字数 1169
阅读 1.7W
收藏 21


 关于分布式锁的概念网上太多了,这里就不罗嗦了。对于开发者来说,最关心的应该是什么情况下使用分布式锁。

使用分布式锁,一般要满足以下几个条件:

· 分布式系统(关键是分布式)

· 共享资源(各系统访问同一资源,资源的载体可能是传统关系型数据库或者NoSQL)

· 同步访问(没有同步访问,谁管你资源竞争不竞争)

场景案例

· 光说不练假把式,上点干货。管理后台的部署架构(多台tomcat服务器+redis+mysql)就满足使用分布式锁的条件。多台服务器要访问redis全局缓存的资源,如果不使用分布式锁就会出现问题。 看如下伪代码:

long N=0L;
//N从redis获取值
if(N<5){
N++;
//N写回redis
}


· 从redis获取值N,对数值N进行边界检查,自加1,然后N写回redis中。 这种应用场景很常见,像秒杀,全局递增ID、IP访问限制等。以IP访问限制来说,恶意攻击者可能发起无限次访问,并发量比较大,分布式环境下对N的边界检查就不可靠,因为从redis读的N可能已经是脏数据。传统的加锁的做法(如java的synchronized和Lock)也没用,因为这是分布式环境,这个同步问题的救火队员也束手无策。在这危急存亡之秋,分布式锁终于有用武之地了。

所谓知己知彼,百战百胜。要想用好他,首先你要了解他。分布式锁可以基于很多种方式实现,比如zookeeper、redis...。不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

这里主要讲如何用redis实现分布式锁。

实现原理

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。

SETNX命令(SET if Not eXists) 语法: SETNX key value 功能: 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。 有了以上Redis知识了,写个简单的分布式锁就不是什么难事。

直接贴java代码!

public class RedisLock implements Lock {

    @Autowired

    protected StringRedisTemplate redisTemplate;

    private static final Logger logger = Logger.getLogger(RedisLock.class);

    // lock flag stored in redis
    private static final String LOCKED = "TRUE";

    // timeout(ms)
    private static final long TIME_OUT = 30000;

    // lock expire time(s)
    public static final int EXPIRE = 60;

    // private Jedis jedis;
    private String key;

    // state flag
    private volatile boolean locked = false;

    private static ConcurrentMap<String, RedisLock> map = Maps.newConcurrentMap();

    public RedisLock(String key) {
        this.key = "_LOCK_" + key;
        redisTemplate = (StringRedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
    }

    public static RedisLock getInstance(String key) {
        return map.getOrDefault(key, new RedisLock(key));
    }

    public void lock(long timeout) {
        long nano = System.nanoTime();
        timeout *= 1000000;
        final Random r = new Random();
        try {
            while ((System.nanoTime() - nano) < timeout) {
                if (redisTemplate.getConnectionFactory().getConnection().setNX(key.getBytes(), LOCKED.getBytes())) {
                    redisTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
                    locked = true;
                    logger.debug("add RedisLock[" + key + "].");
                    break;
                }
                Thread.sleep(3, r.nextInt(500));
            }
        } catch (Exception e) {
        }
    }

    @Override
    public void unlock() {
        if (locked) {
            logger.debug("release RedisLock[" + key + "].");
            redisTemplate.delete(key);
        }
    }

    @Override
    public void lock() {
        lock(TIME_OUT);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
}

基准测试

饭已OK,快来MIXI啦。

~~~等等,还得让评审来评价下这饭做得好不好吃。

写一段测试代码来对比测试下。

public void concurrentTest() {
        final Printer outer = new Printer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                outer.output("I am a boy.");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                outer.output("You are a girl.");
            }
        }).start();
    }
?
不加锁的Printer:
?
class Printer {
      public void output(String name) {

          for (int i = 0; i < name.length(); i++) {
              System.out.print(name.charAt(i));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }
?
?使用java内置的锁ReentrantLock:
Lock lock=new ReentrantLock();class Printer {
      public void output(String name) {
          lock.lock();
          for (int i = 0; i < name.length(); i++) {
              System.out.print(name.charAt(i));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          lock.unlock();
      }
  }
?
?使用分布式锁RedisLock:
Lock lock=new ReentrantLock();class Printer {
      public void output(String name) {
         Lock lock = new RedisLock("lock1");
          lock.lock();
          for (int i = 0; i < name.length(); i++) {
              System.out.print(name.charAt(i));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          lock.unlock();
      }
  }

对比测试结果如下:

项目

不加锁

java内置锁ReentrantLock

加分布式锁RedisLock

测试结果

IY oau m aar eb oa yg.irl.

I am a boy.You are a girl.

You are a girl.I am a boy.

写在后面

话说RedisLock能够正常使用了,也达到了预期效果。是不是这个分布式锁就万无一失呢?

这是一个悲观锁,RedisLock会不断尝试去获取锁,直到超时。也就是说,如果长时间获取不到,就会获取锁失败,相当于没加锁!具体的超时时间设置为多长,有待后期验证,再做优化。

 

本文转载自:

明舞
粉丝 231
博文 424
码字总数 516555
作品 0
程序员
私信 提问
加载中

评论(3)

QAQ-Lee
QAQ-Lee
3. ApplicationContextHolder 这个类也没有找到。
QAQ-Lee
QAQ-Lee
代码不完整或者有误吧?
1. Logger.getLogger(RedisLock.class); 这里应该是:LoggerFactory.getLogger
2. Maps.newConcurrentMap(),这里也没有找到Maps。
请作者解答一下疑惑,谢谢!
F
Freeman211314
谢谢分享。
redis做分布式锁这几个要注意

一、为什么需要分布式锁 随着互联网的兴起,现代软件发生了翻天覆地的变化,以前单机的程序,已经支撑不了现代的业务。无论是在抗压,还是在高可用等方面都需要多台计算机协同工作来解决问题...

编辑之路
2019/05/05
503
0
Spring-data-redis + redis 分布式锁(二)

分布式锁的解决方式 基于数据库表做乐观锁,用于分布式锁。(适用于小并发) 使用memcached的add()方法,用于分布式锁。 使用memcached的cas()方法,用于分布式锁。(不常用) 使用redis的setnx...

xiaolyuh
2017/11/16
0
0
一文弄懂“分布式锁”,一直以来你的选择依据正确吗?

我们本文主要会关注的问题是“分布式锁”的问题。 多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。 常见的分布式...

向南
2018/12/04
0
0
慢谈 Redis 实现分布式锁 以及 Redisson 源码解析

# 产生背景 Distributed locks are a very useful primitive in many environments where different processes must operate with shared resources in a mutually exclusive way. 在某些场景......

2019/01/30
0
0
Redis实现分布式锁与Zookeeper实现分布式锁区别

# Redis实现分布式锁与Zookeeper实现分布式锁区别 **前言: 在学习过程中,简单的整理了一些redis跟zookeeper实现分布式锁的区别,有需要改正跟补充的地方,希望各位大佬及时指出 Redis实现分...

Java周某人
2019/07/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Kettle自定义jar包供javascript使用

我们都知道 Kettle 是用 Java 语言开发,并且可以在 JavaScript 里面直接调用 java 类方法。所以有些时候,我们可以自定义一些方法,来供 JavaScript 使用。 本篇文章有参考自:https://www...

CREATE_17
昨天
82
0
处理CSV文件中的逗号

我正在寻找有关如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且该值可能带有逗号(例如公司名称)。 我们正在研究的一些想法是:带引号的标识符(值“,”值“,”等)或使用|...

javail
昨天
79
0
如何克隆一个Date对象?

将Date变量分配给另一个变量会将引用复制到同一实例。 这意味着更改一个将更改另一个。 如何实际克隆或复制Date实例? #1楼 简化版: Date.prototype.clone = function () { return new ...

技术盛宴
昨天
73
0
计算一个数的数位之和

计算一个数的数位之和 例如:128 :1+2+8 = 11 public int numSum(int num) { int sum = 0; do { sum += num % 10; } while ((num = num / 10) > 0); return sum;......

SongAlone
昨天
124
0
为什么图片反复压缩后普遍会变绿,而不是其他颜色?

作者:Lion Yang 链接:https://www.zhihu.com/question/29355920/answer/119088684 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 业余版概要:安卓的...

shzwork
昨天
71
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部