前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包【第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. Spark项目之电商用户行为分析大数据平台之(四)离线数据采集

  2. MetaMask/provider-engine-3-test

    通过看其test的代码去好好看看它是怎么使用的 1. provider-engine/test/basic.js const test = require('tape') const Provider ...

  3. std::max、std::min error C2589: “(”:“::”右边的非法标记,error C2059: 语法错误:“::”

    在VC++种同时包含头文件#include <windows.h>和#include <algorithm>后就会出现无法正常使用std标准库中的min和max模板函数,经过查 ...

  4. anaconda使用以及创建python3.7+pytorch1.0虚拟环境以及Jupyter notebook初级使用

    查看所有已安装的软件包$ conda list# packages in environment at S:\Users\jiangshan\Anaconda3:## Name Version Bui ...

  5. JAVA框架Struts2 servlet API

    一:servlet API 1)完全解耦接口: 使用ActionContext类进行相关操作: package jd.com.actioncontex; import com.opensymphony ...

  6. Android cannot be cast to android.app.Fragment

    10-21 17:33:45.171: E/AndroidRuntime(7644): java.lang.RuntimeException: Unable to start activity Com ...

  7. jqgrid 宽度自适应

    当jqgrid所在操作区宽度大于了给各列设置宽度之和时,此时表格的宽度未铺满操作区,效果不理想 此时,可以通过配置宽带自适应来现实表格内容自动铺满. 配置属性 shrinkToFit:ture 若要启 ...

  8. odoo返写数据

    #确认按钮 反写回合同页面,当前页面反写数据: def action_split_order_ht(self,cr,uid,ids,context=None): assert len(ids)==1 ...

  9. odoo之ERP系统

    odoo大纲 第一部分:数据库postgressql 大象 第二部分:ORM(API) 第三部分:客户端 用python软件写: .py文件 包含两部分:1.自定义部分,由自己写,定义类和功能. .继 ...

  10. Delphi DBGrid类控件定位到某一行,并更改为选中状态。

    Delphi中,可以使用数据集控件提供的 Locate 成员方法快速定位至某条记录, 然后通过清除数据集控件的选中状态,并重新赋值达到我们的目的. grDirectory.DataSource.Dat ...