redis

1 redis 数据结构

1.1 String

String 是最基本的 key-value 结构,key 是唯一标识,value 是具体的值,value 可以是整数、浮点数、字符串。

内部实现:int 和 SDS
  • 字符串对象保存的是整数值,并且这个整数值可以用 long 类型来表示
  • 字符串对象保存的是一个字符串,并且这个字符申的长度小于等于 32 字节,用 embstr 编码
  • 字符串对象保存的是一个字符串,并且这个字符申的长度大于 32 字节,用 raw 编码

注意,embstr 编码和 raw 编码的边界在 redis 不同版本中是不一样的:

  • redis 2.+ 版本:32 字节
  • redis3.0-4.0:39 字节
  • redis5.0:44 字节
  • SDS 不仅可以保存文本数据,还可以保存二进制数据。
  • SDS 获取字符串长度的时间复杂度是 O (1)
  • Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出
应用场景
  • 缓存对象:比如 json 等
  • 常规计数:比如计算访问次数、点赞、转发等
  • 分布式锁:NX 参数可以实现「key 不存在才插入」,可以用它来实现分布式锁
  • 共享 Session 信息:借助 Redis 对这些 Session 信息进行统一的存储和管理

1.2 List

List 列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向 List 列表添加元素。

内部实现:双向链表或压缩列表Redis 3.2 版本后统一为 quicklist 
  • 如果列表元素小于 512. 列表每个元素的值都小于 64 字节,redis 就会使用压缩列表
  • 不满足上述条件使用双向链表
应用场景
  • 消息队列:
    • 消息保序:使用 LPUSH + RPOP;
    • 阻塞读取:使用 BRPOP;
    • 重复消息处理:生产者自行实现全局唯一 ID;
    • 消息的可靠性:使用 BRPOPLPUSH
  • 排行榜

1.3 Hash

Hash 是一个键值对(key – value)集合,Hash 特别适合用于存储对象。

内部实现:压缩列表或哈希表;Redis 7.0 交由 listpack
  • 如果哈希类型元素个数小于 512 个,所有值小于 64 字节,Redis 会使用压缩列表作为 Hash 类型的底层数据结构
  • 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的 底层数据结构
应用场景
  • 缓存对象:一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储
  • 购物车
  • 添加商品:HSET cart:{用户id} {商品id} 1
  • 添加数量:HINCRBY cart:{用户id} {商品id} 1
  • 商品总数:HLEN cart:{用户id}
  • 删除商品:HDEL cart:{用户id} {商品id}
  • 获取购物车所有商品:HGETALL cart:{用户id}

1.4 Set

Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储

内部实现:哈希表或整数集合
  • 如果集合中的元素都是整数且元素个数小于 512 个,Redis 会使用整数集合作为 Set 类型的底层数据结构
  • 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构
应用场景
  • 点赞
  • 共同关注
  • 抽奖活动

1.5 ZSet

Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值)

内部实现:压缩列表或跳表Redis 7.0 中 listpack
  • 如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构
  • 不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构
应用场景
  • 排行榜
  • 电话、姓名排序

1.6 BitMap

Bitmap,即位图,是一串连续的二进制数组(0 和 1),可以通过偏移量(offset)定位元素

内部实现

Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型

String 类型是会保存为二进制的字节数组,Redis 就把字节数组的每个 bit 位利用起来

应用场景
  • 签到统计
  • 判断用户登陆态
  • 连续签到用户总数

1.7 HyperLogLog

一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数,统计规则是基于概率完成的,不是非常准确。每个 HyperLogLog 键只需要花费 12 KB 内存就可以计算接近 2^64 个不同元素的基数。

内部实现
应用场景:百万级网页 UV 计数

1.8 GEO

用于存储地理位置信息,并对存储的信息进行操作

内部实现

GEO 本身并没有设计新的底层数据结构,而是直接使用了 Sorted Set 集合类型

GEO 类型使用 GeoHash 编码方法实现了经纬度到 Sorted Set 中元素权重分数的转换

应用场景:滴滴叫车

1.9stream

消息队列设计的数据类型

内部实现:
应用场景
  • 消息队列
  • 消息保序:XADD/XREAD
  • 阻塞读取:XREAD block
  • 重复消息处理:Stream 在使用 XADD 命令,会自动生成全局唯一 ID;
  • 消息可靠性:内部使用 PENDING List 自动保存消息,使用 XPENDING 命令查看消费组已经读取但是未被确认的消息,消费者使用 XACK 确认消息
  • 支持消费组形式消费数据

2 持久化方式

2.1 RDB

每隔一段时间就将数据以二进制方式写入硬盘

  • 过程
  • 1.主进程fork出子进程
  • 2.子进程将共享内存文件写入临时RDB
  • 3.写入完成后将原来的RDB文件替换同时子进程退出

若上述过程中,客服端有新增请求回在基于共享内存的内存副本中进行处理,即主进程每次接受一次IO都会通过COW将共享内存数据同步至内存副本,再在内存副本基础上处理新的操作

  • 优缺点
  • 1.性能高,重启加载二进制
  • 2.文件紧凑,二进制文件相对于AOF紧凑
  • 3.可能丢数据,两次备份期间会丢数据

2.2 AOF

每条写命令做日志追加写入日志文件,重启就回放

  • 过程:redis写AOF缓冲区再写入AOF日志文件;但是AOF缓冲区写入AOF日志有三种方式:
    • 1.实时同步:从缓冲区直接写入日志文件
    • 2.设置时间间隔:1s写一次
    • 3.由系统做主:可能出现RDB大范围丢失
  • 日志重写:对AOF瘦身,a=1,a=2,a=3 —-> a=3
  • 优缺点
  • 1.数据可靠,可以通过命令恢复
  • 2.文件较大
  • 3.恢复速度慢
redis持久化方式是rdb和aof的融合体:以 RDB 方式写入到 AOF 文件

3 redis的过期删除

3.1 如何设置过期时间:

  • expire <key> <n>:设置 key 在 n 秒后过期
  • pexpire <key> <n>:设置 key 在 n 毫秒后过期
  • expireat <key> <n>:设置 key 在某个时间戳(精确到)之后过期
  • pexpireat <key> <n>:设置 key 在某个时间戳(精确到毫秒)之后过期

3.2 如何判断已经过期:

每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到一个过期字典(hash),当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:

  • 如果不在,则正常读取键值
  • 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对

3.3 过期删除策略:

  • 定时删除:在设置 key 的过期时间时,同时创建一个定时事件,当时间到达时,由事件处理器自动执行 key 的删除操作
    • 优点:内存友好,保证过期 key 会被尽快删除,也就是内存可以被尽快地释放
    • 缺点:CPU不友好,过期 key 比较多占用较多CPU时间
  • 惰性删除:不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key
    • 优点:CPU友好,每次访问时,才会检查 key 是否过期
    • 缺点:内存不友好,过期 key 不会被直接删除,内存无法被尽快地释放
  • 定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key
  • 优点:限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用
  • 缺点:内存清理方面没有定时删除效果好,同时没有惰性删除使用的系统资源少。难以确定删除操作执行的时长和频率。

3.4 Redis 过期删除策略

惰性删除+定期删除

  • 惰性删除:Redis 在访问或者修改 key 之前,都会检查key是否过期
    • 如果过期,则删除该 key,至于选择异步删除,还是选择同步删除根据 lazyfree_lazy_expire参数配置决定,然后返回 null 客户端
    • 如果没有过期,不做任何处理,然后返回正常的键值对给客户端
  • 定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key
  • 默认每秒进行 10 次过期检查一次数据库
  • 从过期字典中随机抽取 20 个 key;
  • 检查这 20 个 key 是否过期,并删除已过期的 key
  • 如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查

4 内存淘汰策略

4.1 如何设置 Redis 最大运行内存

redis.conf 中,可以通过参数 maxmemory <bytes>

4.2 Redis 内存淘汰策略

1、不进行数据淘汰的策略

  • noeviction:它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,会报错通知禁止写入,不淘汰任何数据,但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作

2、进行数据淘汰的策略

  • 设置过期时间的数据进行淘汰
    • volatile-random:随机淘汰设置了过期时间的任意键值
    • volatile-ttl:优先淘汰更早过期的键值。
    • volatile-lru:3.0 之前,默认的内存淘汰策略,淘汰所有设置了过期时间的键值中,最久未使用的键值
    • volatile-lfu:Redis 4.0 后新增的内存淘汰策略,淘汰所有设置了过期时间的键值中,最少使用的键值
  • 所有数据范围内进行淘汰
  • allkeys-random:随机淘汰任意键值
  • allkeys-lru:淘汰整个键值中最久未使用的键值
  • allkeys-lfu:Redis 4.0 后新增的内存淘汰策略,淘汰整个键值中最少使用的键值

4.3 LRU 算法和 LFU 算法有什么区别

  • LRU最近最少使用:会选择淘汰最近最少使用的数据
    • 实现方式:是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间(24 bits 的 lru 字段是用来记录 key 的访问时间戳)
    • 内存淘汰:随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个
    • 优点
      • 不用为所有的数据维护一个大链表,节省了空间占用
      • 不用在每次数据访问时都移动链表项,提升了缓存的性能
    • 缺点:无法解决缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染
  • LFU最近最不常用:如果数据过去被访问多次,那么将来被访问的频率也更高
  • 实现方式:相比于 LRU 算法的实现,多记录了「数据的访问频次」的信息(高 16bit 存储 ldt记录时间戳,低 8bit 存储 logc记录访问频次,初始为5)
  • 内存淘汰:logc 并不是单纯的访问次数,而是访问频次,logc 会随时间推移而衰减的,对 logc 做完衰减操作后,就开始对 logc 进行增加操作,增加操作并不是单纯的+1,而是根据概率增加,如果 logc 越大的 key,它的 logc 就越难再增加。
  • 所以,Redis 在访问 key 时,对于 logc 是这样变化的:
  • 1.先按照上次访问距离当前的时长,来对 logc 进行衰减,
  • 2.然后,再按照一定概率增加 logc 的值

5 缓存雪崩、击穿、穿透及解决方案

6 什么是主从、集群、哨兵。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇