首页 > 自考资讯 > 自考知识

带你走进河南作文1000字,带你走进江西作文800字

头条共创 2024-07-05

作者:bobobliu,腾讯CSIG后端开发工程师

在这篇文章中,我们将重点介绍Redis的基础知识和常识,以便大家在阅读源码并实践Redis之后,对相关知识点进行讲解。一起。

一、什么是 Redis

Redis是一个用C语言编写的开源的基于内存的键/值数据库,并提供多种语言的API。它的数据结构非常丰富,基本数据类型包括字符串(string)、列表(双向链表)、哈希(键值对的集合)、集合(集合、不重复)和排序集合。 (订单集)被收集)。主要可用于数据库、缓存、分布式锁、消息队列等。

上面列出的数据类型是Redis键值数据类型,它们实际上是数据存储格式,但底层数据结构分为六种主要类型。动态字符串、双向链表、压缩链表、哈希表、跳跃列表、整数数组。各数据类型与底层结构的对应关系如下:

数据类型和底层结构之间的映射

细绳

列表

散列

排序集

简单的动态字符串

双向链表、压缩链表

压缩链表、哈希表

压缩链表、整数数组

压缩链表、跳跃表

各个底层实现的时间复杂度如下:

数据类型和底层结构之间的映射

跳跃坡道

双链表

压缩链表

哈希表

整数数组

O(logN)

之上)

之上)

(1)

之上)

除了字符串类型只有一种数据结构的底层实现之外,其他四种类型都有两种底层实现,而且这四种类型都是集合类型,其中一个key就是集合的数据,可以看到是兼容的。

1.1 Redis 键值是如何保存的呢?

为了快速访问键值对,Redis 使用哈希表来存储所有键值对。所谓哈希桶,就是指所有的键值对。哈希表数组的元素。当然,哈希表中存储的并不是值本身,而是指向该值的指针,如下所示。

哈希桶中的条目元素存储分别指向实际键和值的键和值指针。使用Redis,您只需计算键的哈希值即可在O(1) 时间内找到键值对。但是,如下图所示,存在位置冲突。 4 并且两个键映射到同一位置,导致哈希冲突,导致哈希表运算缓慢。 Redis通过链冲突解决了这个问题,但是随着你的数据不断增长,它会产生越来越多的哈希冲突,增加Redis的查询时间。

600aef6ba04043c6a3d82388c43ec5d4~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720773195&x-signature=qgMwviUmxu5Y08xWtI9JxqH9XCM%3DRedis保存数据图

为了解决上面提到的哈希冲突问题,Redis对哈希表进行了rehash操作。这意味着增加当前哈希桶的数量并使密钥更加分散,从而减少哈希争用问题。主要流程为:

该操作使用两个哈希表。如果需要扩容哈希表A,则为哈希表B分配两倍的空间,并将哈希表A中的数据重新映射到哈希表B,以释放A中的空间。上面的步骤可能有问题。将哈希表A 复制到B 可能需要一定的时间,从而阻塞Redis 线程并阻止其处理其他请求。

针对上述问题,Redis采用了渐进式重新哈希技术。主要流程为:每次处理请求时,Redis 都会将该位置的所有条目复制到哈希表B。位于从未被要求的位置。如果某些键值对没有被操作,Redis可以设置一个重新哈希的计划任务,定期将一些数据移动到哈希表B中,从而缩短重新哈希的过程。

1.2 Redis 为什么采用单线程呢?

首先我们要明确一点:Redis单线程是指网络IO和键值对读写都是在一个线程中完成的,但是Redis持久化、集群数据等事实上是在一个线程中运行的。在了解Redis 的单线程使用之前,可以先了解多线程的开销。

虽然多线程通常可以增加系统吞吐量或提高系统的可扩展性,但多线程通常会同时访问特定的共享资源,提高访问共享资源的准确性,我们需要添加这种机制来保证。首先,有一些开销。事实上,控制多线程并发访问始终是一个难题,如果没有详细的设计,例如单独使用粗粒度互斥体将无法得到令人满意的结果。随着更多线程的添加,并行性不会更改为串行化,因为大多数线程正在等待获取互斥锁以访问共享资源。

这也是Redis使用单线程的主要原因。

值得注意的是,多线程是在Redis6.0中引入的。在Redis 6.0之前,从网络IO处理到处理实际的读写命令都是在单线程中完成的,但是随着网络硬件性能的提高,Redis的性能瓶颈可能会出现在IO处理上。单个主线程处理网络请求无法跟上底层网络硬件的速度。为了解决这个问题,Redis 使用多个IO 线程来提高处理网络请求的并行性。然而,多个IO线程仅用于处理网络请求。

1.3 Redis 单线程为什么还这么快?

IO复用机制:使网络IO操作能够同时处理许多客户端请求,以实现高吞吐量。

IO多路复用机制是指处理多个IO流的线程,通常称为select/epoll机制。对于单线程运行的Redis,这种机制允许内核中同时存在多个监听套接字和连接套接字。内核始终侦听这些套接字上的连接或数据请求。当请求到达时,它会被传递到Redis 线程进行处理。这具有一个Redis 线程处理多个IO 流的效果,从而提高了并发性。

Redis是基于内存的,大部分请求都是内存操作,而且速度非常快。

Redis 基本上有两种基本的内存优化实现方法。

主要执行过程是单线程的,避免了不必要的上下文切换和资源争用,消除了多线程带来的CPU切换和锁定问题。

二、Redis 数据丢失问题

在上一节中,我们大致了解了Redis存储和速度的主要原因。 Redis 通常用作缓存,在读取后端数据库数据之前将其存储在内存中。数据直接从内存中检索,从而实现非常快的响应时间。但是如果服务器宕机了,内存中的数据当然会丢失,虽然可以从后端数据库恢复缓存的数据,但是频繁访问数据库会给数据库带来一些压力。另一方面,数据是从后端数据库获取的,但从慢速数据库读取性能不如Redis,因此应用程序对这些数据的响应也会很慢。

因此,利用Redis实现数据持久化,避免后端数据恢复非常重要。目前Redis的持久化机制主要有两种:AOF(Append Only File)日志和RDB快照。

2.1 AOF 日志

AOF日志是写后日志。即Redis先执行命令,然后将数据写入内存,最后记录日志,如下所示。

e2f0d97aa85648319d9bb324cfff2745~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720773195&x-signature=ys2%2BYEzLWdYBaPvHBz0yculCmfk%3DRedis AOF操作流程

AOF日志以文本格式存储Redis接收到的所有命令。例如,取Redis接收到设置键值命令后记录的日志。 *3表示当前命令被分成三部分,如下所示。每个部分都以$+number 开头,其中数字表示该部分中命令、键和值的总数。

87e5ab1d499f4f759907c24f1b48d045~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720773195&x-signature=6cb0cy96DQIS4TtnsU0s4zVRnc4%3DRedis AOF日志内容

为了避免额外检查的开销,AOF 不检查命令的有效性。如果在第一次记录日志后执行该命令,则通过AOF日志恢复数据时可能会记录错误的命令。命令完成后进行记录不会阻止当前的写入操作。但AOF存在一定的风险。首先,如果AOF 文件在命令刚刚执行后但在保存之前崩溃,AOF 可以避免损坏,但有丢失命令和数据的风险。当前命令被阻塞(因为是先写入,然后记录),但接下来的操作可能有被阻塞的风险(写入磁盘可能会更慢)。这两种情况都取决于AOF 何时写入磁盘。 AOF机制针对这个问题提供了三个选项:Always、Everysec、No(appendfsync的三个选项值)。详细信息如下:

AOF写入磁盘的机制

每次

同步写回:每个命令执行后立即写入磁盘。

每一秒

每秒写回:每条命令执行完毕后,首先将日志写入到AOF文件中的一个缓冲区中,每秒将缓冲区的内容写入磁盘。

操作系统回写:每个命令执行后,日志首先写入AOF文件中的一个缓冲区,操作系统决定何时将缓冲区的内容写入磁盘。

您可以根据不同的场景选择不同的方法。

Always 更可靠,本质上不会造成数据丢失,但对性能影响较大。 Everysec 性能中等,宕机只会丢失1 秒的数据。没有更好的性能,但丢失的数据更多。它下降了。虽然有具体的写回策略,但归根结底,AOF 将所有写命令以文件的形式记录下来。但是,随着命令数量的增加,AOF 文件会变大,并可能超出文件大小限制。另外,如果文件太大,再次写入指令的效率就会降低。当发生停机时,必须重新运行所有AOF 命令以进行灾难恢复。会比较长,也会影响你对Redis的使用。

至此,AOF重写机制就介绍完了。

AOF 重写会根据所有键值对创建一个新的AOF 文件。这显着减少了文件空间。原因如下: AOF 以增量方式添加命令,一次记录一个命令。可能是关键。数值反复改变,导致数据冗余。所以你可以在重写过程中排除这些指令来更新当前的最新状态。

AOF重写过程是通过主线程在后台fork bgrewriteaof子进程来实现的,避免了阻塞主进程,降低性能。整个流程如下:

每次重写AOF 时,fork 进程都会将主线程的内存复制到bgrewriteaof 子进程。这包括数据库数据。复制的是父进程的页表,可以一一复制,不影响main。数据记录在重写日志中。主线程没有被阻塞,因此此时的任何写操作都会首先被缓冲。如果日志已关闭,这也会完成并且也可用于恢复。一旦数据复制完成,新的操作就不会丢失。缓冲区中的数据可以写入新的缓冲区。使用该文件检查数据库的最新状态:

2.2 RDB 快照

在上一节中,您了解了避免Redis 数据丢失的AOF 方法。采用这种方式,如果日志过多,Redis恢复会很慢,影响正常使用。

本节主要介绍Redis数据持久化的另一种方式:内存快照。这意味着任何给定时刻内存中数据的状态都会以文件的形式记录并写入磁盘,因此即使服务器宕机,快照文件也不会丢失,数据仍然可靠。该文件称为RDB(Redis 数据库)文件。事实证明,RDB 与AOF 不同,记录的是特定时间点的数据。所以,在数据恢复时,只需要将RDB文件读入内存即可完成数据恢复。但为了让RDB数据恢复更可靠,快照是全量快照,将内存中的所有数据记录到磁盘上,这可能会阻塞主线程的执行。 Redis提供了两个生成RDB文件的命令:save和bgsave。

save:在主线程执行时会发生阻塞。 bgsave:创建一个专门用于写入RDB文件的子进程。这样可以避免阻塞主线程,也是默认的方法。使用bgsave 命令执行完整快照可确保数据可靠性并避免影响Redis 性能。快照时可以修改数据吗?如果不能的话,快照时如果有新的写操作,数据就会不一致,这是完全意想不到的。 Redis 借用了操作系统的写时复制功能,在快照期间正常处理写操作。

主要流程为:

bgsave 子进程可以由主线程分叉并共享主线程的所有内存数据。 bgsave子进程运行后,主线程开始读取内存数据并将其写入RDB文件。如果主线程都是读操作(比如A),则主线程和bgsave子进程互不影响。如果主线程需要修改某部分数据(比如C),这部分数据不会受到影响。它被复制以产生数据的副本,并且主线程使用该副本。 bgsave子进程可以将原始数据C写入RDB文件。数据在快照期间可能会发生变化

通过上述方法,可以保证快照的一致性,并允许写处理到主线程,从而避免影响业务运行。您多久拍摄一次快照?

理论上来说,快照间隔越短越好,可以减少数据丢失。毕竟fork的子进程不会阻塞主线程,但频繁向磁盘写入数据会给磁盘带来很大的压力。可能会出现许多问题(当前快照正在启动,而下一个快照尚未完成)。另一方面,由fork 产生的子进程不会阻塞,但如果主线程需要更多内存,fork 创建过程将阻塞主线程。

为了解决上述问题,Redis使用了增量快照。创建完整快照后,后续快照应该只记录发生变化的数据,以避免完整快照的开销。

2.3 混合使用 AOF 日志和 RDB 快照

RDB快照比AOF恢复速度更快,但快照的频率很难控制。如果频率太低,则如果计算机在两个快照之间发生故障,您可能会丢失更多数据。太频繁会导致额外的开销。 那么还有什么其他方法可以利用快速RDB 恢复、低开销和最小数据丢失呢?

Redis 4.0 建议混合使用AOF 和RDB 快照。这意味着两次RDB快照期间的所有命令操作都会记录在AOF日志文件中。这样做的好处是不需要频繁执行RDB快照,从而避免了频繁fork对主线程的影响,并且AOF日志不记录所有操作,只记录两次快照期间的操作。它不应该太大,以避免重写开销。

上述方法可以让你享受到RDB的快速恢复的好处和AOF记录简单命令的好处。

关于AOF和RDB的选择:

如果您无法承受丢失数据的损失,那么将内存快照与AOF 结合使用是一个不错的选择。如果你可以容忍每分钟的数据丢失,你可以只使用RDB(如果你只使用AOF)。平衡改进的可靠性和性能。

三、Redis 数据同步

如果Redis崩溃,可以通过AOF和RDB文件恢复数据,以确保数据丢失并提高稳定性。但是,如果您的Redis 实例出现故障,它将无法在恢复期间处理新的数据请求。虽然AOF 和RDB 可以最大限度地减少数据丢失,但它们不能保证最小的服务中断。业务使用可能会受到影响,并且Redis无法保证高可靠。

Redis实际上采用了主/从数据库模型来保证数据复制的一致性。主/从数据库将读和写分开。从库和主库都可以接受写操作。然后主库将写操作同步到从库。

只有主库接收写操作,并阻止客户端修改数据到另一个Redis实例。当然,如果所有库都必须能够执行写入操作,其他客户端可能会读取旧值。实例之间完成更改的锁定和协商等操作会带来额外的开销。

3.1 主从库如何进行第一次数据同步

如果您有多个Redis 实例,可以使用replicaof 命令形成主从数据库之间的关系。要从主数据库复制数据,请在从数据库中输入“replicaof master database ip 6379”。步:

b923633b484b99554~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720773195&x-signature=I4v2SHyXTYfvsKALReIOtnyRQMw%3D" />哨兵机制的三项任务与目标 但这样也会存在一个问题,哨兵判断主从库是否下线如果出现失误呢? 对于从库,下线影响不大,集群的对外服务也不会间断。但是如果哨兵误判主库下线,可能是因为网络拥塞或者主库压力大的情况,这时候也就需要进行选主并让从库和新的主库进行数据同步,这个过程是有一定的开销的,所以我们要尽可能的避免误判的情况。哨兵机制也考虑了这一点,它通常采用多实例组成的集群模式进行部署,也被称为哨兵集群;通过引入多个哨兵实例一起判断,就可以尽可能的避免单个哨兵产生的误判问题。这时候判断主库是否下线不是由一个哨兵决定的,只有大多数哨兵认为该主库下线,主库才会标记为“客观下线”。 简单的来说”客观下线“的标准是当 N 个哨兵实例,有 N/2 + 1 个实例认为该主库为下线状态,该主库就会被认定为“客观下线”。这样就可以尽量的避免单个哨兵产生的误判问题(N/2 + 1 这个值也可以通过参数改变); 如果判断了主库为主观下线,怎么选取新的主库呢? 上面有说道,这一部分也是由哨兵机制来完成的,选取主库的过程分为“筛选 和 打分”。主要是按照一定的规则过滤掉不符合的从库,再按照一定的规则给其余的从库打分,将最高分的从库作为新的主库。 筛选。首先从库一定是正在运行的,还要判断从库之前的网络连接状态,如果总是断连并且超过了一定的阈值,哨兵会认为该从库的网络不好,也会将其筛掉。打分。哨兵机制根据三个规则依次进行打分:从库优先级、从库复制进度以及从库 ID 号;在某一轮有从库得分最高,那么它就是新的主库了,选主过程结束。如果该轮没有出现最高的,继续下一轮。优先级最高的从库。用户可以通过 slave-priority 配置项,给不同的从库设置优先级。选主库的时候哨兵会给优先级高的从库打高分,如果一个从库优先级高,那么就是新主库;从库复制进度最接近。主库的 slave_repl_offset 和从库 master_repl_offset 越接近,得分越高;ID 小的从库得分高。如果上面两轮也没有选出新主库,就会根据从库实例的 ID 来判断,ID 越小的从库得分越高。由此哨兵可以选择出一个新的主库。 由哪个哨兵来执行主从库切换呢? 这个过程和判断主库“客观下线”类似,也是一个投票的过程。如果某个哨兵判断了主库为下线状态,就会给其他的哨兵实例发送is-master-down-by-addr的命令,其他实例会根据自己和主库的连接状态作出 Y 或 N 的响应,Y 相当于赞成票,N 为反对票。一个哨兵获得一定的票数后,就可以标记主库为“客观下线”,这个票数是由参数 quorum 设置的。如下图: 3064efba5f504fc0bb7066a20b514c03~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720773195&x-signature=vuSmSiZXQ8QR9NqJSYlRYZ6b1Z4%3D例如:现在有 3 个哨兵,quorum 配置的是 2,那么,一个哨兵需要 2 张赞成票,就可以标记主库为“客观下线”了。这 2 张赞成票包括哨兵自己的一张赞成票和另外两个哨兵的赞成票。 这个时候哨兵就可以给其他哨兵发送消息,表示希望自己来执行主从切换,并让所有的哨兵进行投票,这个过程称为“Leader 选举”,进行主从切换的哨兵称为 Leader。任何一个想成为 Leader 的哨兵都需要满足两个条件: 拿到半数以上的哨兵赞成票;拿到的票数需要大于等于 quorum 的值。以上就可以选出 Leader 然后进行主从库切换了。

四、Redis 集群

数据量过多如何处理?

当数据量过多的情况下,一种简单的方式是升级 Redis 实例的资源配置,包括增加内存容量、磁盘容量、更好配置的 CPU 等,但这种情况下 Redis 使用 RDB 进行持久化的时候响应会变慢,Redis 通过 fork 子进程来完成数据持久化,但 fork 在执行时会阻塞主线程,数据量越大,fork 的阻塞时间就越长,从而导致 Redis 响应变慢。 Redis 的切片集群可以解决这个问题,也就是启动多个 Redis 实例来组成一个集群,再按照一定的规则把数据划分为多份,每一份用一个实例来保存,这样客户端只需要访问对应的实例就可以获取数据。在这种情况下 fork 子进程一般不会给主线程带来较长时间的阻塞,如下图: cfa75da21c0b416c8ddf11cda04606fb~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720773195&x-signature=xA7Cpj1H47bC%2BeqAhzRRhM48QKw%3D切片集群架构图 将 20GB 的数据分为 4 分,每份包含 5GB 数据,客户端只需要找到对应的实例就可以获取数据,从而减少主线程阻塞的时间。 当数据量过多的时候,可以通过升级 Redis 实例的资源配置或者通过切片集群的方式。前者实现起来简单粗暴,但这数据量增加的时候,需要的内存也在不断增加,主线程 fork 子进程就有可能会阻塞,而且该方案受到硬件和成本的限制。相比之下第二种方案是一种扩展性更好的方案,如果想保存更多的数据,仅需要增加 Redis 实例的个数,不用担心单个实例的硬件和成本限制。在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。 选择切片集群也是需要解决一些问题的: 数据切片后,在多个实例之间怎么分布?客户端怎么确定想要访问的实例是哪一个?Redis 采用了 Redis Cluster 的方案来实现切片集群,具体的 Redis Cluster 采用了哈希槽(Hash Slot)来处理数据和实例之间的映射关系。在 Redis Cluster 中,一个切片集群共有 16384 个哈希槽(为什么 Hash Slot 的个数是 16384),这些哈希槽类似于数据的分区,每个键值对都会根据自己的 key 被影射到一个哈希槽中,映射步骤如下: 根据键值对 key,按照 CRC16 算法计算一个 16bit 的值;用计算的值对 16384 取模,得到 0 ~ 16383 范围内的模数,每个模数对应一个哈希槽。这时候可以得到一个 key 对应的哈希槽了,哈希槽又是如何找到对应的实例的呢? 在部署 Redis Cluster 的时候,可以通过 cluster create 命令创建集群,此时 Redis 会自动把这些槽分布在集群实例上,例如一共有 N 个实例,那么每个实例包含的槽个数就为 16384/N。当然可能存在 Redis 实例中内存大小配置不一的问题,内存大的实例具有更大的容量。这种情况下可以通过 cluster addslots 命令手动分配哈希槽。 redis-cli -h 33.33.33.3 –p 6379 cluster addslots 0,1redis-cli -h 33.33.33.4 –p 6379 cluster addslots 2,3redis-cli -h 33.33.33.5 –p 6379 cluster addslots 4要注意的是,如果采用 cluster addslots 的方式手动分配哈希槽,需要将 16384 个槽全部分配完,否则 Redis 集群无法正常工作。现在通过哈希槽,切片集群就实现了数据到哈希槽、哈希槽到实例的对应关系,那么客户端如何确定需要访问的实例是哪一个呢? 客户端定位集群中的数据 客户端请求的 key 可以通过 CRC16 算法计算得到,但客户端还需要知道哈希槽分布在哪个实例上。在最开始客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端,实例之间会把自己的哈希槽信息发给和它相连的实例,完成哈希槽的扩散。这样客户端访问任何一个实例的时候,都能获取所有的哈希槽信息。当客户端收到哈希槽的信息后会把哈希槽对应的信息缓存在本地,当客户端发送请求的时候,会先找到 key 对应的哈希槽,然后就可以给对应的实例发送请求了。 但是,哈希槽和实例的对应关系不是一成不变的,可能会存在新增或者删除的情况,这时候就需要重新分配哈希槽;也可能为了负载均衡,Redis 需要把所有的实例重新分布。 虽然实例之间可以互相传递消息以获取最新的哈希槽分配信息,但是客户端无法感知这个变化,就会导致客户端访问的实例可能不是自己所需要的了。 Redis Cluster 提供了重定向的机制,当客户端给实例发送数据读写操作的时候,如果这个实例上没有找到对应的数据,此时这个实例就会给客户端返回 MOVED 命令的相应结果,这个结果中包含了新实例的访问地址,此时客户端需要再给新实例发送操作命令以进行读写操作,MOVED 命令如下: GET hello:key(error) MOVED 33.33.33.33:6379返回的信息代表客户端请求的 key 所在的哈希槽为 3333,实际是在 33.33.33.33 这个实例上,此时客户端只需要向 33.33.33.33 这个实例发送请求就可以了。 此时也存在一个小问题,哈希槽中对应的数据过多,导致还没有迁移到其他实例,此时客户端就发起了请求,在这种情况下,客户端就对实例发起了请求,如果数据还在对应的实例中,会给客户端返回数据;如果请求的数据已经被转移到其他实例上,客户端就会收到实例返回的 ASK 命令,该命令表示:哈希槽中数据还在前一种、ASK 命令把客户端需要访问的新实例返回了。此时客户端需要给新实例发送 ASKING 命令以进行请求操作; 值得注意的是 ASK 信息和 MOVED 信息不一样,ASK 信息并不会更新客户端本地的缓存的哈希槽分配信息,也就是说如果客户端再次访问该哈希槽还是会请求之前的实例,直到数据迁移完成。 以上就是 Redis 基础篇的全部内容~ 版权声明:本文转载于今日头条,版权归作者所有,如果侵权,请联系本站编辑删除

猜你喜欢