Redis技术深度解析

发表时间: 2022-07-18 07:30

一、什么是Redis

Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库

二、数据类型

1)String(字符串)

  • string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
  • “字符串(String)”这种数据类型非常简单,对应到数据结构里,就是字符串。
  • string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB

2)List(列表)

  • 列表这种数据类型支持存储一组数据。有对应两种实现方法,一种是压缩列表(ziplist),另一种是双向循环链表
  • 当列表中存储的数据量比较小的时候,列表就可以采用压缩列表的方式实现。需要满足以下条件:列表中保存的单个数据(有可能是字符串类型的)小于 64 字节;列表中数据个数少于 512 个。
  • 关于压缩列表,它并不是基础数据结构,而是 Redis 自己设计的一种数据存储结构。

3)Hash(字典或哈希)

  • 在Redis中,hash哈希被称为字典(dictionary),Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点保存了字典中的一个键值对。
  • 字典类型用来存储一组数据对。每个数据对又包含键值两部分。字典类型也有两种实现方式。一种是压缩列表,另一种是散列表
  • 当存储的数据量较小时,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:字典中保存的键和值的大小都要小于 64 字节;字典中键值对的个数要小于 512 个。
  • 当不能同时满足上面两个条件的时候,Redis 就使用散列表来实现字典类型。
  • Redis 使用MurmurHash2这种运行速度快、随机性好的哈希算法作为哈希函数。对于哈希冲突问题,Redis 使用链表法来解决。除此之外,Redis 还支持散列表的动态扩容、缩容
  • Redis中的哈希采用了典型的挂链解决冲突的方式,当有多个key-value键值对的键名key映射值相同时,系统会将这些键值value以单链表的形式保存,同时为了控制哈希表占用内存大小,Redis采用了双哈希表ht[2]结构,并逐步扩大哈希表容量的策略。

【注意】每对key-value在保存前会通过类似HASH(key) MOD N的方法计算出一个值,以确定在哈希表中所对应的位置。

4) Set(集合)

  • 集合这种数据类型用来存储一组不重复的数据。这种数据类型也有两种实现方法,一种是基于有序数组,另一种是基于散列表
  • Redis 就采用有序数组,同时满足以下两个条件:存储的数据都是整数;存储的数据元素个数不超过 512 个。
  • 不能满足上述条件,即存储的数据量较大时,Redis就采用散列表来存储集合中的数据。

5)Zset(sorted set:有序集合)

  • Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么 可以选择sorted set数据结构。
  • 【实现方式】Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的 是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
  • 实际上,跟 Redis 的其他数据类型一样,有序集合也并不仅仅只有跳表这一种实现方式。
  • 当数据量比较小的时候,Redis 会用压缩列表来实现有序集合。使用压缩列表来实现有序集合,需要满足以下条件:所有数据的大小都要小于 64 字节;元素个数要小于 128 个。

三、Redis的持久化机制

1)RDB

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。Redis 默认方式

RDB优势

  • 利于备份:一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。
  • 数据恢复便捷:对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
  • 性能最大化:对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
  • 相比AOF数据量小:相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB劣势

  • 数据的完整性和一致性不高:系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
  • 备份时占用内存:因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。

RDB持久化配置

Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:

save 900 1              #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。save 300 10            #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。save 60 10000        #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。

除了上面配置的会触发RDB持久化,还有以下几种默认方式:

  • 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令;
  • 执行flushall 命令,清空数据库所有数据,意义不大;
  • 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据。

2)AOF

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

Redis AOF默认关闭,开启需要手动把no改为yes:

# 开启appendonly yes# 指定文件名appendfilename "appendonly.aof"

AOF的优势

  • 数据的完整性和一致性更高:Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。

AOF的劣势

  • 占用磁盘空间大,数据恢复速度慢:对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
  • 运行效率低,频繁同步:根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

AOF持久化配置

在Redis的配置文件中存在三种同步方式,它们分别是:

appendfsync always     #每次有数据修改发生时都会写入AOF文件。appendfsync everysec  #每秒钟同步一次,该策略为AOF的缺省策略。appendfsync no          #从不同步。高效但是数据不会被持久化。

RDB与AOF如何选择

  • 一般来说,如果想达到足以媲美PostgreSQL的数据安全性,应该同时使用两种持久化方式
  • 有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

四、Redis三种集群模式

安装部署:https://www.cnblogs.com/liugp/p/12591721.html

1)主从模式

主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave),数据的复制是单向的,只能由主节点到从节点从节点一般只读,如下图:

1、特点

  • 数据的复制是单向的,只能由主节点到从节点
  • 主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库
  • 从数据库一般都是只读的,并且接收主数据库同步过来的数据
  • 一个master可以拥有多个slave,但是一个slave只能对应一个master
  • slave挂了不影响其他slave的读和master的读和写,重新启动后会将数据从master同步过来
  • master挂了以后,不影响slave的读,但redis不再提供写服务,master重启后redis将重新对外提供写服务
  • master挂了以后,不会在slave节点中重新选一个master

2、工作机制

  • 当slave启动后,主动向master发送SYNC命令。master接收到SYNC命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。
  • 复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。

3、优点

  • 架构简单,部署方便
  • 高可靠性:一方面,采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题。
  • 读写分离策略:从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。

4、缺点

  • master单节点风险:从上面可以看出,master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。
  • 不保证数据的可靠性:在缓存使用,进程重启后,数据丢失,即使有备用的节点解决高可用性,但是仍然不能解决缓存预热问题,因此不适用于数据可靠性要求高的业务;
  • 主节点 的 写能力 受到 单机的限制。

5、主从同步原理

=》全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:

  • 从服务器连接主服务器,发送SYNC命令;
  • 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
  • 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

=》增量同步

  • Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
  • 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

【注意点】

如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。

Redis主从复制的配置十分简单,它可以使从服务器是主服务器的完全拷贝。需要清除Redis主从复制的几点重要内容:

  • Redis使用异步复制:但从Redis 2.8开始,从服务器会周期性的应答从复制流中处理的数据量;
  • 从服务器也可以接受其他从服务器的连接:除了多个从服务器连接到一个主服务器之外,多个从服务器也可以连接到一个从服务器上,形成一个图状结构;
  • Redis主从复制不阻塞主服务器端:也就是说当若干个从服务器在进行初始同步时,主服务器仍然可以处理请求;
  • 主从复制也不阻塞从服务器端:当从服务器进行初始同步时,它使用旧版本的数据来应对查询请求;
  • 主从复制可以用来增强扩展性:使用多个从服务器来处理只读的请求(比如,繁重的排序操作可以放到从服务器去做),也可以简单的用来做数据冗余;

2)Redis Sentinel(哨兵模式)

在Redis 2.8版本开始引入,就有了哨兵这个概念;Redis Sentinel 是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel 集群和 Redis 数据集群。

  • Redis Sentinel 集群是由若干 Sentinel 节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel 的节点数量要满足 2n+1(n>=1)的奇数个。

1、特点

  • 当master挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master
  • 当master重新启动后,它将不再是master而是做为slave接收新的master的同步数据
  • sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群
  • 多sentinel配置的时候,sentinel之间也会自动监控
  • 当主从模式配置密码时,sentinel也会同步将配置信息修改到配置文件中,不需要担心
  • 一个sentinel或sentinel集群可以管理多个主从Redis,多个sentinel也可以监控同一个redis
  • sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了

2、工作机制

  • 每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令
  • 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。
  • 如果一个master被标记为主观下线,则正在监视这个master的所有sentinel要以每秒一次的频率确认master的确进入了主观下线状态
  • 当有足够数量的sentinel(大于等于配置文件指定的值)在指定的时间范围内确认master的确进入了主观下线状态, 则master会被标记为客观下线
  • 在一般情况下, 每个sentinel会以每 10 秒一次的频率向它已知的所有master,slave发送 INFO 命令
  • 当master被sentinel标记为客观下线时,sentinel向下线的master的所有slave发送 INFO 命令的频率会从 10 秒一次改为 1 秒一次
  • 若没有足够数量的sentinel同意master已经下线,master的客观下线状态就会被移除;若master重新向sentinel的 PING 命令返回有效回复,master的主观下线状态就会被移除

3、优点

  • 高可用:主从可以自动切换,系统更健壮,可用性更高。
  • 实现监控多组节点:可以实现一套 Sentinel 监控一组 Redis 数据节点或多组数据节点。

4、缺点

  • 部署复杂:部署相对 Redis 主从模式要复杂一些,原理理解更繁琐。
  • 资源浪费:Redis 数据节点中 slave 节点作为备份节点不提供服务。
  • 默认不支持读写分离:不能解决读写分离问题,实现起来相对复杂。
  • 维护成本大:需要多维护一套监控节点。
  • Redis较难支持在线扩容:对于集群,容量达到上限时在线扩容会变得很复杂。

3)Redis Cluster(集群模式)

之前的两种模式数据都是在一个节点上的,单个节点存储是存在上限的。集群模式就是把数据进行分片存储,当一个分片数据达到上限的时候,就分成多个分片。Redis3.0加入了Redis的集群模式,对数据进行分片,将不同的数据存储在不同的master节点上面,从而实现了海量数据的分布式存储和在线扩容的问题

  • Redis Cluster 集群模式通常具有 高可用、可扩展性、分布式、容错 等特性。
  • Redis Cluster 集群节点最小配置 6 个节点以上(3 主 3 从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
  • Redis Cluster 采用虚拟槽分区,所有的键根据哈希函数映射到 0~16383 个整数槽内,每个节点负责维护一部分槽以及槽所印映射的键值数据。

如上图所示,Redis集群可以看成多个主从架构组合起来的,每一个主从架构可以看成一个节点(其中,只有master节点具有处理请求的能力,slave节点主要是用于节点的高可用)

1、数据分片怎么分?

集群的键空间被分割为16384个slots(即hash槽),通过hash的方式将数据分到不同的分片上的。

HASH_SLOT = CRC16(key) & 16384 

CRC16是一种循环校验算法,这里不是我们研究的重点,有兴趣可以看看。
这里用了位运算得到取模结果,位运算的速度高于取模运算。

2、数据分片之后怎么查,怎么写?

读请求分配给slave节点,写请求分配给master,数据同步从master到slave节点。读写分离提高并发能力,增加高性能。

3、如何做到水平扩展?

  • master节点可以做扩充,数据迁移redis内部自动完成。
  • 当你新增一个master节点,需要做数据迁移,redis服务不需要下线。

【举个栗子】上面的有三个master节点,意味着redis的槽被分为三个段,假设三段分别是0~7000,7001~12000、12001~16383。

  1. 槽需要重新分配,数据也需要重新迁移,但是服务不需要下线。
  2. 现在因为业务需要新增了一个master节点,四个节点共同占有16384个槽。
  3. redis集群的重新分片由redis内部的管理软件redis-trib负责执行。redis提供了进行重新分片的所有命令,redis-trib通过向节点发送命令来进行重新分片。

4、如何做故障转移?

  • 假如下图中红色的节点故障了,此时master3下面的从节点会通过 选举 产生一个主节点。替换原来的故障节点。
  • 此过程和哨兵模式的故障转移是一样的。

5、特点

  • 多个redis节点网络互联,数据共享
  • 所有的节点都是一主一从(也可以是一主多从),其中从不提供服务,仅作为备用
  • 不支持同时处理多个key(如MSET/MGET),因为redis需要把key均匀分布在各个节点上,并发量很高的情况下同时创建key-value会降低性能并导致不可预测的行为
  • 支持在线增加、删除节点
  • 客户端可以连接任何一个主节点进行读写

6、工作机制

  • 选举

集群启动后,主从已分配完成,经过了 N 轮的选举。当某一个主节点宕机,那么从节点需要经过选举成为主节点。简单介绍选举过程:
所有子节点向其他节点发送请求,请求自身成为主节点,其他节点收到请求后,返回投票信息,只有主节点 master 有权投票,且只能投一次,当获取到的票数大于一半人数时(master 个数),就当选 master。
期间,所有子节点发送请求的时机有所有不同。所以基本都有先后顺序,所以很少会出现票数相同情况,如果相同,则重新选举,直到选出 master 为止。
故,需要至少 3 主 3 从,否则节点出现问题,将选举失败。

  • 槽位

在 Redis 集群中,定义了 16384 个逻辑上的槽位。将这些槽位均匀分配给 N 个节点(一主一从为一个节点),此文 3 个节点,自动均匀分配。意思为,0-5460 个槽位分配给第一个节点。
当用户 set 一个值时,除了计算 key 本身的 hashCode 之外,还会调用 C 语言的一个 CRC16 算法,将 key 当 hash 值再计算出一个数字,然后与 16384 取模,得到的数字落在哪个槽位,则会将数据放在对应的节点。
比如,计算出的数字为 16387,则取模 16384 后,得到 3,在 0-5460 之间,则放入对于的第一个节点。其他以此类推。

  • 跳转

大家都知道,主从模式中,只有主节点可以写入数据,而从节点只能读取数据。在 Cluster 集群中,设置值时,如果计算出的槽位在另一台服务器上,则集群连接会自动跳转至相应服务器。

7、优点

  • 无中心架构
  • 数据共享:数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
  • 可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
  • 高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升;
  • 任意节点读写:客户端可以连接任何一个主节点进行读写。

8、缺点

  • 实现复杂,开发成本高;
  • 需要建立配套的周边设施,如监控,域名服务,存储元数据信息的数据库等;
  • 维护成本高。

五、Redis 缓存穿透、击穿、雪崩原因&解决方案

1)缓存穿透原因&解决方案

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

【解决方案】

  • 布隆过滤器:有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
  • 空结果进行缓存,缓存时间设置短:另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

2)缓存击穿原因&解决方案

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。针对的是一个热点key(例如一个秒杀活动),并发量非常大

【解决方案】

  • 使用互斥锁(mutex key):业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

3)缓存雪崩原因&解决方案

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。

【解决方案】

  • 关于缓存崩溃的解决方法,这里提出了三种方案:使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间,还有一种被称为“二级缓存”的解决方法。

缓存标记解释

  1. 缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
  2. 缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。