1 Redis为什么访问快
1.1 数据保存在内存中
Redis的数据读写都是基于内存的,不需要磁盘IO
1.2 基于key-vaule数据结构
- Redis的数据都是以key-value的数据结构储存在散列表中,时间复杂度只有O(1);
- Redis定义了丰富的数据结构,可以根据value的特性选择最高效的数据结构;
1.3 单线程模型
Redis的网络IO和数据读写都是使用单线程模型,这避免了线程切换所带来的消耗;
1.4 IO多路复用
Redis使用的是epoll网络模型:
内核会一直监听新的socket连接事件的和已建立socket连接的读写事件,把监听到的事件放到事件队列,redis使用单线程不停的处理这个事件队列。这避免了阻塞等待连接和读写事件到来。
这些事件绑定了回调函数,会调用redis的处理函数进行处理。
2 Redis的底层数据结构
2.1 字符集String
String是Redis最基本的数据类型,相当于Java中的Map模型,在Redis中,一个key对应一个value。
String是二进制安全的,它可以包含任何数据,比如jpg图片或者一个序列化的对象等。
String的值最大能储存512M的数据
2.2 散列Hash
Hash是一个键值(key=>value)对集合
Hash特别适合用于存储对象
2.3 列表List
List列表是一个简单的字符串列表,按顺序插入
2.4 无序集合Set
Set集合是string类型的无序集合,底层数据结构为哈希表,所以添加、删除、查找的复杂的都为O(1)
2.5 有序集合Sorted Set
Sorted Set与Set一样为string类型,且不允许重复的集合;不同在于插入元素时,会给每个元素分配一个double类型的分数,然后根据该分数进行从小到大的排序。
3 Redis的6种缓存淘汰策略
为了更好的利用内存,使Redis存储的都是缓存的热点数据,Redis设计了相应的内存淘汰机制(也叫做缓存淘汰机制);
当内存数据集大小达到一定的大小时,就会根据maxmemory-policy noeviction配置项配置的策略来进行数据淘汰。
- voltile-lru 从已经设置过期时间的数据集中挑选最近最少使用的数据淘汰
- voltile-ttl 从已经设置过期时间的数据库集当中挑选将要过期的数据
- voltile-random 从已经设置过期时间的数据集任意选择淘汰数据
- allkeys-lru 从数据集中挑选最近最少使用的数据淘汰
- allkeys-random 从数据集中任意选择淘汰的数据
- no-eviction 不淘汰数据,缓存满时,新的写请求报错
4 Redis的数据持久化
由于所有数据保持在内存中,所以对数据的更新将异步地持久化到磁盘上,Redis提供了一些策略来保存数据,比如根据时间或更新次数。
Redis的持久化的支撑是RDB\AOF,
在重启之后,可以再次加载进行使用。而RDB方式是指通过快照完成持久化。当符合一定条件时,redis会自动将内存中的所有数据进行快照并储存到磁盘上。默认储存在redis根目录的配置文件dump.rdb中。
RDB是redis默认的持久化方式。
4.1 AOF日志
AOF日志记录了每一条收到的命令,redis故障宕机恢复时,可以加载AOF日志中的命令进行重放来进行故障恢复。AOF有3种同步策略,如下图:
== 如果不是对丢失数据特别敏感的业务,推荐使用everysec,对主线程的阻塞少,故障后丢失数据只有1s ==
4.2 RDB快照
RDB快照是一个内存快照,记录了redis某一时刻的全部数据。
4.3 混合日志
从redis4.0开始,AOF文件也可以保存RDB快照,AOF重写的时候redis会把AOF文件内容清空,先记录一份RDB快照,这份数据以"REDIS"开头。记录RDB内容后,AOF文件会接着记录AOF命令。故障恢复时,先加载AOF文件中RDB快照,然后回放AOF文件中后面的命令。
4.4 主从同步
redis主从同步时,主节点会先生成一份RDB快照发送给从节点,把快照之后的命令写入主从同步缓存区(replication buffer),从节点把RDB文件加载完成后,主节点把缓存区命令发送给从节点。
4.5 AOF重写
AOF日志是用记录命令的方式追加的,这样可能存在对同一个key的多条命令,这些命令是可以合并成1条的。比如对同一个key的多个set操作日志,可以合成一条。
4.6 阻塞点
AOF重写和RDB快照执行的过程中,redis都会fork一个子进程来执行操作,子进程执行过程中是不是阻塞主线程的。
注意点
- fork子进程的过程中,redis主线程会拷贝一份内存页表(记录了虚拟内存和物理内存的映射关系)给子进程,这个过程是阻塞的,redis主线程内存越大,阻塞时间越长;
- 子进程和redis主线程共用一块儿物理内存,如果新的请求到来,必须使用copy on write的方式,拷贝要修改的数据页到新的内存空间进行修改。
5 Redis的高可用
为了保证Redis的高可用,通常会采取Redis集群+哨兵的形式部署;
当Redis集群中的主节点挂了后,流程如下:
判断主节点下线——选举新主节点——选举哨兵leader——执行主从切换
5.1 判断主节点下线
一般一个哨兵发现主节点下线后,会发通知给其他哨兵,其他哨兵会回复“Y” 或 “N”,如果回复“Y”的超过 n/2 + 1个,则可以判定主节点下线;n指的是哨兵数量;
5.2 选举新的主节点
slave的选举主要会评估slave的以下几个方面:
- 与master断开连接的次数
- Slave的优先级
- 数据复制的下标(用来评估slave当前拥有多少master的数据)
- 进程ID
- sentinel会根据 slave与master的断开连接次数与时间长度,选定候选人;若进行选举时,某个slave与master断开连接,则当时就失去候选资格;
- 选定候选人后,sentinel会根据候选人的优先级进行排序,若某个slave的优先级为0,则永远不会被选举为master
- 若优先级相同,则会根据数据复制的下标,从master复制更多数据的会被选举为master
- 若复制数据下标相同时,则会挑选进程id更小的当master
5.3 选举哨兵leader
第一个判断主节点下线的哨兵节点收到其他节点的回复并确定主节点下线后,就会给其他哨兵发送命令申请成为哨兵leader。
成为哨兵leader的条件如下:
- 收到赞成票必须大于等quorum值
- 必须拿到半数以上的赞成票
如果集群配置了5个哨兵,quorum的值设置为3,其中一个哨兵节点挂了,很有可能会判断到主节点下线,但是因为选举不出哨兵leader而不能切换。如果集群有2个哨兵,其中一个挂了,那必定选不出哨兵leader。
哨兵leader的作用是:
- 通知其他哨兵新主节点地址
- 通知所有从节点新的主节点地址,从节点收到后向新主节点请求主从同步
- 通知客户端连接新主节点
5.4 主从切换
如果客户端的读请求会发送到从节点,可以正常处理。
在客户端收到新主节点地址通知前写请求会失败。
客户端可以采取一些应急措施应对主节点下线,比如缓存写请求。
为了能够及时获取到新主节点信息,客户端可以订阅哨兵的主节点下线事件和新主节点变更事件。