基于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实现分布式锁. 可靠性首先,为了确保 ...
随机推荐
- shell的输入和输出
1.echo echo [option] string -e 解析转义字符 -n 回车不换行,linux系统默认回车换行 转移字符 \c \t \f \n #!/bin/bash #echo e ...
- Android View框架总结(六)View布局流程之Draw过程
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52236145 View的Draw时序图 ViewRootImpl.p ...
- JQuery之动画与特效
显示与隐藏 show(spped,[callback])与hide(spped,[callback]) speed可选填slow.normal.fast,对应的速度分别为600ms.400ms.200 ...
- J2EE进阶(三)struts2 <s:action>标签的用法
J2EE进阶(三)struts2 <s:action>标签的用法 前言 使用action标签,可以允许在jsp页面中直接调用Action,(类似AJAX页面调用)在调用Action时候,可 ...
- Java项目案例:酒店前台客服管理系统
import java.util.Scanner; public class HelloWorld { public static void main(String[] args){ String [ ...
- 从JDK源码角度看线程的阻塞和唤醒
目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...
- 【一天一道LeetCode】#71. Simplify Path
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...
- JAVA应用程序转换为Applet
本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41673295 将一个图形的JAVA应用程序转换为能够嵌入在 ...
- (NO.00001)iOS游戏SpeedBoy Lite成形记(十五)
现在啃第2个问题:如何让玩家输入赌注金额. 实现的方法有很多种,比如可以限制玩家只能从特定的金额中选择,把每个选择做成一个按钮即可.以下是一个假想选择窗口的示意图: 这样没有玩家的输入问题了.缺点是不 ...
- redis简单测试用例(内存不足,可以使用redis)
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,听说他的性能远高于memcached,所以想自己搞个玩下.看到底有什么好处. 在windows下使用redis首先要 ...