Redis 技术详解
Redis 详解
一、 NoSql介绍
NoSql (Not Only Sql),意为不仅仅是SQL,泛指非关系型数据库,NoSql 这门数据库早期就有人提出了,发展到2009年开始越发高涨。
NoSql 应该场景:
- 商城网站对商品数据的频繁查询
- 对热搜商品的排行统计
- 订单超时问题
- 音视频存储等
NOSQL 的四大分类
-
1、键值对(key-value)存储数据库
- 代表产品有:
Redis
,Tokyo Cabinet/Tyrant
,SSDB
,Oracle BDB
- 这类数据库主要用到一个哈希表,这个表中有一个特定的 键 和 一个 指针 指向特定数据
- 特点:key-value模型,对于IT系统来说简单,易部署。
- 但是如果DBA 只对部分值进行查询或更新的时候,key-value 就显得效率低下了,因为他要查找遍历麻烦。
- 代表产品有:
-
2、列存储数据库
- 相关产品:
Cassandra
、HBase
、 - 这部分数据库通常是用来对分布式存储的 海量数据库
- 特点:键仍然存在,他的特点是指向了多个列, 这些列是由 家族 来安排的.
- 相关产品:
-
3、文档型数据库
- 如
MongoDB
、CouchDB
- 文档型数据库,可以看成是键值数据库的升级版,允许之间嵌套键值。
- 文档型数据库,比键值型数据库查询效率更高。
- 如
-
4、图形数据库
- 图形数据库同其他行列一级结构性SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上
- 相关产品:
Neo4J
、InfoGrid
、Infinite Graph
NOSQL 应用场景
- 数据模型比较简单
- 需要灵活性更强的IT系统(系统要求设计灵活,性能要求高)
- 对数据库性能要求高
- 不需要高度一致性(因为NoSql 产品对于事务的支持都不是特别良好)
Redis 优势
- 性能极高,Redis 读 速度是 110000次/s, 写 速度是 81000次/s
- 丰富的数据类型, Strings, List, Hashes, Sets, ZSets
- 原子性 Redis所有操作都是原子性的,意思要么成功执行,要么失败完全不执行,单个操作是原子性的,多个操作也支持事务。通过MULTI 和 EXEC 指令包起来。
- 丰富的特性, Redis 还支持 publish/subscibe,通知,key 过期等特性。
总结
-
Redis 是一个开源的,遵循BSD 基于内存数据存储,被用于作为数据库缓存、消息中间件等
-
Redis 数据内存中: 内存特点:读写块,但断电立即消失。
-
问题: 为什么数据在内存中,但是Redis还是一个数据库呢?
- 答:因为他可以将数据持久化到硬盘中。
总的来说:Redis 是一个内存性的数据库。
二、 Redis的数据类型
Redis总共有 5种 数据类型,分别是:
- String
- List , 无序列表
- Set, 不重复的无序列表
- ZSet, 不重复的有序列表
- Hash, Key-value 键值对存储
1、String 类型内存模型
2、List 列表,相当于 Java中的List 集合,特点:元素有序且可以重复
-
List 在添加(put)或删除数据(pop)时,分左右
-
lpush,从左往右推数据,rpush,从右往左推数据。
-
3、Set 数据类型
- 特点: Set 集合,元素无序,不可重复
- 4、 ZSet 数据类型
- 特点: 元素可排序,不可重复
-
5、 Hash 数据类型
- 特点: 外面一层 大 key,里面 value 存储的是 一对key-value 数据
三、Redis 启动细节
1. Redis启动服务细节
注意:直接使用 ./redis-server 方式启动使用的是 redis-server 这个 shell 脚本中的默认配置
3. Redis修改 默认端口号
vim redis.conf 修改里面 port 7000 保存退出
4. Redis中库的概念
-
库:Redis中 database用来存放一个基本单元,一个库可以存放 key-value 键值对,Redis中每一个库都有一个唯一名称。
库的编号从 0 开始,默认库个数是 16 个,库的编号 0~15 -
如何切换库?
select dbid(库编号)
注意:库与库之间是相互隔离的,比如我在1库中写了一个和2库一样的 key - value值,清除1 库,不会将2库中数据清除掉。
5. Redis中清除库指令
- flushDB 清除当前库中所有数据
- flushAll 清除所有库中的数据
6. Redis中指令
九、 Redis中的持久化
Redis的持久化,过程为将内存中的数据持久化保存到磁盘中,它的持久化分为:
-
快照持久化
-
AOF 持久化(Append Only File) 只追加日志文件
特点: 默认Redis是快照持久化的,需要我们手动配置是否AOF 持久化。
9.1 快照持久化
特点:Redis 操作写命令,写一条数据,然后定期有快照去保存,快照默认是保存的是以 .rdb
结尾的文件,如官方 dump.rdb
,当出现宕机等,重启机器会根据快照重新将数据加载到内存中。
快照生成方式
-
客户端方式: BGSAVE 和 SAVE 指令来保存快照
-
服务器配置自动触发:通过配置定期时间,来定期通过快照保存内存数据
BGSAVE 原理
- 客户端可以使用BGSAVE 指令来创建一个快照,当Redis接收到该指令后,Redis会调用一个 fork 来创建一个子进程,然后子进程负责将数据写到磁盘中,而父进程负责继续处理请求命令。
名词解释: 当创建子进程时,底层操作系统会创建该进程的一个副本,在Unix系统中创建的子进程会进行优化,在刚开始的时候,父子进程会共享内存,直到父进程或子进程对内存进行了写之后,对呗写入的内存共享才会结束。
好处:
- 子进程在写快照的时候,不会阻塞主线程处理Redis读写数据。
- 共享内存,主进程和子进程 共享内存,当没有数据写入Redis时,这时执行BGSAVE,子进程可以取最大共享内存去处理写快照操作。
SAVA 原理
说明:通过SAVE 命令保存快照,那么Redis在保存快照完成之前,不再响应任何客户端请求来的数据。
坏处: 会阻塞Redis数据读写。 注意SAVE 指令并不常用,一般在关闭Redis保存才会用到这个指令。
SAVE 执行场景:
在Redis通过shutdown 命令接收关闭服务器请求时,会自动执行一个SAVE 命令,这时阻塞所有客户端读写请求,不再执行客户端发送的任何命令,并且在save命令执行完毕后关闭服务器。
9.2 AOF 持久化
Redis 操作写命令,每操作一条,会记录操作的日志文件,Redis会根据写的日志文件,如某一时刻Redis 宕机了,但是重启之后他会根据我写的日志文件,再去从硬盘恢复数据到内存。
注意: 每一次的写入记录,都会被记录,恢复数据到内存时,会挨个执行你的写命令,当然同一个 key 恢复到的数据肯定是最后一次写入记录。
日志追加频率:
日志分为3种方式追加,
always | everysec | no
1. always 【谨慎使用】
- 说明: 每个Redis 每个写命令都要写数据到硬盘,严重降低Redis速度。
- 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行 大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
- 注意: 转盘式硬盘在这种频率下 200左右个命令/s ; 固态硬盘(SSD) 几 百万个命令/s ;
- 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的 写入放大 问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
总结: always 方式可以最大限度降低数据的丢失率,但是在大量写入操作执行时会影响Redis性能,且对硬盘伤害更大一些。
2. everysec 【推荐】
-
说明:每秒执行一次同步显式的将多个写命令同步到磁盘, 推荐使用。
-
解释: 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒 之内产生的数据。
总结: 对性能上友好,但是有可能会丢失1秒内的写入数据,适用于对数据严谨性不高的场景,如热搜排行榜等。
3. no 【不推荐】
- 说明: 由操作系统决定何时同步
- 解释: 由系统决定什么时候去同步AOF文件,
- 引入的问题:
- 这种方式不会对Redis性能带来影响,但是系统崩溃时,可能会丢失不定量数据。
- 另外如果用户硬盘处理写入操作不够快的话, 当缓冲区被等待写入硬盘数据填满时, redis会处于 阻塞状态,并导致redis的处理命令请求的速度变慢。
9.3 AOF 代理的问题
AOF 问题: AOF 会导致持久化日志文件变得越来越大,而且重复执行同样命令,会重复记录日志操作到日志文件,如我执行100条相同的 set ,那么将会写100条相同的 set 日志到日志文件,其实99条是多余的,因为宕机重启后仅靠最后一条就可恢复这条key 的数据。
如何解决上述问题?
通过AOF 重写,用来在一定程度上减小 AOF 文件的体积,即文件压缩。
AOF 文件重写原理
注意: 重写 AOF 文件的操作,并没有读取旧的 aof 文件,而是将这个内存中的数据库内容用命令的方式重写了一个新的AOF 文件,替换原有的文件和快照有点类似。
也就是说,它并不是在原来的 aof 中去挑记录,而是重新保存了新的 aof 文件。
重写流程
-
- redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
-
- 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
-
- 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
-
- 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
四、 Redis 分布式缓存
1、缓存优化策略:
-
对放入 Redis中的key 进行优化,key 要简洁一点,可以使用MD5 进行加密处理,
-
MD5特点:
-
- 一切文字经过md5 处理后,都会生成32位16进制字符串。
-
- 不同内容文件经过 md5 进行加密,加密结果一定不一致, 如aa.txt 和 bb.txt 中内容不一致,则加密后结果不一致。
-
- 相同内容,文件多次经过MD5 生成结果始终一致,如 aa和 bb 内容一样,加密一次,再加密一次,多次加密后,只要原内容一样,则最后加密也一样
-
-
推荐: 在 Redis整合 mybatis 过程中,建议将 key 进行 md5 加密处理。
2、面试相关概念:
-
1)什么是缓存穿透,缓存击穿
-
定义: 客户端查询了一个数据库中没有的数据记录,导致缓存在这种情况下无法利用,称为缓存穿透,或者缓存击穿。
-
如何解决?
设置缓存为null: 如mybatis中是已经解决了这个问题,它通过如果差不到,则会向缓存写null 的方式来解决
布隆过滤:可以通过尽可能校验Redis key 的生成方式,提前挡住请求中 key 的请求。
-
问: 当我存储了一个 null 后,恰好这个key 又有数据添加,这时该如何?
答: 这种情况一般我们在添加数据时,会对key 做 清空操作,保证添加后查出的数据都是最新的,如 mybatis 就已经实现了这个功能在添加一条key 数据之后,会对缓存做清空操作。
-
-
2)什么是缓存雪崩
-
在系统运行的某一时刻,突然系统中的缓存全部失效,恰好在这一刻涌来大量客户端请求,导致所有缓存模块无法利用,大量请求涌向数据库导致极端情况,数据库阻塞或者挂起。
-
如何解决?
方案一: 缓存永久存储(不推荐)
方案二: 设置超时时间不同:大多数企业,业务系统非常大,模块多,所以针对不同的模块,放入缓存时,都会设置缓存的一个 TTL 超时时间,
-
五、Redis 架构
一、 主从复制架构
1、 主从复制
说明: 主从复制,仅仅用来解决数据的 冗余备份,从节点不负责外部请求处理, 从节点仅仅用来用同步数据
这种方式无法解决: master节点出现故障的 自动故障转移
注意: salve 从节点,仅用于同步主节点数据,默认数据是只读的,即无法向从节点写数据。
2、架构图:
二 、 哨兵模式
1、说明:
哨兵(sentinel) 是Redis的一个 高可用 解决方案,由一个或多个 sentinel实例组成的 sentinel系统,可以 监视多个主服务器,以及主服务器下的所有 从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器下的某个从服务器提升为新的主服务器。
简单说来: 哨兵就是带有自动故障转移的主从架构。
注意: 首先, 哨兵机制必须是一个 主从架构,即要哨兵监听,则必须有master-salve节点。
2、哨兵是如何监听节点的呢?
其实就是用了 心跳检测 机制,定期给节点发数据包,谁挂掉自然就知道了。
Redis 如何在master节点挂掉后,推举新master?
-
通过 选举机制,一般节点我们推荐 奇数个,方便选举出新master
-
脑裂: 注意避免脑裂的问题,即如果仅是网络抖动,导致Redis误认为master挂掉,又推举新的master,其实旧的master并没有挂掉,这样存在多个master就会有脑裂的问题。
-
如何避免脑裂?
- 一般都会整一组哨兵监控,避免单哨兵监听单主从节点,只要在超过半数以上认为master挂掉,才能去推举新master。
3、master 又活了怎么办?
如果某一时刻,master节点又恢复,哨兵会将该master重新拉回来,这个旧master会作为新master的从节点。
哨兵模式架构原理:
4、哨兵模式存在问题:
-
单节点压力问题: 无论是主从架构,还是哨兵架构,都无法解决单节点压力问题。
-
物理上限问题: 因为Redis会开启持久化,当数据持久化到单台机器上,肯定会有物理存储上限问题
三、集群模式
1、说明: Redis 在 3.0 后开始支持 cluster(集群模式),目前Redis集群支持如下功能:
- 集群节点的自动发现
- slave-master的选举容错
- 在线分片(Sharding shard)等特性
2、集群细节:
- 所有的Redis节点,彼此互联(通过 PING-PANG 机制),内部使用 二进制协议 优化传输速度和带宽
- 节点的fail是通过 集群中超过半数 的节点检测失效,才认为某个节点挂掉了
- 客户端与Redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任意一个节点就可以了
- redis-cluster 把所有的物理节点映射到 [0-16383]slot 上, cluster 负责维护
node<->slot<->value
3、集群原理
解析
-
每一个集群节点,都必须有它的slave节点,方便做 自动故障转移和数据备份
-
集群中的节点,会平均分配 slot(槽),槽用于存储数据,即 数据是存储在槽上 的
-
总共有多少个槽,槽是怎么分配到节点上的?
- Redis 集群中最大有 0~16383 即 16384个槽,这些槽用于存储数据,他会平均分配到Redis中的集群节点中。
- 比如,集群中有3个master-slave节点,节点1 可分配槽为(05000),节点2为(500110001),第三个就是剩余的(10001~16383)
- client调用,集群节点,会如何请求?
- 当client调用集群时,会对一个key 先做 CRC 16算法,会计算该key 落到哪个槽上,然后会看该槽在哪个范围,比如我做 CRC 16算法,落到了3000 这个槽上,那么他属于 0~5000 这个范围,那么他就会请求到集群1上。
- 如果新加集群节点了,槽会如何分配?
- 这就要看你是如何加的,例如可以将 集群1上的槽在分出一半,到新加的那个集群上,那么新的集群就拥有2501~5000 这些槽。
- 新加集群节点后,之前key 经过 CRC 16 算出来的,跑到另一个集群上了怎么办?例如我之前请求到集群1,加集群后,我请求到集群4(新增集群),这时会请求不到数据吗?
- 肯定可以请求到数据,因为同一条数据,你CRC 16 算出来的,肯定是在同一个槽上,而槽上维护的数据,当你重新分配集群节点,槽也跟着分配了,还是请求到那个原来的槽上,当然可以请求到数据,只是请求到新的集群节点上罢了。
- 也就是说,请求到的数据是跟槽走的,CRC 16 加出来在哪个槽,就会一直在哪个槽,只是槽重新分配请求到不同节点上罢了。
-
当集群中某个master 宕机后,slave顶上来后,槽会如何分配?
- 之前该集群节点上master上的槽,会转移到新的master上,故依然可以请求到数据。
- 最大支持多少个节点?
- 因为最大是 0~16383 个槽,顶多就是1个集群分一个,所以最大是 16384 个集群,再大就做不了了, 这说的是master节点,slave你可以任意创建个数。
4、集群进入fail状态的必要条件
- 某个主节点和从节点全部挂掉,我们集群就进入fail状态
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态
- 如果集群任意master挂掉,且当前master没有slave
5、 投票机制
-
Redis是如何去判断某个节点挂掉了呢?
- 答: 是通过集群中超过半数节点投票决定的,超过半数节点投票说某个节点挂了,它才真正是挂了,切不可因为网络抖动等误认为某个节点轻易挂掉。
总结: 集群即解决了 单节点并发压力 问题,又解决了 物理上限问题,同时解决了 主节点发生故障时从节点的自动故障转移