分布式消息队列:kafka

分布式消息队列:kafka

高可用性,可靠性 ,可扩展性,高吞吐量

消息队列中间件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tu2BFzyA-1653474782158)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220418103614369.png)]

消息队列作为中间件,通常应用在分布式系统中;分布式系统需要保证消息的持久化

同时解决幂等问题:网络发生抖动时,消息队列能对同样的数据进行去重,数据排序(能让consumer收到顺序消息)

应用场景

异步处理

注册系统,客户发送的邮件注册内容会放在一个数据库,请求会放在一个消息队列。

服务器有空的时候处理请求,发送消息回去

注册流程

客户端提起请求给服务器,服务器根据客户端的数据操作DB(把客户端的注册请求写入到数据库),

然后服务器调用其它接口发送验证信息,注册中间件完成任务后返回服务器,服务器返回调用客户端。

以上第一步是紧迫的,第二步是非紧迫的,可以推迟异步处理-------mq的介入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XDHNTfdl-1653474782160)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516193437061.png)]

注册请求
存储内容
存储请求
client
server
DB
消息队列
发送邮件

系统解耦

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n6qWCekS-1653474782161)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220418110120448.png)]

分布式系统通过RPC或者restful API交互,RPC严重增加成本(需要知道调用函数名字和参数),restful API过于不安全(http),因此可以通过消息队列进行解耦,一方只提出需要什么数据,不需要考虑数据是如何存储和实现的方法。

流量削锋

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c3x3QmCz-1653474782161)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220418110449473.png)]

当做缓存用,适用于请求大于处理能力时。

日志处理

日志处理紧急度不高,而且需要持久化存储,利用Kalfa比数据库更快

操作数据库的复杂度是B+树的复杂度O(h*log2n),操作消息队列的时间复杂度是O(1),因为消息队列每次都会记录上一个记录的offset,插入在队尾。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CcCAJzSE-1653474782162)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516194442252.png)]

Kalfa体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QeI3Muyr-1653474782162)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516194102092.png)]

一个Kalfa集群内有多个borker,类比于服务器进程,负责处理读写请求,通过zookeeper选举出一个主broker作为controller。

一个broker进程内有自己的数据,这些数据存储在不同的partition分区内,由进程自己选出一个主分区,数据的读写都针对这个分区进行,其他的复制分区只在数据恢复时使用。

zookeeper除了用来选举主broker,还存储每个broker的主分区位置,用于读写。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ewh0j2M-1653474782163)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516195154583.png)]

分布式系统的选举方式:

zookeeper进行选举

集群初始化时,选择leader。

  • 每个服务器节点发起一个投票,广播一个(myId,votes),对i号节点就是(i,0)。

  • 每个服务器节点检查接受到的投票的有效性。

  • 处理投票。对自己发出的投票和收到的投票进行比较,更新自己的投票,然后把更新后的投票再广播出去。

    • 优先选择votes多的节点作为leader
    • votes相同,优先选择myId大的节点作为leader
  • 对于ZK1而言,它的投票是(1, 0),接收ZK2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时ZK2的myid最大,于是ZK2胜。ZK1更新自己的投票为(2, 0),并将投票重新发送给ZK2。

  • 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于ZK1、ZK2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出ZK2作为Leader。

  • 一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。当新的Zookeeper节点ZK3启动时,发现已经有Leader了,不再选举,直接将直接的状态从LOOKING改为FOLLOWING。

集群运行期间,重新选择leader时。(只要leader挂掉,zookeeper停止服务,重选leader)

  • 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
  • 每个Server会发出一个投票。在运行期间,每个服务器上的votes可能不同,此时假定ZK1的votes为124,ZK3的votes为123;在第一轮投票中,ZK1和ZK3都会投自己,产生投票(1, 124),(3, 123),然后各自将投票发送给集群中所有机器。
  • 接收来自各个服务器的投票。与启动时过程相同。
  • 处理投票。与启动时过程相同,由于ZK1事务ID大,ZK1将会成为Leader。
  • 统计投票。与启动时过程相同。
  • 改变服务器的状态。与启动时过程相同。
redis的哨兵集群选举

少部分节点作为哨兵节点,不断和节点通信,了解节点状态;当主节点宕机,哨兵节点选择较新的一个从数据库作为新的主数据库

去中心化选举
raft选举(etcd)

Kalfa生产消费模式

点对点(1生产者—消息队列—1消费者)

发布订阅(n生产者—消息队列—n消费者)

读写流程

写流程

消息写到哪个分区?
  • 主分区(leader partition),复制分区用于选举和故障恢复。写进程先连接zookeeper,拿到各个节点的主分区位置信息,然后选择一个节点进行写入。
  • 使用Kalfa API:高级API是已经定义了一些解决方案的API,低级API只提供了Kalfa的基本接口。
  • 高级API例子:
  • 1.随机写入一个节点。
  • 2.hash方式,每个消息都是key+message,对key%partitions.length,存储到对应的分区。
  • 3.轮询方式,需要记录上次写入的位置,每次写入上一次写入的下一个位置。
如何确保消息的可靠到达?
  • 请求回应方式,数据写入请求携带一个ack字段。
    • ack=0,表示请求不需要返回,写没写如我不关心。(20w条/s)

    • ack=1,表明数据写入成功的时候返回。(可能刚写入数据的节点宕机了,其他副本还没有更新,所以仍然是不可靠的)

    • ack=-1,表明数据写入之后,还需要等待不同broker的数据同步完成才返回。(最可靠,效率最低)(10w条/s)
      - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDgKNkdB-1653474782164)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516202903514.png)]

    • 上图是ack=-1时的流程,生产者先获得所有节点主分区的位置,然后用某种策略写入一个节点的主分区,然后更新其他节点的副本,在最后第4步返回一个ack

    • kalfa的订阅发布模式和redis数据库的是不同的

      • redis是推数据,某节点更新了,广播自己更新的消息;
      • kalfa是拉,和consumer流程是一样的,主动监听其他副本,有变更时改变自己

读流程

消息从哪个分区读取?
  • 点对点模式,消费者指定partition,直接取数据。
  • 发布订阅模式,存在多个partition。
    • 因此需要知道每个分区已经消费到哪个位置了,从那个位置开始消费。(offset而已)
      • 0.9以前,每个分区的消费信息存储在zk中,但是zk是一个高可用性,强一致性(同步与等待)的kv存储,节点负担大。zk只有一个主节点,采用paxos算法。
      • 0.9之后,采用一个额外的主题(包含多个partition)用来存储分区被消费的位置信息。
消费者组如何消费?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-od2oouRT-1653474782164)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516204509865.png)]

  • 消费者组和partition如何对应(上图包含多个consumer和多个partition)
    • 消息生产时,是按顺序存储的(末尾添加,每个Topic内核broker内都是顺序的)
    • 因此,尽量保证消费消息有序执行,至少一个分区内部的消费要有序
      • 一个分区只能有一个消费者,一个消费者可以对应多个分区
      • 当消费者数量大于分区数量,多出来的消费者是多余的
    • 但是这不是全局有序,因为消息是按不同策略写入不同分区的,所以只能做到局部有序
如何确保消息被消费
  • 消息没存进kalfa
  • 消息没执行对应职能(消息从kalfa被取出,但没有被消费者收到,offset就后移了)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YTB7arAc-1653474782165)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516205525868.png)]

consumer和partition的对应策略:高级API的rebalance策略(修正对应关系)。

  • 因为consumer和partition都可能宕机,会导致对应关系的破坏和更新。
  • *方案一,range平均策略,按节点数分成n组,第i组给第i个consumer
  • *方案二,轮询策略,轮询consumer,每次分配一个partition给当前询问的consumer
  • 方案三,粘性方式(尽量维持原来的对应关系)没有rebalance时采用轮询方式,发生rebalance时,尽量维持原来的关系,对被破坏的对象采用轮询方式。

只有方案三是正确的。因为consumer或partition数量变化会导致整个集群停止服务,rebalance,再对外提供服务。因此要减少rebalance的代价。

对应关系是一个socket连接,难以重现建立。

如何确保消息被消费

使用低级API

  • 提交策略:自动提交 x 手动提交 √

    • 自动提交是在数据库操作返回值后自动增加offset值,如果数据库操作失败,数据没有写入,offset后移,那么数据就丢失了;所以设置手动提交(offset不再存储在额外主题,而是其他外部数据库中)。
  • 事务
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FqYYV6RE-1653474782166)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516211007966.png)]

    • 在数据写入DB时成功,但是在DB操作返回consumer时,consumer宕机,数据没有提交到broker,出现数据不一致(重复消费/消息丢失)
    • 因此把offset存储在mysql,把数据提交和存储放在mysql支持的一个事务里就行了。

应用:分布式延时队列

消费者可以在定时结束后执行消费任务。

kalfa

  • 创建额外的主题
  • 创建额外的定时进程,监测这个主题,把过期的消息转存到另一个消息队列中;消费区去另一个具体的消息队列取消息。

rabbitmq

redis集群

go语言实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JL5u3W0M-1653474782166)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220516211744675.png)]

第一行等价于ack=-1

第二行选择分区函数是轮询(roundRobin)

第三行表明执行成功后是否需要返回。

  • 创建额外的定时进程,监测这个主题,把过期的消息转存到另一个消息队列中;消费区去另一个具体的消息队列取消息。

rabbitmq

redis集群

go语言实例

[外链图片转存中…(img-JL5u3W0M-1653474782166)]

第一行等价于ack=-1

第二行选择分区函数是轮询(roundRobin)

第三行表明执行成功后是否需要返回。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星辰的野望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值