Redis 系列 -- SpringBoot中 基于 Redis 实现分布式锁

非 Y 不嫁゛ 提交于 2020-04-06 19:01:11

分布式锁应用场景

锁的应用常出现在一些多线程、高并发的场景中,如秒杀、抢购、12306抢票,还有一些商品库存管理等。在一般的单体应用中,我们通常使用 synchronized、ReentrantLock 等来进行共享变量的管理,从而实现线程锁功能。但随着互联网发展、用户的剧增,单体应用已无法满足需求,多服务器部署、分布式应用越来越被广泛使用。但问题也出现了,对于之前的基于本地变量锁的形式,在不同服务器间无法共享。所以分布式锁应运而生,如基于中间件 redis 、zookeeper等实现的分布式锁。

为什么使用redis 分布式锁

1、redis 读写快

与mysql等传统数据库进行内存、磁盘I\O 流操作过程相比,redis 基于内存操作和I\O多路复用具有更快的读写效率。

2、redis 单线程 操作简单

基于单线程设计,所以redis在处理一些并发问题上具有天生的优势。

使用命令

SETNX命令

SETNX key value

当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

GET命令

GET key

返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令

DEL key

删除给定的一个或多个 key ,不存在的 key 会被忽略。

SpringBoot 中 代码实现

redis锁对象
 package com.tcwong.redis.util;

import java.util.concurrent.TimeUnit;

/**
 * redis锁
 */
public interface RedisLock {

	/**
	 * 获取锁
	 * [@param](https://my.oschina.net/u/2303379) tryMillSecondsTime 尝试时间 秒
	 * [@return](https://my.oschina.net/u/556800)
	 */
	boolean tryLock(long tryMillSecondsTime);

	/**
	 * 获取锁
	 * [@param](https://my.oschina.net/u/2303379) tryTime 尝试时间
	 * [@param](https://my.oschina.net/u/2303379) timeUnit 时间类型
	 * [@return](https://my.oschina.net/u/556800)
	 */
	boolean tryLock(long tryTime, TimeUnit timeUnit);

	/**
	 * 释放锁
	 */
	void unLock();

	/**
	 * 重制锁
	 */
	void reInit();
}


package com.tcwong.redis.util;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.concurrent.TimeUnit;

/**
 * redis锁
 */
public class RedisLockImpl implements RedisLock {

	private RedisTemplate redisTemplate;

	private String key;

	private long expireTime;

	private long tryTime;

	private long maxTryTime;

	private ValueOperations valueOperations;

	public RedisLockImpl(RedisTemplate redisTemplate, String key, Long expireTime, Long maxTryTime) {
		this.redisTemplate = redisTemplate;
		this.key = key;
		this.expireTime = expireTime;
		this.maxTryTime = maxTryTime;
		this.valueOperations = redisTemplate.opsForValue();
	}

	/**
	 * 获取锁
	 * @return
	 */
	private boolean tryLock() {
		while (this.tryTime >= 0) {
			Long expires = System.currentTimeMillis() + this.expireTime ;
			String expiresValue = String.valueOf(expires);
			Boolean ifAbsentFlag = this.valueOperations.setIfAbsent(this.key, expiresValue);
			if (ifAbsentFlag) {
				redisTemplate.expire(this.key, this.expireTime, TimeUnit.MILLISECONDS);
				return true;
			}
			String lastTimeValue = (String)this.valueOperations.get(this.key);
			if (lastTimeValue != null && Long.valueOf(lastTimeValue) < System.currentTimeMillis()) {
				String oldTimeValue = (String) this.valueOperations.getAndSet(this.key, expiresValue);
				this.redisTemplate.expire(this.key, this.expireTime, TimeUnit.MILLISECONDS);
				if (oldTimeValue != null && oldTimeValue.equals(lastTimeValue)) {
					return true;
				}
			}
			this.tryTime -= 100;

			try {
				TimeUnit.MILLISECONDS.sleep(100);
			} catch (InterruptedException e) {
			}
		}
		return false;
	}

	/**
	 * 获取锁
	 * @param tryMillSecondsTime 尝试时长
	 * @return
	 */
	@Override
	public boolean tryLock(long tryMillSecondsTime) {
		this.tryTime = tryMillSecondsTime > this.maxTryTime ? maxTryTime : tryMillSecondsTime;
		return this.tryLock();
	}

	/**
	 * 获取锁
	 * @param tryTime 尝试时间
	 * @param timeUnit 时间类型
	 * @return
	 */
	@Override
	public boolean tryLock(long tryTime, TimeUnit timeUnit) {
		return this.tryLock(timeUnit.toMillis(tryTime));
	}

	/**
	 * 释放锁
	 */
	@Override
	public void unLock() {
		this.redisTemplate.delete(this.key);
	}

	/**
	 * 重制锁
	 */
	@Override
	public void reInit() {
		this.redisTemplate.expire(this.key, 0 , TimeUnit.MILLISECONDS);
	}
}
获取redis 锁
package com.tcwong.redis.util;

import java.util.concurrent.TimeUnit;

/**
 * 获取锁对象
 */
public interface RedisLockFactory {
	/**
	 * 获取锁
	 * @param key redis锁key
	 * @return
	 */
	RedisLock getLock(String key);

	/**
	 * 获取锁
	 * @param key redis锁key
	 * @param tryTime 尝试时间
	 * @param timeUnit 时间类型
	 * @return
	 */
	RedisLock getLock(String key, int tryTime, TimeUnit timeUnit);
}


package com.tcwong.redis.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 获取锁对象
 */
@Component
public class RedisLockFactoryImpl implements RedisLockFactory {

	@Autowired
	private RedisTemplate redisTemplate;

	private long expireTime = 0 ;

	private long maxTryTime = 1000 ;

	private static final String REDIS_KEY_PRE = "REDIS_KEY_PRE";

	/**
	 * 获取锁
	 * @param key redis锁key
	 * @return
	 */
	@Override
	public RedisLock getLock(String key) {
		return new RedisLockImpl(this.redisTemplate,REDIS_KEY_PRE + key,this.expireTime,this.maxTryTime);
	}

	/**
	 * 获取锁
	 * @param key redis锁key
	 * @param tryTime 尝试时间
	 * @param timeUnit 时间类型
	 * @return
	 */
	@Override
	public RedisLock getLock(String key, int tryTime, TimeUnit timeUnit) {
		return new RedisLockImpl(this.redisTemplate, REDIS_KEY_PRE + key,timeUnit.toMillis(tryTime),this.maxTryTime);
	}
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!