img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

穿透:缓存不存在,数据库不存在,高并发,少量key

击穿:缓存不存在,数据库存在,高并发,少量key

雪崩:缓存不存在,数据库存在,高并发,大量key

语义有些许差异,但是,都可以使用限流的互斥锁,保障数据库的稳定


redis事务是怎么实现的

MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
​
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

EXEC 命令负责触发并执行事务中的所有命令:

如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。

然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。

如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。

使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。

从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作,具体信息请参考文档的后半部分。

事务中的错误

使用事务时可能会遇上以下两种错误:

事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。

对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。

不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。

在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。

至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。

为什么 Redis 不支持回滚(roll back)

如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。


redis集群方案有哪些

常见集群分类

主从复制集群

分片集群

redis有那些:

主从复制集群,手动切换

带有哨兵的HA的主从复制集群

客户端实现路由索引的分片集群

使用中间件代理层的分片集群

redis自身实现的cluster分片集群


redis主从复制的原理是什么

主从复制机制

当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave , :包括客户端的写入、key 的过期或被逐出等等。
​
当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。
​
当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。

主从复制的关注点

Redis 使用异步复制,slave 和 master 之间异步地确认处理的数据量
​
一个 master 可以拥有多个 slave
​
slave 可以接受其他 slave 的连接。除了多个 slave 可以连接到同一个 master 之外, slave 之间也可以像层叠状的结构(cascading-like structure)连接到其他 slave 。自 Redis 4.0 起,所有的 sub-slave 将会从 master 收到完全一样的复制流。
​
Redis 复制在 master 侧是非阻塞的。这意味着 master 在一个或多个 slave 进行初次同步或者是部分重同步时,可以继续处理查询请求。
​
复制在 slave 侧大部分也是非阻塞的。当 slave 进行初次同步时,它可以使用旧数据集处理查询请求,假设你在 redis.conf 中配置了让 Redis 这样做的话。否则,你可以配置如果复制流断开, Redis slave 会返回一个 error 给客户端。但是,在初次同步之后,旧数据集必须被删除,同时加载新的数据集。 slave 在这个短暂的时间窗口内(如果数据集很大,会持续较长时间),会阻塞到来的连接请求。自 Redis 4.0 开始,可以配置 Redis 使删除旧数据集的操作在另一个不同的线程中进行,但是,加载新数据集的操作依然需要在主线程中进行并且会阻塞 slave 。
​
复制既可以被用在可伸缩性,以便只读查询可以有多个 slave 进行(例如 O(N) 复杂度的慢操作可以被下放到 slave ),或者仅用于数据安全。
​
可以使用复制来避免 master 将全部数据集写入磁盘造成的开销:一种典型的技术是配置你的 master Redis.conf 以避免对磁盘进行持久化,然后连接一个 slave ,其配置为不定期保存或是启用 AOF。但是,这个设置必须小心处理,因为重新启动的 master 程序将从一个空数据集开始:如果一个 slave 试图与它同步,那么这个 slave 也会被清空。
任何时候数据安全性都是很重要的,所以如果 master 使用复制功能的同时未配置持久化,那么自动重启进程这项应该被禁用。

Redis 复制功能是如何工作的

每一个 Redis master 都有一个 replication ID :这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master 也持有一个偏移量,master 将自己产生的复制流发送给 slave 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新 slave 的状态。复制偏移量即使在没有一个 slave 连接到 master 时,也会自增,所以基本上每一对给定的

Replication ID, offset

都会标识一个 master 数据集的确切版本。

当 slave 连接到 master 时,它们使用 PSYNC 命令来发送它们记录的旧的 master replication ID 和它们至今为止处理的偏移量。通过这种方式, master 能够仅发送 slave 所需的增量部分。但是如果 master 的缓冲区中没有足够的命令积压缓冲记录,或者如果 slave 引用了不再知道的历史记录(replication ID),则会转而进行一个全量重同步:在这种情况下, slave 会得到一个完整的数据集副本,从头开始。

下面是一个全量同步的工作细节:

master 开启一个后台保存进程,以便于生产一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master 将数据集文件传输给 slave, slave将之保存在磁盘上,然后加载文件到内存。再然后 master 会发送所有缓冲的命令发给 slave。这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同。

你可以用 telnet 自己进行尝试。在服务器正在做一些工作的同时连接到 Redis 端口并发出 SYNC 命令。你将会看到一个批量传输,并且之后每一个 master 接收到的命令都将在 telnet 回话中被重新发出。事实上 SYNC 是一个旧协议,在新的 Redis 实例中已经不再被使用,但是其仍然向后兼容:但它不允许部分重同步,所以现在 PSYNC 被用来替代 SYNC。

之前说过,当主从之间的连接因为一些原因崩溃之后, slave 能够自动重连。如果 master 收到了多个 slave 要求同步的请求,它会执行一个单独的后台保存,以便于为多个 slave 服务。

无需磁盘参与的复制

正常情况下,一个全量重同步要求在磁盘上创建一个 RDB 文件,然后将它从磁盘加载进内存,然后 slave以此进行数据同步。

如果磁盘性能很低的话,这对 master 是一个压力很大的操作。Redis 2.8.18 是第一个支持无磁盘复制的版本。在此设置中,子进程直接发送 RDB 文件给 slave,无需使用磁盘作为中间储存介质。

Redis热点数据如何处理

怎么发现热key 方法一:凭借业务经验,进行预估哪些是热key 其实这个方法还是挺有可行性的。比如某商品在做秒杀,那这个商品的key就可以判断出是热key。缺点很明显,并非所有业务都能预估出哪些key是热key。

方法二:在客户端进行收集 这个方式就是在操作redis之前,加入一行代码进行数据统计。那么这个数据统计的方式有很多种,也可以是给外部的通讯系统发送一个通知信息。缺点就是对客户端代码造成入侵。

方法三:在Proxy层做收集 有些集群架构是下面这样的,Proxy可以是Twemproxy,是统一的入口。可以在Proxy层做收集上报,但是缺点很明显,并非所有的redis集群架构都有proxy。

方法四:用redis自带命令 (1)monitor命令,该命令可以实时抓取出redis服务器接收到的命令,然后写代码统计出热key是啥。当然,也有现成的分析工具可以给你使用,比如redis-faina。但是该命令在高并发的条件下,有内存增暴增的隐患,还会降低redis的性能。 (2)hotkeys参数,redis 4.0.3提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可。但是该参数在执行的时候,如果key比较多,执行起来比较慢。

方法五:自己抓包评估 Redis客户端使用TCP协议与服务端进行交互,通信协议采用的是RESP。自己写程序监听端口,按照RESP协议规则解析数据,进行分析。缺点就是开发成本高,维护困难,有丢包可能性 (1)利用jvm本地缓存 比如利用ehcache,或者一个HashMap都可以。在你发现热key以后,把热key加载到系统的JVM中。 针对这种热key请求,会直接从jvm中取,而不会走到redis层。 假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。 现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。

(2)备份热key 这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。 假设redis的集群数量为N,步骤如下图所示

回收进程如何工作

理解回收进程如何工作是非常重要的:

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

双写一致性问题如何解决?

先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力更新即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。 在这里,我们讨论三种更新策略:

  1. 先更新缓存,再更新数据库。(不可取)
  2. 先更新数据库,再更新缓存。(不可取)
  3. 先删除缓存,再更新数据库。(不可取)
  4. 先更新数据库,再删除缓存。(可取,有问题待解决)
大前提:

先读缓存,如果缓存没有,才从数据库读取。

(1)先更新数据库,再更新缓存

这套方案,大家是普遍反对的。为什么呢?有如下两点原因。 原因一(线程安全角度) 同时有请求A和请求B进行更新操作,那么会出现 (1)线程A更新了数据库 (2)线程B更新了数据库 (3)线程B更新了缓存 (4)线程A更新了缓存 这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。 原因二(业务场景角度) 有如下两点: (1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。 (2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。

(2)先删缓存,再更新数据库

该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形: (1)请求A进行写操作,删除缓存 (2)请求B查询发现缓存不存在 (3)请求B去数据库查询得到旧值 (4)请求B将旧值写入缓存 (5)请求A将新值写入数据库 上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。 那么,如何解决呢?采用延时双删策略

(1)先淘汰缓存 (2)再写数据库(这两步和原来一样) (3)休眠1秒,再次淘汰缓存 这么做,可以将1秒内所造成的缓存脏数据,再次删除。 那么,这个1秒怎么确定的,具体该休眠多久呢? 针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。 如果你用了mysql的读写分离架构怎么办? ok,在这种情况下,造成数据不一致的原因如下,还是两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。 (1)请求A进行写操作,删除缓存 (2)请求A将数据写入数据库了, (3)请求B查询缓存发现,缓存没有值 (4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值 (5)请求B将旧值写入缓存 (6)数据库完成主从同步,从库变为新值 上述情形,就是数据不一致的原因。还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。 采用这种同步淘汰策略,吞吐量降低怎么办? ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。 第二次删除,如果删除失败怎么办? 这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库: (1)请求A进行写操作,删除缓存 (2)请求B查询发现缓存不存在 (3)请求B去数据库查询得到旧值 (4)请求B将旧值写入缓存 (5)请求A将新值写入数据库 (6)请求A试图去删除,请求B写入对的缓存值,结果失败了。 ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。 如何解决呢?

(3)先更新数据库,再删缓存

首先,先说一下。老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。 这种情况不存在并发问题么? 不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生 (1)缓存刚好失效 (2)请求A查询数据库,得一个旧值 (3)请求B将新值写入数据库 (4)请求B删除缓存 (5)请求A将查到的旧值写入缓存 ok,如果发生上述情况,确实是会发生脏数据。 然而,发生这种情况的概率又有多少呢? 发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。 假设,有人非要抬杠,有强迫症,一定要解决怎么办? 如何解决上述并发问题? 首先,给缓存设有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。 还有其他造成不一致的原因么? 有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。这也是缓存更新策略(2)里留下的最后一个疑问。 如何解决? 提供一个保障的重试机制即可,这里给出两套方案。

方案一: 如下图所示

流程如下所示

(1)更新数据库数据;

(2)缓存因为种种问题删除失败

(3)将需要删除的key发送至消息队列

(4)自己消费消息,获得需要删除的key

(5)继续重试删除操作,直到成功 然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

方案二

流程如下图所示:

(1)更新数据库数据

(2)数据库会将操作信息写入binlog日志当中

(3)订阅程序提取出所需要的数据以及key

(4)另起一段非业务代码,获得该信息

(5)尝试删除缓存操作,发现删除失败

(6)将这些信息发送至消息队列

(7)重新从消息队列中获得该数据,重试操作。

**备注说明:**上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。至于oracle中,博主目前不知道有没有现成中间件可以使用。另外,重试机制,博主是采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。

redis和MongoDB的区别

Redis和MongoDB是两种不同类型的数据库,它们在很多方面有所不同:

  1. 数据存储方式:

    • Redis:Redis是一种基于内存的数据存储系统,数据通常存储在内存中,可以持久化到硬盘上,但主要用于缓存、会话存储和快速访问数据。
    • MongoDB:MongoDB是一种文档型数据库,数据以文档的形式存储在集合中,支持丰富的查询操作和灵活的数据结构。
  2. 数据模型:

    • Redis:Redis支持键值对存储,提供丰富的数据结构如字符串、列表、集合、有序集合和哈希表等。
    • MongoDB:MongoDB是文档型数据库,数据以JSON格式的文档存储,一个文档可以容纳各种类型的数据。
  3. 持久化:

    • Redis:Redis提供多种持久化方式,如快照(snapshotting)和AOF(Append Only File),但主要用于缓存和临时数据存储。
    • MongoDB:MongoDB默认将数据持久化到硬盘上,保证数据的持久性和安全性。
  4. 查询语言:

    • Redis:Redis提供简单的键值对查询和部分数据结构的查询操作。
    • MongoDB:MongoDB支持丰富的查询语言,如基本的查询、范围查询、聚合操作等,支持复杂的查询需求。
  5. 适用场景:

    • Redis适用于需要高性能的缓存和计数器等场景,对读写速度要求很高的场景。
    • MongoDB适用于需要复杂查询和数据分析的场景,适合存储半结构化数据和大量文档数据的场景。
  6. 数据一致性:

    • Redis:Redis是单线程的,可以通过事务和管道操作来确保数据的一致性,但在分布式环境下需要额外的处理来处理数据一致性。
    • MongoDB:MongoDB支持副本集和分片集群,可以提供数据的高可用性和数据一致性,能够满足更高级别的数据一致性需求。
  7. 扩展性:

    • Redis:Redis支持主从复制和集群模式,可以通过横向扩展来提高性能和容量。
    • MongoDB:MongoDB支持副本集和分片集群,并且能够自动处理数据分片和负载均衡,支持海量数据存储和高并发访问。
  8. ACID特性:

    • Redis:Redis是内存数据库,不支持事务的ACID特性。
    • MongoDB:MongoDB支持事务操作,可以确保数据的原子性、一致性、隔离性和持久性,支持复杂的事务操作。

总体来说,Redis更注重性能和速度,适用于需要快速存取数据的场景,而MongoDB更注重数据结构和查询能力,适用于需要复杂查询和数据分析的场景。在选择数据库时,需要根据具体的业务需求和数据特点来进行权衡和选择。

总的来说,Redis更适合作为缓存或会话存储使用,而MongoDB更适合作为存储大量文档型数据和进行复杂查询操作。根据具体的需求和场景选择合适的数据库系统是非常重要的。

Kafka

什么是kafka

Kafka 最初是由 LinkedIn 即领英公司基于 Scala 和 Java 语言开发的分布式消息发布-订阅系统,现已捐献给Apache 软件基金会。其具有高吞吐、低延迟的特性,许多大数据实时流式处理系统比如 Storm、Spark、Flink等都能很好地与之集成。

总的来讲,Kafka 通常具有 3 重角色:

• **存储系统:**通常消息队列会把消息持久化到磁盘,防止消息丢失,保证消息可靠性。Kafka 的消息持久化机制和多副本机制使其能够作为通用数据存储系统来使用。

• **消息系统:**Kafka 和传统的消息队列比如 RabbitMQ、RocketMQ、ActiveMQ 类似,支持流量削峰、服务解耦、异步通信等核心功能。

• **流处理平台:**Kafka 不仅能够与大多数流式计算框架完美整合,并且自身也提供了一个完整的流式处理库,即 Kafka Streaming。Kafka Streaming 提供了类似 Flink 中的窗口、聚合、变换、连接等功能。

一句话概括:**Kafka** 是一个分布式的基于发布**/订阅模式的消息中间件,在业界主要应用于大数据实时流式计算领域,起解耦合和削峰填谷的作用。**


Kafka

kafka的几个特色: 消息系统,具有系统解耦,冗余存储,流量消峰,缓存,可恢复的功能(Mq的作用,解耦,异步削峰) 存储系统,可以把消息持久化到磁盘,保证消息不丢失 特点

  • 快:单个kafka服务每秒可处理数以千计客户端发来的几百MB数据。
  • 可扩展性:一个单一集群可作为一个大数据处理中枢,集中处理各种类型业务
  • 持久化:消息被持久化到磁盘(可处理TB数据级别数据但仍保持极高数据处理效率),并且有备份容错机制
  • 分布式:着眼于大数据领域,支持分布式,集群可处理每秒百万级别消息
  • 实时性:生产出的消息可立即被消费者消费

img

Kafka的组件:

  • topic:消息存放的目录即主题
  • Producer:生产消息到topic的一方,负责创建消息,然后投递到kafka环境中
  • Consumer:订阅topic消费消息的一方,连接到kafka,然后消费消息
  • Broker:Kafka的服务实例就是一个broker,大多数情况下,可以认为broker就是一台kafka服务器。

如下图所示,Producer生产的消息通过网络发送给Kafka cluster,而Consumer从其中消费消息

img

topic:kafka中的消息是以主题为单位进行归类,生产者负责将消息发送到特定的主题topic,消费者负责订阅主题进行消费。 主题是一个逻辑上的概念,它可以分成很多的分区,也就是partitions,一个分区只属于一个主题,很多时候也把分区叫做主题分区,同一个主题下的不同分区的内容是不同,分区在存储层面可以看做是一个可以追加的日志文件,消息在被追加到分区日志文件的时候,会分配一个偏移量offset。offset是消息在分区中的唯一标识,kafka通过offset保证消息在分区中的顺序,offset不跨域分区, kafka保证的是分区有序而不是主题有序 消息发送时都被发送到一个topic,其本质就是一个目录,而topic由是由一些Partition Logs(分区日志)组成,其组织结构如下图所示:

img

主题有4个分区,消息被顺序写入到每个分区文件的末尾,kafka的分区跨域分布在不同的broker上面,也就是一个topic可以横跨多个broker。 我们可以看到,每个Partition中的消息都是有序的,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值。

img

每条消息在发送到kafka之前,会根据分区规则选择存储到哪个具体的分区,如果分区规则设置的合理,所有的消息都可以均匀的分配到不同的分区中。如果一个主题只对应一个文件,那么这个文件所在的机器的IO将会成为这个主题的瓶颈,分区解决了这个问题。在创建主题的时候,可以指定分区的个数,当然也可以在创建完主题之后修改分区的数量,通过增加分区的数量可以进行水平的扩展。 kafka为分区引入了多副本的机制,通过增加副本数量,可以提高容灾能力,同一个分区的不同副本中保存的消息是相同的(同一时刻,副本之间的消息并非完全一致),副本之间是 一主多从的关系,其中leader副本负责处理读写请求,follower副本只负责与leader副本的消息同步,副本处于不同的broker中,当leader副本发送故障,会从follower副本中重新选择新的leader副本对外提供服务。 kafka通过多副本机制,实现了故障自动转移,当kafka集群中的某个broker失效,依然能保证对外提供服务。 。 下图中kafka集群有4个broker,某个主题有3个分区,而且副本因子也是3个,因此每个分区便有了一个leader副本和两个follower 副本。生产者和消费者只与leader副本进行交互,follower副本只负责消息的同步,但是很多时候follower副本的消息会滞后于leader副本。

img

kafka消费者也具备容灾的能力,当消费者使用pull从服务端拉去消息,并且保存了消费的具体offset,当消费者宕机恢复后,会根据之前保存的消费者位置重新进行消费,这样就不会造成消息丢失。 分区中的所有副本统称AR(Assigned Replicas),所有与leader副本保持一定程度同步的副本(包括leader副本)组成ISR(In-Sync Replicas),ISR是AR集合的一个子集。消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本有一定程度的滞后,这里说的一定程度是指可以忍受的范围内,这个参数可以通过配置。与leader副本同步滞后过多的副本(不包含leader副本)组成OSR(Out-of-Snyc-Replicas),也就是AR=ISR+OSR,正常情况下,所有的follower副本都应该与该leader副本保持一定程度的同步,也就是AR=ISR。 leader副本负责维护和跟踪ISR集合中所有follower副本的滞后状态,当follower落后太多或者失效时,leader副本会把它从ISR中删除,如果OSR中的follower副本追上了leader副本,那么leader副本会把它从OSR移到ISR中。默认情况下,在leader副本发送故障,只有在ISR集合中副本才有机会被选中为leader,在OSR集合中的副本没有任何机会。 ISR和HW和LEO也有密切关系,HW是高水位,标识了一个特定的消息的偏移量,消费者只能拉取到这个offset之前的消息

img

它代表一个日志文件的一个分区,这个日志文件分区中有9条消息,第一条消息的offset为0,最后一条消息的offset为8,offset为9的消息用虚线表示,代表下一条待输入的消息的offset,日志文件的HW为6,表示消费者只能拉去到offset为0至5之间的消息,而offset为6的消息对消费者是不可见的。 LEO是log end offset,它标识当前日志文件中下一条待写入消息的offset,上图中的9为LEO位置。分区ISR集合中的每个副本都会维护自身的LEO,而ISR集合中最小的LEO为分区的HW,对消费者而言,只能消费HW之前的消息。 为了更好理解ISR集合,以及HW和LEO之间的关系,通过下图说明

img

假设某个分区的ISR集合中有3个副本,也就是一个leader副本和两个follower副本,此时分区的LEO和HW都是3,消息3和消息4从生产者发出之后先被存入leader副本,在消息写入leader之后,follower副本会发送拉去消息请求。

img

在同步过程中,不同的follower副本同步的效率也不同,如下图

img

在某一时刻follower1完全跟上了leader,但是follower2只同步了消息3,因此leader副本的LEO值为5, follower1的LEO为5,follower2的LEO为4,那么当前的HW为4,所以消费者只能消费offset为0-3的消息。 所有的副本都成功的写入了消息3和消息4,这个分区的Hw和LEO都是5,因此消费者可以消费到offset为0-4之间的消息了。

Broker 和 Controller 的区别

在 Kafka 中,Broker 和 Controller 是不同的概念,它们代表着 Kafka 集群中的不同角色和职责。

  1. Broker(代理):Broker 是 Kafka 集群中的基本组件,指的是运行在实际硬件节点上的 Kafka 服务器实例。每个 Broker 都负责存储和管理一部分数据,接收客户端的生产者和消费者请求,并参与消息的复制和分发。多个 Broker 组成一个 Kafka 集群,共同构成了一个高可用、高性能的分布式消息系统。
  2. Controller(控制器):Controller 是 Kafka 集群中的一个特殊角色,负责协调和管理整个集群的状态,包括分区的分配、副本的调度、故障检测和恢复等。一个 Kafka 集群中只有一个 Controller,它与其他 Broker 区别开来,扮演着集群管理者的角色。Controller 负责监测集群的健康状态,处理节点故障,执行重新平衡操作等。

因此,Broker 和 Controller 是 Kafka 集群中两个不同的概念和角色。Broker 是 Kafka 集群中实际运行的服务器节点,负责存储数据并处理消息传输;而 Controller 是一个特殊的 Kafka 服务器角色,负责管理和协调整个 Kafka 集群的状态和操作。在一个 Kafka 集群中,每个 Broker 都可以扮演普通节点和 Controller 的角色。

简述kafka架构设计是什么样

语义概念

1 broker
Kafka 集群包含一个或多个服务器,服务器节点称为broker。
​
broker存储topic的数据。如果某topic有N个partition,集群有N个broker,那么每个broker存储该topic的一个partition。
​
如果某topic有N个partition,集群有(N+M)个broker,那么其中有N个broker存储该topic的一个partition,剩下的M个broker不存储该topic的partition数据。
​
如果某topic有N个partition,集群中broker数目少于N个,那么一个broker存储该topic的一个或多个partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。
​
2 Topic
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
​
类似于数据库的表名
​
3 Partition
topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个segment文件存储。partition中的数据是有序的,不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。
​
4 Producer
生产者即数据的发布者,该角色将消息发布到Kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partition。
​
5 Consumer
消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。
​
6 Consumer Group
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个CG。topic的消息会复制-给consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
​
7 Leader
每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的partition。
​
8 Follower
Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(ISR)列表中删除,重新创建一个Follower。
​
9 Offset
kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka

KAFKA天生是分布式的,满足AKF的XYZ轴特点,扩展性,可靠性,高性能是没得说

而且,kafka具备自己的特色,比如动态ISR集合,是在强一致性,过半一致性之外的另一个实现手段

Kafka 保持高可靠性

Kafka 保持高可靠性的主要机制和策略包括:

  1. 副本机制(Replication):Kafka 使用副本机制来确保数据的可靠性和容错能力。每个主题的分区会配置多个副本,这些副本会分布在不同的 Broker 上,形成 Leader-Follower 结构。Leader 负责处理所有的读写请求,Followers 负责与 Leader 保持数据同步。当 Leader 失效时,Kafka 会从 Followers 中选举一个新的 Leader,确保数据的连续性和可用性。
  2. ISR(In-Sync Replica)机制:Kafka 使用 ISR 机制来处理副本间的同步问题。ISR 是与 Leader 副本保持同步的一组副本,只有 ISR 中的副本才会参与数据的读写操作。如果某个副本无法与 Leader 保持同步,该副本会被剔除出 ISR,直到恢复正常同步。
  3. 故障检测和自愈:Kafka 实时监控 Broker 节点的健康状态,包括 Leader 和 ISR 的状态,以及副本的同步情况。一旦检测到节点的异常,Kafka 会快速做出响应,比如进行副本重新选择、故障转移等操作,确保集群在故障发生时能够快速自愈。
  4. 复制数据和数据恢复:Kafka 在多个 Broker 之间复制数据,确保数据的备份和冗余。即使某个 Broker 出现故障,也可以通过其他副本上的数据来进行数据恢复,保证数据的持久性和可靠性。
  5. ZooKeeper 的协调和选举:Kafka 使用 ZooKeeper 来实现分布式协调和一致性,包括 Controller 选举、重新选举、节点注册和状态管理等。ZooKeeper 保证了 Kafka 集群中各节点之间的正确协同操作,确保集群的稳定性和可靠性。

通过以上机制和策略,Kafka 能够保持高可靠性,即使在节点故障、网络分区等异常情况下,依然能够保证数据的完整性和可用性,从而满足高性能、高吞吐量的消息传输和数据处理需求。

Kafka中的ISR、AR又代表什么?

在 Apache Kafka 中,ISR 和 AR 分别代表以下概念:

分区中的所有副本统称为AR(Assigned Replicas)。所有与leader副本保持一定程度同步的副本(包括leader副本在内)组成ISR(In-Sync Replicas),ISR集合是AR集合中的一个子集。

在 Kafka 中,ISR 和 AR 是数据复制和高可用性的重要概念,对确保消息传递的可靠性和完整性起着关键作用。

消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本而言会有一定程度的滞后。前面所说的“一定程度的同步”是指可忍受的滞后范围,这个范围可以通过参数进行配置。与leader副本同步滞后过多的副本(不包括leader副本)组成OSR(Out-of-Sync Replicas),由此可见,AR=ISR+OSR。

Kafka生产者设计



Kafka消费者设计

Kafka中有那些地方需要选举?这些地方的选举策略又有哪些?

partition leader(ISR),controller(先到先得)

失效副本是指什么?有那些应对措施?

不能及时与leader同步,暂时踢出ISR,等其追上leader之后再重新加入

Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失

broker在故障后的消息丢失

消费者在消费过程中的消息丢失

Kafka 是一种高可靠的消息系统,但在特定情况下仍可能发生消息丢失的情况。以下是一些可能导致 Kafka 消息丢失的场景:

  1. 生产者发送消息失败:如果生产者在发送消息时发生错误(比如网络故障、生产者应用程序崩溃等),导致消息未成功发送到 Kafka 服务器,这可能会导致消息丢失。
  2. 不可恢复的副本丢失:如果 Kafka 中某个分区的所有副本都发生了不可恢复的故障,且没有足够的副本进行数据恢复,那么该分区的消息可能会永久丢失。
  3. 消费者提交偏移量失败:如果消费者在消费消息后没有正确提交偏移量,或者提交偏移量时发生错误,那么在消费者重启或重新加入消费组时可能会出现消息重复消费或消息丢失的情况。
  4. 配置错误导致消息被丢弃:如果 Kafka 的配置不当,比如设置了消息过期时间、缓冲区大小不足、消息压缩设置不正确等,可能会导致消息被系统自动丢弃。
  5. 磁盘故障:如果 Kafka 的数据存储磁盘发生故障,可能会导致部分或全部消息数据丢失。

为了尽量避免消息丢失,可以采取一些措施,如:

  • 配置正确的副本数量和副本策略,以确保数据的高可靠性。
  • 生产者设置消息的可靠性保证级别,如设置为“all”以确保消息在发送到服务器之后被持久化。
  • 消费者正确提交偏移量,确保消费者状态跟踪正确。
  • 监控 Kafka 集群状态,及时发现并处理潜在的故障问题。

ACK机制

ack有3个可选值,分别是1,0,-1。

ack=0:生产者在生产过程中的消息丢失

简单来说就是,producer发送一次就不再发送了,不管是否发送成功。

ack=1:broker在故障后的消息丢失

简单来说就是,producer只要收到一个分区副本成功写入的通知就认为推送消息成功了。这里有一个地方需要注意,这个副本必须是leader副本。只有leader副本成功写入了,producer才会认为消息发送成功。

注意,ack的默认值就是1。这个默认值其实就是吞吐量与可靠性的一个折中方案。生产上我们可以根据实际情况进行调整,比如如果你要追求高吞吐量,那么就要放弃可靠性。

ack=-1:生产侧和存储侧不会丢失数据

简单来说就是,producer只有收到分区内所有副本的成功写入的通知才认为推送消息成功了。

Offset机制

kafka消费者的三种消费语义

at-most-once:最多一次,可能丢数据

at-least-once:最少一次,可能重复消费数据

exact-once message:精确一次


Kafka是pull?push?以及优劣势分析

Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。

Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。

一些消息系统比如Scribe和Apache Flume采用了push模式,将消息推送到下游的consumer。

这样做有好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。

消息系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。

最终Kafka还是选取了传统的pull模式。

Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。

Push模式必须在不知道下游consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。

如果为了避免consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。

Pull模式下,consumer就可以根据自己的消费能力去决定这些策略。

Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到达。

为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发


Kafka中zk的作用是什么

Zookeeper是分布式协调,注意它不是数据库

在 Kafka 集群中,ZooKeeper 扮演着重要的角色,用于存储集群的元数据信息、协调分布式节点之间的一致性,并提供可靠的选举机制。因此,在 Kafka 中的重新选举过程通常会通过 ZooKeeper 来实现。

kafka中使用了zookeeper的分布式锁和分布式配置及统一命名的分布式协调解决方案

在kafka的broker集群中的controller的选择,是通过zk的临时节点争抢获得的

brokerID等如果自增的话也是通过zk的节点version实现的全局唯一

kafka中broker中的状态数据也是存储在zk中,不过这里要注意,zk不是数据库,所以存储的属于元数据

而,新旧版本变化中,就把曾经的offset从zk中迁移出了zk

在 Kafka 中,通常需要进行选举的地方主要包括:

  1. Controller 选举:Kafka 集群中的 Controller 是负责协调和管理整个集群的节点,包括分区的分配、副本的调度、故障检测和恢复等。当当前的 Controller 节点发生故障或失效时,会触发 Controller 选举,重新选举一个新的 Controller 节点来接管集群管理工作。
  2. 分区的 Leader 选举:每个分区在 Kafka 中都会有一个 Leader 副本,负责处理读写请求。当 Leader 副本发生故障或失效时,会触发分区的 Leader 选举,选择一个新的 Leader 副本来接替原 Leader 的工作。
  3. ISR(In-Sync Replica)集合的选举:在 Kafka 中,每个分区的副本集合中包含了 ISR(In-Sync Replica)集合,即处于同步状态的副本。当 Leader 副本无法和 ISR 集合中的大多数副本保持同步时,需要进行 ISR 集合的选举,选择新的同步副本集合继续保证数据的一致性。

Kafka 使用 ZooKeeper 来进行选举算法的协调和管理。选举策略主要包括以下几种:

  1. Controller 选举策略:Kafka 通过 ZooKeeper 原生提供的顺序节点和临时节点特性来实现 Controller 的选举。当多个节点竞争成为 Controller 时,通过 ZooKeeper 的顺序节点机制来保证选举的顺序性和唯一性。
  2. Leader 选举策略:Kafka 中的 Leader 选举采用的是基于副本的选举策略,即在副本集合中选择一个最合适的副本作为新的 Leader。Kafka 使用 ISR 集合和 LEO(Log End Offset)等信息来选择合适的 Leader。
  3. ISR 集合的选举策略:ISR 集合的选举策略主要包括了最佳副本(优先选择与 Leader 最接近的副本)、副本同步延迟等指标来选择新的同步副本。

这些选举策略通过 ZooKeeper 实现了分布式协调和一致性,确保 Kafka 集群能够在节点故障或动态变化时正常运行并保持数据一致性。


Kafka中高性能如何保障

首先,性能的最大瓶颈依然是IO,这个是不能逾越的鸿沟

虽然,broker在持久化数据的时候已经最大努力的使用了磁盘的顺序读写

更进一步的性能优化是零拷贝的使用,也就是从磁盘日志到消费者客户端的数据传递,因为kafka是mq,对于msg不具备加工处理,所以得以实现

然后就是大多数分布式系统一样,总要做tradeoff,在速度与可用性/可靠性中挣扎

ACK的0,1,-1级别就是在性能和可靠中权衡

Kafka中是怎么体现消息顺序性的?

每个分区内,每条消息都有一个offset,故只能保证分区内有序。

在 Kafka 中,消息的顺序性是通过分区(Partition)以及分区内消息的顺序保证来实现的:

  1. 分区(Partition):

    • Kafka 中的每个主题(Topic)都可以划分为多个分区,每个分区都是一个有序的消息队列。
    • 生产者写入消息时,根据分区策略选择将消息写入哪个分区,不同分区之间的消息是并发写入的,但同一个分区内的消息是有序的。
  2. 分区内消息顺序保证:

    • 在同一个分区内,消息的顺序是保证的,即消息按照写入的顺序依次排列。
    • Kafka 保证针对同一个分区内的消息,无论是生产者写入还是消费者消费,消息都是有序的。

通过上述机制,Kafka 在分区级别实现了消息的顺序性保证。如果业务对消息的顺序性要求非常高,可以将相关消息发送到同一个分区中来确保消息的顺序不被打乱。值得注意的是,要实现消息的全局顺序性,即跨分区的消息也要保持顺序性,可能需要在应用层进行额外的处理和控制。

Kafka如何实现高性能io?

img

一、批量消息

虽然我们是一笔一笔消息的发送给kafka,但是kafka并不是立即就发送出去的,而是先将消息缓存起来,再一批一起等个合适的时机一起发送出去。

消费端收到消息不是一个个拆出来一个个处理,而是直接作为一批一起处理,一起读写磁盘io,一起复制,这都大大加快了io的速度。

二、顺序读写

磁盘io分为顺序读写和随机读写,而顺序读写的速度要比随机读写的速度要快得多。所以kafka采用顺序读写的机制,收到消息的时候在日志文件顺序中写下多条消息,发送的时候又是顺序的读出多条消息。

三、page cache

page cache也就是os cache,只有page cache中没有才会到磁盘中进行读写,这就减少了一些与磁盘io的次数。

四、零拷贝

如果要读写的消息不在page cache中,那么就会发生多次拷贝。从磁盘到page cache,再从page cache到用户缓存,再从用户缓存到socket缓存,最后再发送。

为了减少不必要的拷贝,kafka通过零拷贝技术,直接将page cache中的数据复制到socket缓存中。

“消费组中的消费者个数如果超过topic的分区,那么就会有消费者消费不到数据”这句话是否正确

这句话是基本正确的。在 Kafka 中,每个消费者组(Consumer Group)可以有多个消费者实例,每个消费者实例可以订阅一个或多个分区。消费者组内的消费者个数如果超过了订阅主题的分区数量,会导致部分消费者无法消费数据。

具体来说,如果消费者组中的消费者个数大于订阅主题的分区数量,超出的消费者将无法获得分配给它们的分区,因为每个分区只能分配给一个消费者实例来消费。这样就会导致超出分区数量的消费者无法消费任何数据,从而产生“消费者消费不到数据”的现象。

为了充分利用 Kafka 的并行处理能力和保证数据的完整性,通常建议消费者组内的消费者个数不要超过订阅主题的分区数量,以确保每个分区都有对应的消费者来消费数据。如果希望增加消费者实例以提高消费速度,可以考虑增加主题的分区数量来匹配消费者组内的消费者个数,以实现更好的负载均衡和性能表现。


kafka的rebalance机制是什么

rebalance时read和wite

消费者分区分配策略

Range 范围分区(默认的)

RoundRobin 轮询分区

Sticky策略

触发 Rebalance 的时机

Rebalance 的触发条件有3个。

  • consumer group组成员个数发生变化。例如有新的 consumer 实例加入该消费组或者离开组。
  • 订阅的 Topic 个数发生变化。
  • 订阅 Topic 的分区数发生变化。
  • consumer消费超时

Coordinator协调过程

消费者如何发现协调者

消费者如何确定分配策略

如果需要再均衡分配策略的影响

RabbitMQ 保证消息不丢失

RabbitMQ 通过持久化和确认机制来保证消息不丢失。

以下是 RabbitMQ 保证消息不丢失的关键方式:

  1. 消息持久化:在发送消息时,可以设置消息的 delivery mode 为 2(即 PERSISTENT),使消息变为持久化消息。持久化消息会被写入磁盘,即使 RabbitMQ 服务器重启,消息也能够恢复。但需要注意,持久化消息会导致性能略有降低,因为需要将消息写入磁盘。
  2. 生产者确认机制:在生产者发送消息到队列时,可以开启生产者确认机制(Publisher Confirms)。生产者发送消息后,等待 RabbitMQ 发送确认信息给生产者,告知消息是否已经成功写入队列。如果收到确认信息,生产者可以确定消息已经安全地发送到了队列中。如果未收到确认信息,则可以重发消息,确保消息不丢失。
  3. 消费者确认机制:在消费者从队列中获取消息并处理后,可以开启消费者确认机制(Consumer Acknowledgements)。消费者在处理消息后,向 RabbitMQ 确认消息已经处理完毕。当 RabbitMQ 收到确认后,会将消息标记为已消费,然后删除消息。如果消费者未发送确认信息,消息将会被重新投递到队列中。
  4. 高可用和镜像队列:RabbitMQ 支持镜像队列(Mirrored Queues)功能,可以将队列的消息在多个节点间同步,实现高可用的队列。当某个节点故障时,消息依然可以从其他节点中获取。镜像队列可以提高系统的可用性,防止消息丢失。
  5. 确保消息的顺序:RabbitMQ 本身是支持消息顺序的,在单个队列中,消息的顺序是得到保证的。但在分布式系统中,默认情况下无法保证多个队列之间消息的顺序,可能会导致发送和接收消息的顺序不一致。需要根据业务需求,利用单队列保证消息顺序或采用其他机制来解决。

综上所述,通过消息持久化、生产者和消费者确认机制、高可用和镜像队列等方式,RabbitMQ 可以有效地保证消息不丢失。在使用过程中,根据实际业务场景和需求选择合适的策略和配置,以确保消息传递的可靠性和完整性。

RabbitMQ的架构设计是什么样的

是AMQP的实现,相关概念语义

Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输

Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

Queue:消息的载体,每个消息都会被投到一个或多个队列。

Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.

Routing Key:路由关键字,exchange根据这个关键字进行消息投递。

vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。

Producer:消息生产者,就是投递消息的程序.

Consumer:消息消费者,就是接受消息的程序.

Channel:消息通道,在客户端的每个连接里,可建立多个channel.

核心概念

在mq领域中,producer将msg发送到queue,然后consumer通过消费queue完成P.C解耦

kafka是由producer决定msg发送到那个queue

rabbitmq是由Exchange决定msg应该怎么样发送到目标queue,这就是binding及对应的策略

Exchange

Direct Exchange:直接匹配,通过Exchange名称+RountingKey来发送与接收消息. Fanout Exchange:广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略Routing Key. Topic Exchange:主题匹配订阅,这里的主题指的是RoutingKey,RoutingKey可以采用通配符,如:*或#,RoutingKey命名采用.来分隔多个词,只有消息这将队列绑定到该路由器且指定RoutingKey符合匹配规则时才能收到消息; Headers Exchange:消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头:(如:x-mactch=all或者x_match=any),只有请求头与消息头匹配,才能接收消息,忽略RoutingKey. 默认的exchange:如果用空字符串去声明一个exchange,那么系统就会使用”amq.direct”这个exchange,我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去

复杂与精简

在众多的MQ中间件中,首先学习Rabbitmq的时候,就理解他是一个单机的mq组件,为了系统的解耦,可以自己在业务层面做AKF

其在内卷能力做的非常出色,这得益于AMQP,也就是消息的传递形式、复杂度有exchange和queue的binding实现,这,对于P.C有很大的帮助


RabbitMQ如何确保消息发送和消息接收

消息发送确认

1 ConfirmCallback方法

ConfirmCallback 是一个回调接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中。

2 ReturnCallback方法

通过实现 ReturnCallback 接口,启动消息失败返回,此接口是在交换器路由不到队列时触发回调,该方法可以不使用,因为交换器和队列是在代码里绑定的,如果消息成功投递到 Broker 后几乎不存在绑定队列失败,除非你代码写错了。

消息接收确认

RabbitMQ 消息确认机制(ACK)默认是自动确认的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,假如你用回滚了也只是保证了数据的一致性,但是消息还是丢了,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。

消息确认模式有:

AcknowledgeMode.NONE:自动确认。 AcknowledgeMode.AUTO:根据情况确认。 AcknowledgeMode.MANUAL:手动确认。 消费者收到消息后,手动调用 Basic.Ack 或 Basic.Nack 或 Basic.Reject 后,RabbitMQ 收到这些消息后,才认为本次投递完成。

Basic.Ack 命令:用于确认当前消息。 Basic.Nack 命令:用于否定当前消息(注意:这是AMQP 0-9-1的RabbitMQ扩展) 。 Basic.Reject 命令:用于拒绝当前消息。 Nack,Reject后都有能力要求是否requeue消息或者进入死信队列


RabbitMQ事务消息原理是什么

事务V.S确认

确认是对一件事的确认

事务是对批量的确认

增删改查中,事务是对于增删改的保证

发送方事务

开启事务,发送多条数据,事务提交或回滚是原子的,要么都提交,要么都回滚

消费方事务

消费方是读取行为,那么事务体现在哪里呢

rabbitmq的消费行为会触发queue中msg的是否删除、是否重新放回队列等行为,类增删改

所以,消费方的ack是要手动提交的,且最终确定以事务的提交和回滚决定


RabbitMQ死信队列、延时队列分别是什么

死信队列

DLX(Dead Letter Exchange),死信交换器

当队列中的消息被拒绝、或者过期会变成死信,死信可以被重新发布到另一个交换器,这个交换器就是DLX,与DLX绑定的队列称为死信队列。 造成死信的原因:

  • 信息被拒绝
  • 信息超时
  • 超过了队列的最大长度
过期消息:
在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。
​
队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒
​
单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒

延迟队列

延迟队列存储的是延迟消息

延迟消息指的是,当消息被发发布出去之后,并不立即投递给消费者,而是在指定时间之后投递。如:

在订单系统中,订单有30秒的付款时间,在订单超时之后在投递给消费者处理超时订单。

rabbitMq没有直接支持延迟队列,可以通过死信队列实现。

在死信队列中,可以为普通交换器绑定多个消息队列,假设绑定过期时间为5分钟,10分钟和30分钟,3个消息队列,然后为每个消息队列设置DLX,为每个DLX关联一个死信队列。

当消息过期之后,被转存到对应的死信队列中,然后投递给指定的消费者消费。

ZOOKeeper


ZooKeeper数据模型

ZooKeeper的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper树中的每个节点被称为—Znode。

和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。但也有不同之处:

    Znode兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子Znode。用户对Znode具有增、删、改、查等操作(权限允许的情况下)
    
    Znode具有原子性操作,读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作
    
    Znode存储数据大小有限制。ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,当时常规使用中应该远小于此值
    
    Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。

节点类型

Znode有两种,分别为临时节点和永久节点。 节点的类型在创建时即被确定,并且不能改变。 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。

永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。    Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10位数字,没有数值的数位用0补充,例如“0000000001”)

在ZooKeeper中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。

1、持久节点(PERSISTENT)

该数据节点别创建后,就会一直存在于ZooKeeper服务器上,直到有删除操作来主动删除该节点。

2、持久顺序节点(PERSISTENT_SEQUENTIAL)

持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。

3、临时节点(EPHEMERAL)

临时节点的生命周期和客户端的回话绑定在一起,如果客户端会话失效,那么这个节点就会被自动地清理掉。

ZooKeeper规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。

4、临时顺序节点(EPHEMERAL_SEQUENTIAL)

zookeeper提供了什么

Apache ZooKeeper 是一个开源的分布式协调服务,它提供了以下主要功能:

  1. 分布式配置管理:ZooKeeper 可以用于集中管理分布式系统的配置信息。它提供了一个共享的、高可用的目录结构,可以在其中存储和更新配置数据,并且能够实时通知客户端配置的变化。

  2. 命名服务:ZooKeeper 的目录结构可以作为一个命名空间,用于存储和查询分布式系统中的节点信息。使用目录结构,开发人员可以在分布式环境中方便地注册、查找和发现服务。

  3. 分布式锁:ZooKeeper 提供了分布式锁的实现,可以用于协调多个节点之间的互斥访问。通过在 ZooKeeper 上创建临时有序节点,可以实现分布式锁的获取和释放。

  4. 选举机制:ZooKeeper 提供了一种分布式选举机制,可以帮助协调多个节点之间的领导者选举过程。通过 ZooKeeper 的顺序节点机制,节点可以按序创建和监听节点,从而实现选举算法。

  5. 时间管理:ZooKeeper 提供了一个基于时间的机制,可以为分布式系统提供高可用、实时可信的时钟。它使用了 Paxos 算法,保证所有的写操作是按照全局的顺序进行的。

    四种类型的数据节点 Znode

在ZooKeeper中,有四种类型的数据节点(Znode):

  1. 持久节点(Persistent Znode):持久节点创建后就一直存在于ZooKeeper中,除非显式删除。持久节点适合存储重要的配置信息、状态数据等,它们不会因为客户端的断开或重新连接而被删除。
  2. 临时节点(Ephemeral Znode):临时节点的生命周期与创建它的客户端会话相关联。当与创建临时节点的客户端会话断开时,该节点会被自动删除。临时节点通常用于表示临时状态、临时任务等,例如在线用户、临时服务实例等。
  3. 有序节点(Sequential Znode):有序节点是在创建节点时自动追加一段唯一递增的序列号。有序节点可用于实现全局唯一的标识、顺序访问等场景。持久节点、临时节点都可以是有序节点。
  4. 持久顺序节点(Persistent Sequential Znode):持久顺序节点是持久节点的有序版本,它会在创建节点时自动追加序列号。与持久节点类似,持久顺序节点会一直存在于ZooKeeper中,除非显式删除。

通过这四种节点类型的组合,可以构建出复杂的数据结构和场景,实现节点的注册、监听、查找等分布式协调任务。


Zookeeper watch机制是什么

ZooKeeper是用来协调(同步)分布式进程的服务,提供了一个简单高性能的协调内核,用户可以在此之上构建更多复杂的分布式协调功能。

多个分布式进程通过ZooKeeper提供的API来操作共享的ZooKeeper内存数据对象ZNode来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型,与Java的多线程并发模型一致,他们的线程或进程都是”共享式内存通信“。

Java没有直接提供某种响应式通知接口来监控某个对象状态的变化,只能要么浪费CPU时间毫无响应式的轮询重试,或基于Java提供的某种主动通知(Notif)机制(内置队列)来响应状态变化,但这种机制是需要循环阻塞调用。

而ZooKeeper实现这些分布式进程的状态(ZNode的Data、Children)共享时,基于性能的考虑采用了类似的异步非阻塞的主动通知模式即Watch机制,使得分布式进程之间的“共享状态通信”更加实时高效,其实这也是ZooKeeper的主要任务决定的—协调。Consul虽然也实现了Watch机制,但它是阻塞的长轮询。

ZooKeeper的Watch特性

  1. Watch是一次性的,每次都需要重新注册,并且客户端在会话异常结束时不会收到任何通知,而快速重连接时仍不影响接收通知。
  2. Watch的回调执行都是顺序执行的,并且客户端在没有收到关注数据的变化事件通知之前是不会看到最新的数据,另外需要注意不要在Watch回调逻辑中阻塞整个客户端的Watch回调
  3. Watch是轻量级的,WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径。ZooKeeper服务端只会通知客户端发生了什么,并不会告诉具体内容。

Zookeeper状态

Disconnected:客户端是断开连接的状态,不能连接服务集合中的任意一个 SyncConnected:客户端是连接状态,连接其中的一个服务 AuthFailed:鉴权失败 ConnectedReadOnly:客户端连接只读的服务器 SaslAuthenticated:SASL认证 Expired:服务器已经过期了该客户端的Session

Zookeeper事件类型

None:无 NodeCreated:节点创建 NodeDeleted:节点删除 NodeDataChanged:节点数据改变 NodeChildrenChanged:子节点改变(添加/删除)

ZAB(ZooKeeper Atomic Broadcast)协议

ZAB(ZooKeeper Atomic Broadcast)协议是Apache ZooKeeper使用的一种原子广播协议,用于实现分布式一致性。ZooKeeper是一个分布式协调服务,它使用ZAB协议来保证分布式环境中的数据一致性和可靠性。

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

per内存数据对象ZNode来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型,与Java的多线程并发模型一致,他们的线程或进程都是”共享式内存通信“。

Java没有直接提供某种响应式通知接口来监控某个对象状态的变化,只能要么浪费CPU时间毫无响应式的轮询重试,或基于Java提供的某种主动通知(Notif)机制(内置队列)来响应状态变化,但这种机制是需要循环阻塞调用。

而ZooKeeper实现这些分布式进程的状态(ZNode的Data、Children)共享时,基于性能的考虑采用了类似的异步非阻塞的主动通知模式即Watch机制,使得分布式进程之间的“共享状态通信”更加实时高效,其实这也是ZooKeeper的主要任务决定的—协调。Consul虽然也实现了Watch机制,但它是阻塞的长轮询。

ZooKeeper的Watch特性

  1. Watch是一次性的,每次都需要重新注册,并且客户端在会话异常结束时不会收到任何通知,而快速重连接时仍不影响接收通知。
  2. Watch的回调执行都是顺序执行的,并且客户端在没有收到关注数据的变化事件通知之前是不会看到最新的数据,另外需要注意不要在Watch回调逻辑中阻塞整个客户端的Watch回调
  3. Watch是轻量级的,WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径。ZooKeeper服务端只会通知客户端发生了什么,并不会告诉具体内容。

Zookeeper状态

Disconnected:客户端是断开连接的状态,不能连接服务集合中的任意一个 SyncConnected:客户端是连接状态,连接其中的一个服务 AuthFailed:鉴权失败 ConnectedReadOnly:客户端连接只读的服务器 SaslAuthenticated:SASL认证 Expired:服务器已经过期了该客户端的Session

Zookeeper事件类型

None:无 NodeCreated:节点创建 NodeDeleted:节点删除 NodeDataChanged:节点数据改变 NodeChildrenChanged:子节点改变(添加/删除)

ZAB(ZooKeeper Atomic Broadcast)协议

ZAB(ZooKeeper Atomic Broadcast)协议是Apache ZooKeeper使用的一种原子广播协议,用于实现分布式一致性。ZooKeeper是一个分布式协调服务,它使用ZAB协议来保证分布式环境中的数据一致性和可靠性。

[外链图片转存中…(img-yq3ktffo-1715523751691)]
[外链图片转存中…(img-PsOHOtiB-1715523751691)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐