Redis分布式锁的实现
前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包【第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分布式锁的实现的更多相关文章
- 利用redis分布式锁的功能来实现定时器的分布式
文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...
- Redis分布式锁
Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...
- redis分布式锁和消息队列
最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...
- redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- spring boot redis分布式锁
随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...
- Redis分布式锁的正确实现方式
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- Redis分布式锁---完美实现
这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...
- redis分布式锁实践
分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...
- Redis分布式锁的try-with-resources实现
Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
随机推荐
- Spark项目之电商用户行为分析大数据平台之(四)离线数据采集
- MetaMask/provider-engine-3-test
通过看其test的代码去好好看看它是怎么使用的 1. provider-engine/test/basic.js const test = require('tape') const Provider ...
- std::max、std::min error C2589: “(”:“::”右边的非法标记,error C2059: 语法错误:“::”
在VC++种同时包含头文件#include <windows.h>和#include <algorithm>后就会出现无法正常使用std标准库中的min和max模板函数,经过查 ...
- anaconda使用以及创建python3.7+pytorch1.0虚拟环境以及Jupyter notebook初级使用
查看所有已安装的软件包$ conda list# packages in environment at S:\Users\jiangshan\Anaconda3:## Name Version Bui ...
- JAVA框架Struts2 servlet API
一:servlet API 1)完全解耦接口: 使用ActionContext类进行相关操作: package jd.com.actioncontex; import com.opensymphony ...
- 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 ...
- jqgrid 宽度自适应
当jqgrid所在操作区宽度大于了给各列设置宽度之和时,此时表格的宽度未铺满操作区,效果不理想 此时,可以通过配置宽带自适应来现实表格内容自动铺满. 配置属性 shrinkToFit:ture 若要启 ...
- odoo返写数据
#确认按钮 反写回合同页面,当前页面反写数据: def action_split_order_ht(self,cr,uid,ids,context=None): assert len(ids)==1 ...
- odoo之ERP系统
odoo大纲 第一部分:数据库postgressql 大象 第二部分:ORM(API) 第三部分:客户端 用python软件写: .py文件 包含两部分:1.自定义部分,由自己写,定义类和功能. .继 ...
- Delphi DBGrid类控件定位到某一行,并更改为选中状态。
Delphi中,可以使用数据集控件提供的 Locate 成员方法快速定位至某条记录, 然后通过清除数据集控件的选中状态,并重新赋值达到我们的目的. grDirectory.DataSource.Dat ...