Redis高级知识

一、Redis中的事务

1. 基本概念

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis事务的主要作用就是串联多个命令防止别的命令插队。

2. 执行过程

在这里插入图片描述
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。组队的过程中可以通过discard来放弃组队。

3. 错误处理

在这里插入图片描述
组队中某个命令出现了报告错误,执行时整个队列所有命令都会被取消。
在这里插入图片描述
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

3. Redis事务的三特性

  1. 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  2. 没有隔离级别的概念:因为事务提交前任何指令都不会被实际执行。
  3. 不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

二、Redis的缓存问题

在这里插入图片描述

1. 缓存穿透

1.1 问题描述

在这里插入图片描述
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

1.2 问题解决

  1. 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
  2. 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  3. 采用布隆过滤器:布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。布隆过滤器其实就是将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。参考:https://baijiahao.baidu.com/s?id=1711662604558494579&wfr=spider&for=pc和https://baijiahao.baidu.com/s?id=1720626538844137912&wfr=spider&for=pc
  4. 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

2. 缓存击穿

2.1 问题描述

在这里插入图片描述
key对应的数据存在,但在redis中过期(只有某个key过期,这个key被反复调用,而不是大量的key过期),此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

2.2 问题解决

key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

  1. 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
  2. 实时调整:现场监控哪些数据热门,实时调整key的过期时长。
  3. 使用锁:
    (1) 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
    (2) 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key。
    (3) 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key。
    (4) 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
    在这里插入图片描述

3. 缓存雪崩

在这里插入图片描述

3.1 问题描述

在这里插入图片描述
key对应的数据存在,但在Redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。缓存雪崩与缓存击穿的区别在于这里针对很多key在缓存中过期,前者则是某一个key在缓存中过期。

3.2 问题解决

  1. 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)。
  2. 使用锁或队列:用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况。
  3. 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
  4. 将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

三、Redis实现分布式锁

1. 使用的指令

# set<key><value>
ex second :设置键的过期时间为 second 秒。使用格式:set key value ex second
px millisecond :设置键的过期时间为 millisecond 毫秒。使用格式:set key value px millisecond
nx :只在键不存在时,才对键进行设置操作。使用格式:set key value nx
xx :只在键已经存在时,才对键进行设置操作。使用格式:set key value xx

2. 图解分布式锁流程

在这里插入图片描述

3. 锁的优化

3.1 优化一 设置锁的过期时间

在这里插入图片描述
上述的方式存在死锁问题即setnx刚好获取到锁,业务逻辑出现异常,导致锁无法释放,对于此问题,我们可以通过设置过期时间,自动释放锁来解决。

3.2 优化二 UUID防止误删

在这里插入图片描述
经过优化一之后的锁,依然存在漏洞,比如如果业务逻辑的执行时间是7s。执行流程如下
1.index1业务逻辑没执行完,3秒后锁被自动释放。
2.index2获取到锁,执行业务逻辑,3秒后锁被自动释放。
3.index3获取到锁,执行业务逻辑。
4.index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁,导致index3的业务只执行1s就被别人释放。
5.最终等于没锁的情况。
面对此问题,我们可以在setnx获取锁时,设置一个指定的唯一值(例如:uuid;)在释放前获取这个值,判断是否自己的锁,是则释放。

3.3 优化三 LUA解决原子性问题

经过优化二之后的锁,依然存在漏洞,就是删除操作缺乏原子性。比如:
1.index1执行删除时,查询到的lock值确实和uuid相等。
2.index1执行删除前,lock刚好过期时间已到,被Redis自动释放在Redis中没有了lock,没有了锁。
3.index2获取了lock,index2线程获取到了cpu的资源,开始执行方法。
4.index1执行删除,此时会把index2的lock删除,index1 因为已经在方法中了,所以不需要重新上锁。index1有执行的权限。index1已经比较完成了,这个时候,开始执行删除的index2的锁。面对此问题,我们可以使用LUA脚本解决。

3.4 优化四 看门狗进行延时

当锁马上到达过期时间,但实际上我们还需要继续使用这个锁时,可以采用现成的框架工具(比如看门狗)来动态的增加锁的过期时间。

四、Redis的其他问题

1. Redis为什么快

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)。
  2. 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的。
  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
  4. 使用多路 I/O 复用模型,非阻塞 IO。
  5. 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

2. Redis的单线程

Redis核心就是如果数据全都在内存里,单线程的去操作就是效率最高的,因为多线程的本质就是CPU模拟出来多个线程,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。Redis用单个CPU绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理,在内存的情况下,这个方案就是最佳方案。

3. Redis的IO多路复用

在这里插入图片描述

3.1 概述

  • I/O多路复用,I/O就是指的我们网络I/O,多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程,在Redis中,串起来理解就是很多个网络I/O复用一个线程来处理这些连接。

3.2 作用

  • 为什么Redis中要使用I/O多路复用这种技术呢?因为Redis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以I/O操作在一般情况下往往不能直接返回,这会导致某一文件的I/O阻塞导致整个进程无法对其它客户提供服务。而I/O多路复用就是为了解决这个问题而出现的,即为了让单线程(进程)的服务端应用同时处理多个客户端的事件,Redis采用了I/O多路复用机制。

3.3 详解

3.3.1 前置知识

文件描述符(File Descriptor):Linux系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符。可以理解文件描述符是一个索引,这样,要操作文件的时候,我们直接找到索引就可以对其进行操作了。我们将这个索引叫做文件描述符(File Descriptor),简称FD。

3.3.2 流程
  1. 一个socket客户端与服务端连接时,会生成对应一个套接字描述符(套接字描述符是文件描述符的一种),每一个socket网络连接其实都对应一个文件描述符。
  2. 多个客户端与服务端连接时,Redis使用I/O多路复用程序将客户端socket对应的FD注册到监听列表(一个队列)中。当客户端执行read、write等操作命令时,I/O多路复用程序会将命令封装成一个事件,并绑定到对应的FD上。
  3. 文件事件处理器使用I/O路复用模块同时监控多个文件描述符(FD)的读写情况,当accept、read、write和close文件事件产生时,文件事件处理器就会回调FD绑定的事件,并准备开始处理相关命令。
  4. 文件事件分派器接收到I/O多路复用程序传来的套接字FD后,并根据套接字产生的事件类型,将套接字派发给相应的事件处理器来进行处理相关命令的操作。

4. Redis过期键的删除问题

4.1 基本概念

我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

4.2 过期删除策略

  1. 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  2. 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  3. 定期过期:Redis会将设置了过期时间的key放到一个独立的字典中,并对该字典进行每秒10次的过期扫描,过期扫描不会遍历字典中所有的key,而是采用了一种简单的贪心策略。该策略的删除逻辑如下:从过期字段中随机选出20个key,删除这20个key中过期的key,如果已经过期的key比例超过四分之一,则重复上面的步骤,该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

5. rehash的过程

5.1 基本概念

Redis通过链式哈希解决冲突,也就是同一个桶里面的元素使用链表保存。但是当链表过长就会导致查找性能变差可能。所以Redis为了追求快,使用了两个全局哈希表,用于rehash操作,增加现有的哈希桶数量,减少哈希冲突。

5.2 过程概述

开始默认使用【hash表1】保存键值对数据,【hash表2】此刻没有分配空间。当数据越来越多的触发rehash操作,则执行以下操作:① 给【hash表2】分配更大的空间;② 将【hash表1】的数据重新映射拷贝到【hash表2】中;③ 释放【hash表1】的空间。

5.3 注意

将【hash表1】的数据重新映射到hash表2的过程并不是一次性的,这样会造成Redis阻塞,无法提供服务,因此采用了渐进式rehash,这样每次处理客户端请求的时候,先从【hash表1】第一个索引开始,将这个位置的所有数据拷贝到【hash表2】中,就这样将 rehash 分散到多次请求过程中,避免耗时阻塞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值