杨哥Redis

杨哥Redis

<span style="font-weight: bold;" class="bold">阳哥三板斧

  1. 理论
  2. 实操
  3. 小总结

Redis是什么

Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。
键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。

Redis十大数据类型

image.png
image.png

String

  • string是redis最基本的类型,一个key对应一个value。
  • string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。
  • string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
    :::tips
    案例:点赞总数量
    :::

List

队列
<span style="font-weight: bold;" class="bold">底层:双端链表
:::tips
案例:微信公众号订阅的消息
:::

Hash

同样是key-value结构,但是value是哈希结构
哈希表
:::tips
案例:京东早期的购物车(现在只适用于中小厂了)
:::

Set

集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员):::tips案例:

  • 微信抽奖小程序
  • 点赞查看共同好友
  • QQ推荐可能认识的人
    :::

Zset

有序集合(<span style="font-weight: bold;" class="bold">sorted set 有序集合)

  • 不同的是每个元素都会关联一个double类型的分数
  • zset集合是通过哈希表实现的

地理空间 GEO

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,包括添加地理位置的坐标。获取地理位置的坐标。计算两个位置之间的距离。根据用户给定的经纬度坐标来获取指定范围内的地理位置集合

<span style="font-weight: bold;" class="bold">本质:zset经纬度 -> geo hash:::tips案例

  • 附近的酒店
    :::

基数统计 HyperLogLog

uv统计日活用户数量统计:::tips案例

  • UV统计
  • PV统计
    :::

位图 Bitmap

image.png0和1表示的二进制bit数组:::tips案例:

  • 打卡签到
  • 广告是否被点击
  • 上下班签到统计
    :::

位域 BitField

通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。

说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。

了解即可

流 Stream

类似mq
不常用,建议右转rabbitMQ


常用操作命令

命令不区分大小写 key区分大小写

常见的key

String

Set命令

<span style="font-weight: bold;" class="bold">SET key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX] [GET]

  • EX seconds – 设置键key的过期时间,单位时秒
  • PX milliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值
  • KEEPTTL – 获取 key 的过期时间
  • <span style="font-weight: bold;" class="bold">GET – 返回 key 存储的值,如果 key 不存在返回空

mset mget
同时获取多个值

getrange setrange
切片

INCR key 增加数字INCRBY key increment 增加指定的数字

DECR
DECRBY
数值-1命令同上 不过多赘述

List

image.png

Set

image.png

image.png

Zset

image.png

Bitmap

image.png

持久化

image.png
RDB and AOF
RDB:将一整个数据库进行备份,如同快照
AOF:将每个命令一一写进文件中

RDB(默认)

:::tips特点:

  • 适合大规模的数据恢复
  • 按照业务定时备份
  • 对数据完整性和一致性要求不高
  • RDB文件比AOF文件加载快
    :::

触发方式

自动触发

配置文件中的默认配置
重启的时候 使用rdb文件恢复数据

手动触发

命令:save和bgsave
save:阻塞的执行
bgsave(默认):开一个线程进行数据的保存
flush操作
:::tips
使用bgsave 不用save
注:
bg:Background
:::
RDB 在执行快照的时候,数据能修改吗?
可以的,执行 bgsave 过程中,Redis 依然<span style="font-weight: bold;" class="bold">可以继续处理操作命令的,也就是数据是能被修改的,关键的技术就在于<span style="font-weight: bold;" class="bold">写时复制技术(Copy-On-Write, COW)。

AOF

<span style="font-weight: bold;" class="bold">流程:<span style="font-weight: bold;" class="bold">执行命令后 先到缓冲区 再写进文件:::tips存在三种策略

  • Always 总是
  • everysec(默认) 每秒钟一次
  • no 写进缓冲区 由操作系统来决定什么时候写进文件

总结:
image.png
:::

AOF重写机制

由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,
文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。

为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的峰值时,Redis就会自动启动AOF文件的内容压缩,

<span style="font-weight: bold;" class="bold">触发时机

  1. 当系统达到配置文件中的配置时,进行重写
  2. 可以手动使用命令 bgrewriteaof 来重写。
    :::tips
    原理:
    在重写之前,创建一个重写子进程(不影响redis的正常工作)这和父进程之间共享数据,并压缩指令,然后写到一个临时文件当中。
    注意:父子间数据独立,如果父进程新增了数据 就会发生写时复制
    与此同时,主进程会将产生的新指令写进缓冲区中,AOF的工作也正常。等到子进程重写完成后,发信号给父进程,把缓冲区的指令写进新的AOF文件中。
    重写AOF并没有读取原来旧的AOF文件 而是用数据库是数据进行了完整的备份

也就是说,在 bgrewriteaof 子进程执行 AOF 重写期间,主进程需要执行以下三个工作:

  • 执行客户端发来的命令;
  • 将执行后的写命令追加到 「AOF 缓冲区」;
  • 将执行后的写命令追加到 「AOF 重写缓冲区」;

:::

redis6和7 AOF文件的区别

image.png
7之后 底层进行了变化 对aof文件进行了拆分

混合使用

一起使用时 AOF优先级高于RDB
RDB做全量备份 AOF做增量备份

关闭缓存

save “”
appendonly no

Redis事务

一次性执行多个命令

Redis事务允许执行一组命令 在一个步骤中,它们以命令为中心 MULTI,EXEC,DISCARD和WATCH。 Redis Transaction做出了两个重要保证:

  • |事务中的所有命令都按顺序序列化和执行。另一个客户端发送的请求永远不会在Redis事务的执行过程中提供服务。这保证了命令作为单个隔离操作执行。||| 1 单独的隔离操作 | Redis的事务仅仅是保证事务里的操作会被连续独占的执行,redis命令执行是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的 |
    | ——————– | ———————————————————————————————————————————————- |
    | 2 没有隔离级别的概念 | 因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这种问题了 |
    | 3不保证原子性 | Redis的事务不保证原子性,也就是不保证所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力 |
    | 4 排它性 | Redis会保证一个事务内的命令依次执行,而不会被其它命令插入 |

执行流程

对于事务中存在语法错误的 全部不执行
对于事务中操作错误的 正确的执行,错误的不执行
:::info
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
:::

watch监控

:::tips
相当于乐观锁,在事务执行过程中会进行判断
:::

命令

multi 开始事务
exec 执行事务
discard 放弃事务
watch<span style="font-weight: bold;" class="bold"> 乐观锁 cas? check and set

Redis复制

首次连接 全量复制(自身数据会被覆盖清除)

主机收到sync命令后 会触发RDB 等到结束后 将这个发给从机

后面则是稳步续传(支持断点续传(内部存着一个偏移量))

缺点

  • 从机多了之后 网络通信变慢(从机可以继续套娃从机)
  • master挂了 那就是真的挂了

Redis哨兵

哨兵选举出新的master后 会修改配置文件

<span style="font-weight: bold;" class="bold">选举 Raft算法

何时选举

首先哨兵进行主观下线,然后其它哨兵如果也认为主观下线,则转为客观下线,转而进行选举

  1. 在哨兵中选出兵王
  2. 兵王再从redis中选出领导

选举过程

1.比权限
2.比偏移量
3.Run id 比大小?

Redis管道

pipeline缓冲的指令只是会依次执行,不保证原子性,如果执行中指令发生异常,将会继续执行后续的指令
使用pipeline组装的命令个数不能太多,不然数据量过大客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存

Redis集群

  1. 集群可以多个master 、
  2. 集群自带故障转移功能

建议最多1000个

hash

普通hash
一致性hash
缺点:数据倾斜

hash槽

为什么槽位是16384

来源于github官方解答
1.心跳包太大
2.节点不过1000
槽位小 压缩比例高

常见命令

<span style="font-weight: bold;" class="bold">命令不区分大小写 key区分
unlink key 非阻塞删除
expire 设置过期时间

Redis高级

redis工作线程是单线程 别的(网络io)则是多线程

Redis为什么这么快

IO多路复用image.png网络使用多线程 但是操作还是单线程(不用加锁):::info

  • Redis 基于内存,内存的访问速度是磁盘的上千倍;
  • Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
  • Redis 内置了多种优化过后的数据结构实现,性能非常高。:::为什么不使用单线程:
  • 单线程编程维护简单
  • Redis的瓶颈不在cpu,而是在内存和网络
  • 多线程会存在死锁,上下文切换等问题,有可能更加影响性能

big Key问题

多大

string10kb以内 别的类型数量不超过5000

如何发现

  1. redis-cli –bigkeys
  2. memory usage

<span style="font-weight: bold;" class="bold">2、借助开源工具分析 RDB 文件。
通过分析 RDB 文件来找出 big key。这种方案的前提是你的 Redis 采用的是 RDB 持久化。

如何删除

渐进式删除

:::info<span style="font-weight: bold;" class="bold">开启 lazy-free(惰性删除/延迟释放) :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。:::

  1. 配置文件 禁用命令
  2. 使用scan命令

删除

String del或者unlink
针对别的类型 使用渐进式删除
删除key的某一部分内容 再把key删除,例hash 把里面的value删除一些 再删除key

数据一致性

Cache Aside Pattern(旁路缓存模式)

<span style="font-weight: bold;" class="bold">Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。Cache Aside Pattern 中服务端需要同时维系 db 和 cache,并且是以 db 的结果为准。下面我们来看一下这个策略模式下的缓存读写步骤。<span style="font-weight: bold;" class="bold">写:

  • 先更新 db
  • 然后直接删除 cache 。

简单画了一张图帮助大家理解写的步骤。

image.png<span style="font-weight: bold;" class="bold">读 :

  • 从 cache 中读取数据,读取到就直接返回
  • cache 中读取不到的话,就从 db 中读取数据返回
  • 再把数据放到 cache 中。

简单画了一张图帮助大家理解读的步骤。

image.png

灵魂拷打

能不能先删除cache再更新缓存
image.png

更新数据库再更新Redis

image.png

双检加锁

查询redis,没有就加锁 ,继续查一遍redis,再没有就查询数据库,然后写进redis

缓存一致性问题

1.更新数据库 再更新缓存

延迟双删
需要睡眠一段时间,然后再删除redis中的数据
删除两次redis的数据

Redis数据统计

Hyperloglog
UV:独立访客(需要去重)
PV:页面浏览量(不去重
DAU:日活量
MAU:月活

布隆过滤器

bitmap
有,绝对有
无,肯定无

缓存问题

缓存预热

缓存雪崩

redis挂了 或者大面积key的失效

缓存穿透

redis无 mysql无 mysql被暴击
解决:
缓冲空对象
布隆过滤器

缓存击穿

大量请求同时请求一个key 但是他失效了 被打爆

  1. 双检加锁
  2. 两个缓存 (A缓存套着B缓存

分布式锁

  1. 自动过期
  2. 防止误删别人的key (value判断)
  3. lua脚本保证原子性
  4. 自动续期

可重入性
使用hset 哈希结构

实现JUC的Lock接口

红锁

redis官方推荐

缓存淘汰

redis采用惰性删除+定期删除

Redis内存

默认是0,即无限占用
当内存满了之后,会报oom

删除策略

LRU 最近 最长时间未被使用
LFU 最近 最不常用 频率

过期中/所有

IO多路复用

select->poll->epoll
同步异步针对服务者,阻塞非租塞针对调用者

右边面试题 这里TODO了

源码


杨哥Redis
https://hexo-blog-five-swart.vercel.app/post/brother-yang-redis-o59h.html
作者
方立
发布于
2024年4月20日
许可协议