前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包【第X个领取的人红包最大】,基本功能实现后,就要考虑这一操作在短时间内多个用户争抢同一资源的并发问题了,类似于很多应用如淘宝、京东的秒杀活动场景。所谓的秒杀就是多个线程对资源进行操作,而实现秒杀,就必须控制线程对资源的争抢。

传统方法

  而最传统简单暴力的方法就是在秒杀的业务关键代码块外用Java的synchronized关键字锁住,但这种方式下的锁粒度比较高,比如两个线程同时执行秒杀方法,这两个线程操作的是不同的商品,从业务上讲应该是可以同时进行的,而两个线程会去争抢同一个锁,这是没必要的,而且synchronized是线程同步锁,只允许一个进程的一个线程访问,分布式场景下无法控制同步。这时候,分布式锁上场了。

场景

  论分布式锁,查阅了很多资料,有很多方法可以实现,如zookeeper、redis等等,而他们的共同点都是通过状态值来标识锁,进而通过状态值来实现锁的占用与释放。比如现在有一个秒杀场景,db有一张表,对应有商品ID和库存,秒杀成功库存-1,现有500个线程秒杀商品1,另有500个线程秒杀商品2。通常具有秒杀场景的业务系统都比较复杂,承载的业务量非常巨大,并发量也很高。这样的系统往往采用分布式的架构来均衡负载。那么这1000个并发就会是从不同的地方过来,商品库存就是共享的资源,也是这1000个并发争抢的资源,这个时候我们需要将并发互斥管理起来,把和商品ID相关的字符串作为状态值来标识锁,这样就只有争抢同一商品的线程互斥,不会导致所以线程互斥。

  下面介绍下redis分布式锁的实现。

实现原理   

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

三个命令  

   setNx key value  [key不存在时设置对应value;key已存在不做任何操作;意‘set if not exists’]

   get  [key不存在返回nil;key已存在返回值]

getSet  [设置key值为value,并返回key的旧值]

上代码就是一把梭[锁]

 package com.pagoda.eshop.customer.redis.lock;

 /**
* 分布式锁接口
* @Author: 小海
* @Description:
* @Date: Create in 17:28 2017/11/8
*/
public interface IRedisLock { /**
* 获取锁
* @param lockKey
* @return
* @throws InterruptedException
*/
boolean lock(String lockKey) throws InterruptedException; /**
* 释放锁
* @param lockKey
*/
void unlock(String lockKey);
}
 package com.pagoda.eshop.customer.redis.lock.impl;

 import com.pagoda.eshop.customer.redis.lock.IRedisLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisCluster; /**
* 基于redis实现分布式锁
* 备注-> https://www.cnblogs.com/novaCN/p/6417330.html[类模板/方法模板的配置-电商代码规范]
* @Author: 小海
* @Description:
* @Date: Create in 17:28 2017/11/8
*/
@Service
public class RedisLock implements IRedisLock{ private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired
private JedisCluster jedis; private String lockKey; private static final int DEFAULT_LOOP_INTERVAL_MILLIS = 100; /**
* 锁超时时间,防止线程在入锁以后,无限的执行等待
*/
private int expireMsecs = 5 * 1000; /**
* 锁等待时间,防止线程饥饿死锁
*/
private int timeoutMsecs = 10 * 1000; /**
* 锁标识
*/
private volatile boolean locked = false; /**
* 锁key后缀
*/
private static final String LOCKKEY_SUFFIX = ":lock"; private String get(final String key) {
Object obj = null;
try {
obj = jedis.get(key);
} catch (Exception e) {
logger.error("get redis error, key : ", key);
}
return obj == null ? null : obj.toString();
} /**
* 若key不存在,将key的值设为value,并返回true
* 若key已经存在,则setnx不做任何动作,并返回false
* @param key
* @param value
* @return
*/
private boolean setNX(final String key, final String value) {
Object obj = null;
try {
obj = jedis.setnx(key, value);
} catch (Exception e) {
logger.error("setNX redis error, key : ", key);
}
return ((Long) obj).intValue() == 0 ? false : true;
} /**
* 设置现在的锁到期时间并返回上一个锁到期时间
* @param key
* @param value
* @return 上一个锁的到期时间
*/
private String getSet(final String key, final String value) {
Object obj = null;
try {
obj = jedis.getSet(key, value);
} catch (Exception e) {
logger.error("getSet redis error, key : ", key);
}
return obj == null ? null : (String) obj;
} /**
* 获取锁
*
* 实现思路:
* 主要是使用了redis的setnx命令,缓存了锁
* reids缓存的key是锁的key,所有的共享,value是锁的到期时间
*
* 执行过程:
* 1.通过setnx尝试设置某个key的值,若锁不存在,则返回true,成功获得锁
* 2.若锁已经存在,则通过get获取锁的到期时间,和当前时间比较,超时的话,则通过getset设置新的值并返回上一个线程锁的到期时间
* 3.若通过get和getset获取到的线程锁的到期时间一致的话,则返回true,成功获得锁
* 4.若无法满足1或3的条件,则睡眠一小段时间,一定时间内循环1~3操作,尝试加锁
* 5.若超出锁等待时间,则返回false,获取锁失败
*
* @return 若获得锁,返回true;若执行超时,返回false
* @throws InterruptedException
*/
@Override
public boolean lock(String lockKey) throws InterruptedException {
lockKey = lockKey + LOCKKEY_SUFFIX;
int timeout = timeoutMsecs;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
// 对加锁做时效性检测,设置锁到期时间
String expiresStr = String.valueOf(expires);
if (this.setNX(lockKey, expiresStr)) {
// 成功获得锁
locked = true;
return true;
}
// 当前锁的到期时间
String currentValueStr = this.get(lockKey);
// 若锁已超时[获取锁的客户端执行时间过长,进程被kill掉,或因为其他异常崩溃,导致无法释放锁,就会造成死锁],则重新加锁
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
// 如果多个线程同时走到这里,但是走到这里时每个线程拿到的oldValueStr肯定不可能一样
String oldValueStr = this.getSet(lockKey, expiresStr); // 如果多个线程同时走到这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
// 成功获得锁
locked = true;
return true;
}
}
timeout -= DEFAULT_LOOP_INTERVAL_MILLIS;
Thread.sleep(DEFAULT_LOOP_INTERVAL_MILLIS);
}
return false;
} /**
* 释放锁
*/
@Override
public void unlock(String lockKey) {
if (locked) {
jedis.del(lockKey);
locked = false;
}
}
}

===============后续

致谢:感谢您的阅读!一些问题请跳转自 http://www.cnblogs.com/0201zcr/p/5942748.html

Redis分布式锁的实现的更多相关文章

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

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

  2. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  3. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

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

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

  5. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

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

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

  7. Redis分布式锁---完美实现

    这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...

  8. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  9. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

  10. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

随机推荐

  1. <数据结构与算法分析>读书笔记--利用Java5泛型实现泛型构件

    一.简单的泛型类和接口 当指定一个泛型类时,类的声明则包括一个或多个类型参数,这些参数被放入在类名后面的一对尖括号内. 示例一: package cn.generic.example; public ...

  2. 真实的物理机安装Centos7系统后网卡只有lo没有eno1的解决办法:实际上是物理机未安装网驱动卡

    问题症状: 我真实的物理机安装Centos7系统后,在/etc/sysconfig/目录下查看,发现网卡只有lo没有eno1,出现该问题的实际原因是物理机未安装网驱动卡. 解决办法: 不多说了,让我们 ...

  3. Python2.7-copy_reg

    copy_reg 模块,提供了在 pickle 或是 copy 特定对象时,可以运行一个指定的函数,作为对象的构造器 模块方法: copy_reg.constructor(object):声明一个可调 ...

  4. ThinkCenter安装CentOS7

    重启电脑按F12选择从光盘启动: 选择install CentOS7,并按“E”键 进行编辑 编辑后,并按Ctrl+X 查看并找到你需要的盘符名称,如:sr0:随后强制重启电脑. 并修改如下: 按Ct ...

  5. Kafka设计解析(二十二)Flink + Kafka 0.11端到端精确一次处理语义的实现

    转载自 huxihx,原文链接 [译]Flink + Kafka 0.11端到端精确一次处理语义的实现 本文是翻译作品,作者是Piotr Nowojski和Michael Winters.前者是该方案 ...

  6. 从裸机编程到嵌入式Linux编程思想的转变------分而治之:驱动和应用程序

    笔者学习嵌入式Linux也有一段时间了,很奇怪的是很多书讲驱动编程方面的知识,也有很多书将ARM9方面的知识,但是从以前51形式的(对寄存器直接操作,初始化芯片的功能模块)编程方法,和思维模式,变换为 ...

  7. 带你看懂大数据采集引擎之Flume&采集目录中的日志

    一.Flume的介绍: Flume由Cloudera公司开发,是一种提供高可用.高可靠.分布式海量日志采集.聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于采集数据:同时,flum ...

  8. MAC下配置ssh让SourceTree通过秘钥访问远程仓库

    问题描述 由于TortoiseGit没有MAC版本,我们使用了SourceTree来替代. 在帮同事解决Mac下的Git的时候,碰到一个问题:SourceTree无法使用ssh方式提交代码,这是由于没 ...

  9. Asp.Net_优化

    ASP.NET: 一.返回多个数据集 检查你的访问数据库的代码,看是否存在着要返回多次的请求.每次往返降低了你的应用程序的每秒能够响应请求的次数.通过在单个数据库请求中返回多个结果集,可以减少与数据库 ...

  10. 记录一次Docker For Windows10镜像加速器配置

    1.访问https://www.daocloud.io 注册账号 2.访问资源->加速器,或者直接访问网址https://www.daocloud.io/mirror,页面中间有加速配置,例如我 ...