Redis(十四)Redisson由简到消费实例分析和实现

16 篇文章 5 订阅
11 篇文章 0 订阅

前言

RedLock:官方权威的用Redis实现分布式锁管理器的算法。

Java实现RedLock原理的Reddison : RLock。引用RedLock实现原理中的分布式锁实现目标:

在描述我们的设计之前,我们想先提出三个属性,这三个属性在我们看来,是实现高效分布式锁的基础。

  1. 安全属性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
  2. 效率属性A:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
  3. 效率属性B:容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。

引入Redisson依赖

		<!--Redisson -->
		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>2.7.0</version>
		</dependency>

锁的实现

Redisson对线程进行识别,同一线程重复获取锁返回true。

		Jedis jedis = JedisPoolDemo.getJedis();
		jedis.del(LOCK_TITLE);
		RedissonClient redissonClient = initRedissonObject();
		RLock rLock = redissonClient.getLock(LOCK_TITLE);
		
		if (rLock.tryLock()) {
			
			try {
				System.err.println("获取锁");
				System.err.println("未解锁尝试获取锁,注意这里是同一线程,当返回:" + rLock.tryLock());
			}finally {
				rLock.unlock();
			}
			
		}else {
			
		}


		jedis.close();

查看TryLock源码

这里注意是线程之间的请求锁情况,同一个线程造demo是不可取的。

    boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) 

  • waitTime:等待锁时间;
  • leaseTime:租界/使用锁的时间
  • TimeUnit unit:时间单位。

笔者翻译:如果锁可获取尽快的返回true,如果锁被其它线程持有,这个方法tryLock将会保持请求尝试获取锁的这一操作,直到它的waitTime时间过去,对请求放弃并在这个过程中返回false。如果锁被(当前线程)获取,直到租借时间接数,他将一直持有该锁。

boolean org.redisson.api.RLock.tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException



Returns true as soon as the lock is acquired. If the lock is currently held by another thread in this or any other process in the distributed system this method keeps trying to acquire the lock for up to waitTime before giving up and returning false. If the lock is acquired, it is held until unlock is invoked, or until leaseTime have passed since the lock was granted - whichever comes first.

Parameters:

waitTime the maximum time to aquire the lock

leaseTime lease time

unit time unit

Returns:

true if lock has been successfully acquired

Throws:

InterruptedException - - if the thread is interrupted before or during this method.

无参的tryLock注解翻译: 

经典用法: 
    Lock lock = ...;
      if (lock.tryLock()) {
        try {
          // manipulate protected state
        } finally {
          lock.unlock();
        }
      } else {
        // perform alternative actions
      }}

能获取锁就返回true,不能就false
   @return {@code true} if the lock was acquired and {@code false} otherwise
返回false,如果(线程获取锁等待的)时间小于等于0,该方法(tryLock)根本不会等待/
If the specified waiting time elapses then the value {@code false} is returned. If the time is less than or equal to zero, the method will not wait at all.


1、返回true,如果锁能够被获取返回true。
2、如果锁无法获取,对于线程调度,使得该线程(请求锁操作)无效,这时候陷入休眠(waitTime)直到三种情况出现:
    1、锁被当前线程获取;
    2、其它线程中止当前线程,并且支持中断(当前线程的)锁请求;
    3、指定的等待时间过去;


If the lock is available this method returns immediately with the value {@code true}.
If the lock is not available then
     the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:
     1、The lock is acquired by the current thread; or
     2、Some other thread {@linkplain Thread#interrupt interrupts} the current thread, and interruption of lock acquisition is supported; or
     3、The specified waiting time elapses

多线程尝试获取锁

默认tryLock

线程获取锁后,没有解锁,模拟死锁,会一直等待下去。所以api推荐用finally来释放。getHoldCount方法:返回当前线程持有锁的数量。0代表当前线程(main)并不持有锁,因为被子线程持有。

	//消费线程
	static class WalletConsumer implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			Jedis jedis = JedisPoolDemo.getJedis();
			jedis.del(LOCK_TITLE);
			RedissonClient redissonClient = initRedissonObject();
			RLock rLock = redissonClient.getLock(LOCK_TITLE);
			System.err.println(Thread.currentThread().getName() + "获取锁:" + rLock.tryLock());
			jedis.close();
		}
	}
	
    //多线程获取锁
    public static void main(String[] args) {
		Jedis jedis = JedisPoolDemo.getJedis();
		jedis.del(LOCK_TITLE);

		RedissonClient redissonClient = initRedissonObject();
		RLock rLock = redissonClient.getLock(LOCK_TITLE);
		
		Executor exu = Executors.newFixedThreadPool(6);
		exu.execute(new WalletConsumer());  //true
		Thread.sleep(500);
		exu.execute(new WalletConsumer());
		System.err.println(rLock.getHoldCount());  //false

	}

使用try设置waitTime和leaseTime

仅以waitTime作为示例,leaseTime不累述:

System.err.println(Thread.currentThread().getName() + "获取锁:" + rLock.tryLock(3,TimeUnit.SECONDS));

源码中

 

并发实例

分为连个部分:验证demo和消费实例,前面

public class RedissonLock {
	
    private static final String LOCK_TITLE = "redisLock_wallet";
    private static final String KEY = "wallet";
	static RedissonClient redisson;

    //构造Redisson对象
	public static RedissonClient initRedissonObject() {
		Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379");
        //config.useSingleServer().setPassword("");
        redisson = Redisson.create(config);
		return redisson;
	}


	//消费线程
	static class WalletConsumer implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			Jedis jedis = JedisPoolDemo.getJedis();
			RedissonClient redissonClient = initRedissonObject();
			RLock rLock = redissonClient.getLock(LOCK_TITLE);
			int begin = (int) System.currentTimeMillis();
			
				try {
					if (rLock.tryLock(60,1,TimeUnit.MINUTES)) {
						
						try {
						int wallet =Integer.valueOf(jedis.get(KEY)) - 1;
						jedis.set(KEY, String.valueOf(wallet));
						System.err.println(Thread.currentThread().getName() + "消费了钱包" + wallet);
						//System.err.println(Thread.currentThread().getName() + "获取锁");
						
						}finally {
							System.err.println(Thread.currentThread().getName() + "耗时" + TimeUnit.SECONDS.convert( (int)System.currentTimeMillis() - begin, TimeUnit.MILLISECONDS));
							rLock.unlock();
							jedis.close();
						}
					}
				} catch (NumberFormatException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		}
	}
	
	public static void main(String[] args) {
		Jedis jedis = JedisPoolDemo.getJedis();
		jedis.del(LOCK_TITLE);
		jedis.set(KEY, "100");
		
		RedissonClient redissonClient = initRedissonObject();
		RLock rLock = redissonClient.getLock(LOCK_TITLE);
		
		Executor exu = Executors.newFixedThreadPool(100);
		for (int i = 0; i < 100; i++) {
			exu.execute(new WalletConsumer());
		}
	}
}

验证demo

(线程数设置为6,且删去消费过程代码,打开输出注解)  注意:如果同一个线程上锁两次,只解锁一次,其它进程将无法获取锁,因为它还是被该线程持有锁(可重入锁),需上几次解锁几次。

 

消费实例:

Redis服务端 

关于锁的值

这里注意,下列代码main线程进行了两次tryLock,所以value得出为2,便于展示。

		Jedis jedis = JedisPoolDemo.getJedis();
		jedis.del(LOCK_TITLE);
		RedissonClient redissonClient = initRedissonObject();
		RLock rLock = redissonClient.getLock(LOCK_TITLE);
		
		if (rLock.tryLock()) {
			
			try {
				System.err.println("获取锁");
				System.err.println("未解锁尝试获取锁,注意这里是同一线程,当返回:" + rLock.tryLock());
			}finally {
				Map mapKey = jedis.hgetAll(LOCK_TITLE);
				for (Object value : mapKey.values()) {
					System.err.println("value" + value + "长度:" + mapKey.size());
				}
				System.err.println("解锁前:" + jedis.hgetAll(LOCK_TITLE));
				rLock.unlock();
				System.err.println("解锁后:" + jedis.hgetAll(LOCK_TITLE));
				//rLock.unlock();
			}
			
		}else {
			
		}
		jedis.close();

尝试获取锁的值得出:

其原因

在进行tryLock的时候实际调用的是:

tryLock ->tryAcquire -> tryAcquireAsync -> tryLockInnerAsync -> 源码Lua(保证原子性)

    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }

两个if的意思分别为:

1、如果key值不存在,设置key值(锁)的值为1,和过期时间;

2、如果key值存在并且是自己线程的,设置key值+1,重新设置过期时间,并且返回nil(redis的null)。

报错

11:36:08.800 [redisson-netty-1-2] DEBUG org.redisson.command.CommandAsyncService - connection released for command (EVAL) and params [if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);, 1, redisLock_wallet, 30000, 0abb01af-5c60-494f-bf7a-d3c98cdb9cf6:1] from slot NodeSource [slot=null, addr=null, redirect=null] using connection RedisConnection@164340833 [redisClient=[addr=/127.0.0.1:6379], channel=[id: 0x16ce8925, L:/127.0.0.1:49669 - R:/127.0.0.1:6379]]
Exception in thread "main" org.redisson.client.RedisException: ERR Error running script (call to f_9401052d872adfd0179ef8c8e8c028512707629a): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value . channel: [id: 0x16ce8925, L:/127.0.0.1:49669 - R:/127.0.0.1:6379] command: CommandData [promise=org.redisson.misc.RedissonPromise@75581474, command=(EVAL), params=[if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pe..., 1, redisLock_wallet, 30000, 0abb01af-5c60-494f-bf7a-d3c98cdb9cf6:1], codec=org.redisson.client.codec.LongCodec@6bc57b88]
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:266)
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:126)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)

原因:用于锁的键值已经存在。

参考文章

《Redis官方文档》用Redis构建分布式锁 译者:yy-leo 校对:方腾飞(红体标记重点)

  Redisson(redlock, redis锁, 分布式锁) 核心代码(加锁解锁)介绍说明

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长臂人猿

客官们众筹请博主喝杯奶茶吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值