最近有一个竞拍的项目会用到分布式锁,网上查到的结果是有三种途径可以实现。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的分布式锁两种实现方式的更多相关文章

  1. 基于redis的分布式锁二种应用场景

    “分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种.具体到业务场景中,我们要考虑二种情况: 一.抢不到锁的请求,允许丢弃(即:忽略) ...

  2. 基于Redis的分布式锁真的安全吗?

    说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...

  3. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  4. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  5. 基于 redis 的分布式锁实现 Distributed locks with Redis debug 排查错误

    小结: 1. 锁的实现方式,按照应用的实现架构,可能会有以下几种类型: 如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访 ...

  6. 转载:基于Redis实现分布式锁

    转载:基于Redis实现分布式锁  ,出处: http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如 ...

  7. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  8. 基于redis的分布式锁的分析与实践

    ​ 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于 ...

  9. [Redis] 基于redis的分布式锁

    前言分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁. 可靠性首先,为了确保 ...

随机推荐

  1. 两个activity或者activity和fragment传值

    使用Fragment的时候可能需要在两个Fragment之间进行参数的传递,开始想着可以使用SharedPreferences进行处理,想想这些简单的参数没有必要使用这么麻烦的方式去实现,翻了一下Fr ...

  2. Android开发学习之路--RxAndroid之简单原理

      学习了RxAndroid,其实也就是RxJava了,但是还是不是非常清楚到底RxAndroid有什么用呢?为什么要使用RxAndroid呢?这篇文章讲得不错,RxJava的原理.但是这里还是把整个 ...

  3. Servlet概述-servlet学习之旅(一)

    Servlet概述 servlet是server+applet的缩写.applet是运行于客户端浏览器的java小程序,java诞生的时候,因为applet而闻名于世,但是现在已经没有多少热使用了,而 ...

  4. 动手实现linux中的cp命令(可自行拓展)

    我们在学习系统编程的时候,一定会有这样的经历,让你动手实现一个简单的cp命令,也就是拷贝相应的文件到对应的目录,或者说是复制吧,当然,实现非常的简单,我们来看看源码吧: #include <st ...

  5. 深入浅出Java MVC(Model View Controller) ---- (JSP + servlet + javabean实例)

    在DRP中终于接触到了MVC,感触是确实这样的架构系统灵活性不少,现在感触最深的就是使用tomcat作为服务器发布比IIS好多了,起码发布很简单,使用起来方便. 首先来简单的学习一下MVC的基础知识, ...

  6. UNIX环境高级编程——网络编程常用函数及结构

    IP地址的转换 #include <arpa/inet.h>  int inet_aton(const char *strptr,                     struct i ...

  7. UNIX环境高级编程——线程和fork

    当线程调用fork时,就为子进程创建了整个进程地址空间的副本.子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量.读写锁和条件变量的状态.如果父进程包含多个线程,子进程在fork返回以后 ...

  8. C++为什么要设计友元函数和友元类

    1. 首先要提一下java,比如java定义一个类1.class,会生成字节码.java有反射机制,可以找到类对象,直接修改类的私有属性.逐渐反射机制成为了一种标准,sun做成标准,JDK的API函数 ...

  9. 在go中使用json作为主要的配置格式

    最近在用go重构,在先前的代码中,我们使用的ini文件进行配置,但是因为很多历史遗留问题,导致配置混乱,维护困难,自然也需要考虑重构了. 通用配置格式 通用的配置格式有很多,常用的就有ini,json ...

  10. iOS模仿京东商城中的选择地区样式

    在ViewController文件中创建添加地址界面: @property(nonatomic,strong)UILabel *selectAreaLabel;//地区显示@property(nona ...