基于Redis的分布式锁两种实现方式
最近有一个竞拍的项目会用到分布式锁,网上查到的结果是有三种途径可以实现。1.数据库锁机制,2.redis的锁,3.zookeeper。考虑到使用mysql实现会在性能这一块会受影响,zookeeper又是不怎么会。所以使用redis来实现了。
第一种:使用redis的watch命令进行实现
如上图所示:session1在执行修改之前使用watch命令监视了age,然后又在session2更新了age之后,session1在执行exec,在该命令执行的时候应该会检查age值是否更改,现在是已经发生了改变,所以返回执行失败。
基于上述图示写了一段java代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction; import java.util.List; public class RedisWatchTest extends Thread { private String auctionCode;
public RedisWatchTest
(String auctionCode) {
super(auctionCode);
this.auctionCode = auctionCode;
}
private static int bidPrice = 100; public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "主线程运行开始!");
//更改key为a的值
Jedis jedis=new Jedis("127.0.0.1",6379);
jedis.set("goodsprice","0");
System.out.println("输出初始化值:"+jedis.get("goodsprice"));
jedis.close();
RedisWatchTest thread1 = new RedisWatchTest("A001");
RedisWatchTest thread2 = new RedisWatchTest("B001");
thread1.start();
thread2.start();
try{
thread1.join();
thread2.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "主线程运行结束!");
} @Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行开始 ");
Jedis jedis=new Jedis("127.0.0.1",6379);
try {
if(Thread.currentThread().getName()=="B001"){
sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//监视KEY
jedis.watch("goodsprice");
//A先进
String v = jedis.get("goodsprice");
Integer iv = Integer.valueOf(v);
//条件都给过
if(bidPrice > iv){
Transaction tx = jedis.multi();// 开启事务
Integer bp = iv + 100;
//出价成功,事务未提交
tx.set("goodsprice",String.valueOf(bp));
System.out.println("子线程" + auctionCode + "set成功");
try {
if(Thread.currentThread().getName()=="A001"){
sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
List<Object> list = tx.exec();
if (list == null ||list.size()==0) {
System.out.println("子线程" + auctionCode + ",出价失败");
}else{
System.out.println("子线程"+this.auctionCode +", 出价:"+ jedis.get("goodsprice") +",出价时间:"
+ System.nanoTime());
}
}else{
System.out.println("出价低于现有价格!");
}
jedis.close();
System.out.println(Thread.currentThread().getName() + "线程运行结束");
} }
执行结果:
main主线程运行开始!
输出初始化值:0
B001线程运行开始
A001线程运行开始
子线程A001set成功
子线程B001set成功
子线程B001, 出价:100,出价时间:76269463819581
B001线程运行结束
子线程A001,出价失败
A001线程运行结束
main主线程运行结束!
上述代码是在主线程里面开了两个子线程,首先让B001先等待1s时间,让A001先watch最高价,然后在A001事务exec之前让他等待2s时间。这个时候B001已经出价成功了,所以最后应当返回A001出价失败。
第二种:使用redis的setnx命令进行实现
关于setnx的详解参考的是下面这个文章,拿了他的两个加锁和解锁的正确实现方式。
https://www.cnblogs.com/linjiqin/p/8003838.html下面是通过setnx实现的相关代码
import redis.clients.jedis.Jedis; import java.util.Collections; /**
* @author chen
* @date 2018/4/30 16:09
*/
public class RedisSetNXTest extends Thread{ private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; private String auctionCode;
public RedisSetNXTest
(String auctionCode) {
super(auctionCode);
this.auctionCode = auctionCode;
}
private static int bidPrice = 100; public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "主线程运行开始!");
//更改key为a的值
Jedis jedis=new Jedis("127.0.0.1",6379);
jedis.set("goodsprice","0");
System.out.println("输出初始化值:"+jedis.get("goodsprice"));
jedis.close();
RedisSetNXTest thread1 = new RedisSetNXTest("A001");
RedisSetNXTest thread2 = new RedisSetNXTest("B001");
thread1.start();
thread2.start();
try{
thread1.join();
thread2.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "主线程运行结束!");
} @Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行开始 ");
Jedis jedis=new Jedis("127.0.0.1",6379);
try {
if(Thread.currentThread().getName()=="B001"){
sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//让A先拿到锁
boolean isOk= tryGetDistributedLock(jedis, "goods_lock", Thread.currentThread().getName() , 10000); try {
if(Thread.currentThread().getName()=="A001"){
sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} if(isOk) {
System.out.println("子线程"+this.auctionCode +"拿到锁");
String v = jedis.get("goodsprice");
Integer iv = Integer.valueOf(v);
//条件都给过
if(bidPrice > iv){ Integer bp = iv + 100;
//出价成功,事务未提交
jedis.set("goodsprice",String.valueOf(bp));
System.out.println("子线程"+this.auctionCode +", 出价:"+ jedis.get("goodsprice") +",出价时间:"
+ System.nanoTime()); }else{
System.out.println("出价低于现有价格!");
}
boolean isOk1= releaseDistributedLock(jedis, "goods_lock", Thread.currentThread().getName());
if(isOk1){
System.out.println("子线程"+this.auctionCode +"释放锁");
} }else{ System.out.println("子线程" + auctionCode + "未拿到锁");
}
jedis.close();
System.out.println(Thread.currentThread().getName() + "线程运行结束");
}
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false; } private static final Long RELEASE_SUCCESS = 1L; /**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])
else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections
.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false; }
}
执行结果:
main主线程运行开始!
输出初始化值:0
A001线程运行开始
B001线程运行开始
子线程B001未拿到锁
B001线程运行结束
子线程A001拿到锁
子线程A001, 出价:100,出价时间:77389730033100
子线程A001释放锁
A001线程运行结束
main主线程运行结束!
同样代码是在主线程里面开了两个子线程,先让B001等待,让A001先去拿到锁。然后让B001在没有拿到锁的情况下去操作redis,代码做出判断该现场未拿到锁,后执行的A001因为拿到了锁,所以可以进行出价。
这两种方式实现应该都是属于乐观锁吧,上述实现可能暂时不适应什么秒杀之类的并发环境。总之具体问题还是得具体分析吧。
基于Redis的分布式锁两种实现方式的更多相关文章
- 基于redis的分布式锁二种应用场景
“分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种.具体到业务场景中,我们要考虑二种情况: 一.抢不到锁的请求,允许丢弃(即:忽略) ...
- 基于Redis的分布式锁真的安全吗?
说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...
- 基于redis的分布式锁(转)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 基于redis的分布式锁(不适合用于生产环境)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 基于 redis 的分布式锁实现 Distributed locks with Redis debug 排查错误
小结: 1. 锁的实现方式,按照应用的实现架构,可能会有以下几种类型: 如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访 ...
- 转载:基于Redis实现分布式锁
转载:基于Redis实现分布式锁 ,出处: http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如 ...
- redis系列:基于redis的分布式锁
一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...
- 基于redis的分布式锁的分析与实践
前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于 ...
- [Redis] 基于redis的分布式锁
前言分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁. 可靠性首先,为了确保 ...
随机推荐
- Linux下yum安装MySQL yum安装MySQL指定版本
yum安装MySQL 1. 查看有没有安装过 yum list installed MySQL* (有存在要卸载yum remove MySQL*) rpm -qa | grep my ...
- cocos2d-x 3.11 游戏开发环境搭建流程
cocos2d-x 3.11.1 游戏开发环境搭建流程 1. 准备下面的软件 1) Windows7 64Bit+ VS2013 (VC++) 这个不用多说. 2) cocos2d-x-3.11.1. ...
- 14 Fragment的V4包的使用
activity_main.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android ...
- 【Netty源码学习】入门示例
Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. 也就是说,Netty ...
- parcel和parcelable
Parcel 在英文中有两个意思,其一是名词,为包裹,小包的意思: 其二为动词,意为打包,扎包.邮寄快递中的包裹也用的是这个词.Android采用这个词来表示封装消息数据.这个是通过IBinder通信 ...
- 1085. Perfect Sequence (25) -二分查找
题目如下: Given a sequence of positive integers and another positive integer p. The sequence is said to ...
- Sharepoint Solution Gallery Active Solution时激活按钮灰色不可用的解决方法
在做CRM与sharepoint集成的时候,需要在sharepoint中上传crmlistcomponent组件,上传后需要激活,但会碰到激活按钮是灰色的无法点击的问题,如下图中这样,包括点击组件后面 ...
- 安卓一键分享到qq,微信,微博,官方SDK非第三方
当我们项目中需要集成分享功能时,我们通常会采取一下几个办法: 1.调用系统自带分享 优点:简单快速,几行代码搞定,不需添加任何额外包: 缺点:系统会调出手机内部所有带分享功能的APP,且界面风格跟随系 ...
- UNIX环境高级编程——线程同步之条件变量以及属性
条件变量变量也是出自POSIX线程标准,另一种线程同步机制.主要用来等待某个条件的发生.可以用来同步同一进程中的各个线程.当然如果一个条件变量存放在多个进程共享的某个内存区中,那么还可以通过条件变量来 ...
- DB Query Analyzer 6.04 is distributed, 78 articles concerned have been published
DB Query Analyzer 6.04 is distributed,78 articles concerned have been published DB Query Analyz ...