一、Redis基础
1. 概念:Redis(Remote Dictionary Server)是一个使用C语言编写的,开源的高性能非关系型(NOSql--Not Only Sql)键值对数据库。
2. 基础数据结构:Redis可以存储键和不同类型数据结构值之间的映射关系。键的类型只能是字符串,而值除了支持最基础的五种数据类型外,还支持一些高级数据类型(如:布隆过滤器,Stream等)
redis支持的基础数据结构
3. 特性:与传统数据库不同的是Redis的数据是存在内存中的,所以读写速度非常快,因此Redis被广泛应用于缓存方向。官方称每秒可以处理超过10万次读写操作,是已知性能最快的Key - Value 数据库。此外,Redis也经常用来做分布式锁。
4. Redis的优缺点
优点:
① 读写性能优异,Redis能读的速度是11万次/s,写的速度是8.1万次/s。
② 支持数据持久化,支持AOF和RDB两种持久化方式。
③ 支持事务,Redis的所有操作都是原子性的,同时redis还支持对几个操作合并后的原子性执行。
④ 数据结构丰富,除了String类型,还支持hash/set/zset/list等数据结构,每个数据结构都有其独特的函数。
⑤ 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点:
① 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写。
② Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失效,需要等待机器重启或者手动切换前端ip才能恢复。
③ 主机宕机,宕机钱有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的为,降低了系统的可用性。
④ Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成很大的浪费。
5. 在项目中使用redis的优势:如果我们把数据放在Redis中,也就是直接放在内存之中,让服务器直接去读取内存中的数据,这样会提高系统的响应速度(高性能),并且会极大地减轻数据库的压力(应对高并发)
6. 使用缓存会出现的问题:
① 缓存雪崩问题:
现象:在同一时刻,一批高热度的key同时失效,导致原本访问redis的请求都去访问数据库,造成数据库短时间内CPU和内存压力激增,严重会导致宕机。
出现的原因:缓存数据设置的过期时间是相同的,导致大量key到期被清除
解决方法:把缓存过期时间设置成随机数,这样会大幅较少同一时间失效的key
另外关于Redis服务器宕机,大量请求访问数据库的问题,解决方法如下:
事发前:实现 Redis 的高可用(主从架构 + Sentinel 或者 Redis Cluster),尽量避免 Redis 挂掉这种情况发生。
事发中:万一 Redis 真的挂了,我们可以设置本地缓存(ehcache) + 限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)。
事发后:Redis 持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
② 缓存穿透问题:
现象:客户端请求查询一个不存在的key(如负数的id),导致绕过缓存,直接访问数据库,如果大量请求,也会导致数据库发生异常。
出现的原因:请求的数据不命中缓存,导致要查询数据库。
解决方法:a.使用布隆过滤器提前拦截 b. 把不存在的数据也放到缓存中,设置一个较短的过期时间。
关于布隆过滤器:
它是一个很长的二进制向量,当插入数据时, 运用一系列随机映射函数(hash函数)计算出多个位置,然后将1填充进二进制向量里。当要进行查询的时候,会把key进行同样的hash函数计算,查看相应位置是否都为1,如果有一位为0,那么该key不存在。若计算后出现相同的结果,也不能证明两个数据是一样的。 布隆过滤器只能判断数据是否一定不存在,而无法判断数据是否一定存在。
redis官方在4.0版本推出了布隆过滤器, 有两个基本指令,bf.add 添加元素,bf.exists 查询元素是否存在。如果想要一次添加多个,就需要用到 bf.madd 指令。同样如果需要一次查询多个元素是否存在,就需要用到 bf.mexists 指令。
③ 双写一致性问题:
现象:客户端拿到的数据不是数据库的最新数据(缓存里的是旧数据 )。
出现原因:在并发环境中,当线程1进行更新操作的以后,线程2从缓存中读取了旧的数据,就会出现数据不一致问题。
解决方法:a. 先修改数据库,再删除缓存 b. 先删除缓存,再修改数据库
Redis早期为什么选择单线程版本?
1. 使用单线程模型能带来更好的可维护性,方便开发和调试;
2. 使用单线程模型也能并发地处理客户端的请求;(I/O多路复用机制——它使用 I/O 多路复用机制同时监听多个文件描述符的可读和可写状态,一旦受到网络请求就会在内存中快速处理,由于绝大多数的操作都是纯内存的,所以处理的速度会非常地快。)
3. Redis服务中运行的绝大多数操作的性能瓶颈都不是cpu;
二、Redis的数据类型
1. 字典(hash)
概念:字典是Redis服务器中出现最为频繁的复合型数据结构,除了hash结构的数据会用到字典外,整个Redis数据库的所有key和value也组成一个全局字典,还有带过期时间的key也是一个字典。(存储在RedisDb数据结构中)
结构:Redis中的字典相当于Java中的HashMap,内部实现也差不多类似,都是通过“数组+链表”的链地址法来解决部分哈希冲突。
扩容机制:字典结构内部包含两个hashTable,通常情况下只有一个hashTable有值,但在字典扩容缩容时,需要分配新的hashTable,然后进行渐进式搬迁(rehash),这时候两个hashTable分别存储旧的和新的hashTable,待搬迁结束后,旧的将被删除,新的hashTable取而代之。
扩容条件:正常情况下,当hash表中元素的个数等于第一维元素的长度时,就会开始扩容,扩容的新数组是原数组大小的2倍。不过如果当Redis正在做bgsave(持久化命令),为了减少内存也得过多分离,Redis尽量不去扩容,但是如果hash表非常满了,达到了第一维数组长度的5倍,这时候就会强制扩容。当hash表因为元素逐渐被删除变得越来越稀疏时,Redis会对hash表进行缩容,条件是元素个数低于数组长度的10%,缩容不会考虑Redis是否在做bgsave。
2. 压缩列表
这是Redis为了节约内存而使用的一种数据结构,zset和hash容器对象会在元素个数较少的时候,采用压缩列表(ziplist)进行压缩。压缩列表是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙。
压缩列表(zip1ist)是列表和哈希的底层实现之一。
当一个列表只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表的底层实现。
当一个哈希只包含少量键值对,比且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做哈希的底层实现。
3. Stream结构
Redis Stream 从概念来说,就像是一个仅追加内容的消息链表,把所有加入的信息都一个一个串起来,每个消息都有一个唯一的ID和内容,这很简单,让它复杂的是从Kafka借鉴的另一种概念:消费者组(Consumer Group),为了不让链表过长,它提供了一个定长 Stream 功能。在 xadd 的指令提供一个定长长度 maxlen,就可以将老的消息干掉,确保最多不超过指定长度
Consumer Group:消费者组,可以简单看成记录流状态的一种数据结构。消费者既可以选择使用 XREAD 命令进行 独立消费,也可以多个消费者同时加入一个消费者组进行 组内消费。同一个消费者组内的消费者共享所有的 Stream 信息,同一条消息只会有一个消费者消费到,这样就可以应用在分布式的应用场景中来保证消息的唯一性。
last_delivered_id:用来表示消费者组消费在 Stream 上 消费位置 的游标信息。每个消费者组都有一个 Stream 内 唯一的名称,消费者组不会自动创建,需要使用 XGROUP CREATE指令来显式创建,并且需要指定从哪一个消息 ID 开始消费,用来初始化 last_delivered_id 这个变量。
pending_ids:每个消费者内部都有的一个状态变量,用来表示 已经 被客户端 获取,但是还没有 ack 的消息。记录的目的是为了 保证客户端至少消费了消息一次,而不会在网络传输的中途丢失而没有对消息进行处理。如果客户端没有 ack,那么这个变量里面的消息 ID 就会越来越多,一旦某个消息被 ack,它就会对应开始减少。这个变量也被 Redis 官方称为 PEL (Pending Entries List)。
Stream和kafka的比较:
Redis 基于内存存储,这意味着它会比基于磁盘的 Kafka 快上一些,也意味着使用 Redis 我们 不能长时间存储大量数据。不过如果您想以 最小延迟 实时处理消息的话,您可以考虑 Redis,但是如果 消息很大并且应该重用数据 的话,则应该首先考虑使用 Kafka。
另外从某些角度来说,Redis Stream 也更适用于小型、廉价的应用程序,因为 Kafka 相对来说更难配置一些。
4. list结构
在版本3.2之前,列表底层的编码是ziplist和linkedlist实现的,当列表对象中元素的长度比较小或者数量比较少的时候,采用ziplist来存储(时间换空间),ziplist是存储在一段连续的内存上,所以存储效率很高;当列表对象中元素的长度比较大或者数量比较多的时候,则会转而使用双向列表linkedlist来存储,便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大,容易产生内存碎片。
但是在版本3.2之后,重新引入 quicklist,列表的底层都由quicklist实现。quickList是一个ziplist组成的双向链表。每个节点使用ziplist来保存数据。本质上来说,quicklist里面保存着一个一个小的ziplist
quickList就是一个标准的双向链表的配置,有head 有tail;
每一个节点是一个quicklistNode,包含prev和next指针。
每一个quicklistNode 包含 一个ziplist,*zp 压缩链表里存储键值。
所以quicklist是对ziplist进行一次封装,使用小块的ziplist来既保证了少使用内存,也保证了性能。
三、Redis持久化
1. 概念:Redis的数据全部存储在内存中,如果服务器突然宕机,没来得及持久化的数据就会全部丢失,因此必须要用到Redis的持久化机制,他会将内存中的数据持久化到硬盘里面。
2. Redis持久化步骤:
① 客户端向数据库发送写命令(数据在客户端的内存中)
② 数据库接收到客户端的写请求(数据在服务器的内存中)
③ 数据库调用系统 API将数据写入磁盘(数据在内核缓冲区中)
④ 操作系统将写缓冲区传输到磁盘控控制器(数据在磁盘缓存中)
⑤ 操作系统的磁盘控制器将数据写入实际的物理媒介中(数据在磁盘中)
3. 持久化方式
① RDB:当满足特定条件时,它将生成数据集的时间点快照。此条件可以由用户配置 Redis 实例来控制,也可以在运行时修改而无需重新启动服务器。快照作为包含整个数据集的单个 .rdb 文件生成。(如果实例被关闭,会丢失未保存的数据)
② AOF(Append Only File - 仅追加文件):每次执行修改内存中数据集的写操作时,都会记录该操作。假设 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例顺序执行所有的指令,也就是「重放」,来恢复 Redis 当前实例的内存数据结构的状态。(相对于RDB速度很慢)
③ 混合持久化:将rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小,于是在 Redis 重启的时候,可以先加载rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
④ RDB与AOF的优缺点:
RDB的优点:
1. 只有一个.rdb文件,方便持久化
2. 容灾性好,文件可以安全地保存到硬盘
3. 性能最大快,使用fork子进程进行持久化操作,主进程可以继续执行查询命令。使用子进程进行持久化时,主进程不会进行任何IO操作。
4. 相对于数据集大时,比AOF效率高
缺点:RDB 是间隔一段时间的持久化策略,在持久化之间Redis发生故障,将会丢失部分数据
AOF的优点:
1. 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
2. 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
3. AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)
缺点:
1. AOF 文件比 RDB 文件大,且恢复速度慢。
2. 数据集大的时候,比 rdb启动效率低。
4. 数据恢复优先级:
如果只配置 AOF ,重启时加载 AOF 文件恢复数据;
如果同时配置了 RDB 和 AOF ,启动只加载 AOF 文件恢复数据;
如果只配置 RDB,启动将加载 dump 文件恢复数据。
四、Redis集群
1. 主从复制
概念:是指将一台Redis服务器的数据,复制到其他服务器里面,前者称为主节点(master),后者称为从节点(slave),而且复制是单向的,只能是主节点到从节点。Redis支持主从复制和从从复制。
作用:
① 数据冗余:实现了数据的热备份,是持久化之外一种数据冗余的方式。
② 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
③ 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,从节点提供读服务,提高redis服务器的并发量。
④ 高可用基石:主从复制还是哨兵和集群能够实施的基础。
2. Sentinel 哨兵
概念:提供主动监控节点状态并自动修复故障的服务。
哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据;
数据节点:主节点和从节点都是数据节点;
功能:
①监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
②自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
③配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。
④ 通知(Notification):哨兵可以将故障转移的结果发送给客户端。
3. Redis集群
概念:集群中的每一个 Redis 节点都互相两两相连,客户端任意直连到集群中的任意一台,就可以对其他 Redis 节点进行读写的操作。
原理:Redis 集群中内置了16384 个哈希槽。当客户端连接到 Redis 集群之后,会同时得到一份关于这个集群的配置信息,当客户端具体对某一个 key 值进行操作时,会计算出它的一个 Hash 值,然后把结果对 16384求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。再结合集群的配置信息就能够知道这个 key 值应该存储在哪一个具体的 Redis 节点中,如果不属于自己管,那么就会使用一个特殊的MOVED 命令来进行一个跳转,告诉客户端去连接这个节点以获取数据。
优点:
①数据分区:数据分区(或称数据分片)是集群最核心的功能。集群将数据分散到多个节点,一方面突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。Redis 单机内存大小受限问题,在介绍持久化和主从复制时都有提及,例如,如果单机内存太大,bgsave 和 bgrewriteaof 的 fork 操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……
②高可用:集群支持主从复制和主节点的自动故障转移(与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务。
keys和scan的区别
使用 keys 指令可以扫出指定模式的 key 列表。但是要注意 keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。