redis环境搭建

redis在java、spring、springboot中的实现

redis锁

1、添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、application.yml配置

spring:redis:host: 192.168.65.128password: root

3、注入redisTemplate对象

@Autowired
private RedisTemplate redisTemplate;
/*** 单体架构版本 - 查询所有学生(双重锁机制)
*/
@Override
public List<Student> queryAll() {//先查询redis是否有缓存该数据List<Student> students= (List<Student>) redisTemplate.opsForValue().get("students");//判断缓存中是否存在if(students== null){synchronized (this) {students= (List<Student>) redisTemplate.opsForValue().get("students");if(students== null) {System.out.println("查询了数据库了!!!!!");//查询数据库students= stuDao.queryAll();//重建缓存redisTemplate.opsForValue().set("students", students);redisTemplate.expire("students", 5, TimeUnit.MINUTES);}}}return students;
}

redis分布式锁

理解什么是分布式锁?

要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。

进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

分布式锁的原理

基于redis的setnx命令,setnx的作用就是设置一个key值,如果在redis中该key值不存在就设置成功,如果存在就会设置失败。在分布式集群环境下,多个服务器的线程同时设置一个key,哪个服务器的线程设置成功,就表示该服务器的线程获得了锁对象,其他线程必须等待。获得锁的线程需要记得,在某个时刻进行锁的释放(删除那个key)。

分布式锁的使用场景

线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。

有这样一个情境,线程A和线程B都共享某个变量X。

如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。

如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。

分布式锁的实现

分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。

实现的时候要注意的几个关键点:

1、锁信息必须设置过期超时,不能让一个线程长期占有一个锁而导致死锁;

2、同一时刻只能有一个线程获取到锁。

几个要用到的redis命令:

setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。

get(key):获得key对应的value值,若不存在则返回nil。

getset(key, value):先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value。

expire(key, seconds):设置key-value的有效期为seconds秒。

代码实现

1、分布式锁工具类的封装

/*** 分布式锁的工具类*/
@Component
public class LockUtil {@Autowiredprivate RedisTemplate redisTemplate;//redis原始连接对象private RedisConnection redisConnection;//lua脚本的缓存签名字符串private String lockSha;private String unlockSha;//解决多线程访问同一个共享变量的时候容易出现的并发问题,使得同一线程共享,不同线程间访问隔离private ThreadLocal<String> threadLocal = new ThreadLocal<>();/*** 添加分布式锁的Lua脚本* 1、为什么要给锁添加超时时间?* 当获取锁后,程序突然宕机,导致锁无法进行释放。* 2、为什么要使用lua脚本* 当获取锁后,在设置过期时间之前程序突然宕机,导致锁无法进行释放。* 加锁和设置过期时间必须是原子性,所以考虑使用lua脚本实现。*/private String lockLua = "local key = KEYS[1]\n" +"local value = ARGV[1]\n" +"local time = ARGV[2]\n" +"\n" +"local result = redis.call('setnx', key, value)\n" +"if result == 1 then\n" +"  --当前获得了分布式锁\n" +"  --设置锁的过期时间\n" +"  redis.call('expire', key, time)\t\n" +"  return true\t\n" +"end\n" +"\n" +"--没有获得分布式锁\n" +"return false";//解锁的lua脚本private String unlockLua = "--要删除的是什么锁\n" +"local key = KEYS[1]\n" +"local uuid = ARGV[1]\n" +"\n" +"--获取锁中的uuid\n" +"local lockUUID = redis.call('get', key)\n" +"\n" +"--判断是不是自己上的锁\n" +"if uuid == lockUUID then\n" +"  --是自己上的锁,删除\n" +"  redis.call('del', key)\n" +"  return true\n" +"end\n" +"\n" +"--不是自己上的锁\n" +"return false";@PostConstructpublic void init(){//获得原始连接,因为redis模板对象中没有封装对lua脚本的方法redisConnection = redisTemplate.getConnectionFactory().getConnection();//缓存lua脚本到redis端lockSha = redisConnection.scriptLoad(lockLua.getBytes());unlockSha = redisConnection.scriptLoad(unlockLua.getBytes());}/*** 加锁的方法* 为什么要设置uuid。* 当线程A获取到锁时,并设置了锁的超时时间,由于某种原因当线程A执行业务所需要的时间大于设置的锁超时时间时,锁失效后,线程B获取到了锁,* 在线程B执行过程中,线程A执行完毕后,释放锁的时候,就会误删线程B的锁。* @return*/public boolean lock(String key, int timeout){//设置uuid,在解锁时,根据uuid进行删除对应的锁。遵循锁由谁创建则由谁释放锁String uuid = UUID.randomUUID().toString();threadLocal.set(uuid);//执行加锁的lua脚本boolean flag = redisConnection.evalSha(lockSha, ReturnType.BOOLEAN, 1,key.getBytes(), uuid.getBytes(), (timeout + "").getBytes());return flag;}/*** 解锁的方法* @return*/public boolean unlock(String key){String uuid = threadLocal.get();//执行解锁的luaboolean flag = redisConnection.evalSha(unlockSha, ReturnType.BOOLEAN, 1,key.getBytes(), uuid.getBytes());return flag;}
}
@Autowired
private RedisTemplate redisTemplate;@Autowired
private LockUtil lockUtil;/**
* 分布式集群版 - 分布式锁
* @return
*/
@Override
public List<Student> queryAll() {//先查询redis是否有缓存该数据List<Student> students= (List<Student>) redisTemplate.opsForValue().get("students");//判断缓存中是否存在if(students== null) {//通过lua脚本获得分布式锁,返回true说明获取到了锁boolean flag = lockUtil.lock("lock", 120);if(flag){//查询数据库students= stuDao.queryAll();//3M//重建缓存redisTemplate.opsForValue().set("students", students);redisTemplate.expire("students", 5, TimeUnit.MINUTES);//重建缓存后,释放锁lockUtil.unlock("lock");} else {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}return this.queryAll();}}return students;
}

思考

Redis用setnx+expire实现分布式锁存在什么隐患,如何改进?

用Redis实现分布式锁,2.6.12之前版本方案:setnx加锁,del释放锁,如果锁没释放,设置过期时间,到了时间,del释放锁。但是,这会存在一些问题。

  1. setnx和expire不是原子操作: 一旦redis宕机,expire没有设置成功,锁就无法释放。只有一个请求的setnx可以成功,任何一个请求的expire都可以成功。请求比较密集,过期时间一直刷新,导致锁一直有效。
  2. 超时后,删除其他线程的锁: 在线程A执行过程中,锁已释放,A还在执行业务,但是还未删除锁。线程B获取锁执行业务,线程A执行完,A误删B的锁。
  3. 多个线程并发获取锁、释放锁: 同一时间有线程A、B在访问同一代码块。

对于上面的隐患,Redis已改善。下面,我们针对隐患逐一改善。

  1. Redis2.6.12以上版本,可以用set获取锁: set可以实现setnx和expire,这个是原子操作。也可以通过lua脚本来实现。
  2. Lua删除锁: 通过uuid来控制锁由谁创建则由谁释放锁,Lua是原子操作。
  3. 让获取锁的线程开启一个守护线程,给线程还没执行完,又快要过期的锁续航: 大概是这样的,线程A还没执行完,守护线程每当快过期时,延时expire时间。当线程A执行完,关闭守护线程。如果中间宕机,锁超过超时,守护线程也不在了,自动释放锁。

写作不易,既然来了,不妨点个关注,点个赞吧!!!

redis锁和分布式锁的实现相关推荐

  1. Redlock——Redis集群分布式锁

    欢迎关注方志朋的博客,回复"666"获面试宝典 前言 分布式锁是一种非常有用的技术手段.实现高效的分布式锁有三个属性需要考虑: 安全属性:互斥,不管什么时候,只有一个客户端持有锁 ...

  2. redistemplate分布式锁实现_基于 Redis SETNX 实现分布式锁

    环境与配置 Redis 任意版本即可 SpringBoot 任意版本即可,但是需要依赖 spring-boot-starter-data-redis <dependency><gro ...

  3. 阿里JAVA面试题剖析:一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?...

    面试原题 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 面试官心理分析 其实一般问问题,都是这么问的,先 ...

  4. Redis 集群分布式锁与 API 网关分布式限流

    https://www.infoq.cn/article/FoQGIk*BzdQWJJ0tKqrJ Redis 集群的历史 Redis 在 3.0 前一般有两种集群方案,一是 proxy(Twempr ...

  5. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  6. Redis进阶-细说分布式锁

    文章目录 Pre 引 分布式锁演进 V1 分布式锁演进 V2 分布式锁演进 V3 分布式锁演进 V4 分布式锁演进 V5 终极版-分布式锁演进(Redisson ) V6 Code Redisson分 ...

  7. 基于 Redis 实现的分布式锁

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:我的大学到研究生自学 Java 之路,过程艰辛,不放弃,保持热情,最终发现我是这样拿到大厂 offer 的! 作 ...

  8. redis系列:分布式锁

    1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  9. Redis 如何实现分布式锁?

    锁是多线程编程中的一个重要概念,它是保证多线程并发时顺利执行的关键.我们通常所说的"锁"是指程序中的锁,也就是单机锁,例如 Java 中的 Lock 和 ReadWriteLock ...

  10. 小王,在 Java 中如何利用 redis 实现一个分布式锁服务呢???

    作者:杨高超 juejin.im/post/5a4984af6fb9a0450b66bc57 在现代的编程语言中,接触过多线程编程的程序员多多少少对锁有一定的了解.简单的说,多线程中的锁就是在多线程环 ...

最新文章

  1. 简单工厂模式_计算器实现
  2. linux红帽子桌面模式ftp,Linux操作系统配置Vsftp的方法
  3. 「镁客·请讲」虚之实康成:等风来不如先发制人,打磨好硬件产品才是王道...
  4. mysql优化(九)
  5. 查看oracle监听服务状态,(总结)Oracle监听服务lsnrctl参数及查询状态详解
  6. vi 多窗口同步滚动--适用于人工文件比较
  7. 记一次线上cpu飙升100%的排查过程
  8. php 变量源码,PHP源码中变量存储方式
  9. 看qq加密相册_用 Python 爬取 QQ 空间说说和相册
  10. 解决bootstrap dropdown 下拉菜单有时候不能显示的问题
  11. C++14::lambda函数的类型
  12. Android RecyclerView拖放
  13. VB.NET的数据库基础编程[zz]
  14. matlab如何持续存贮多维矩阵,matlab多维矩阵在内存中存储顺序
  15. 关于学校软件安装错误:“an error ocurred installing TAP device”的个人解决办法
  16. 火星时代Web前端开发完整版
  17. 几个比较好的app开发框架
  18. MATLAB数值计算学习笔记(二)误差理论和非线性方程求解
  19. rke 部署的时候报错:Failed to set up SSH tunneling for host
  20. 优酷路由器刷openwrt固件一

热门文章

  1. libra协议实现(学习笔记)
  2. Torrance–Sparrow BRDF Model公式推导
  3. CSS flex 排版与动画 — 重学 CSS「1024 彩蛋」
  4. 谷歌地图推出、暴雪公司成立 | 历史上的今天
  5. 创文html5作品欣赏,我市举办创文作品展暨陆丰记忆20年图片展
  6. Datatables实现表格行内编辑功能
  7. MATLAB 2021b详解
  8. 现代软件企业竞争环境
  9. 科沃斯机器人充电红灯_我新买科沃斯360款扫地机人充电一直是红灯亮
  10. 三年阿里P6水平,5年阿里P7水平