八股文

  • 概述
  • 数据结构
  • 持久化机制
  • 缓存常见问题

概述

Redis的优点?为什么快?

  1. 基于内存操作:绝大部分操作和数据都在内存中,相比传统磁盘文件操作减少了IO。
  2. 高效的数据结构:优化的String、Hash、List、Set、Zset等数据结构
  3. 采用单线程:省去上下文切换和CPU的开销,同时不存在资源竞争,避免死锁。

    • 单线程网络请求模块使用单线程进行处理,其他模块仍用多个线程。因为Redis的瓶颈不是CPU,最有可能是机器内存或者网络带宽,并且单线程易于实现。
    • 多线程:Redis 6.0以后,多线程用于处理网络数据的读写和协议解析,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。
  4. I/O多路复用:一个服务端进程(复用)同时处理多个套接字描述符(多路),根据Socket上的事件来选择对应的事件处理器进行处理。

TODO: select, poll, epoll

Why is Redis so fast?

Redis的缺点?为什么不做主数据库只做缓存?

  1. 内存限制:数据库容量受到物理内存的限制,不能用作海量数据的高性能读写。
  2. 数据持久化:尽管采用了数据持久化机制,如果服务器崩溃或断电,内存中数据仍可能丢失。
  3. 数据安全:不具备像主数据库一样复杂的认证和审计机制。
  4. 结构化查询:作为键值(Key-Value)数据库,对结构化查询支持较差。
  5. 事务处理:对复杂的事务无能为力,比如跨多个键的事务处理。
  6. 在线扩容:在集群容量达到上限时在线扩容会变得很复杂。

Redis应用场景

  1. 缓存热点数据:缓解数据库的压力。用户在访问业务数据时,先到Redis中拿;如果不存在,再到MySQL中拿,接着把访问过的数据写入Redis。
  2. 社交网络:Redis的哈希、集合等数据结构能很方便的的实现排行榜、共同好友等功能;利用Redis原子性的自增操作,可以实现计数器的功能,比如统计用户点赞数等;也可作限速器,如秒杀场景中防止用户快速点击带来不必要的压力。
  3. 消息队列:Redis提供了发布/订阅模式及阻塞队列功能,能够实现简单的消息队列,实现异步操作。
  4. 分布式锁:分布式场景下,无法使用单机环境下的锁对多个节点上的进程进行同步。可以使用Redis自带的 SETNXSET if Not eXists)命令或 RedLock 分布式锁实现。

    特性SETNXRedLock
    适用场景单Redis实例的分布式锁分布式环境,多个Redis实例
    性能高(单实例操作)相对较低(需协调多个实例)
    容错性单点故障会导致锁失效支持部分节点失效
    过期机制无(需额外配合 EXPIRE 设置)内置过期时间,防止死锁

为什么用Redis而不用Map做缓存?

  • Map实现的是本地缓存,其生命周期随着JVM的销毁而结束,且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性;而Redis的分布式缓存具有一致性,各实例共用一份缓存数据。
  • Redis可单独部署,在多个项目间共享
  • Redis的缓存可以持久化,Map是内存对象,程序一重启数据就没了。
  • Redis可以用几十G内存来做缓存,Map不行。
  • Redis可以处理每秒百万级的并发。
  • Redis缓存有过期机制和丰富的API。

Redis的内存用完了会怎样?

如果达到设置的上限,写命令会返回错误信息(读命令还可以正常返回);若配置了内存淘汰机制,会冲刷掉旧的内容。

Redis如何做内存优化?

尽可能的将数据模型抽象到一个哈希表里面。比如一个用户对象,不要为这个用户的名称,姓氏,邮箱等设置单独的Key,而是将这个用户的所有信息存储到一张哈希表里。

数据结构

String

底层由 SDS(简单动态字符串)实现:具有 Len 属性;拼接前会自动扩容。用于:

  • 缓存对象:JSON等。
  • 共享Session信息:解决了分布式系统下多服务器Session不一致的问题。
  • 分布式锁:利用 SETNX 命令。
  • 计数器:支持原子性数值操作,用于访问次数、点赞、库存等。


Redis 3.2前,HashList 默认在元素数量小于512且所有值都小于64字节时使用压缩列表,否则分别使用哈希表双向链表Set 默认在元素数量小于512且都是整数时使用 整数集合,否则使用哈希表

Redis 3.2后,List 底层数据结构只由QuickList实现,替代了双向链表和压缩列表。Redis 7.0中,压缩列表被废弃,由ListPack来实现。

Hash

O(1) 的复杂度快速查询数据,应用场景如:

  • 缓存对象:对象一般为 String + Json ,变化频繁的属性抽出来用 Hash 存储。
  • 购物车:以用户id为 key ,商品id为 field ,商品数量为 value

 
拉链法/链地址法(Chaining):当哈希冲突发生时,多个键值对被存储在同一个哈希桶Bucket)中,通过链表链接起来。

渐进式Rehash(‌惰性Rehash‌):由于 Rehash 是一个较耗时的操作,一次性重新计算所有链表在桶中的位置可能会引起客户端长时间阻塞,因此Redis通过渐进式方式优化:维护两个哈希表,一个是当前正在使用的表 ht[0] ,另一个是新分配的空表 ht[1] 。除了通过定时任务逐步处理部分哈希桶以外(主动Rehash),期间对哈希表操作(如查询、插入、删除)时,也会顺序将 ht[0] 中索引位置上的所有数据迁移到 ht[1] 上,最终在某个时间点所有键值对都迁移完成。触发条件如下:

负载因子 = 已存储键值对数量 / 哈希桶数量。
负载因子 ≥ 1 且没有执行 RDB 快照或 AOF 重写时,就会进行 Rehash
负载因子 ≥ 5 时,无论是否执行 RDB 快照或 AOF 重写,都会强制 Rehash

List

有序可重复集合。可以通过 LPUSHBRPOP 来实现左进右出的消息队列,数据消费者阻塞地获取列表尾部的数据。

Set

无序去重集合。可用于:

  • 共同好友、爱好等:支持交集、并集等方法。
  • 抽奖活动Set 类型有去重功能,可以保证同一个用户不会中奖两次。
Set的差集、并集和交集计算复杂度较高,数据量较大时会导致Redis实例阻塞。在主从集群中,为了避免主库被阻塞,可以选择一个从库完成聚合统计;或者把数据返回给客户端,由客户端来完成聚合统计。

Zset (Sorted Set)

带权有序去重集合。基于哈希表跳表实现,访问中间元素时间复杂度是 O(log N) 。每个元素有一个score参数。相较于List更耗内存。典型应用场景:排行榜延时队列(由 score 记录时间戳)。

Bitmap

位图。用于数据量大且二值统计的场景,如签到用户登录态资源占用

Hyperloglog

用于基数统计(如UV(Unique Visitor)统计),仅需12KB就能统计2^64个数据(正常每个数据需要8 Bytes)。基于概率实现,标准误差是0.81%。

Geospatial

存储地理空间信息,支持距离计算、范围查询等操作,可用于定位附近的人打车距离计算等。使用 GeoHash 编码方法实现经纬度到Zset中元素权重分数的转换。

Stream

有序存储序列数据,专门为消息队列设计。相较于List的特性:可自动生成全局唯一消息ID(时间戳-序列号),支持消费者组


持久化机制

AOF(Append-Only File)

每执行一条写操作命令,就将该命令以追加的方式写入到AOF文件;在恢复时,以逐一执行命令的方式来进行数据恢复。

缺点

  • AOF文件会随着操作增加而变得很大,需要定期重写。
  • 用AOF的方式来恢复数据很慢,因为Redis执行命令是由单线程负责的。
  • 由于每次写操作都需要记录到日志,性能可能低于RDB。

RDB(Redis DataBase)

xxx


缓存常见问题

缓存击穿

缓存穿透

缓存雪崩


2024-11-20 技术学习·none