一、简介

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

二、redis命令介绍

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

三、实现思路

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

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

四、编码实现

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

  1. 首先引入创建springboot工程,引入redis 。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <!-- 开启web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  2.创建一个锁类

/**
* 全局锁,包括锁的名称
* Created by fangzhipeng on 2017/4/1.
*/
public class Lock {
private String name;
private String value; public Lock(String name, String value) {
this.name = name;
this.value = value;
} public String getName() {
return name;
} public String getValue() {
return value;
} }

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

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /**
* Created by fangzhipeng on 2017/4/1.
*/
@Component
public class DistributedLockHandler { private static final Logger logger = LoggerFactory.getLogger(DistributedLockHandler.class);
private final static long LOCK_EXPIRE = 30 * 1000L;//单个业务持有锁的时间30s,防止死锁
private final static long LOCK_TRY_INTERVAL = 30L;//默认30ms尝试一次
private final static long LOCK_TRY_TIMEOUT = 20 * 1000L;//默认尝试20s @Autowired
private StringRedisTemplate template; /**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @return true 获取成功,false获取失败
*/
public boolean tryLock(Lock lock) {
return getLock(lock, LOCK_TRY_TIMEOUT, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
} /**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取超时时间 单位ms
* @return true 获取成功,false获取失败
*/
public boolean tryLock(Lock lock, long timeout) {
return getLock(lock, timeout, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
} /**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取锁的超时时间
* @param tryInterval 多少毫秒尝试获取一次
* @return true 获取成功,false获取失败
*/
public boolean tryLock(Lock lock, long timeout, long tryInterval) {
return getLock(lock, timeout, tryInterval, LOCK_EXPIRE);
} /**
* 尝试获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取锁的超时时间
* @param tryInterval 多少毫秒尝试获取一次
* @param lockExpireTime 锁的过期
* @return true 获取成功,false获取失败
*/
public boolean tryLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
return getLock(lock, timeout, tryInterval, lockExpireTime);
} /**
* 操作redis获取全局锁
*
* @param lock 锁的名称
* @param timeout 获取的超时时间
* @param tryInterval 多少ms尝试一次
* @param lockExpireTime 获取成功后锁的过期时间
* @return true 获取成功,false获取失败
*/
public boolean getLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
try {
if (StringUtils.isEmpty(lock.getName()) || StringUtils.isEmpty(lock.getValue())) {
return false;
}
long startTime = System.currentTimeMillis();
do{
if (!template.hasKey(lock.getName())) {
ValueOperations<String, String> ops = template.opsForValue();
ops.set(lock.getName(), lock.getValue(), lockExpireTime, TimeUnit.MILLISECONDS);
return true;
} else {//存在锁
logger.debug("lock is exist!!!");
}
if (System.currentTimeMillis() - startTime > timeout) {//尝试超过了设定值之后直接跳出循环
return false;
}
Thread.sleep(tryInterval);
}
while (template.hasKey(lock.getName())) ;
} catch (InterruptedException e) {
logger.error(e.getMessage());
return false;
}
return false;
} /**
* 释放锁
*/
public void releaseLock(Lock lock) {
if (!StringUtils.isEmpty(lock.getName())) {
template.delete(lock.getName());
}
} }

  4.用法:

@Autowired
DistributedLockHandler distributedLockHandler;
Lock lock=new Lock("lockk","sssssssss);
if(distributedLockHandler.tryLock(lock){
doSomething();
distributedLockHandler.releaseLock();
}

五、注意点

在使用全局锁时为了防止死锁采用 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. React 学习(四) ---- 生命周期函数

    现在我们能修改状态,页面可以进行交互了,但是还有一种状态改变没有解决,那就是倒计时效果,时间一直在变化,组件状态也一直在改变,但我们什么都没有做,如果要实现这样的效果,需要怎么处理? 我们都知道,改变 ...

  2. iOS 通知推送APNS

    结合网上各个资料,再简单整理的一份. 一.APNS推送说明 1.你的IOS应用需要去注册APNS消息推送功能. 2.当苹果APNS推送服收到来自你应用的注册消息就会返回一串device token给你 ...

  3. 【数学建模】day02-整数规划

    基本类似于中学讲的整数规划--线性规划中变量约束为整数的情形. 目前通用的解法适合整数线性规划.不管是完全整数规划(变量全部约束为整数),还是混合整数规划(变量既有整数又有实数),MATLAB都提供了 ...

  4. HTML查漏补缺 【未完】

    1.命名锚 HTML 链接 - name 属性 name 属性规定锚(anchor)的名称. 您可以使用 name 属性创建 HTML 页面中的书签. 书签不会以任何特殊方式显示,它对读者是不可见的. ...

  5. 常用的redis服务命令。

    卸载服务:redis-server --service-uninstall 开启服务:redis-server --service-start 停止服务:redis-server --service- ...

  6. 微信小程序——demo合集及简单的文档解读【五】

    官方Demo https://github.com/wechat-miniprogram/miniprogram-demo 其他Demo https://www.cnblogs.com/ytkah/p ...

  7. Android 自定义ListView 修改数据

    当我们修改了 自定义ListView,如何更新界面上的控件呢? 两种方法: 1 重新绑定adapter (不推荐) Adapter_InboundPO adapter =(Adapter_Inboun ...

  8. [BJWC2010] 严格次小生成树

    [BJWC2010]严格次小生成树算法及模板 所谓次小生成树,即边权之和第二小的生成树,但所谓严格,就是不能和最小的那个相等. 求解严格次小生成树的方法一般有倍增和LCT两种.当然LCT那么高级的我当 ...

  9. 【XSY2679】修墙 最短路

    题目描述 有一个\((n+1)\times (m+1)\)的网格,每条边都有一个边权.有一些格子是城市.你要用一个环圈住所有城市,要求环上所有边的边权和最小.重合的边边权算多次.保证左上角\((1,1 ...

  10. Codeforces Global Round 2 D. Frets On Fire (动态开点线段树,沙雕写法)

    题目链接:D. Frets On Fire 思路:明明可以离散化+二分写,思路硬是歪到了线段树上,自闭了,真实弟弟,怪不得其他人过得那么快 只和查询的区间长度有关系,排完序如果相邻的两个点的差值小于等 ...