Redis+IDEA极速了解和实现单机锁和分布式锁

单机下:

 

只适用于单机环境下(单个JVM),多个客户端访问同一个服务器

1.synchronized

package com.cloud.SR.controller;import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController
public class TestConrtoller1 {@Value("${server.port}")private String serverPort;@Resourceprivate StringRedisTemplate stringRedisTemplate;@GetMapping("/buy1")public String shopping(){synchronized (this){String result = stringRedisTemplate.opsForValue().get("goods:001");int total = result == null? 0 :Integer.parseInt(s);if(total > 0){int realTotal = total - 1;stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realCount ));System.out.println("剩余商品为:"+realCount +",提供服务的端口号:"+serverPort);return "剩余商品为:"+realTotal +",提供服务的端口号:"+serverPort;}else{System.out.println("购买商品失败!");}return "购买商品失败!";}}
}

2.ReentrantLock

@RestController
public class TestConrtoller2 {@Value("${server.port}")private String serverPort;// 使用ReentrantLock锁解决单体应用的并发问题Lock lock = new ReentrantLock();@AutowiredStringRedisTemplate stringRedisTemplate;@RequestMapping("/buy2")public String index() {lock.lock();try {String result = stringRedisTemplate.opsForValue().get("goods:001");int total = result == null ? 0 : Integer.parseInt(result);if (total > 0) {int realTotal = total - 1;stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realTotal));System.out.println("购买商品成功,库存还剩:" + realTotal + ",服务端口为:"+serverPort);return "购买商品成功,库存还剩:" + realTotal + ",服务端口为:"+serverPort;} else {System.out.println("购买商品失败!");}} catch (Exception e) {lock.unlock();} finally {lock.unlock();}return "购买商品失败!";}
}

分布式下:

而在服务器分布式集群下,,单个服务器的synchronized和ReentrantLock

 

1.SETNX

SET key value [EX seconds] [PX milliseconds] [NX|XX]
  • EX seconds – 设置键key的过期时间,单位时秒
  • PX milliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值

例子:

1.set lock01 01 NX :意思就是说只要谁把key为lock01的值设置为01且key不存在的时候就能拿到锁

2. set lock01 01 NX EX 30 :在例1的基础上把锁设置的时间设置为30秒后过期。避免有服务挂了而没有释放锁的情况、或者业务处理完但一直拿着锁不释放导致死锁。

项目中使用SETNX:

  • template.opsForValue().setIfAbsent()

测试的话就得本机模拟集群,当然有虚拟机的也可以用两台虚拟机,但此处用两台JVM即可完成简易集群
本机实现集群的可以看这篇文章:http://t.csdn.cn/jvZFx

 先让集群跑起来,然后启动Nginx,再通过Jmeter实现高并发的秒杀环节

 用template.opsForValue().setIfAbsent()命令进行加锁。加上了过期时间后就解决了key无法删除的问题,但如果key设置的时间太短,当业务处理的时间长于key设置的时间,key过期后其他请求就可以设置这个key而当这个线程再回来处理这个程序的时候就会把人家设置的key给删除了,因此我们规定谁设置的锁只能由谁删除。

finally {// 谁加的锁,谁才能删除if(template.opsForValue().get(REDIS_LOCK).equals(value)){template.delete(REDIS_LOCK);}

而新的问题就是finally块的判断和del删除操作不是原子操作,并发的时候也会出问题。因此采用lua(原子性)来进行删除

finally {// 谁加的锁,谁才能删除,使用Lua脚本,进行锁的删除Jedis jedis = null;try{jedis = RedisUtils.getJedis();String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +"then " +"return redis.call('del',KEYS[1]) " +"else " +"   return 0 " +"end";Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));if("1".equals(eval.toString())){System.out.println("-----del redis lock ok....");}else{System.out.println("-----del redis lock error ....");}}catch (Exception e){}finally {if(null != jedis){jedis.close();}}

总的代码: 

@RestController
public class TestConrtoller3 {@Value("${server.port}")private String serverPort;public static final String REDIS_LOCK = "good_lock";@AutowiredStringRedisTemplate stringtemplate;@RequestMapping("/buy3")public String shopping(){// 每个人进来先要进行加锁,key值为"good_lock",且用UUID保证每个人的锁不同String value = UUID.randomUUID().toString().replace("-","");try{// 为key加一个过期时间Boolean flag = stringtemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS);// 加锁失败if(!flag){return "抢锁失败!";}System.out.println( value+ " 抢锁成功");String result = stringtemplate.opsForValue().get("goods:001");int total = result == null ? 0 : Integer.parseInt(result);if (total > 0) {// 如果在此处需要调用其他微服务,处理时间较长。。。int realTotal = total - 1;stringtemplate.opsForValue().set("goods:001", String.valueOf(realTotal));System.out.println("购买商品成功,库存还剩:" + realTotal + ",服务端口为"+serverPort);return "购买商品成功,库存还剩:" + realTotal + "服务端口为"+serverPort;} else {System.out.println("购买商品失败");}return "购买商品失败!";}finally {// 谁加的锁,谁才能删除,使用Lua脚本,进行锁的删除Jedis jedis = null;try{jedis = RedisUtils.getJedis();String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +"then " +"return redis.call('del',KEYS[1]) " +"else " +"   return 0 " +"end";Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));if("1".equals(eval.toString())){System.out.println("-----del redis lock ok....");}else{System.out.println("-----del redis lock error ....");}}catch (Exception e){}finally {if(null != jedis){jedis.close();}}}}
}

2.Redisson(推荐)

考虑缓存续命,以及Redis集群部署下,异步复制造成的锁丢失:主节点没来得及把刚刚set进来这条数据给从节点,就挂了。所以直接上RedLockRedisson落地实现

@RestController
public class TestConrtoller4 {@Value("${server.port}")private String serverPort;public static final String REDIS_LOCK = "good_lock";@AutowiredStringRedisTemplate stringtemplate;@AutowiredRedisson redisson;@RequestMapping("/buy4")public String shopping(){RLock lock = redisson.getLock(REDIS_LOCK);lock.lock();// 每个人进来先要进行加锁,key值为"good_lock"String value = UUID.randomUUID().toString().replace("-","");try{String result = stringtemplate.opsForValue().get("goods:001");int total = result == null ? 0 : Integer.parseInt(result);if (total > 0) {// 如果在此处需要调用其他微服务,处理时间较长int realTotal = total - 1;stringtemplate.opsForValue().set("goods:001", String.valueOf(realTotal));System.out.println("购买商品成功,库存还剩:" + realTotal + "件, 服务端口为"+serverPort);return "购买商品成功,库存还剩:" + realTotal + "件, 服务端口为"+serverPort;} else {System.out.println("购买商品失败");}return "购买商品失败";}finally {if(lock.isLocked() && lock.isHeldByCurrentThread()){lock.unlock();}}}
}

Redis工具类

 
import com.myfutech.common.util.constant.RedisPrefix;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;import java.nio.charset.StandardCharsets;
import java.util.UUID;/*** 基于redis分布式锁*/
@Slf4j
public class RedisLockUtils {/*** 默认轮休获取锁间隔时间, 单位:毫秒*/private static final int DEFAULT_ACQUIRE_RESOLUTION_MILLIS = 100;private static final String UNLOCK_LUA;static {StringBuilder sb = new StringBuilder();sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");sb.append("then ");sb.append("    return redis.call(\"del\",KEYS[1]) ");sb.append("else ");sb.append("    return 0 ");sb.append("end ");UNLOCK_LUA = sb.toString();}/*** 获取锁,没有获取到则一直等待,异常情况则返回null** @param redisTemplate     redis连接* @param key               redis key* @param expire            锁过期时间, 单位 秒* @return                  当前锁唯一id,如果没有获取到,返回 null*/public static String lock(RedisTemplate redisTemplate, final String key, long expire){return lock(redisTemplate, key, expire, -1);}/*** 获取锁,acquireTimeout时间内没有获取到,则返回null,异常情况返回null** @param redisTemplate     redis连接* @param key               redis key* @param expire            锁过期时间, 单位 秒* @param acquireTimeout    获取锁超时时间, -1代表永不超时, 单位 秒* @return                  当前锁唯一id,如果没有获取到,返回 null*/public static String lock(RedisTemplate redisTemplate, final String key, long expire, long acquireTimeout){try {return acquireLock(redisTemplate, key, expire, acquireTimeout);} catch (Exception e) {log.error("acquire lock exception", e);}return null;}/*** 获取锁,没有获取到则一直等待,没有获取到则抛出异常** @param redisTemplate     redis连接* @param key               redis key* @param expire            锁过期时间, 单位 秒* @return                  当前锁唯一id,如果没有获取到,返回 null*/public static String lockFailThrowException(RedisTemplate redisTemplate, final String key, long expire){return lockFailThrowException(redisTemplate, key, expire, -1);}/*** 获取锁,到达超时时间时没有获取到,则抛出异常** @param redisTemplate     redis连接* @param key               redis key* @param expire            锁过期时间, 单位 秒* @param acquireTimeout    获取锁超时时间, -1代表永不超时, 单位 秒* @return                  当前锁唯一id,如果没有获取到,返回 null*/public static String lockFailThrowException(RedisTemplate redisTemplate, final String key, long expire, long acquireTimeout){try {String lockId = acquireLock(redisTemplate, key, expire, acquireTimeout);if (lockId != null) {return lockId;}throw new RuntimeException("acquire lock fail");} catch (Exception e) {throw new RuntimeException("acquire lock exception", e);}}private static String acquireLock(RedisTemplate redisTemplate, String key, long expire, long acquireTimeout) throws InterruptedException {long acquireTime = -1;if (acquireTimeout != -1) {acquireTime = acquireTimeout * 1000 + System.currentTimeMillis();}synchronized (key) {String lockId = UUID.randomUUID().toString();while (true) {if (acquireTime != -1 && acquireTime < System.currentTimeMillis()) {break;}//调用tryLockboolean hasLock = tryLock(redisTemplate, key, expire, lockId);//获取锁成功if (hasLock) {return lockId;}Thread.sleep(DEFAULT_ACQUIRE_RESOLUTION_MILLIS);}}return null;}/***  释放锁** @param redisTemplate     redis连接* @param key               redis key* @param lockId            当前锁唯一id*/public static void unlock(RedisTemplate redisTemplate, String key, String lockId) {try {RedisCallback<Boolean> callback = (connection) ->connection.eval(UNLOCK_LUA.getBytes(StandardCharsets.UTF_8),ReturnType.BOOLEAN, 1,(RedisPrefix.LOCK_REDIS_PREFIX + key).getBytes(StandardCharsets.UTF_8), lockId.getBytes(StandardCharsets.UTF_8));redisTemplate.execute(callback);} catch (Exception e) {log.error("release lock exception", e);}}/*** 获取当前锁的id** @param key       redis key* @return          当前锁唯一id*/public static String get(RedisTemplate redisTemplate, String key) {try {RedisCallback<String> callback = (connection) -> {byte[] bytes = connection.get((RedisPrefix.LOCK_REDIS_PREFIX + key).getBytes(StandardCharsets.UTF_8));if (bytes != null){return new String(bytes, StandardCharsets.UTF_8);}return null;};return (String)redisTemplate.execute(callback);} catch (Exception e) {log.error("get lock id exception", e);}return null;}private static boolean tryLock(RedisTemplate redisTemplate, String key, long expire, String lockId) {RedisCallback<Boolean> callback = (connection) ->connection.set((RedisPrefix.LOCK_REDIS_PREFIX + key).getBytes(StandardCharsets.UTF_8),lockId.getBytes(StandardCharsets.UTF_8), Expiration.seconds(expire), RedisStringCommands.SetOption.SET_IF_ABSENT);return (Boolean)redisTemplate.execute(callback);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/227.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wps插入图片显示不全、混乱

问题如下&#xff1a; 原因&#xff1a; 格式混乱 解决办法&#xff1a; 1、统一格式&#xff0c;使用格式刷统一文档的格式 2、Ctrl A 全选&#xff0c;重新选择行距 3、重新粘贴图片&#xff08;选择嵌入型&#xff09;

【Hello mysql】 mysql的内置函数

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍mysql的基内置函数 mysql的内置函数 日期函数获取年月日获取时分秒获取时间戳在日期的基础上加上日期在日期的基础上减去日期计算两个日期之差创建一张表 记录生日创建一个留言表 字符串函数获取emp表的ename列的字符集…

Dubbo分布式服务框架,springboot+dubbo+zookeeper

一Dubbo的简易介绍 1.Dubbo是什么&#xff1f; Dubbo是一个分布式服务框架&#xff0c;致力于提供高性能和透明化的RPC远程服务调用方案&#xff0c;以及SOA服务治理方案。 简单的说&#xff0c;dubbo就是个服务框架&#xff0c;如果没有分布式的需求&#xff0c;其实是不需…

idea编译时遇到的bug

1、对象重复定义 问题描述&#xff1a; D:\workspace\spark\src\main\Scala\WordCount.scala:3:8 WordCount is already defined as object WordCount object WordCount { 解决参考博客&#xff1a;Error:(21, 8) FlumePushWordCount is already defined as object FlumePush…

rust abc(5): 常量

文章目录 1. 目的2. 基本用法2.1 说明2.2 运行结果 3. 不推荐或不正确用法3.1 不推荐用小写字母作为常量名字3.2 常量名称中含有小写字母就会报warning3.3 定义常量时&#xff0c;不指定数据类型会编译报错 4. const 和 immutable 的区别4.1 const 可以在函数外声明&#xff0c…

基于深度学习的高精度安全帽及背心检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度安全帽及背心检测识别系统可用于日常生活中或野外来检测与定位安全帽及背心目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的安全帽及背心目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系…

WebDAV之π-Disk派盘 + Solid Explorer

Solid Explorer 支持WebDAV方式连接π-Disk派盘。 Solid Explorer 是一款非常优秀的 Android 文件管理器&#xff0c;Material Design 设计风格&#xff0c;双栏布局&#xff0c;可拖拽操作、支持 ROOT 权限、多媒体浏览器、压缩包支持&#xff0c;Chromecast 流支持等众多功…

微信为什么使用 SQLite 保存聊天记录?

概要 SQLite 是一个被大家低估的数据库&#xff0c;但有些人认为它是一个不适合生产环境使用的玩具数据库。事实上&#xff0c;SQLite 是一个非常可靠的数据库&#xff0c;它可以处理 TB 级的数据&#xff0c;但它没有网络层。接下来&#xff0c;本文将与大家共同探讨 SQLite 在…

基于Tensorflow来重现GPT v1模型

OpenAI推出的ChatGPT模型让我们看到了通用人工智能的发展潜力&#xff0c;我也找了GPT的相关论文来进行研究。OpenAI在2017年的论文Improving Language Understanding by Generative Pre-Training提出了GPT的第一个版本&#xff0c;我也基于这个论文来用Tensorflow进行了复现。…

Keepalived 安装与配置

安装 Keepalived apt -y install keepalived 里边有一个杠y&#xff0c;就是我安装的时候里面有yes&#xff0c;就直接是yes 添加 Keepalived 配置 安装好之后, 下一步就开始去来写这个配置文件了&#xff0c;就在这里面去建一个 etc 当中&#xff0c;就是在这个 etc 当中建一个…

认识企业级定时任务Quartz

文章目录 前言一、实现一个Quartz的小案例1.创建一个maven项目2.添加Quartz依赖3.创建一个配置文件配置Quartz信息4.创建一个Job类继承Job接口5.编写主方法逻辑进行测试6.测试运行结果 二、Job和JobDetail总结 前言 目前仍有大部分企业仍在使用Quartz这种定时任务框架&#xf…

45. 跳跃游戏 II (贪心)

题目链接&#xff1a;力扣 解题思路&#xff1a;贪心&#xff0c;尽可能地找到下一跳能够跳到的最远距离&#xff0c;这样到达终点时&#xff0c;所需跳跃次数最少 以nums [2,3,1,1,4,2]为例&#xff1a; 以当前位置begin作为起跳点&#xff0c;能够跳跃的最远距离为m&#…

MySQL每日一练:多表查询——连接查询、子查询

目录 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; emp表&#xff1a; 2、插入数据&#xff1a; dept表&#xff1a; emp表&#xff1a; 3、 按条件查找 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; create table dept (…

以太网(Ethernet)入门了解

以太网&#xff08;Ethernet&#xff09;是一种常见的局域网&#xff08;LAN&#xff09;通信协议&#xff0c;它是由Xerox公司于1970年代中期开发的。以太网是一种基于广播技术的开放式网络协议&#xff0c;它允许设备在共享通信介质上进行通信。以下是关于以太网的基本概念、…

MySQL 多表查询练习

1.创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) );创建score表。SQL代码如下&#xff1a; CREATE TABLE s…

OpenCV 入门教程:Laplacian算子和Canny边缘检测

OpenCV 入门教程&#xff1a; Laplacian 算子和 Canny 边缘检测 导语一、Laplacian 算子二、Canny 边缘检测三、示例应用3.1 图像边缘检测3.2 边缘增强 总结 导语 边缘检测在图像处理和计算机视觉领域中起着重要的作用。 Laplacian 算子和 Canny 边缘检测是两种常用的边缘检测…

CAT1模块 EC800M HTTP使用总结记录

分享记录一下 CAT1 模块EC800 HTTP 协议使用流程 ...... by 矜辰所致目录 前言一、基础说明1.1 CAT1 与 4G1.2 EC800M 模块1.3 HTTP 二、开始使用2.1 硬件设计部分2.2 模块上电流程2.3 PDP 上下文2.3.1 什么是 SGSN 和 GGSN &#xff1f; 三、 HTTP 流程3.1 客户端3.1.1 PDP 上…

Ubuntu18.04 系统安装 Docker

1、首先更新软件源&#xff1a; sudo apt-get updatesudo apt-get upgrade 2、安装Docker&#xff1a; sudo apt install docker -y 3、查看安装的Docker apt list docker 4、查看docker 进程 ps -ef|grep docker 5、查看docker 版本有问题 6、开启Docker服务 systemctl…

10_SPI_Flash 连续写实验

10_SPI_Flash 连续写实验 1. 实验目标2. 连续写方法3. 操作时序4. 流程框图4.1 顶层模块4.2 连续写模块 5. 波形图6. RTL6.1 flash_seq_wr_ctrl6.2 spi_flash_seq_wr 7. Testbench 1. 实验目标 使用页写指令&#xff0c;将串口发送过来的连续不定量数据写入 Flash。本实验中&a…

Web安全——数据库mysql学习

数据库mysql基础 Web安全分享一、数据库的基本操作1、MYSQL登录与退出2、MYSQL数据库的一些解释3、MYSQL注释符有三种&#xff1a; 二、数据库的一些基本操作1、数据库的增删改查(sql语句) 三、table 表的操作1、查看表结构2、查看表的内容3、建立表4、约束条件5、修改表的操作…