Skip to content

Latest commit

 

History

History
295 lines (239 loc) · 20.7 KB

File metadata and controls

295 lines (239 loc) · 20.7 KB

Redis

使用场景:1.做数据库。2.缓存热点数据。3.消息队列服务器

Redis中的key都是 string 类型

Redis内部编码: TYPE命令实际返回的就是当前键的数据结构类型,但这些只是Redis对外的数据结构

  • String 的内部编码方式:
    • raw:最基本的形式,类似 Java 中的 byte[] 数组。
    • int:当值是整数时,直接用 int 类型存储。
    • embstr:当字符串较短时,使用优化的 embstr 存储。 Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现
  • String 类型的具体应用场景
    • 缓存功能:作为缓存层,提高读写速度,减轻后端数据库压力。
    • 计数功能:实现快速计数,如视频播放次数统计。
    • 共享会话:集中管理用户会话,支持分布式系统。
    • 手机验证码:存储验证码,设置过期时间,确保安全性。

Redis如何对过期key的处理?

  • 定期删除:每隔一段时间,抽取一部分数据检查,看是否有过期的数据,将其删除
  • 惰性删除:当用户操作数据时,检测一遍这个数据是否过期,如果过期就删除,再给用户返回key不存在

Redis 过期键处理和内存淘汰策略

  • 现有策略:Redis 采用了定期删除和惰性删除相结合的策略。
  • 内存淘汰策略
  • 定时器方式的考虑,基于优先级队列或时间轮可以实现高效的删除。

Redis处理请求:

  • 单线程处理请求:Redis 使用一个线程处理所有的客户端请求,但这并不意味着Redis服务器内部只有一个线程。
  • 内部多线程:Redis 内部确实有多个线程,但这些线程主要用于处理网络 I/O。
  • 命令执行顺序:尽管多个客户端看起来是同时发起请求,但从微观角度看,这些命令仍然是按线性顺序执行的,且命令的执行顺序是不确定的,但绝不会有两个命令同时执行。
  • 请求排队:多个请求同时到达Redis服务器时,会先在队列中排队,然后由Redis服务器逐个取出并执行。

通用命令

SET :设置键值对,并可设置过期时间和条件。 mset :一次性设置多个键值对。 setnx :如果键不存在,则设置键值对。 setex :设置键值对并指定过期时间(秒)。 GET :获取键对应的值。 mget :一次性获取多个键对应的值。 KEYS :获取所有符合特定模式的键。 EXISTS :检查一个或多个键是否存在。 DEL :删除一个或多个键。 EXPIRE :为指定的键设置过期时间(以秒为单位)。 TTL :获取键的剩余生存时间(以秒为单位)。 TYPE :返回键对应的值的类型。 FLUSHALL :删除Redis中所有数据库的数据。 incr :将键的值加1,如果键不存在则创建键并初始化为0。 incrby :将键的值增加指定的整数。 decr :将键的值减1,如果键不存在则创建键并初始化为0。 decrby :将键的值减少指定的整数。 incrbyfloat :将键的值增加指定的浮点数。(Redis存储整数,是直接使用int类型存的,而存储小数,本质上是当作字符串来存储)

Redis在共享会话场景(Sessio)的应用

  • 背景:分布式 Web 服务将用户的 Session 信息保存在各自的服务器中,负载均衡导致用户请求可能被分配到不同的服务器,造成用户需要重新登录的问题。
  • CookieSession
    • Cookie: 浏览器存储数据的机制。
    • Session: 服务器存储数据的机制。
  • 会话的概念
    • 会话:客户端和服务端在交互过程中产生的专属于该客户端的中间状态数据。
    • 目的:确保服务器能够识别和记住客户端的多次访问状态。
  • 会话管理的重要性
    • 一致性:确保客户端在多次访问中的一致性体验。
    • 数据共享:多个服务器之间共享会话数据,避免因服务器切换导致的信息丢失。
  • 解决方案
    • 使用 Redis 集中管理 Session 信息,确保无论用户被均衡到哪台 Web 服务器上,都能从 Redis 中查询和更新 Session 信息。

Redis在手机验证码场景应用

  • 用户在登录的时候,为了保证用户账号的安全,我们会使用验证码.
  • 当用户登录的时候,redis就会在服务器中保存一个与用户对应的验证码,这个验证码具有过期时间(比如在5分钟内有效).
  • 在用户输入验证码之后,会从redis中查询对应的键值对,校验用户的验证码.
  • 当然为了用户反复接收验证码,导致redis压力过大,一般规定在一分钟之内,最多接收一次验证码,如果手机没有验证码,可以尝试在一分钟之后重新获取验证码.

hash命令

HSET :设置 hash 中 指定的字段(field)的值(value)。 语法 :HSET key field value [field value ...]

HGET :获取 hash 中指定字段的值。 语法 :HGET key field

HEXISTS :判断 hash 中是否 存在 指定的字段。(在hexists命令中,不支持一次性查询多个field,一次只可以查询 一个field.)

HDEL :删除 hash 中指定的字段。 HKEYS :获取 hash 中的 所有 field。 HVALS :获取 hash 中的 所有 value。 HGETALL :获取 hash 中的所有 field 以及对应的 value 。 HLEN :获取 hash 中的所有 field 的个数。 HINCRBY :将 hash 中 field 对应的数值添加指定的值。

哈希的内部编码:

  • ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认 512 个),同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为哈希的内部实现。ziplist 使用更加紧凑的结构实现多个元素的连续存储。
  • hashtable(哈希表):当哈希类型无法满足 ziplist 的条件时,Redis 会自动切换到使用哈希表作为哈希的内部实现。

list命令

列表类型用于 存储多个有序的字符串,如 a、b、c、d、e 五个元素从左到右组成一个有序列表,每个字符串称为元素,最多可存储 $(2^{32}-1)$ 个元素。 列表支持 两端插入(push)、弹出(pop)、获取指定范围或索引的元素等操作。

LPUSH :是按照键入在命令中的顺序,从左向右将命令中的元素插入到list中的 语法 :LPUSH key 1 2 3 4,那么最后list呈现的结果为:4 3 2 1,采取的为头插。 RPUSH :从右侧插入元素。 LRANGE :获取指定范围的元素。 LPOP :从左侧弹出元素。 RPOP :从右侧弹出元素。 LINSERT :在指定位置插入元素。 LSET ;修改指定下标的元素。 LTRIM :指定范围内元素保留,剩余的元素删除。 LREM :删除元素。 阻塞命令 Redis 的列表类型还提供了一些具有 阻塞性质 的命令 BLPOP :读取并删除列表头部元素,如果列表为空则用户陷入阻塞。 BLPOP key [key ...] timeout BRPOP :读取并删除列表尾部元素,如果列表为空则用户陷入阻塞。

  • 超时时间(timeout): 参数 timeout 的单位是秒。如果你把它设置为 0,代表无限期阻塞(永久等待),直到有数据为止。如果设置为 5,则等 5 秒,如果还没数据就返回 nil(空)。
  • 同时监听多个队列: 你可以一次性监听多个 Key,比如 BRPOP queue_high queue_low 0。 Redis 会按顺序检查,如果都没有,就阻塞。一旦任何一个队列有数据进来了,它就会立刻返回那个队列的名字和数据。(这可以用来实现队列优先级,比如优先处理 VIP 用户的任务)。
  • 只阻塞当前客户端,不阻塞 Redis: 新手常有的误区是怕阻塞命令把整个 Redis 卡死。不用担心,Redis 是非常聪明的,BRPOP 仅仅只会阻塞发起这条命令的那个客户端连接,Redis 服务器本身依然可以极其流畅地处理其他客户端发来的成千上万条读写命令。
  • 在多线程中,有一个生产消费模型,其可以基于阻塞队列实现,主要满足以下两个性质: 如果阻塞队列满了,那么生产者阻塞 如果阻塞队列空了,那么消费者阻塞

Redis实现消息队列

Redis 可以使用 lpush + brpop 命令组合实现经典的阻塞式生产者-消费者模型队列。 流程: 生产者 :客户端使用 lpush 从列表左侧插入元素。 消费者 :多个消费者客户端使用 brpop 命令阻塞式地从队列中“争抢”队首元素。

set命令

集合类型也是保存多个字符串类型的元素的,和 list 类型不同的是:

  • 无序性:集合中的元素没有顺序。
  • 唯一性:集合中的元素不允许重复。

SADD :将一个或多个元素添加到集合中。 SMEMBERS :获取集合中的所有元素。 SISMEMBER :判断一个元素是否在集合中。 SCARD :获取集合的基数(元素个数)。 SPOP :从集合中删除并返回一个或多个元素。(由于set内的元素是⽆序的,所以取出哪个元素实际是未定义⾏为,即可以看作随机的) SRANDMEMBER :获取set中的随即元素。 SMOVE :将一个元素从源集合移动到目标集合。 SREM :将指定的元素从集合中删除。 除了基本的集合命令,Redis还提供了集合之间的一些命令。

zset命令

有序集合(Zset)是Redis中的一种数据结构,它结合了哈希表和跳跃列表的特性。每个 member 都有一个分数(score),根据这个分数进行排序。

特点

  • member 不能重复,但分数可以相同。
  • 通过分数实现有序性。
  • 支持范围查询、排名计算等功能。

ZADD :添加或更新元素。 ZCARD :获取基数。 ZCOUNT :计算分数范围内的元素数量。 ZRANGE :获取指定区间内的元素。 ZREVRANGE :获取指定区间内的元素(降序)。 ZRANGEBYSCORE :获取指定分数范围内的元素。 ZPOPMAX :用于删除并返回有序集合中分数最高的一个或多个元素。在有序集合中,最大值相当于最后一个元素(尾删)。如果多个元素的score相同,那么会按照member的字典序进行比较,字典序高的先删除。 ZPOPMIN :删除并返回最低分的元素。 BZPOPMAX :读取并删除zset最大元素,如果没有元素则陷入阻塞。 BZPOPMIN :读取并删除zset最小元素,如果没有元素则陷入阻塞。 ZRANK :获取成员的下标。

有序集合的内部编码:

  • ziplist(压缩链表)
  • skiplist(跳表):跳表是一种特殊的链表结构,具有多层次的索引机制,类似于B+树的部分性质。

应用场景排行榜系统:有序集合(Zset)最典型的应用场景之一就是排行榜系统,例如微博热搜、游戏天梯排行等。这类应用的关键点在于排行榜的分数是实时变化的。

其它数据类型命令

Streams :应用场景:主要用为队列(阻塞队列) geospatial :用来存储坐标 (经纬度) HyperLogLog :应用场景只有一个:估算集合中的元素个数。 假设Set存储userid,每个userid按照8个字节算: 1亿UV => 8亿字节 => 0.8G => 800MB HyperLogLog可以最多使用12KB空间,实现上述效果: HyperLogLog不存储元素的内容,但是能够记录元素的特征,从而在新增元素的时候,能够知道当前新增的元素是一个已经存在的元素,还是一个崭新的第一次出现的元素。 bitmaps :使用 bit 位来表示整数。位图本质上,就还是一个集合,属于是 Set 类型针对整数的特化版本。bitmap 节省空间,也存在一定的可逆性。内存占用(结合使用场景选择):hyperloglog<bit<set bitfields :位域本质上是精确进行位操作的一种方法

Redis持久化

当向 Redis 中插入数据时,需要同时考虑如何将数据写入内存和硬盘。为此,Redis 提现了两种不同的持久化策略:RDB(Redis Database)模式与 AOF(Append Only File)模式。

RDM :RDB 持久化是把当前进程数据生成 快照 保存到硬盘的过程,触发 RDB 持久化过程分为 手动触发和自动触发。

触发机制 : 手动触发 :

  • save 命令:执行的时候,Redis就会全力以赴的进行"快照生成"操作,此时就会阻塞Redis的其他客户端的命令,结果类似与keys *,所以我们一般不建议使用save命令.
  • bgsave 命令:不会阻塞影响到其他的Redis客户端, 是由于Redis引入了多进程的方式来实现这个功能. 其中一个进程负责继续接收客户端的请求, 另一个进程负责生成快照并存储到硬盘中. 也就是Redis会创建出一个子进程,子进程会完成持久化操作. 持久化会把数据写入到新的文件中, 然后使用新的文件替换旧的文件。

除了手动触发之外,Redis 运行自动触发 RDB 持久化机制,

  • 使用 save 配置。如 "save m n" 表示 m 秒内数据集发生了 n 次修改,自动 RDB 持久化。
  • 从节点进行全量复制操作时,主节点自动进行 RDB 持久化,随后将 RDB 文件内容发送给从节点。
  • 执行 shutdown 命令关闭 Redis 时,执行 RDB 持久化。

RDB优缺点 : 优:

  • RDB是⼀个紧凑压缩的⼆进制⽂件,代表Redis在某个时间点上的数据快照,⾮常适⽤于备份,全量复制等场景

  • Redis加载RDB恢复数据远远快于AOF的方式

缺:

  • RDB⽅式数据没办法做到实时持久化 / 秒级持久化,因为bgsave每次运⾏都要执⾏fork创建⼦进程,属于重量级操作,频繁执⾏成本过⾼

  • RDB ⽂件使⽤特定⼆进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有⻛险。

AOF :AOF持久化机制 以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。

AOF重写机制涉及 创建一个子进程 来进行重写工作,而父进程继续处理客户端请求。

如果Redis上同时存在AOF文件和RDB快照的时候,此时以AOF文件为主,RDB直接被忽略.

Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制

  • always配置虽然最安全但性能差;命令写入aof_buf后立即调用fsync同步
  • no配置提高了性能但增加了数据丢失的风险;不主动调用fsync,依赖操作系统
  • everysec是默认且推荐的配置,它能在数据安全性和性能间取得较好的平衡。每秒由后台线程调用fsync同步

Redis的事务

Redis 的事务和 MySQL 的事务概念上是类 似的,都是把一系列操作绑定成一组,让这一组能够批量执行。然而,需要注意的是 Redis 事务与 MySQL 事务存在以下几点区别:

  • 弱化的原子性: Redis 没有 "回滚机制"。它只能保证这些操作 "批量执行",但不能做到 "一个失败就恢复到初始状态"。
  • 不保证一致性: Redis 不涉及 "约束",也没有回滚机制。MySQL 的一致性体现在运行事务前后结果的合理性,不会出现中间非法状态。
  • 不需要隔离性: Redis 事务没有隔离级别,因为 Redis 是单线程处理请求,不会并发执行事务。
  • 不需要持久性: Redis 数据保存在内存中,是否开启持久化由 redis-server 自行决定,这与事务无关。

Redis 事务本质上是在服务器上创建了一个 "事务队列"。 每次客户端在事务中进行一个操作,都会先将命令发送给服务器并放入 "事务队列"(但并不会立即执行),而是在收到 EXEC 命令后,才真正执行队列中的所有操作。

Lua 脚本增强 尽管 Redis 本身不支持像编程语言那样的条件判断(如 if),但它可以引入 Lua 脚本来实现复杂的逻辑判断。通过 Lua 脚本,我们可以实现上述条件的判断逻辑,进一步强化 Redis 事务的应用能力。

事务操作

  • multi:开启事务
  • exec:提交事务
  • discard:取消事务
  • watch:让事务可以监听外部的变化,如果监听的数据被外部改变,操作失效

WATCH 的实现原理类似于我们在并发编程中学习的乐观锁机制,即解决 CAS (Compare-And-Swap)中的 ABA 问题的策略。乐观锁假设冲突很少发生,并在检测到冲突时采取相应的措施。与 ABA 问题中的实现策略相似,Redis 的 WATCH 基于版本号机制实现了乐观锁。

watch使用了一种版本号的机制,每个数据都有一个版本号,每次修改key的值时,都会修改其版本号。

  • 在watch key时,会记录当前的版本号。
  • 在事务提交时,检测当前的版本号是否与之前的版本号相同,如果相同那么提交成功,如果版本号不同,说明有别的用户修改了数据,导致版本号修改,当前事务将不会执行并返回失败。
  • WATCH 本质上是给 EXEC 加上了一个对特定 key 的判定条件:只有当所有被 WATCH 的 key 自从 WATCH 开始以来没有被修改过的情况下,事务才会被执行;否则,事务将被取消。

乐观锁和悲观锁

  • 乐观锁:乐观锁假设在加锁之前,锁冲突的概率较低。因此,在加锁之前不会进行任何检查,而是直接进行操作。如果在操作完成后发现冲突,则会进行重试。
  • 悲观锁:悲观锁假设在加锁之前,锁冲突的概率较高。因此,在加锁之前会进行检查,确保没有其他线程正在操作该资源。如果发现冲突,则会阻塞等待。

Redis分布式系统

  • 主从模式
    • 定义:包含一个主服务器(Master)和多个从服务器(Slave)。所有写操作都在主服务器上执行,而读操作可以在从服务器上分散进行。
    • 数据一致性:从服务器的数据是从主服务器复制而来,且不允许直接修改,以保证数据的一致性和完整性。
    • 工作原理:写入操作集中在主节点,查询操作则被分配到从节点,确保主节点的数据是最新的,同时减轻其负载。
  • 主从 + 哨兵模式
    • 概述:在主从模式基础上加入了哨兵(Sentinel)组件,用于监控集群健康状况,并在主节点故障时自动选择一个新的主节点。
    • 功能:提供了自动化的故障检测与恢复机制,减少了人工干预的需求。
  • 集群模式
    • 特点:允许多个Redis实例协同工作,形成一个逻辑上的大型数据库,支持水平扩展。
    • 适用场景:适合需要 更大存储容量或更高性能的应用环境。

主从模式拓扑结构

  • 一主一从拓扑
    • 特点:最简单的拓扑结构。
    • 优化:可以通过 关闭主节点的AOF,只在从节点上开启AOF来减少主节点的压力。
    • 缺陷及改进:一旦主节点宕机,需要让主节点从从节点获取AOF文件再重启,以防止数据丢失和同步问题。
  • 一主多从拓扑
    • 适用场景:适用于读数据比重较大的场景,可以将 读命令负载均衡 到不同的从节点上来分担压力。
    • 缺点:不适用于写数据比重较大的场景,因为随着从节点个数增加,主节点的网络传输压力也会增大。
  • 树形拓扑
    • 特点:从节点不仅可以复制主节点数据,还可以作为其他从节点的主节点继续向下层复制。
    • 优势:相比一主多从结构,可以减少主节点网络传输压力,不需要那么高的网卡带宽。
    • 缺点:数据修改后的 同步延时更长。

主从复制原理

  • 保存信息:首先保存主节点的信息,包括IP和端口。
  • 建立连接:之后 建立主从节点之间的TCP连接(经历三次握手)。
  • 验证连接:给主节点发送PING命令,验证其是否正常工作。
  • 数据同步:包括全量同步(首次连接时同步所有数据)和命令持续复制(增量同步,后续每一步操作都进行同步)。

三种复制方式

  • 全量复制 触发条件:首次与主节点进行数据同步时,或在主节点不方便进行部分复制的情况下,从节点会进行全量复制。此时的 replicationid 和 offset 分别是未知和 -1。
  • 部分复制 适用场景:当从节点已经持有主节点绝大部分的数据时,为了减少开销,可以进行部分复制。例如在网络抖动后重新建立连接时,只需同步中断期间的数据变化即可。
  • 实时复制
    • 描述:主从节点在建立复制连接后,主节点会把自己收到的修改操作,通过 TCP 长连接的方式,源源不断的传输给从节点,从节点就会根据这些请求来同时修改自身的数据,保持从节点和主节点数据的一致性。
    • 机制:从节点和主节点之间维持一个TCP长连接,主节点通过此连接将收到的修改数据请求发送给从节点。为确保网络连接可用,引入了 心跳包机制——主节点 每隔10秒 给从节点发送一个 PING 命令,从节点接收到后返回 PONG;同时,从节点每秒向主节点上报当前复制进度(offset)。