前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包【第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. CentOS 6.5安装配置NFS服务器

    OS:centos 6.5 服务端:10.1.11.201 客户端:10.1.11.202 10.1.11.203 10.1.11.204 1.服务端的配置 安装必须的yum包: yum -y ins ...

  2. Linux下jdk&tomcat的安装

    unbantu: 1.下载相应版本的jdk及tomcat:sudo wget ${url} 2.解压: tar zxvf jdk-7u79-linux-x64.tar.gz​ tar zxvf apa ...

  3. Javascript中的Form表单知识点总结

    Javascript中的Form表单知识点总结 在HTML中,表单是由form元素来表示的,但是在javascript中,表单则由HTMLFormElement类型,此元素继承了HTMLElement ...

  4. Python2.7-hmac

    hmac 模块,基于密钥的哈希算法 1.模块对象 1.1 HMAC 对象 1.1.1 初始化构建类:需要通过模块方法 hmac.new(key[, msg[, digestmod]]) 创建一个新对象 ...

  5. jqgrid 配置行号及行号的宽度

    有时,我们想把jqgrid的行号按指定的宽度显示出来,如何实现? 通过 rownumbers:true  设置启用行号 通过 rownumWidth 配置行号列的宽度 $("#jqGrid& ...

  6. 第15章 RCC—使用HSE/HSI配置时钟

    第15章     RCC—使用HSE/HSI配置时钟 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku. ...

  7. 利尔达推出工控解决方式 串口转以太网模块LSD1ES-W5500_S2E0

    利尔达最近推出工控解决方式,串口转以太网模块LSD1ES-W5500_S2E0,模块基于WIZnet-W5500. 同一时候,这也是利尔达科技集团成为WIZnet代理商后,自行推出的第一款基于WIZn ...

  8. 线程池原理及python实现

    为什么需要线程池 目前的大多数网络服务器,包括Web服务器.Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短. 传统多线程方案中我们采 ...

  9. 2017-2018-2 20155224 『网络对抗技术』Exp9:Web安全基础

    实验完成情况 共完成11题 准备工作 在浏览器输入localhost:8080/WebGoat打开webgoat,左侧选择题目 右键选择Inspect Element开始调试 Injection Fl ...

  10. # 2017-2018-2 20155319『网络对抗技术』Exp7:网络欺诈防范

    2017-2018-2 20155319『网络对抗技术』Exp7:网络欺诈防范 一.原理与实践说明 1.实践目标 本实践的目标是:理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法. 2. ...