文档章节

redis分布式锁

明舞
 明舞
发布于 2015/10/16 14:05
字数 1169
阅读 8409
收藏 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会不断尝试去获取锁,直到超时。也就是说,如果长时间获取不到,就会获取锁失败,相当于没加锁!具体的超时时间设置为多长,有待后期验证,再做优化。

 

本文转载自:

明舞
粉丝 229
博文 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做分布式锁这几个要注意

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

编辑之路
05/05
436
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. 在某些场景......

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

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

Java周某人
07/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
43分钟前
5
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
今天
8
0
详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

箭头函数是ES6的API,相信很多人都知道,因为其语法上相对于普通函数更简洁,深受大家的喜爱。就是这种我们日常开发中一直在使用的API,大部分同学却对它的了解程度还是不够深... 普通函数和...

OBKoro1
今天
7
0
轻量级 HTTP(s) 代理 TinyProxy

CentOS 下安装 TinyProxy yum install -y tinyproxy 启动、停止、重启 # 启动service tinyproxy start# 停止service tinyproxy stop# 重启service tinyproxy restart 相关配置 默认...

Anoyi
今天
2
0
Linux创建yum仓库

第一步、搞定自己的光盘 #创建文件夹 mkdir -p /media/cdrom #挂载光盘 mount /dev/cdrom /media/cdrom #编辑配置文件使其永久生效 vim /etc/fstab 第二步,编辑yun源 vim /ect yum.repos.d...

究极小怪兽zzz
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部