简介
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings),散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了复制(replication),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence), 并通过Redis哨兵(Sentinel) 和自动分区(Cluster)提供高可用性(high availability)
支持的数据类型
string、hash、list、set、sortedset
Redis性能
读的速度是110000次/s,写的速度是81000次/s
缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时间全部失效,引起数据库压力过大甚至down机。
和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
- 使用高可用的分布式缓存集群,确保缓存的高可用性
缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案
- 对于热点数据,慎重考虑过期时间,确保热点期间key不会过期,甚至有些可以设置永不过期。
- 使用互斥锁(比如Java的多线程锁机制),第一个线程访问key的时候就锁住,等查询数据库返回后,把值插入到缓存后再释放锁
缓存穿透
缓存和数据库中都没有的数据,而用户不断发起请求,这时就会导致数据库压力过大。
解决方案
- 设置一个拦截器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤,避免对底层数据存储系统造成压力
- 访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间
缓存一致性
缓存与实际数据不一致
- 建立缓存的时候应该以不常发生变化的数据为主
- 当数据库中的数据发生变化时,应优先清除缓存中的数据
哨兵机制
在主从副用的模式下,一旦主节点由于故障不能提供服务,需要手动将从节点晋升为主节点,同时还要通知客户端更新主节点地址。而哨兵模式可以在主节点宕机以后会内部投票,重新选取一个主节点。
哨兵功能与作用
- 会不断地检查你的主服务器和从服务器是否运作正常。
- 每10秒每个sentinel节点对master节点和slave节点执行info操作
- 每2秒每个sentinel节点通过master节点的channel(sentinel:hello)交换信息
- 每1秒每个sentintel节点对master节点和slave节点以及其余的sentinel节点执行ping操作
- 当 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 当没有足够数量的Sentinel同意主服务器下线时,主服务器的客观下线状态就会被移除。
- 当主服务器重新向Sentinelf的PING命令返回有效回复时,主服务器的主观下线状态就会被移除
- 出现故障后会将故障的主节点转移
选举流程
- 哨兵选举:
- 当哨兵发现主节点下线,开始进行哨兵Master选举
- 谁优先发现主节点下线并把它设置为主观下线的哨兵会拥有选举权
- 如果该sentinel节点发现自己的票数已经过半且达到了quorum的值,就会成为领导者
- 如果这个过程出现多个sentinel成为领导者,则会等待一段时间重新选举
- Master选举:
- 优先级最高
- 数据偏移量最大
- runid最小(最早启动的)
- 状态开始进行更换:
- 被选中的节点升级为主节点 通知其他的从节点从新的主节点更新数据,并且修改从节点的配置文件。
- 如果是原来的主节点恢复,也会降为从节点开始从新主节点同步数据。
- 通知客户端
- 当所有节点配置结束后,sentinel会通知客户端节点变更信息。
- 客户端写数据的时候连接新的master节点
Redis高可用
集群
通过集群,Redis解决了写操作无法负载均衡以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
哨兵
在复制的基础上,哨兵实现了自动化的故障恢复。缺陷是写操作无法负载均衡,存储能力受到单机的限制。
复制
复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。
- 复制主要实现了数据的多机备份以及对于读操作的负载均衡和简单的故障恢复。
- 缺陷是故障恢复无法自动化、写操作无法负载均衡、存储能力受到单机的限制。
哨兵
在复制的基础上,哨兵实现了自动化的故障恢复。缺陷是写操作无法负载均衡,存储能力受到单机的限制。
数据的持久化
redis是一个内存数据库,数据保存在内存中,虽然内存的数据读取速度快,但是很容易发生丢失,Redis的持久化主要是通过数据备份,将数据存储在硬盘,保证数据不会因进程退出而丢失。redis还为我们提供了持久化的机制,分别是RDB和AOF
RDB
RDB是redis默认的持久化方式,按照一定的时间将内存中的数据以快照的形式保存到硬盘中。
产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照周期。
- RDB的优势和劣势
- 优势:
- RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
- 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作
- 恢复数据集时的速度比 AOF 的恢复速度要快。
- 劣势:
- 当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据
- 父进程修改内存子进程不会反应出来
- 所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
- 优势:
- RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照,也是默认的持久化方式
- RDB提供了三种触发机制:save、bgsave、自动化
- save触发方式:
- 该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。
- bgsave触发模式:
- 执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
- Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
- 自动触发:
- 自动触发是由配置文件来完成的。
- save触发方式:
AOF
将redis执行的写入的命令记录到单独的日志文件中,重启redis会重新将持久化的日志文件恢复数据,两种方式同时开启时,会优先选择aof恢复
- AOF的优势和劣势
- 优势
- AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。
- AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
- AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。
- 劣势
- 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
- AOF支持的写QPS会比RDB支持的写QPS低
- 优势
- 全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,Redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
- rewrite策略
- 重写日志,减少日志文件的大小,redis提供了bgrewriteaof命令。
- 将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写。
- 将整个内存中的数据库内容用命令的方式重写了一个新的aof文件
- AOF的触发策略
- 每修改同步always,同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
- 每秒同步everysec,异步操作,每秒记录 如果一秒内宕机,有数据丢失
- 不同no,从不同步
主从复制集群
Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。(主要就是主节点处理写的请求,从节点处理读的请求)
数据同步
- Redis可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步
- 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步
Redis的Slot槽
Redis 集群使用数据分片而非一致性哈希来实现。
一个 Redis 集群包含16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 一个redis最多有16384个节点,超过16384的节点数就会有一个节点没有槽,没有槽就管不了数据。
使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽 (CRC16的算法岁16384进行取余)
因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞,所以无论是添加新节点还是移除已存在节点,又或者改变某个节点包含的哈希槽数量,都不会造成集群下线。
对象保存到经过取余的那个槽中,并指定到node节点上,每个node都是平均分配的node段,16384个槽不能重复也不能丢失,否和会造成对象重复或者无法存储。node会互相监听一旦有node退出或者加入,会按照槽为单位做数据迁移,如果有掉线的,会平分打哦别的节点上
槽位分配案例
- 每个节点的那负责处理一部分哈希槽,比如有a、b、c三个节点用户将新的节点 d 添加到集群中,集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了,如果要从集群中移除节点A,只需要将节点 A 中的所有哈希槽移动到节点 B 、 C、D、然后再移除空白的节点 A 就可以了。因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。