Redis 完整

Redis

什么是 redis ?

是一种基于键值对的 NoSql 型数据库。与 hashMap 不同的是,Redis 中的 value 支持 string(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、GEO(地理信息定位)等多种数据结构。而 HashMap 只是 redis 中的 hash ,他们俩结构是相同的。

Redis 数据结构

Redis 有什么用

三分恶面渣逆袭:Redis的作用

缓存

redis 最常用的就是作为缓存。而之所以用 redis 作为缓存,是因为 redis 中的数据保存在内存之中,非常快,所以用 Redis 缓存可以极大地提高应用的响应速度和吞吐量。

排行榜/计数器

redis 中的数据结构 zset 适合去做排行榜的功能,同时原子递增可以实现计数器。

分布式锁

img

写文章-CSDN创作中心

Redis中的数据类型

String

可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。

redis 自己构建的一种动态字符串。

有什么用:

字符串主要有以下几个典型的使用场景:

  • 缓存功能

  • 计数

  • 共享 Session

哈希

键值对集合,key 是字符串,value 是一个 Map 集合,比如说 value = {name: '沉默王二', age: 18},name 和 age 属于字段 field,沉默王二 和 18 属于值 value。

哈希类似于JDK 1.8之前的 HashMap ,底层实现都是数组加链表。

主要用于缓存对象和用户信息。

list

list 就是链表数据结构的实现,双向链表。

列表主要有以下两个使用场景:

  • 消息队列

  • 文章列表

Set

Set 像 Java 中的 hashSet。是无序集合,集合中的元素是唯一的,不允许重复。

基于 Set 轻易实现交集、并集、差集的操作,所以就可以实现一些共同关注,共同点赞的事。

sorted set

Zset,有序集合,比 set 多了一个排序属性 score(分值)。也就是说可以排序。

可以做排行榜。

Redis 线程模型

Redis为什么这么快//Redis的单线程模型

  1. 基于内存的数据存储。

    redis 中的数据都存储在内存之中,所以数据的读取更快。

  2. 单线程模型

    Redis 由于单线程模型,所以减少了锁竞争和线程切换带来的消耗。

  3. IO 复路

    IO 复路就是内核会一直监听socket的请求,谁有请求,就会将谁交给 redis 处理。

  4. 高效数据结构

    Redis 中的数据结构种类多,而且都经过优化。

IO 复路?

引用知乎上一个高赞的回答来解释什么是 I/O 多路复用。假设你是一个老师,让 30 个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:

  • 第一种选择:按顺序逐个检查,先检查 A,然后是 B,之后是 C、D。。。这中间如果有一个学生卡住,全班都会被耽误。这种模式就好比,你用循环挨个处理 socket,根本不具有并发能力。

  • 第二种选择:你创建 30 个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者- 线程处理连接。

  • 第三种选择,你站在讲台上等,谁解答完谁举手。这时 C、D 举手,表示他们解答问题完毕,你下去依次检查 C、D 的答案,然后继续回到讲台上等。此时 E、A 又举手,然后去处理 E 和 A。

第一种就是阻塞 IO 模型,第三种就是 I/O 复用模型。du

Linux 系统有三种方式实现 IO 多路复用:select、poll 和 epoll。

epoll 方式就是将用户的 socket 对应的 fd 注册进 epoll ,这样epoll 就可以监测用户信息是否到达,从而避免大量无效的无用操作。

为什么早期不用多线程||后面为什么又多线程

早期单线程

因为 Redis 是基于内存的,而多线程的目的是,让CPU资源得到充分利用。所以说没必要去利用多线程,本身就已经是基于内存的了。redis 的瓶颈最有可能是内存的大小或者网络限制。

后期多线程

后期多线程是为了提高网络 IO 能力,也就是提高了数据的读写能力,redis 的命令的执行仍然是单线程。/

这样做的⽬的是因为 Redis 的性能瓶颈在于⽹络 IO ⽽⾮ CPU,使⽤多线程能提升 IO 读写的效率,从⽽整体提⾼ Redis 的性能。

Redis 内存管理

关于过期时间

为什么要设置过期时间?

防止内存超出范围。

那怎么看出来一个键值对过没过过期?

利用的 hash 表,键保存的就是键值对的键值,value 是 longlong 类型的过期时间。

Redis 的过期数据回收策略有哪些?

  1. 惰性删除:访问 key 的时候,如果发现已过期,那么删除。如果一直不访问,可能一直不删除。对 CPU 友好,但是浪费内存资源

  2. 定期删除:定期的随机测试一些建,如果发现过期,那么删除过期的键。

  3. 延迟队列:将键都放进队列中,谁过期了,谁出去。这样能保证每个过期的键都能被删除,但是维护队列本身就很麻烦。

  4. 定时删除:为每个键都加一个定时器,如果时间到了就删除对应的键,但是这样对CPU 压力是最大的,因为要为每个键设置一个定时器。

Redis 中采用的是定期删除 + 惰性删除。

Redis 的内存淘汰策略

Redis 的内存是会发生不足的。那我们应该怎么解决?

Redis 内存不足的解决办法
  1. 修改配置文件,redis.conf 中的 maxmemory

  2. 也可以通过命令 set maxmemory 动态设置内存上限

  3. 修改内存淘汰策略

  4. Redis 集群

内存淘汰策略

当 Redis 所用内存达到 maxmemory 上限时,会触发相应的溢出控制策略。

Redis 提供了 6 种内存淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。

  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。

  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。

  4. allkeys-lru(least recently used):从数据集(server.db[i].dict)中移除最近最少使用的数据淘汰。

  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。

  6. no-eviction(默认内存淘汰策略):禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错。

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。

  2. allkeys-lfu(least frequently used):从数据集(server.db[i].dict)中移除最不经常使用的数据淘汰。

Redis 持久化

RDB

RDB 是创建快照,每隔指定的一段时间创建快照然后保存到磁盘中的 RDB 文件中。

可以通过两种方式手动创建

  • save

  • bgsave、

以下场景会自动触发 RDB 持久化:

①、在 Redis 配置文件(通常是 redis.conf)中,可以通过save指令配置自动触发 RDB 持久化的条件。这个指令可以设置多次,每个设置定义了一个时间间隔(秒)和该时间内发生的变更次数阈值。

save 900 1
save 300 10
save 60 10000

这意味着:

  • 如果至少有 1 个键被修改,900 秒后自动触发一次 RDB 持久化。

  • 如果至少有 10 个键被修改,300 秒后自动触发一次 RDB 持久化。

  • 如果至少有 10000 个键被修改,60 秒后自动触发一次 RDB 持久化。

满足以上任一条件,RDB 持久化就会被自动触发。

②、当 Redis 服务器通过 SHUTDOWN 命令正常关闭时,如果没有禁用 RDB 持久化,Redis 会自动执行一次 RDB 持久化,以确保数据在下次启动时能够恢复。

③、在 Redis 复制场景中,当一个 Redis 实例被配置为从节点并且与主节点建立连接时,它可能会根据配置接收主节点的 RDB 文件来初始化数据集。这个过程中,主节点会在后台自动触发 RDB 持久化,然后将生成的 RDB 文件发送给从节点。

save

会同步的将数据集的快照存入磁盘中的 RDB 文件中。这个操作会阻塞所有客户端请求直到 RDB 文件被完全写入磁盘,会阻塞主线程。

bgsave

会异步的将数据集的快照存入磁盘中的 RDB 文件中。这个命令会立即返回,所以不会耽误Redis 的主线程。会 fork 出一个子线程去创建快照,子线程执行,不会阻塞主线程。

AOF

AOF 是通过将操作数据集的命令,追加到 AOF 文件后,如果需要持久化,在重新执行一遍 AOF 的命令。

流程
  • 命令追加:所有命令 append 到 AOF 缓冲区,这个缓存区在内存中,暂时存放命令。

  • 文件写入( write ):系统会将AOF缓冲区中的内容写入到AOF文件中。这个写入操作并不是立即完成的,而是先写入到操作系统的系统内核缓存中。此刻 write 方法被调用。

  • 文件同步:为了保证数据的安全性,需要将文件系统缓存中的数据真正地写入到磁盘上。可以通过修改配置中的 appendfsync 来决定同步策略:

    • always:每次写命令执行后都立即同步数据到磁盘。

    • everysec:每秒同步一次数据到磁盘。

    • no:由操作系统决定何时同步数据到磁盘。

  • 文件重写:随着 AOF 文件越来越大,文件是需要被重写的。AOF 重新会创建一个新的 AOF 文件,并且会遍历整个数据集,这个文件会包含重建数据库的最小数据集。在重写的过程中,命令还是会添加到旧的 AOF 文件中,但同时也会记录到缓冲区中,一旦重写完成,将被写入新的 AOF 文件。

    AOF 重写操作由 BGREWRITEAOF 命令触发,它会创建一个子进程来执行重写操作,因此不会阻塞主进程。

两种方式优缺点

AOF 相当于 RDB 来说,更灵活,实时性更好,可以设置不同的 fsync 策略,如每秒同步一次,每次写入命令就同步,或者完全由操作系统来决定何时同步。

RDB 更适合做备份数据。使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快,但是可能会丢失最后一次快照之后的数据。

一般来说,要保证数据的安全性,两种方式应该结合使用。

如果发生故障,怎么恢复?

发生故障,可以RDB 或 AOF 恢复,将 RDB 或 AOF 文件拷贝到 Redis 数据目录下。如果使用 AOF 恢复,配置文件开启 AOF,然后启动 redis-server 即可。

具体:

如果都存在优先加载 AOF。

Redis 启动时加载数据的流程:

  1. AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件。

  2. AOF 关闭或者 AOF 文件不存在时,加载 RDB 文件。

  3. 加载 AOF/RDB 文件成功后,Redis 启动成功。

  4. AOF/RDB 文件存在错误时,Redis 启动失败并打印错误信息

Redis4.0后的混合持久化

混合持久化是RDB在AOF 重写的时候生成一份快照,在持久化的时候,或者恢复数据时,先加载 RDB快照,然后再执行 AOF 文件中的命令,这样又快又不会丢失数据。

Redis 缓存设计

Redis 作为缓存的时候,会发生缓存击穿,缓存穿透,缓存雪崩三种问题。

缓存击穿

概念

某个热点数据,在某时刻过期,那么大量请求在缓存中找不到这个数据,那么请求就会直达数据库,数据库压力瞬间增大。

解决办法
  1. 互斥锁:给这个 key 加锁,使只能有一个线程访问 key,加载数据到缓存,其他请求等到缓存有之后,再去访问缓存。

  2. 使用异步的方式,不断刷新 key 的过期时间,或者干脆不设置过期时间。

缓存穿透

缓存穿透指的是一直查询一个数据库中不存在的数据,导致每次在缓存中都找不到,请求总是直达数据库。

解决办法
  1. 布隆过滤器:查询缓存前,利用布隆过滤器判断数据是否存在,如果不存在,直接返回,不进行查询。

  2. 空值缓存:当我们查询数据的时候,如果没在数据库内,那么我们在缓存中添加一个 key ,null 的键值对,并且设置一个较短的过期时间,来避免占用资源。

缓存雪崩

缓存雪崩是指在某一个时间点,由于大量的缓存数据同时过期或缓存服务器突然宕机了,导致所有的请求都落到了数据库上(比如 MySQL),从而对数据库造成巨大压力,甚至导致数据库崩溃的现象。

总之就是,崩了,崩的非常严重,就叫雪崩了(电影电视里应该看到过,非常夸张)。

解决办法
  1. 集群 Redis

  2. 备份缓存

  3. 设置不同的过期时间 (在原有的过期时间基础上 添加一个随机值)

  4. 限流与降级

    1. 可以用一些限流策略,例如令牌桶,从而限制访问流量。

    2. 可以降级,例如去关闭一些核心业务

如何保证缓存与数据库一致性

正常逻辑,如果只是读的话并不涉及一致性问题,但是如果写的话,需要考虑一致性问题。

在用 Redis 当缓存的时候,我们去更新数据库,总会造成缓存与数据库不一致的问题。

有两种处理方法:先处理缓存还是先操作数据库。

需要注意的是无论是哪种方法,都推荐删除缓存替代修改缓存,修改缓存成本更高,删缓存业务逻辑更简单。

先处理缓存
数据不一致

并发线程会导致数据不一致。

首先线程 1 发布了一个修改数据库的请求,于是他先清除 redis 中的缓存,然后更新数据库,但是更新的过程是需要一定时间,并且有网络延迟的。此时线程2发起查数据的请求,先查 redis 中的数据,redis 中没有,就查数据库,此时数据库还没更新完,还是脏数据。查数据库之后就要返回缓存到redis中,那么redis中还是脏数据,白删了。

解决方法(延迟双删)

redis 中还会出现脏数据,会导致后面的请求如果想查数据,返回的一直是错误的数据。

所以我们可以在数据库更新之后,再删一次 redis 中的缓存。这样只有线程2 一个线程读的是脏数据,其他之后的线程都是正确的数据。(无法保证强一致性)。

又因为更新数据库需要一定的时间,所以要等一段时间再次删除缓存,解决办法叫延迟双删。

强一致性

如果要保证强一致性,一次都不能错,那就只能加锁了,但是加锁会影响吞吐量,用redis 的目的就是增加吞吐量,所以一般不推荐这么做。

先处理数据库
数据不一致

先更新数据库,此时并发线程2查询数据,查询缓存,读出来的是脏数据。等更新完数据库,就删除 redis 缓存,此时如果有数据再读,redis 如果为空,查询数据库,数据库返回缓存到 redis ,之后的线程查询的就都是正确的了。

所以只有线程 2 读的是脏数据。

处理缓存和处理数据库都有的问题

删除失败怎么办?

如果删除失败的话读的一直是老数据,所以引入删除重试的机制。

通过异步请求的方式,发送消息将需要删除的 key 放入至 mq 之中,然后系统监听 mq,重新删除。

但是这个方法仍然有问题,mq 是放在整个流程内的,代码耦合度高,我们单独将它拿出来,可以利用cannal.

cannal 是阿里研发的,用途是基于 MySQL 增量日志,提供增量数据订阅和消费。cannal 客户端可以是一个 springboot 应用。也就是说我们将删除重试的操作放在 springboot 应用中去解决,解决了代码耦合度问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值