一、简介

一般来说,对数据进行加锁时,程序先通过acquire获取锁来对数据进行排他访问,然后对数据进行一些列的操作,最后需要释放锁。Redis 本身用 watch命令进行了加锁,这个锁是乐观锁。使用 watch命令对于频繁访问的键会引起性能的问题。

二、redis命令介绍

  • SETNX命令(SET if Not eXists)
  1. 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0
  • SETEX命令
  1. 设置超时时间
  • GET命令
  1. 返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil
  • DEL命令
  1. 删除给定的一个或多个 key ,不存在的 key 会被忽略。

三、实现思路

由于redis的setnx命令天生就适合用来实现锁的功能,这个命令只有在键不存在的情况下为键设置值。获取锁之后,其他程序再设置值就会失败,即获取不到锁。获取锁失败。只需不断的尝试获取锁,直到成功获取锁,或者到设置的超时时间为止。

另外为了防治死锁,即某个程序获取锁之后,程序出错,没有释放,其他程序无法获取锁,从而导致整个分布式系统无法获取锁而导致一系列问题,甚至导致系统无法正常运行。这时需要给锁设置一个超时时间,即setex命令,锁超时后,从而其它程序就可以获取锁了。

四、编码实现

本文采用springboot结合redis 取实现的,所以你需要装一个redis。

  1. 首先引入创建springboot工程,引入redis 。
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. <scope>test</scope>
  5. </dependency>
  6.  
  7. <!-- 开启web-->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12.  
  13. <!-- redis-->
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-data-redis</artifactId>
  17. </dependency>

  2.创建一个锁类

  1. /**
  2. * 全局锁,包括锁的名称
  3. * Created by fangzhipeng on 2017/4/1.
  4. */
  5. public class Lock {
  6. private String name;
  7. private String value;
  8.  
  9. public Lock(String name, String value) {
  10. this.name = name;
  11. this.value = value;
  12. }
  13.  
  14. public String getName() {
  15. return name;
  16. }
  17.  
  18. public String getValue() {
  19. return value;
  20. }
  21.  
  22. }

  3.创建分布式锁的具体方法,思路已经说清楚了,代码注释也写好了,就不讲解了。

  1. import org.apache.commons.lang.StringUtils;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.data.redis.core.StringRedisTemplate;
  6. import org.springframework.data.redis.core.ValueOperations;
  7. import org.springframework.stereotype.Component;
  8.  
  9. import java.util.concurrent.TimeUnit;
  10.  
  11. /**
  12. * Created by fangzhipeng on 2017/4/1.
  13. */
  14. @Component
  15. public class DistributedLockHandler {
  16.  
  17. private static final Logger logger = LoggerFactory.getLogger(DistributedLockHandler.class);
  18. private final static long LOCK_EXPIRE = 30 * 1000L;//单个业务持有锁的时间30s,防止死锁
  19. private final static long LOCK_TRY_INTERVAL = 30L;//默认30ms尝试一次
  20. private final static long LOCK_TRY_TIMEOUT = 20 * 1000L;//默认尝试20s
  21.  
  22. @Autowired
  23. private StringRedisTemplate template;
  24.  
  25. /**
  26. * 尝试获取全局锁
  27. *
  28. * @param lock 锁的名称
  29. * @return true 获取成功,false获取失败
  30. */
  31. public boolean tryLock(Lock lock) {
  32. return getLock(lock, LOCK_TRY_TIMEOUT, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
  33. }
  34.  
  35. /**
  36. * 尝试获取全局锁
  37. *
  38. * @param lock 锁的名称
  39. * @param timeout 获取超时时间 单位ms
  40. * @return true 获取成功,false获取失败
  41. */
  42. public boolean tryLock(Lock lock, long timeout) {
  43. return getLock(lock, timeout, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
  44. }
  45.  
  46. /**
  47. * 尝试获取全局锁
  48. *
  49. * @param lock 锁的名称
  50. * @param timeout 获取锁的超时时间
  51. * @param tryInterval 多少毫秒尝试获取一次
  52. * @return true 获取成功,false获取失败
  53. */
  54. public boolean tryLock(Lock lock, long timeout, long tryInterval) {
  55. return getLock(lock, timeout, tryInterval, LOCK_EXPIRE);
  56. }
  57.  
  58. /**
  59. * 尝试获取全局锁
  60. *
  61. * @param lock 锁的名称
  62. * @param timeout 获取锁的超时时间
  63. * @param tryInterval 多少毫秒尝试获取一次
  64. * @param lockExpireTime 锁的过期
  65. * @return true 获取成功,false获取失败
  66. */
  67. public boolean tryLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
  68. return getLock(lock, timeout, tryInterval, lockExpireTime);
  69. }
  70.  
  71. /**
  72. * 操作redis获取全局锁
  73. *
  74. * @param lock 锁的名称
  75. * @param timeout 获取的超时时间
  76. * @param tryInterval 多少ms尝试一次
  77. * @param lockExpireTime 获取成功后锁的过期时间
  78. * @return true 获取成功,false获取失败
  79. */
  80. public boolean getLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
  81. try {
  82. if (StringUtils.isEmpty(lock.getName()) || StringUtils.isEmpty(lock.getValue())) {
  83. return false;
  84. }
  85. long startTime = System.currentTimeMillis();
  86. do{
  87. if (!template.hasKey(lock.getName())) {
  88. ValueOperations<String, String> ops = template.opsForValue();
  89. ops.set(lock.getName(), lock.getValue(), lockExpireTime, TimeUnit.MILLISECONDS);
  90. return true;
  91. } else {//存在锁
  92. logger.debug("lock is exist!!!");
  93. }
  94. if (System.currentTimeMillis() - startTime > timeout) {//尝试超过了设定值之后直接跳出循环
  95. return false;
  96. }
  97. Thread.sleep(tryInterval);
  98. }
  99. while (template.hasKey(lock.getName())) ;
  100. } catch (InterruptedException e) {
  101. logger.error(e.getMessage());
  102. return false;
  103. }
  104. return false;
  105. }
  106.  
  107. /**
  108. * 释放锁
  109. */
  110. public void releaseLock(Lock lock) {
  111. if (!StringUtils.isEmpty(lock.getName())) {
  112. template.delete(lock.getName());
  113. }
  114. }
  115.  
  116. }

  4.用法:

  1. @Autowired
  2. DistributedLockHandler distributedLockHandler;
  3. Lock lock=new Lock("lockk","sssssssss);
  4. if(distributedLockHandler.tryLock(lock){
  5. doSomething();
  6. distributedLockHandler.releaseLock();
  7. }

五、注意点

在使用全局锁时为了防止死锁采用 setex命令,这种命令需要根据具体的业务具体设置锁的超时时间。另外一个就是锁的粒度性。比如在redis实战中有个案列,为了实现买卖市场交易的功能,把整个交易市场都锁住了,导致了性能不足的情况,改进方案只对买卖的商品进行加锁而不是整个市场。

如何在springcloud分布式系统中实现分布式锁?的更多相关文章

  1. java就业指南 zookeeper分布式系统 zookeeper实现分布式锁 有用

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个 分布式系统都无法同时满足一致性(Consistency).可用性 ...

  2. redis中的分布式锁

    分布式锁的实现场景 在平时的开发中,对于高并发的开发场景,我们不可避免要加锁进行处理,当然redis中也是不可避免的,下面是我总结出来的几种锁的场景 Redis分布式锁方案一 使用Redis实现分布式 ...

  3. 分布式锁之三:Redlock实现分布式锁

    之前写过一篇文章<如何在springcloud分布式系统中实现分布式锁?>,由于自己仅仅是阅读了相关的书籍,和查阅了相关的资料,就认为那样的是可行的.那篇文章实现的大概思路是用setNx命 ...

  4. 如何用Redlock实现分布式锁

    转载请标明出处: http://blog.csdn.net/forezp/article/details/70305336 本文出自方志朋的博客 之前写过一篇文章<如何在springcloud分 ...

  5. 基于zookeeper或redis实现分布式锁

    前言 在分布式系统中,分布式锁是为了解决多实例之间的同步问题.例如master选举,能够获取分布式锁的就是master,获取失败的就是slave.又或者能够获取锁的实例能够完成特定的操作. 目前比较常 ...

  6. Spring Cloud分布式微服务系统中利用redssion实现分布式锁

    在非分布式系统中要实现锁的机制很简单,利用java.util.concurrent.locks包下的Lock和关键字synchronized都可以实现.但是在分布式系统中,如何实现各个单独的微服务需要 ...

  7. 分布式锁中的王者方案-Redisson

    上篇讲解了如何用 Redis 实现分布式锁的五种方案,但我们还是有更优的王者方案,就是用 Redisson. 缓存系列文章: 缓存实战(一):20 图 |6 千字|缓存实战(上篇) 缓存实战(二):R ...

  8. SpringCloud(5)之分布式锁实现

    01为什么用分布式锁 在讨论这个问题之前,我们先来看一个业务场景:系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用 ...

  9. Redis 中的原子操作(3)-使用Redis实现分布式锁

    Redis 中的分布式锁如何使用 分布式锁的使用场景 使用 Redis 来实现分布式锁 使用 set key value px milliseconds nx 实现 SETNX+Lua 实现 使用 R ...

随机推荐

  1. SOJ 1685:chopsticks(dp)

    题目链接 说实话挺喜欢soj的界面,简简单单,没有多余的东西hhh(但是简单到连内存限制,时间限制都看不到了. 题意是有个“奇葩”的主人公,吃饭要用三根筷子.两根短的一根长的. 现在给你n根筷子,要在 ...

  2. React Router 4.0 ---- 嵌套路由和动态路由

    嵌套路由,从广义上来说,分为两种情况:一种是每个路由到的组件都有共有的内容,这时把共有的内容抽离成一个组件,变化的内容也是一个组件,两种组件组合嵌套,形成一个新的组件.另一种是子路由,路由到的组件内部 ...

  3. 本科理工男如何学习Linux

    我是一个本科学电子的理工男,但是一直对计算机感兴趣,所以平时自己在课下喜欢学一些与计算机有关的东西.由于对计算机感兴趣,所以后来我参加了学校的计算机社团,在那里接受一些培训和指导.当时在社团里看到师兄 ...

  4. 【嵌入式】Arduino编程基础到应用全解析

    Arduino Author: Andrew.Du 基础 基础语法: setup() loop() pinMode(引脚,模式) pinMode(13,OUTPUT):设置13号引脚为输出 //在使用 ...

  5. 彻底弄懂 HTTP 缓存机制及原理 | 干货

    来源:www.cnblogs.com/chenqf/p/6386163.html 前言 Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个 ...

  6. 部署crm项目

    准备工作 使用xftp将项目传到linux 将knight 传到linux上 将项目的数据导出 mysqldum -uroot -p --all-database > alldb.dump 在w ...

  7. Jquery实现检测用户输入用户名和密码不能为空

    要求 1.用户名和密码为空点击登录时提示相应的提示 2.获取用户名输入框时,错误提示清除 思路 1.创建1个input-text标签和1个input-password标签,1个input-botton ...

  8. Civil 3d设置横断面图样式

    一位网友提出这样一个问题: 在使用SectionView.StyleName属性时, 会抛出异常:need to override property StyleName. 我测试的结果一样, 同时测试 ...

  9. Spring 使用介绍(三)—— 资源

    一.Resource接口 Spring提供Resource接口,代表底层外部资源,提供对底层外部资源的一致性访问接口 public interface InputStreamSource { Inpu ...

  10. linux shell系列10 判断某个月中的星期六和星期天

    #!/bin/bashread -p "请输入月份:" month #输入要查找的月份 mon=`date -d "0 month ago" +%m` #计算本 ...