10万+QPS 真的只是因为单线程和基于内存?.txt2020年06月26日21时48分12秒

作者: 发表时间:2020-06-26 19:37:52 浏览人数:861

所以你想得太少了。我认为redis的速度是基于很多方面的:不仅仅是单线程和内存,还有底层数据结构的设计、网络通信、主从、岗哨和集群等~

首先,redis底层使用的数据结构很多,但并没有直接用于实现密钥-值对数据库。相反,对象系统是基于数据结构创建的。(你认为这是一种面向对象的编程吗~)

在执行命令之前,redis可以根据对象的类型判断该对象是否可以执行给定的命令。它可以通过为不同使用场景中的对象设置多种不同的数据结构来实现,从而实现优化对象在不同场景中的使用效率。

我们都知道redis的set命令实际上是针对字符串的,但它也可以设置值。底层是怎么做的?

它将字符串对象的encoding属性标识为redis_uencoding_uint,表示与此键对应的值是long类型的整数。

string对象的encoding属性被标识为redis_uuencoding_uuraw,表示该值此时是一个简单的动态字符串。

正是由于使用了对象,通过类型、编码和PRT属性,同一个对象可以适应不同的场景,使得不同的变化不需要创建新的键值对,这使得redis的对象使用效率非常高。

Embstr编码是专门用来保存短字符串的,因此它通过调用内存分配函数来分配一段连续的空间。该空间包含redisobject和sdsshdr两种结构,可以很好地利用缓存带来的优势。原始编码用于保存长字符串。通过调用内存分配函数两次,创建redisobject结构和sdshdr结构

Len:记录buf数组中使用的字节数,等于SDS lock save string free的长度:记录buf数组中未使用的字节数buf[]:字节数组,用于保存字符串

由于SDS的free属性,可以避免缓冲区溢出。在字符串展开之前,根据自由属性判断直接展开是否满足。如果不是,则需要先执行内存重新分配,然后展开字符串。

我们都知道修改字符串长度可能会触发内存重新分配,但redis有两种优化内存重新分配策略:

空间预分配用于优化 SDS的字符串增长操作:当SDS API修改SDS并需要扩展SDS时,程序不仅会分配SDS修改所需的空间,还会为SDS分配额外的未使用空间,并使用free属性记录分配的字节数。通过空间预分配策略,下一个字符串扩展可以充分利用上次预分配的未使用空间,而不会触发内存重新分配操作。

在对象中使用数字是非常常见的,例如设置用户的年龄、学生的分数、博客中文章的排名等等。因此,为了避免重复创建与数字对应的字符串对象,redis将共享一个范围内与整数对应的字符串对象。

目前,redis在初始化服务器时会创建10000个字符串对象。这些对象包含从0到9999的所有整数值。当服务器需要使用值为0到9999的字符串对象时,服务器将使用这些共享对象而不是新创建的对象。

当然,我们也可以修改redis.h/redis SHARED。integers常量修改创建的共享字符串对象的数量。

我们都知道redis是用C语言开发的,所以SDS遵循C字符串以空字符结尾的约定,因此SDS可以重用由字符串定义的许多函数。图书馆。

Zlbytes:记录整个压缩列表占用的内存字节数;当内存重新分配到压缩列表时,或计算Zlend使用zltail记录压缩列表的结束节点与压缩列表的起始地址之间的距离。有了这个偏移量,程序就可以在不遍历整个压缩列表的情况下确定压缩列表的结束节点的地址zlen:记录压缩列表中包含的节点数;当这个属性的值大于uint16_uin max(65535)时,实际需要遍历整个压缩列表的节点数要计算的列表。Entryx:压缩列表中包含的每个节点。节点的长度由节点保存的内容决定。Zlend:特殊值0xff(十进制255)用于标记压缩列表的结尾。

压缩列表是为节省内存而开发的顺序数据结构。因此,压缩列表被用作redis中列表键和散列键的底层实现之一。

当一个列表键只包含几个列表项,并且每个列表项是一个小整数值或一个短字符串时,redis将使用压缩列表作为列表键的底层实现。当散列键只包含少量的键值对,并且每个键值对的键和值都是小整数值或短字符串时,redis将使用压缩列表作为散列键的底层实现。

压缩列表的使用不仅使数据非常紧凑,节省了内存,而且利用其结构实现了非常简单的顺序遍历、反向遍历、O(1)复杂度的获取长度和内存大小。

Intset是redis用来存储整数值的集合抽象数据结构。它可以保存为t的整数值int16、int32或Int64,并确保集合中不出现重复的元素。

结构intset的类型{uint32_t编码;uint32_t长度;int8_t内容[];}intset

尽管intset结构声明contents属性int8是T类型的数组,但事实上contents数组不包含任何int8,contents数组的真正类型取决于encoding属性的值。

避免使用错误的类型,并调整添加的新元素的长度。只要升级,小于相应长度的值就可以保存在中。如果长度不足,则无法再次升级。此外,intset最多可以升级两次,而不必担心升级过多会导致性能下降。节省内存,只要你需要升级操作,这样你就可以很好的节省内存。

因为整数集中没有降级操作,从另一个角度看,升级操作也会浪费内存:如果整数集中只有一个值是Int64_uut,其他值比它小,但整数集的编码将保持in t set_uuenc_uint64,即,小于Int64的整数T仍将使用Int64的空间来保存。

Redis使用基于reactor模式的网络通信。它使用I/O多路复用程序同时监听多个套接字,并根据套接字当前执行的任务将不同的事件处理器与套接字相关联。

当侦听套接字准备好执行接受、读取、写入和关闭等操作时,将生成与该操作相对应的文件事件,然后文件事件分派器将调用与该套接字关联的事件处理器来处理这些事件。

由于redis是单线程的,I/O多路复用器将使用队列来控制生成事件的套接字的并发性;队列中的套接字被有序、同步地分配给文件事件调度器,一次分配一个。

Redis封装了上述I/O复用机制。编译程序时,会自动选择系统性能更高的I/O复用函数库作为redis I/O复用程序的底层实现。

我们都知道文件事件的发生是随机的,因为redis服务器永远不知道客户端何时发送下一个命令,所以在文件事件发生之前程序是不能被阻止的。

毕竟,redis是一个线程。文件事件和时间事件的处理都在同一个线程中。如果线程一直被aeapiprol函数阻塞,即使时间事件结束,资源也将无法执行。

因此,redis有一个策略,即aeapipoll函数的更大阻塞时间由到达时间最接近当前时间的时间事件决定。这种方法不仅可以避免服务器对时间事件的频繁轮询(忙等待),而且可以保证aeapipoll函数不会长时间阻塞。

文件事件和时间事件的处理是同步、有序和原子执行的。服务器不会在进程中间中断事件处理,也不会抢占事件。因此,无论是文件事件的处理者还是时间事件的处理者,他们都会尽量减少程序的阻塞时间,并在必要时主动放弃执行权,从而减少阻塞时间不足的可能性。

redis服务器的很多功能都需要使用系统的当前时间,需要通过系统调用来获取系统的当前时间。

这两次只用于对时间精度要求不高的功能,如打印日志、计算服务器联机时间等;对于设置密钥过期时间、添加慢查询日志等对时间精度要求高的功能,服务器仍会每次调用系统获取。

从服务器向主服务器发送同步命令。主服务器收到sync命令后,在后台生成一个RDB文件(bgsave),并使用一个缓冲区记录从现在开始执行的所有写命令。当执行主服务器的bgsave命令时,生成的RDB文件被发送到从服务器;从服务器接收并加载RDB文件。主机将缓冲区中的所有写命令发送给从机;从机执行这些命令。此时,主服务器和从服务器的数据库都将达到一致状态。

假设主服务器和辅助服务器断开连接。重新连接辅助服务器时,它们需要再次执行同步。实际上,在重新连接辅助服务器时,数据库状态与主服务器的状态基本相同。缺少的是主服务器在断开连接过程中接收到的写入命令。每次断开连接后,需要再次执行完全同步操作,这将浪费主服务器的性能。毕竟,bgsave命令此时需要读取主服务器的完整数据库状态。

使用PSYNC命令而不是sync命令,复制操作分为完全重新同步和部分重新同步。只有当从属服务器的次复制或断开连接时间过长时,才会执行完全重新同步。在从服务器断开连接并重新连接一小段时间后,它只需要向主服务器发送自己的偏移量(复制偏移量)。主服务器将发送自己的偏移量,然后将从服务器丢失的写命令从复制积压缓冲区发送到从服务器。只要从服务器接收并执行这些写命令,它就可以将数据库更新到主服务器的当前状态。

在命令传播阶段,从服务器默认每秒向主服务器发送一次命令:replconf ack replication_uoffset,其中replication_uoffset是服务器当前的复制偏移量。

心跳检测不仅可以检测主从服务器之间的网络状态。从机还将其复制偏移量发送到主机,主机检查从机的命令是否丢失。心跳检测还可以帮助实现Min-slaves配置选项:

最小从机写入3min从机更大滞后10说明:当从机服务器数小于3台,或3台从机服务器滞后值大于等于10秒时,主服务器拒绝执行写入命令。这里的lag值是上面提到的info replication命令的lag值。

默认情况下,sentinel会以每两秒一次的频率通过命令连接将发布命令发送到所有受监控的主服务器和从服务器。该命令附有哨兵自己的信息和被监控主服务器的信息。然后接收命令的主服务器和从服务器将发送“哨兵”:Hello频道发送此信息。

这样,监听同一主服务器的哨兵就可以知道对方的存在,根据通道消息更新哨兵站的哨兵字典,并与其他哨兵建立指挥联系,方便主服务器离线检查和领导哨兵选举等。

redis集群中的每个节点通过任务协议交换各自的状态信息,该协议由meet、Ping和pong三条消息实现。这三条消息的主体由两个cluster.h/clustermsgdatamission结构组成。

通过使用gossip协议,簇内节点的更新信息可以像病毒一样传播。这样,不仅扩散速度快,而且可以在每个节点之间不发送消息的情况下同步集群中的最新信息。

到目前为止,所有的设计,我能想到的,使redis优越在这里。当然,它比那更强大~

大家都知道,使用redis很简单,只是几个命令来来往往,但是当你深入到redis底层的设计和实现时,你会发现这真的是一个值得深入研究的开源中间件!!!

统计 字数: 4773 汗字: 3568 数字:30 大写字母:42 小写字母:587 符号:546 总字节数:8341 共:99行2020年06月26日21时48分13秒

Top
RELATEED CONSULTING相关咨询
选择下列产品马上在线沟通
服务时间:9:00-19:00
你可能遇到了下面的问题