0


面试笔记系列六之redis+kafka+zookeeper基础知识点整理及常见面试题

Redis

redis 为什么那么快

Redis 的读写速度快主要归功于以下几个因素:

  1. 内存存储: Redis 是基于内存存储的数据库系统,数据完全存储在内存中,这使得数据的读写速度非常快,因为内存的读写速度通常远远快于磁盘。

  2. 单线程模型: Redis 使用单线程模型来处理客户端请求,这意味着它在任何时候都只有一个线程在处理请求,避免了多线程的上下文切换和锁竞争带来的开销。单线程模型简化了并发控制,降低了系统的复杂度,使得 Redis 在处理大量并发请求时依然能够保持高效率。

  3. 非阻塞 I/O: Redis 使用非阻塞的 I/O 处理方式,可以在一个线程中处理多个并发的网络连接,通过事件轮询机制实现高效的 I/O 处理。这使得 Redis 能够充分利用系统资源,提高了并发处理能力。

  4. 优化的数据结构: Redis 内置了丰富的数据结构,如字符串、列表、哈希表、集合和有序集合等,这些数据结构在设计上都经过了优化,能够高效地支持各种数据操作,从而提高了读写的速度。

  5. 持久化策略: Redis 支持多种持久化策略,如快照和日志追加(AOF),通过将内存中的数据定期或实时地写入到磁盘中,保证数据的持久性,同时尽量减少对性能的影响。

综合以上因素,Redis 能够实现快速的读写操作,使其成为许多应用场景下的首选缓存和数据存储解决方案。

redis持久化机制:RDB和AOF

Redis 持久化

Redis 提供了不同级别的持久化方式:

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
  • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
  • 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
  • 最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:#### RDB的优点
  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心(可能加密),非常适用于灾难恢复.
  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.#### RDB的缺点
  • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
  • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.#### AOF 优点
  • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。#### AOF 缺点
  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。#### 4.X版本的整合策略在AOF重写策略上做了优化在重写AOF文件时,4.x版本以前是把内存数据集的操作指令落地,而新版本是把内存的数据集以rdb的形式落地这样重写后的AOF依然追加的是日志,但是,在恢复的时候是先rdb再增量的日志,性能更优秀

Redis做分布式锁用什么命令?

SETNX 格式:setnx key value 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作,操作失败。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

加锁:set key value nx ex 10s

释放锁:delete key

Redis做分布式锁死锁有哪些情况,如何解决?

情况1:加锁,没有释放锁。需要加释放锁的操作。比如delete key。

情况2:加锁后,程序还没有执行释放锁,程序挂了。需要用的key的过期机制。

Redis如何做分布式锁?

假设有两个服务A、B都希望获得锁,执行过程大致如下:

Step1: 服务A为了获得锁,向Redis发起如下命令: SET productId:lock 0xx9p03001 NX EX 30000 其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一,“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"EX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。

Step2: 服务B为了获得锁,向Redis发起同样的命令: SET productId:lock 0000111 NX EX 30000 由于Redis内已经存在同名key,且并未过期,因此命令执行失败,服务B未能获得锁。服务B进入循环请求状态,比如每隔1秒钟(自行设置)向Redis发送请求,直到执行成功并获得锁。

Step3: 服务A的业务代码执行时长超过了30秒,导致key超时,因此Redis自动删除了key。此时服务B再次发送命令执行成功,假设本次请求中设置的value值为0000222。此时需要在服务A中对key进行续期,watch dog。

Step4: 服务A执行完毕,为了释放锁,服务A会主动向Redis发起删除key的请求。注意: 在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。比如当前场景下,Redis中的锁早就不是服务A持有的那一把了,而是由服务2创建,如果贸然使用服务A持有的key来删除锁,则会误将服务2的锁释放掉。此外,由于删除锁时涉及到一系列判断逻辑,因此一般使用lua脚本,具体如下:

if redis.call("get", KEYS[1])==ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Redis作为异步队列及延时队列

Redis 可以被用作异步队列(Async Queue)和延时队列(Delayed Queue),提供了可靠且高效的消息队列解决方案。下面分别说明 Redis 如何作为异步队列和延时队列:

1. 异步队列(Async Queue):

使用 Redis 作为异步队列时,通常采用 List 数据结构来实现。生产者向 List 中 Push 消息,而消费者则通过 Pop 或者 Blocking Pop 的方式来获取消息,实现生产者和消费者之间的解耦和异步处理。

  • 生产者(Producer):

    1. 生产者将任务或消息 Push 到 Redis 的 List 中,作为队列的尾部。
    2. 生产者可以根据需要向队列中不断 Push 新的消息。
  • 消费者(Consumer):

    1. 消费者通过 Pop 命令或 BRPOP(Blocking Pop)命令从队列的头部获取消息。
    2. 消费者处理完消息后,可以继续获取下一个消息。

通过 Redis List 实现的异步队列,生产者和消费者之间实现了解耦,可以提高系统整体的可靠性和性能。

2. 延时队列(Delayed Queue):

使用 Redis 作为延时队列时,通常需要借助 Redis 的 Sorted Set 数据结构和一些定时任务处理的机制来实现延时消息的投递和消费。

  • 生产者(Producer):

    1. 生产者将消息 Push 到 Redis 的 Sorted Set 中,将消息的到期时间作为 Score。
    2. 消息即时加入 Sorted Set 中,但在到达到期时间之前不会被取出。
  • 消费者(Consumer):

    1. 消费者定时轮询 Sorted Set,获取 Score 小于当前时间的消息。
    2. 消费者根据到期时间取出消息,开始处理延时消息。

通过 Redis 的 Sorted Set 结构,延时队列可以实现消息的有序存储和自动过期,消费者可以按照一定规则获取到期的消息,实现延时消息的可靠处理。

总之,Redis 作为异步队列和延时队列的解决方案,通过合理地使用 List 和 Sorted Set 数据结构,结合生产者和消费者的配合,可以实现高效、可靠的消息队列系统,提高系统的性能和可靠性。

redis的过期键有哪些删除策略

过期精度

在 Redis 2.4 及以前版本,过期期时间可能不是十分准确,有0-1秒的误差。

从 Redis 2.6 起,过期时间误差缩小到0-1毫秒。

过期和持久

Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的。

要想过期的工作处理好,计算机必须采用稳定的时间。 如果你将RDB文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的 keys装载时就会过期)。

即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。

Redis如何淘汰过期的keys

Redis keys过期有两种方式:被动和主动方式。

当一些客户端尝试访问它时,key会被发现并主动的过期。

当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。

具体就是Redis每秒10次做的事情:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步奏1.

这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。

redis缓存如何回收

回收策略

noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键

如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。

一般的经验规则:

  • 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
  • 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
  • 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。

allkeys-lruvolatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。

为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。

在复制AOF文件时如何处理过期

为了获得正确的行为而不牺牲一致性,当一个key过期,

DEL

将会随着AOF文字一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。

然而,当slaves连接到master时,不会独立过期keys(会等到master执行DEL命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master。

Redis 红锁

Redis 红锁(Redis RedLock)是一种分布式锁解决方案,主要用于在分布式环境下实现互斥访问保护,防止多个客户端同时修改共享资源而引发的数据一致性问题。

红锁的概念是由 Redis 的作者 Antirez 在他的博客中提出的,它通过对多个独立的 Redis 实例进行加锁操作来提供更高的可靠性和安全性。

下面是红锁的基本原理:

  1. 选择 N 个 Redis 实例:为了提高可靠性,红锁使用了多个独立的 Redis 实例。实例之间可以在不同的主机或集群中部署。
  2. 加锁操作:- 客户端根据要锁定的资源生成一个唯一的标识(例如资源的名称或 ID)。- 对于每个 Redis 实例,客户端尝试使用 SETNX 命令来将一个带有唯一标识的锁键设置为一个特定的值,同时设置一个过期时间。- 如果 SETNX 操作成功,即说明该实例成功获取到了锁,客户端记录成功获取锁的实例数量。- 如果 SETNX 操作失败,即说明该实例的锁已经被其他客户端持有,客户端尝试获取其他实例的锁。
  3. 锁的有效性:- 客户端需要计算获取锁操作所花费的时间,确保在获取锁的过程中没有超过最大有效性时间的一半。这是为了避免在获取到锁后,锁突然失效时出现不一致的情况。- 续约过程 为了防止锁在某个进程意外崩溃或网络故障导致无法释放,Redis红锁通过续约机制来保证锁的有效性。 续约过程包含以下几个步骤 1.获取当前时间戳(timestamp)。 2.使用比较并交换(compare-and-swap)的方式更新锁的过期时间。 3.检查锁是否已经超时。如果超时,则继续下一步;否则,等待一段时间再重试
  4. 解锁操作:- 客户端对所有成功获取锁的实例,使用 DEL 命令删除锁键,释放锁资源。
  5. 判断锁的获取情况:- 客户端统计成功删除锁键的实例数量,如果数量超过半数(即大多数实例),则认为客户端成功获取了锁。

通过使用红锁,客户端可以通过多个独立的 Redis 实例来实现分布式锁的高可靠性和安全性。但需要注意,红锁并不是一个由 Redis 官方提供的内置实现,而是一个由社区推广的解决方案,需要根据具体的业务场景和需求进行实现和安全性评估。

扩展

绝对时间点过期

相对时间点过期

时钟轮算法


redis线程模型有哪些,单线程为什么快

IO模型维度的特征

IO模型使用了多路复用器,在linux系统中使用的是EPOLL

类似netty的BOSS,WORKER使用一个EventLoopGroup(threads=1)

单线程的Reactor模型,每次循环取socket中的命令然后逐一操作,可以保证socket中的指令是按顺序的,不保证不同的socket也就是客户端的命令的顺序性

命令操作在单线程中顺序操作,没有多线程的困扰不需要锁的复杂度,在操作数据上相对来说是原子性质的

架构设计模型

自身的内存存储数据,读写操作不设计磁盘IO

redis除了提供了Value具备类型还为每种类型实现了一些操作命令

实现了计算向数据移动,而非数据想计算移动,这样在IO的成本上有一定的优势

且在数据结构类型上,丰富了一些统计类属性,读写操作中,写操作会O(1)负载度更新length类属性,使得读操作也是O(1)的

说一下你知道的redis高可用方案

主从 哨兵模式:

sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能:

集群监控:负责监控 redis master 和 slave 进程是否正常工作。

消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。

故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。

配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布 式选举 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的 哨兵通常需要 3 个实例,来保证自己的健壮性。 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演 练。

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成 16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行 方案说明 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了 16384 个槽位

每份数据分片会存储在多个互为主从的多节点上 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步) 同一分片多个节点间的数据不保持强一致性 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点 扩容时需要需要把旧节点的数据迁移一部分到新节点 在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端 口号,比如 16379。 16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、 故障转移授权。

优点

无中心架构,

支持动态扩容,

对业务透明

具备Sentinel的监控和自动Failover(故障转移)能力

客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

运维也很复杂,

数据迁移需要人工干预

布式逻辑和存储模块耦合等

缓存预热

缓存预热指在用户请求数据前先将数据加载到缓存系统中,用户查询事先被预热的缓存数据,以提高系统查询效率。缓存预热一般有系统启动加载、定时加载等方式。


缓存雪崩、缓存穿透、缓存击穿在实际中如何处理

缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

  1. 设置热点数据永不过期:对于一些热点数据,可以将其设置为永不过期,确保这部分数据不会因缓存失效而导致频繁访问数据库。可以在缓存层或应用层逻辑中识别出热点数据,并确保其始终处于有效状态。
  2. 延迟双删策略:当一个请求发现缓存失效时,可以先去查询数据库,若数据库中存在该数据,则主动更新缓存并返回结果。若数据库中不存在该数据,也需要进行缓存更新,但缓存的值为一个短暂的占位符(如null或空字符串),并设置一个较短的过期时间,避免其他请求同时击穿到数据库。
  3. 互斥锁(Mutex Lock)机制:在缓存失效的时候,对于请求来说,先去获取一个互斥锁,只允许一个线程或进程访问数据库,其他线程或进程等待该请求的结果。当第一个线程完成数据库查询后,再更新缓存并释放锁。这样可以保证只有一个请求能够访问数据库,其他请求则从缓存中获取数据。
  4. 限流和降级:通过限流和降级策略,当缓存失效时,可以限制对数据库的访问量,保护数据库不至于被大量请求压垮。可以通过限流算法、熔断机制等手段来控制请求量。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案

Redis 缓存雪崩是指由于大量缓存同时失效或因其他原因导致请求直接落到数据库上,对数据库造成瞬间的压力增大,甚至宕机。为了防止缓存雪崩,可以采取以下方案:

  1. 设置合理的过期时间:合理设置缓存的过期时间,避免出现大量缓存同时失效的情况。可以使用随机过期时间来分散缓存失效的时间点,减少缓存同时失效的概率。
  2. 使用热点数据永不过期:对于一些热点数据,可以设置永不过期,确保这部分数据不会在同一时间大规模失效,从而减轻缓存雪崩的影响。
  3. 限流和降级:通过限流和降级策略,当缓存失效时,可以限制对数据库的访问量,保护数据库不至于被大量请求压垮。可以通过限流算法、熔断机制等手段来控制请求量。
  4. 多级缓存策略:引入多级缓存,例如使用本地缓存和分布式缓存相结合,可以降低缓存失效的风险。即使分布式缓存发生失效,本地缓存还可以提供一定程度的缓解。
  5. 预热缓存:系统启动时,可以预先加载部分热点数据到缓存中,避免出现大量缓存失效后导致的压力突增。

通过以上方案的综合应用,可以有效地减少缓存雪崩对系统的影响,保障系统的稳定性和性能。

总结

穿透:缓存不存在,数据库不存在,高并发,少量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协议来保证分布式环境中的数据一致性和可靠性。

ZAB协议主要包括两个阶段:崩溃恢复阶段和消息广播阶段。

  1. 崩溃恢复阶段(Crash Recovery): 在崩溃恢复阶段,ZooKeeper 通过选举算法选出一个Leader节点来负责协调和管理数据的更新。Leader节点负责将事务请求转化为事务Proposal并进行广播。Follower节点和Observer节点则负责接收并复制Leader节点发送的事务Proposal。在崩溃恢复阶段中,Zookeeper会使用类似于Paxos算法的过程来选举Leader节点,确保选出的Leader节点具有多数派支持。
  2. 消息广播阶段(Message Broadcast): 在消息广播阶段,Leader节点将广播事务Proposal给所有Follower节点和Observer节点。一旦超过一半节点成功将事务Proposal写入本地存储,Leader节点就会广播Commit消息,这意味着事务已经成功提交。Follower和Observer节点会将Commit消息应用到自身数据状态上,并且返回成功响应给Leader节点。Leader节点收到多数派的响应后,会继续广播下一个事务Proposal。这个过程实现了事务的强一致性,保证了多个节点之间的数据视图是一致的。

ZAB协议通过保证Leader节点的强一致性写入来达到数据一致性的目的。当客户端向ZooKeeper发送修改数据的请求时,ZooKeeper会将请求转化为事务Proposal,通过ZAB协议进行广播和复制,最终保证所有节点上的数据视图是一致的。

总结来说,ZAB协议是ZooKeeper用于实现分布式一致性的核心协议,通过Leader节点的选举与事务广播,保证了分布式环境下数据的可靠性和一致性。

Watcher使用的注意事项

Watcher是一次触发器,假如需要持续监听数据变更,需要在每次获取时设置Watcher 会话过期:当客户端会话过期时,该客户端注册的Watcher会失效 事件丢失:在接收通知和注册监视点之间,可能会丢失事件,但Zookeeper的状态变更和数据变化,都会记录在状态元数据信息和ZK数据节点上,所以能够获取最终一致的ZK信息状态 避免Watcher过多:服务器会对每一个注册Watcher事件的客户端发送通知,通知通过Socket连接的方式发送,当Watcher过多时,会产生一个尖峰的通知

zk的命名服务、配置管理、集群管理分别是什么

分布式协调

大于等于一的情况下,才会有协调,在协调的事务进行分类得到一些名词,语义能够接受就可以

命名服务

通过使用有序节点的特性做到协调命名规则

通过zk的事务ID递增,做到有序行命名规则

通过使用自己点做map映射,做到1:N的命名映射,比如DNS

顺序关系、映射关系

配置管理

配置、元数据、状态等语义可以通过ZK的节点1MB存储,或者通过zk的节点目录结构特性存储

并且通过watch机制,满足配置变化的全局通知能力

配置管理是指对分布式系统中的配置参数进行集中管理和动态更新的过程。ZooKeeper可以用作分布式配置管理的中心,通过在ZooKeeper中创建持久化的节点,并将节点的值设置为配置参数,可以实现对配置的集中存储和管理。同时,客户端可以注册Watcher来监听配置节点的变化,以便在配置更新时及时获取新的配置信息并进行相应的调整。

集群管理

通过zk的排他性,有序性

满足分布式锁、分布式选主、队列锁

串行化回调调度

分布式调度等

集群管理是指对分布式系统中的节点集群进行监控、管理和协调的过程。ZooKeeper可以作为集群管理的工具,通过节点的注册和心跳机制,实现对节点的监控和故障检测。同时,可以使用ZooKeeper来维护集群的状态和协调集群中的各个节点。例如,可以使用ZooKeeper进行主节点选举、节点的动态加入和退出、分布式锁的管理等。

标签: 面试 笔记 redis

本文转载自: https://blog.csdn.net/u014755700/article/details/136350943
版权归原作者 小裕哥略帅 所有, 如有侵权,请联系我们删除。

“面试笔记系列六之redis+kafka+zookeeper基础知识点整理及常见面试题”的评论:

还没有评论