1、单机锁

考虑在并发场景并且存在竞态的状况下,我们就要实现同步机制了,最简单的同步机制就是加锁。

加锁可以帮我们锁住资源,如内存中的变量,或者锁住临界区(线程中的一段代码),使得同一个时刻只有一个线程能访问某一个区域。

如果是单实例(单进程部署),那么单机锁就可以满足我们的要求了,如synchronized,ReentrantLock。

因为在一个进程中的不同线程可以共享这个锁。

2、分布式锁

但是如果场景来到了分布式系统呢?

分布式系统部署在不同的机器上,或者只是简单的多进程部署。这样各个进程之间无法共享同一个锁。

这时候我们要加分布式锁。

分布式锁大概就是这么一个东西:通过共享的存储缓存一个状态值,用状态值的变化标识锁的占用和释放。

可以通过mysql,redis,zk等实现分布式锁,这里我们实现一个redis的。如果你用java其实使用zk会很简单。

3、为什么redis能用来实现分布式锁?

1)Redis是单进程单线程模式

redis实现为单进程单线程模式,这样多个客户端并不存在竞态关系。

2)原子性原语

redis提供了可以实现原子操作的原语如setnx、getset等。

setnx

)SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

可用版本:
>= 1.0.
时间复杂度:
O()
返回值:
设置成功,返回 。
设置失败,返回 。

getset

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

当 key 存在但不是字符串类型时,返回一个错误。

可用版本:
>= 1.0.
时间复杂度:
O()
返回值:
返回给定 key 的旧值。
当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

4、实现

package com.xiaoju.dqa.fusor.utils;

import com.xiaoju.dqa.fusor.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class DistributeLockUtil { // 锁超时时间, 防止死锁
private static final long LOCK_TIMEOUT = 60; @Autowired
private RedisClient redisClient; private boolean locked = false; public boolean lock(String key) {
String expireTime = String.valueOf(System.currentTimeMillis() + LOCK_TIMEOUT * 1000);
/*
* setnx 返回1
* 说明: 1)key不存在, 2)成功写入锁, 并更新锁的生存时间
* 也就是get锁
* */
if (redisClient.setnx(key, expireTime) == 1) {
locked = true;
return true;
}
/*
* 没有get锁, 下面进入判断锁超时逻辑
* */
String currentExpireTime = redisClient.get(key);
/*
* 锁生存时间已经过了, 说明锁已经超时
* */
if (Long.parseLong(currentExpireTime) < System.currentTimeMillis()) {
String oldValueStr = redisClient.getSet(key, expireTime);
/*
* 判断锁生存时间和你改的写那个时间是否相等
* 相当于你竞争了一个更新锁
* */
if (oldValueStr.equals(currentExpireTime)) {
locked = true;
return true;
}
}
return false;
} public void release(String key) {
if (locked) {
redisClient.del(key);
locked = false;
}
} }

5、死锁

为了解决死锁,这里设置了锁的超时时间。

    private static final long LOCK_TIMEOUT = 60;

并通过setnx时更新锁生存时间来维护锁超时的判定。

String expireTime = String.valueOf(System.currentTimeMillis() + LOCK_TIMEOUT * 1000);
...
if (redisClient.setnx(key, expireTime) == 1) {
...
}
...
String oldValueStr = redisClient.getSet(key, expireTime);
...

为什么要使用这种方式,而不是expire呢?

因为setnx和expire不能作为一个原子性的操作存在,设想如果setnx之后,在执行expire之前出现了异常,那么锁将没有超时时间。也就是死锁。

6、解决锁超时引入的竞态

设想三个客户端,C0,C1,C2

如果C0持有锁并且崩溃,锁没有释放。

C1和C2同时发现了锁超时。

然后都通过getset去拿到了旧值,在对比了旧值和之前值之后,如果相等,那么说明“我”成功修改了旧值,那么我就拿到了锁。

7、 时钟同步

我们看到foo.lock的value值为时间戳,所以要在多客户端情况下,保证锁有效,一定要同步各服务器的时间,如果各服务器间,时间有差异。时间不一致的客户端,在判断锁超时,就会出现偏差,从而产生竞争条件。
锁的超时与否,严格依赖时间戳,时间戳本身也是有精度限制,假如我们的时间精度为秒,从加锁到执行操作再到解锁,一般操作肯定都能在一秒内完成。这样的话,我们上面的CASE,就很容易出现。所以,最好把时间精度提升到毫秒级。这样的话,可以保证毫秒级别的锁是安全的。

8、一些处理不了的情况

设想三个客户端,C0,C1,C2

如果C0持有锁很长,锁已经超时。这时候有C1,C2判断锁超时了,然后通过超时竞争,C1拿到了锁。

这时C0醒了过来,删除了C1的锁。

这时,C1认为自己独占了锁,其他的进程也进入了竞争锁的情况

对于这种情况,这里是没有提供解决办法的。

思路是:你降级你的锁,比如给你的锁加上uuid,对不同的业务或者不同的session加上对应粒度的锁。

可以看看这篇博客。

http://www.cnblogs.com/kangoroo/p/6953187.html

分布式锁的实现(redis)的更多相关文章

  1. Springboot分布式锁实践(redis)

    springboot2本地锁实践一文中提到用Guava Cache实现锁机制,但在集群中就行不通了,所以我们还一般要借助类似Redis.ZooKeeper 之类的中间件实现分布式锁,下面我们将利用自定 ...

  2. 基于zookeeper实现分布式锁和基于redis实现分布所的区别

    1,实现方式不同 zookeeper实现分布式锁:通过创建一个临时节点,创建的成功节点的服务则抢占到分布式锁,可做业务逻辑.当业务逻辑完成,连接中断,节点消失,继续下一轮的锁的抢占. redis实现分 ...

  3. 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  4. 如何用 redis 造一把分布式锁

    基本概念 锁 wiki:In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechan ...

  5. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  6. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  7. 基于Redis实现分布式锁(1)

    转自:http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部 ...

  8. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  9. Redis分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

随机推荐

  1. Kendo UI 使用小知识点汇总

    本攻略适用于了解的kendo UI 的基本语法的人使用.如果还不了解Kendo UI的基本语法,请大力观摹Demo ,并自己动用写几个控件. 1.获取Kendo UI的控件实体,不必存成全局变量.   ...

  2. Web in Linux小笔记001

    Linux灾难恢复: Root密码修复 Centos single Filesystem是硬盘文件根目录,无法再cd ..就像macitosh 硬盘图标 Pwd:显示绝对路径 MBR修复 模拟MBR被 ...

  3. [转载]PS各个工具的字母快捷键和英文全名

    原文地址:PS各个工具的字母快捷键和英文全名作者:Tycho     选框-Marquee(M)     移动-move(V)     套索-Lasso(L)     魔棒-Wand(W) 喷枪-in ...

  4. JAVA基础第六组(5道题)

    26.[程序26] 题目:请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续 判断第二个字母.         1.程序分析:用情况语句比较好,如果第一个字母一样,则判断用情况语句 ...

  5. SNS团队Beta阶段第五次站立会议(2017.5.26)

    1.立会照片 2.每个人的工作 成员 今天已完成的工作 明天计划完成的工作 罗于婕 生词本功能测试,bug修复 发音图标的改进 龚晓婷 辅助完善历史纪录的功能 对于历史记录功能的测试 林仕庄 继续完善 ...

  6. 团队作业八-Beta版本冲刺计划及安排

    Beta版本冲刺计划及安排 目录: 1.介绍小组新加入的成员,他担任的角色 2.下一阶段需要改进完善的功能 3.下一阶段新增(或修改)的功能 4.需要改进的团队分工 5.需要改进的工具流程 6.冲刺的 ...

  7. 团队作业4--第一次项目冲刺(Alpha版本)预备工作

    小组说明 我们组是从周一开始对项目进行研究讨论并编程的,因为我们看截止日期是周日,就从周一才开始,起步晚了,是我们认识上的失误,导致我们周一周二的步伐没有协调好,项目进展的不稳定,但是我们在上周末并不 ...

  8. 201521123112《Java程序设计》第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 在做PTA5.3的时候一段看起来比较复杂的代码: List<En ...

  9. 201521123029《java程序设计》第2周学习总结

    1. 本周学习总结 (1)完成了课后习题,对所学知识进一步巩固. (2)学会了JAVA中的选择结构,循环结构,其中条件必须为布尔表达式,与C++非0即真不同. (3)学会java中的break和con ...

  10. java第十四次作业

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...