zoukankan      html  css  js  c++  java
  • 基于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因为拿到了锁,所以可以进行出价。

    这两种方式实现应该都是属于乐观锁吧,上述实现可能暂时不适应什么秒杀之类的并发环境。总之具体问题还是得具体分析吧。

  • 相关阅读:
    代理(reGeorg)
    弱口令爆破技巧
    无法解析@NotBlank
    LC 1723. Find Minimum Time to Finish All Jobs (dp+二分)
    帝国cms 联合多张表查询
    php 根据白名单替换字符转中的链接 封装的函数
    php 正则匹配域名后的整个链接和只匹配域名
    JVM系列(一):垃圾回收之MinorGC,MajorGC和FullGC的区别
    spring事务的执行原理
    java基础系列(八):Semphore,CountDownLatch和CyclicBarrier的使用
  • 原文地址:https://www.cnblogs.com/chenjianxiang/p/8981134.html
Copyright © 2011-2022 走看看