当前位置:网站首页>Redis-分布式锁
Redis-分布式锁
2022-06-22 20:31:00 【kaico2018】
分布式锁
为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
应用场景
1、处理效率提升:应用分布式锁,可以减少重复任务的执行,避免资源处理效率的浪费;
2、数据准确性保障:使用分布式锁可以放在数据资源的并发访问,避免数据不一致情况,甚至数据损失等。
例如:
- 分布式任务调度平台保证任务的幂等性。
- 分布式全局id的生成
使用Redis 实现分布式锁
思路:Redis实现分布式锁基于SetNx命令,因为在Redis中key是保证是唯一的。所以当多个线程同时的创建setNx时,只要谁能够创建成功谁就能够获取到锁。
Set 命令: 每次 set 时,可以修改原来旧值;
SetNx命令:每次SetNx检查该 key是否已经存在,如果已经存在的话不会执行任何操作。返回为0 如果已经不存在的话直接新增该key。
1:新增key成功, 0:失败
获取锁的时候:当多个线程同时创建SetNx k,只要谁能够创建成功谁就能够获取到锁。
释放锁:可以对该key设置一个有效期可以避免死锁的现象。
单机版Redis实现分布式锁
使用原生Jedis实现
1、增加maven依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2、编写Jedis连接Redis工具类
public class RedisClientUtil {
//protected static Logger logger = Logger.getLogger(RedisUtil.class);
private static String IP = "www.kaicostudy.com";
//Redis的端口号
private static int PORT = 6379;
//可用连接实例的最大数目,默认值为8;
//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
private static int MAX_ACTIVE = 100;
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
private static int MAX_IDLE = 20;
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static int MAX_WAIT = 3000;
private static int TIMEOUT = 3000;
//在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
private static boolean TEST_ON_BORROW = true;
//在return给pool时,是否提前进行validate操作;
private static boolean TEST_ON_RETURN = true;
private static JedisPool jedisPool = null;
/** * redis过期时间,以秒为单位 */
public final static int EXRP_HOUR = 60 * 60; //一小时
public final static int EXRP_DAY = 60 * 60 * 24; //一天
public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一个月
/** * 初始化Redis连接池 */
private static void initialPool() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "123456");
} catch (Exception e) {
//logger.error("First create JedisPool error : "+e);
e.getMessage();
}
}
/** * 在多线程环境同步初始化 */
private static synchronized void poolInit() {
if (jedisPool == null) {
initialPool();
}
}
/** * 同步获取Jedis实例 * * @return Jedis */
public synchronized static Jedis getJedis() {
if (jedisPool == null) {
poolInit();
}
Jedis jedis = null;
try {
if (jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
e.getMessage();
// logger.error("Get jedis error : "+e);
}
return jedis;
}
/** * 释放jedis资源 * * @param jedis */
public static void returnResource(final Jedis jedis) {
if (jedis != null && jedisPool != null) {
jedisPool.returnResource(jedis);
}
}
public static Long sadd(String key, String... members) {
Jedis jedis = null;
Long res = null;
try {
jedis = getJedis();
res = jedis.sadd(key, members);
} catch (Exception e) {
//logger.error("sadd error : "+e);
e.getMessage();
}
return res;
}
}
3、编写Redis锁的工具类
public class RedisLock {
private static final int setnxSuccss = 1;
/** * 获取锁 * * @param lockKey 定义锁的key * @param notLockTimeOut 没有获取锁的超时时间 * @param lockTimeOut 使用锁的超时时间 * @return */
public String getLock(String lockKey, int notLockTimeOut, int lockTimeOut) {
// 获取Redis连接
Jedis jedis = RedisClientUtil.getJedis();
// 定义没有获取锁的超时时间
Long endTimeOut = System.currentTimeMillis() + notLockTimeOut;
while (System.currentTimeMillis() < endTimeOut) {
String lockValue = UUID.randomUUID().toString();
// 如果在多线程情况下谁能够setnx 成功返回0 谁就获取到锁
if (jedis.setnx(lockKey, lockValue) == setnxSuccss) {
jedis.expire(lockKey, lockTimeOut / 1000);
return lockValue;
}
// 否则情况下 在超时时间内继续循环
}
try {
if (jedis != null) {
jedis.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/** * 释放锁 其实就是将该key删除 * * @return */
public Boolean unLock(String lockKey, String lockValue) {
Jedis jedis = RedisClientUtil.getJedis();
// 确定是对应的锁 ,才删除
if (lockValue.equals(jedis.get(lockKey))) {
return jedis.del(lockKey) > 0 ? true : false;
}
return false;
}
}
4、测试方法
private RedisLock redisLock = new RedisLock();
private String lockKey = "kaico_lock";
/** * 测试Jedis实现分布式锁 * @return */
@GetMapping("/restLock1")
public String restLock1(){
// 1.获取锁
String lockValue = redisLock.getLock(lockKey, 5000, 5000);
if (StringUtils.isEmpty(lockValue)) {
System.out.println(Thread.currentThread().getName() + ",获取锁失败!");
return "获取锁失败";
}
// 2.获取锁成功执行业务逻辑
System.out.println(Thread.currentThread().getName() + ",获取成功,lockValue:" + lockValue);
// 3.释放lock锁
redisLock.unLock(lockKey, lockValue);
return "";
}
使用Springboot实现
依赖于之前的项目
1、编写锁的工具类方法
@Component
public class SpringbootRedisLockUtil {
@Autowired
public RedisTemplate redisTemplate;
// 解锁原子性操作脚本
public static final String unlockScript="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
+ "then\n"
+ " return redis.call(\"del\",KEYS[1])\n"
+ "else\n"
+ " return 0\n"
+ "end";
/** * 加锁,有阻塞 * @param name * @param expire * @param timeout * @return */
public String lock(String name, long expire, long timeout) throws UnsupportedEncodingException {
long startTime=System.currentTimeMillis();
String token;
do{
token=tryLock(name,expire);
if(token==null){
//设置等待时间,若等待时间过长则获取锁失败
if((System.currentTimeMillis()-startTime)>(timeout-50)){
break;
}
try {
Thread.sleep(50);//try it again per 50
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}while (token==null);
return token;
}
/** * 解锁 * @param name * @param token * @return */
public Boolean unlock(String name, String token) throws UnsupportedEncodingException {
byte[][] keyArgs=new byte[2][];
keyArgs[0]= name.getBytes(Charset.forName("UTF-8"));
keyArgs[1]= token.getBytes(Charset.forName("UTF-8"));
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
try{
Long result = connection.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keyArgs);
if(result!=null&&result>0){
return true;
}
}finally {
RedisConnectionUtils.releaseConnection(connection,connectionFactory);
}
return false;
}
/** * 加锁,无阻塞 * @param name * @param expire * @return */
public String tryLock(String name, long expire) throws UnsupportedEncodingException {
String token= UUID.randomUUID().toString();
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
try{
Boolean result = connection.set(name.getBytes(Charset.forName("UTF-8")), token.getBytes(Charset.forName("UTF-8")),
Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
if(result!=null&&result){
return token;
}
}
finally {
RedisConnectionUtils.releaseConnection(connection,connectionFactory);
}
return null;
}
}
2、测试类
@Autowired
private SpringbootRedisLockUtil springbootRedisLockUtil;
@PostMapping("/restLock1")
public void restLock2() throws UnsupportedEncodingException {
String token;
token=springbootRedisLockUtil.lock(Thread.currentThread().getName(),1000,11000);
if(token!=null){
System.out.println("我拿到锁了哦!");
}
else{
System.out.println("我没有拿到锁!");
}
springbootRedisLockUtil.unlock(Thread.currentThread().getName(),token);
}
集群版Redis实现分布式锁
等搭建完集群版本再实现
边栏推荐
- Summary of differences between localstorage, sessionstorage and cookies
- 6月25日PMI认证考点防疫要求及考场安排
- Makefile:1860: recipe for target ‘cmake_check_build_system‘ failed make: *** [cmake_check_build_syst
- Can the characteristics of different network structures be compared? Ant & meituan & NTU & Ali proposed a cross architecture self supervised video representation learning method CaCl, performance SOTA
- 第018讲:函数:灵活即强大 | 课后测试题及答案
- Lesson 023 and 024: recursion: these little bunnies, Hanoi Tower after class test questions and answers
- List of outstanding talents: no crystal vibration, one drag, eight burn and upgrade [chapter]
- VS代码一键整理快捷键
- Lesson 028: Documents: because I know you, I will never forget the after-school test questions and answers [no title]
- CyCa children's physique etiquette Shenzhen training results assessment successfully concluded
猜你喜欢

7-9 超级玛丽

IDC发布中国数据治理报告 亿信华辰第一

British teddy bear joins the pubg mobile game

Database summary: common problems and Optimization in MySQL development

Microsoft edge browser will support network speed measurement, built-in calculator and unit conversion tool

【路径规划】第一周: 大杂烩

Implementation of depth traversal adjacency table in Figure 6-7

Lesson 025: Dictionary: after class test questions and answers when the index is not easy to use

7-1 creating a binary tree from a preorder sequence

第030讲:文件系统:介绍一个高大上的东西 | 课后测试题及答案
随机推荐
2022年朝阳区科技创新课之“产品创新与成果转化”训练营活动圆满结束
kali2021安装RTL8188GU无线网卡[TL-WN726N]驱动
Registration of spa project development
Leetcode daily question - 513 Find the value in the lower left corner of the tree
June 25 PMI certification examination site epidemic prevention requirements and examination room arrangement
Lesson 030: file system: introduce a big thing | after class test questions and answers
如何在物联网低代码平台中使用数据字典功能?
Lesson 027: Set: in my world, you are the only after-school test question and answer
localStorage、sessionStorage 和 cookie 的区别大总结
Lesson 014-15: string (see lesson 27-32 of the new version of little turtle) | after class test questions and answers
Self service library system Tkinter interface and openpyxl form comprehensive design case
Redis核心技术与实战:学习总结目录
卸载mavros
Cannot re register id: pommeffacompetition-v0 problem solving
Lesson 028: Documents: because I know you, I will never forget the after-school test questions and answers [no title]
6月25日PMI认证考点防疫要求及考场安排
6-3 二叉树的非递归遍历
Lesson 022: function: recursion is god horse after class test questions and answers
Redis usage scenario sharing (project practice)
Implementation of depth traversal adjacency table in Figure 6-7