当前位置:首页 > 运维 > 正文内容

Redis详知详解

MuWind1个月前 (04-04)运维17

来自知乎文章:Redis详知详解 - 知乎 (zhihu.com)在这里做一些改动方便阅读

Redis学习

三大主线:

1.高性能:线程模型,数据结构,持久化,网络框架

2.高可靠性:主从复制,哨兵机制

3.高可拓展:数据分片,负载均衡


01 Redis 基本架构

可以存储的数据

Redis是一个键值数据库。基本数据模型是key-value模型;

不同的键值数据库所支持的key类型差异不大,但是在对value类型支持时却有着较大的差别,所以在对键值数据库进行选择时,一个重要的考虑因素是它所支持的value类型。例如,Memcached支持的value类型只有string,而Redis支持的value类型包括了string,哈希表,列表,集合等。

可以对数据进行的操作

PUT:新写入或者更新一个key-value对

GET:根据key值读取相应的value值

DELETE:根据key值删除整个key-value对

SCAN:根据一段的key值范围返回相应的value值。

采用了内存,键值数据库包括了访问框架,索引模块,操作模块,储存模块。




访问模式:

通过函数库调用的方式供外部应用使用

通过网络框架以Socket通信的形式对外提供键值对操作

RocksDB以动态链接库的形式使用,而Memcached和Redis通过网络框架访问;

索引方式:

Memcached和Redis采用了哈希表作为key-value的索引

Redis 主要通过网络框架进行访问,而不再是动态库了,这也使得 Redis 可以作为一个基础性的网络服务进行访问,扩大了 Redis 的应用范围。

Redis 数据模型中的 value 类型很丰富,因此也带来了更多的操作接口,例如面向列表的LPUSH/LPOP,面向集合的 SADD/SREM 等。

Redis的持久化模块能支持两种方式:日志(AOF)和快照(RDB),这两种持久化方式具有不同的优劣势,影响到 Redis 的访问性能和可靠性。

SimplekV 是个简单的单机键值数据库,但是,Redis 支持高可靠集群和高可扩展集群,因此,Redis 中包含了相应的集群功能支撑模块

02 Redis数据结构

值的组织方式:

Redis底层对键值对中值的保存形式,其中String对应底层的简单动态字符串;其他的List,Hash,Set等都对应了两种数据结构;

而List Hash set Sorted Set对应四种数据结构都对应了底层两种实现方式,称为集合类型,特点是一个键对应了一个集合的数据。

键和值的组织结构

为了实现从键到值的快速访问,Redis使用了一个哈希表来保存所有的键值对。(全局哈希表)

是键到集合类型指针地址的映射;

哈希冲突:不同的key经过哈希映射之后,到了同一个哈希桶中。

当哈希表过长时,就会对哈希表进行rehash操作,就是增加目前hash桶的数量,让逐渐增多的entry元素能够在更多的桶之间分散保存,减少单个桶的元素数量。

rehash过程:

1.给哈希表2分配更大的空间,例如是当前哈希表1的两倍

2.将哈希表1的数据拷贝到哈希表2

3.释放哈希表1

4.完成后,下一次哈希表1就可以作为扩容使用。

存在问题:如果随着业务增长此时进行拷贝,由于数据量过多,会造成Redis线程阻塞?(迁移数据时无法进行其他请求)

渐进式rehash:在原有的rehash中,我们的操作是统一在一段时间中进行rehash,此时我们的操作是,当客户对这个key进行访问时,我们对这个key对应的桶进行rehash,分摊了开销。

集合数据的操作效率

与底层数据结构有关,使用哈希表比链表效率高

压缩列表和跳表

压缩列表

其中前三个字段分别是:列表长度,列表尾的偏移量,列表中的entry个数,zlend表示列表结束。在列表中如果查找第一个或者最后一个元素,可以只通过表头的三个字段的长度直接定位,复杂度是O(1),而查找其他元素时,就需要逐个查找。

跳表:顾名思义,跳表就是能跳!!!

不同操作的复杂度

单元操作(对每一种集合的单个数据实现增删改查)O(1)

范围操作,指集合类型中的遍历操作,返回集合中的所有数据,此类操作一般是O(N)

统计操作,统计集合中所有元素的个数,如果采用的是压缩列表的数据结构,O(1)

对头尾操作,压缩列表O(1)

03 Redis高性能IO模型

前言

通常所说的Redis单线程,指的是Redis的网络IO和键值对的读写是由一个线程来完成的,这是Redis对外提供键值存储服务的主要流程。其他功能,如持久化,异步删除,集群数据同步等,是由其他额外的线程执行的。

Redis为什么用单线程

多线程的巨大开销:

一般情况下,在系统可用资源数足够时,我们如果能够通过,增加线程数量可以增大我们的吞吐量,但是由于多个线程对共享资源的同时访问,就需要额外的机制来维护共享资源的正确性,这就导致了额外的开销。

为了保证队列长度的正确性,大量线程被强制串行化。

单线程的Redis为什么快?

通常来说,单线程的处理能力比多线程差很多,但是Redis却能够使用单线程模型,达到每秒十万级的处理能力。

1.Redis是内存操作,而且采用了更加高效的数据结构。例如哈希表和跳表。

2.网络端采用了多路复用机制。

在阻塞模式下,Redis线程有很大机率会阻塞在accept和recv。这是阻塞模式,而Socket也提供了非阻塞模式。

非阻塞模式:在accpet和recv上设置非阻塞模式,Redis线程可以在此时执行其他操作。

基于多路复用的高性能I/O模型

这就是我们经常听到的select/epoll机制,简单来说,在Redis运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或者数据请求。一旦请求到达,就会交给Redis线程处理。


事件队列,储存在操作系统的内核内存中。

04 AOF日志

主要问题:如何实现Redis的持久化?

1. AOF日志是如何实现?

数据库是写前日志,而Redis的AOF是写后日志。

数据库记录,修改后的数据的值。Redis的AOF记录修改的语句。

为了避免开销,在记录日志时,并不会对语法进行检查。所以采用写后日志,避免了语法出错。

缺点:

虽然不会阻塞当前对Redis的写入操作,但是会阻塞下一个对Redis的写入操作。

如果程序在写入日志前宕机,则会丧失此时日志,如果用作数据库则会丧失数据。

2. 三种写回策略

Always,同写同回,写完立马同步到磁盘上。

Evevrysec,每秒写回

No,操作系统控制写回,先写入内存缓冲区,再由操作系统决定何时将缓冲区内容写回。

3.日志文件过大

4.重写会不会阻塞主线程

每次AOF重写,Redis都会先执行一个内存拷贝,用于重写;然后使用两个日志来保证在重写过程中新写入的数据不会丢失。

AOF是写日志,但是如果要恢复的话,就需要一条一条执行,如果我们有快照,就不需要管理这个过程,而是能够直接获取状态了。

05 内存快照 RDB

主要问题:

为什么需要内存快照?为了解决AOF数据恢复过慢问题。

要对哪些数据进行快照?

做快照时如何数据还能够被增删改吗?

给哪些数据进行快照?

Redis执行的是全量快照。

save在主线程中执行,会导致阻塞。

bgsave创建一个子线程,专门用于写入RDB文件,避免了主线程的阻塞。

做快照时如何数据还能够被增删改吗?在RDB时可以进行写操作,这是由于Copy-On-Write机制。

如何确定快照频率?频率过高,带来了巨大开销,磁盘以及fork阻塞,以及内存等等问题。

Redis4.0提出了混合使用AOF日志和内存快照的方式。

在两次快照之间使用AOF记录这期间的所有命令操作。

所以一旦发生宕机,可以使用最近快照+AOF的形式恢复数据。

06 数据同步:主从库的数据一致性

为什么需要主从数据库?一个实例在恢复期间是无法服务新来的数据存取请求的。为了保证服务的质量一般都采用增加副本冗余量,将一个数据保存在多个实例上。

·读操作:主库、从库都可以接收。

·写操作:首先到主库执行,然后,主库将写操作同步给从库。

主从库间如何第一次同步?

当启动多个Redis实例时,可以通过replicaof命令形成主库和从库关系。之后会按照三个阶段完成数据的第一次同步。

例如现在有实例1(172.16.19.3)和实例2(172.16.19.5)

replicaof 172.16.19.3

此时实例2就会成为实例1的从库,并从实例1上复制数据。

第一次同步的三个阶段:

第一阶段是主从库建立连接、协商同步。

1.发送psync命令,表示要进行数据同步,主库根据这个命令参数来启动复制。psync命令包含了主库的runID,和复制进度offset两个参数。此时由于不知道主库ID和复制进度,所以runID设置为?,offset设置为-1表示第一次复制。

2.主库响应:FULLRESYNC,表示这是一次全量复制,主库把当前的数据都复制给从库。

第二阶段:主库把本地RDB文件同步给从库。在此期间产生的新的数据,会储存在专门的replication buffer内存中,在RDB完成发送后,再同步给从库。

主从级联模式:

在主-从关系模式中,所有的从库都是和主库进行连接,所有的全量复制和RDB文件传输也都是在主库进行。导致主库不断地fork生成子线程,可能会导致主库阻塞。通过主-从-从的模式,我们可以将主库生成RDB和传输RDB的压力以级联的方式分散到从库上。

基于长连接的命令传播

不可避免的网络阻塞或者断开

主从库网络断了之后

Redis2.8前重新进行全量复制

Redis2.8后

断开后,写入操作不仅写入replicationbuffer中还写入repl_backlog_buffer中,连接后从库发送自己的位置,与主库对比,主库将之间的差距写给从库。

如果读取速度过慢则会出现数据被覆写的,导致主从库数据不一致问题。

07 哨兵机制

主库崩溃后如何进行写操作?

哨兵机制的基本流程:

1.监控

哨兵是一个运行在特殊模式下的Redis进程,主从库实例运行时,他也在运行。哨兵对所有主从库发送PING命令,如果主从库没有在规定时间内响应则会被标记为下线。如果主库下线则会自动切换主库。

2.选主

按照一定规则选择一个从库作为新的主库。

3.通知

执行通知任务时,哨兵会把新主库的连接信息发送给其他从库,让他们执行replicadf命令和新主库建立连接。同时把连接信息发送给客户端,让他们把请求发送到新主库上。

主观下线和客观下线

主观下线,PING命令没回应,对于从库没影响直接下线就好。

对于主库如果误判则会产生极大开销。为了避免该问题一般采用哨兵集群来进行判断。多数同意下线则才是客观下线。

如何选择新的主库?

筛选+打分:

优先级最高的从库得分高

例如内存大的优先级高

和旧主库同步程度最近的从库得分高

slave_repl_offset和master_repl,从库中根据slave_repl_offset来决定谁更新

ID号小的从库得分高

优先级和复制进度相同的情况下,ID号最小的从库得分最高。

08 哨兵集群

在进行哨兵集群配置,并不需要知道其他哨兵实例的IP端口。

基于pub/sub机制的哨兵集群

发布订阅机制,只要哨兵和主库进行连接后,就可以在主库上发布消息,也可以订阅消息,当多个哨兵实例都连接上了主库,他们之间就能够彼此知道IP地址和端口。

和kafka一样只有订阅一个频道的应用才能通过发布消息进行信息交换。其中哨兵之间的信息交换频道是“sentinel:hello”.

哨兵通过INFO命令向主库请求slave列表

切换主库后客户端如何知道从库和新主库信息?

哨兵实例也提供了订阅发布机制

客户端读取哨兵配置文件,客户端就可以从哨兵处订阅以上信息。

由哪个哨兵实际进行主从切换?

上一节中讲到了客观下线,此时主库下线的话无法通过主库进行消息订阅和发布。当主库下线后,哨兵给其他哨兵发送Y或N,当一个哨兵获得了仲裁所需的票数后就可以标记主库为客观下线。此时这个哨兵发送命令希望由自己来自行主从库的切换,称为leader选举。

成为leader哨兵条件:

拿到半数以上赞成票

拿到的票数大于配置文件中的quorum值

如果有两个哨兵实例则需要配置quorum值为2,因为如果是1,如果有个哨兵挂掉此时拿不到半数以上赞成票。

假设有一个Redis集群是"一主四从",同时配置包含了5个哨兵实例的集群,quorum值为2,那么在运行过程中,如果3个哨兵实例发生故障,此时Redis主从如果有故障,还能正确判断主库"客观下线"嘛?还能进行主从库切换嘛?是不是哨兵实例越多越好?如果调大 down-after-milliseconds 值,能否减少误判?

可以判断客观下线,因为quorum=2,当一个哨兵判断主库"主观下线"后,询问另一个哨兵,当2个哨兵都判定"主观下线",满足quorum值,因此主库"客观下线"。

不能完成客观下线,必须达到半数以上才能选出leader。

哨兵在判断"主观下线"和选举"哨兵领导者"的时候都需要和其他节点进行通信,交换信息。哨兵实例越多,通信次数也就越多,部署越多哨兵在不同机器上,节点越多带来的机器故障风险越大,这些都会影响到哨兵的通信和选举。出问题时候也会意味着选举时间变长,切换主从的时间变长。

适当调大down-after-milliseconds值,当哨兵与主库之间网络存在短时波动时,可以降低误判的概率。但是调大down-after-milliseconds值也意味着主从切换的时间会变长,对业务的影响时间越久,我们需要根据实际场景进行权衡,设置合理的阈值。

09 切片集群

e.g.:用单个实例储存较大数据,此时Redis fork子线程进行RDB持久化时,会耗时十分严重。fork操作和储存数据所占内存大小成正比,所以一般来说对该Redis实例进行切片组建切片集群。

主要问题:

数据切片后如何实现实例的分布?

客户端如何定位所要访问的数据在哪个实例上?

数据切片和实例的对应分布关系

切片集群是一种保存大量数据的通用机制,该机制可有大量不同的实施方案。

Redis3.0后官方提供了Redis Cluster机制。

Redis Cluster采用了哈希槽的机制来处理数据和实例之间的映射关系。Cluster方案中一个切片集群有16384个哈希槽,每个键值对都会根据他的key映射到一个哈希槽中。

在创建切片集群时,Cluster会将哈希槽分配到不同的实例上。

在客户端和实例建立连接后就会发送哈希槽分配信息给客户端,当集群刚刚建立时,每个实例只知道自己被分配了哪些哈希槽,而不知道其他哈希槽信息。Redis实例会发送自己的哈希槽信息给其他实例,每个实例都具有了哈希槽映射关系。

客户端会在本地耶缓存哈希槽信息。

集群中有实例增加和删除,Redis重新分配哈希槽。

为了负载均衡需要把哈希槽重新分配一次。

客户端此时缓存是失效的,Cluster提供了重定向机制,如果在该实例没有找到key值的哈希槽,会参考这个实例上最新的哈希槽对照表,重定向到正确的实例。

MOVED:重定向

ASK:哈希槽中的数据还没有迁移完毕



标签: Redis

“Redis详知详解” 的相关文章

快速且简单通用的批量转码方法

快速且简单通用的批量转码方法

前情提要:MC服务器迁移到HomeServer后,突然有一天玩家跟我说挑战乱码,我打开挑战文件一看:而且挑战文件都在一个文件夹共有291个,一个个扔到npp里面转成utf8也不是事儿。简单说一下批量转码方法,以下适用于所有用yum的机器:先安装依赖:yum install -y&...

centos开启samba

centos开启samba

samba是玩nas经常用的服务了,这里说一下怎么安装:登录机器,yum安装:yum install samba systemctl enable samba配置方法:vi /etc/samba/smb.conf[nasdata] commen...

OpenWrt无法保存配置无法生效的解决方法

原因:意外断电导致硬盘卡在只读状态进入openwrt后台按回车输入下面命令,重新挂载即可:mount -o remount rw /...

zblog强制打开调试模式

zblog强制打开调试模式

之前写zblog of cloudflare插件的时候,写错了代码,因为没开调试模式我也不知道哪里报错,这里记录一下:修改 zb_system/function/c_system_base.php,将第 22 行的//注释删除掉,再保存即可。...

Zerotier配合Nginx实现内网穿透

Zerotier配合Nginx实现内网穿透

之前博客网站一直用的家里机器配合香港Azure做frp内网穿透,用CF CDN进行数据分发,不提frp的虚拟局域网模式在跨国数据传输时面临的数据审查和路由方向会给速度及稳定性造成非常大的影响,其可能有的内存溢出和服务重启时的持续掉线问题也是很抓狂的,在这里,使用zerotier为两机打洞连接p2p,...

CentOS设置开机启动

AIO出现了硬盘问题,重启后发现离线下载服务没有开机自启,快速给开一下:先写一个开机自启的脚本:#!/bin/sh #chkconfig: 2345 80 90 #description:aria2开机自启 aria2c --conf-path=/e...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。