消息队列学习笔记(二)

1.消息复制的问题

    RocketMQ 提供新、老两种复制方式:传统的主从模式和新的基于 Dledger 的复制方式。传统的主从模式性能更好,但灵活性和可用性稍差,而基于 Dledger 的复制方式,在 Broker 故障的时候可以自动选举出新节点,可用性更好,性能稍差,并且资源利用率更低一些。Kafka 提供了基于ISR的更加灵活可配置的复制方式,用户可以自行配置,在可用性、性能和一致性这几方面根据系统的情况来做取舍。但是,这种灵活的配置方式学习成本较高。并没有一种完美的复制方案,可以同时能够兼顾高性能、高可用和一致性。你需要根据你实际的业务需求,先做出取舍,然后再去配置消息队列的复制方式。

2.RocketMQ的NameServer

    在RocketMQ中,NameServer是一个独立的进程,为Broker、生产者和消费者提供服务。NameServer最主要的功能就是,为客户端提供寻址服务,协助客户端找到主题对应的 Broker 地址。此外,NameServer 还负责监控每个 Broker 的存活状态。

    RouteInfoManager中5个Map对象,保存了集群所有的Broker和主题的路由信息。

  • topicQueueTable 保存的是主题和队列信息,其中每个队列信息对应的类 QueueData 中,还保存了 brokerName。需要注意的是,这个 brokerName 并不真正是某个 Broker 的物理地址,它对应的一组 Broker 节点,包括一个主节点和若干个从节点。
  • brokerAddrTable 中保存了集群中每个 brokerName 对应 Broker 信息,每个 Broker 信息用一个 BrokerData 对象表示。
  • brokerLiveTable 中,保存了每个Broker当前的动态信息,包括心跳更新时间,路由数据版本等等。
  • clusterAddrTable 中,保存的是集群名称与 BrokerName 的对应关系。
  • filterServerTable 中,保存了每个Broker对应的消息过滤服务的地址,用于服务端消息过滤。

    根据Broker请求过来的路由信息,依次对比并更新clusterAddrTable、brokerAddrTable、topicQueueTable、brokerLiveTable和filterServerTable这5个保存集群信息和路由信息的 Map 对象中的数据。

    客户端在启动后,会启动一个定时器,定期从NameServer上拉取相关主题的路由信息,然后缓存在本地内存中,在需要的时候使用。

3.Kafka 中 ZooKeeper的使用

    Kafka 在 ZooKeeper 中保存的元数据,主要就是 Broker 的列表和主题分区信息两棵树。这份元数据同时也被缓存到每一个 Broker 中。
    Kafka 在每个 Broker 中都维护了一份和 ZooKeeper 中一样的元数据缓存,并不是每次客户端请求元数据就去读一次 ZooKeeper。由于 ZooKeeper 提供了 Watcher 这种监控机制,Kafka 可以感知到 ZooKeeper 中的元数据变化,从而及时更新 Broker 中的元数据缓存。
    客户端并不直接和 ZooKeeper 来通信,而是在需要的时候,通过 RPC 请求去 Broker 上拉取它关心的主题的元数据,然后保存到客户端的元数据缓存中,以便支撑客户端生产和消费。

缺点:目前 Kafka 的这种设计,集群的可用性是严重依赖 ZooKeeper 的,也就是说,如果 ZooKeeper 集群不能提供服务,那整个 Kafka 集群也就不能提供服务了,这其实是一个不太好的设计。

可行的解决办法:如果你需要要部署大规模的 Kafka 集群,建议的方式是,拆分成多个互相独立的小集群部署,每个小集群都使用一组独立的 ZooKeeper 提供服务。这样,每个 ZooKeeper 中存储的数据相对比较少,并且如果某个 ZooKeeper 集群故障,只会影响到一个小的 Kafka 集群,故障的影响面相对小一些。

4.事务

    RocketMQ 是把这些消息暂存在一个特殊的队列中,待事务提交后再移动到业务队列中;而 Kafka 直接把消息放到对应的业务分区中,配合客户端过滤来暂时屏蔽进行中的事务消息。

    RocketMQ 和 Kafka 的事务,它们的适用场景是不一样的,RocketMQ 的事务适用于解决本地事务和发消息的数据一致性问题,而 Kafka 的事务则是用于实现它的 Exactly Once 机制,应用于实时计算的场景中。

5.消息队列使用场景

    消息队列最常被使用的三种场景:异步处理、流量控制(削峰填谷)和服务解耦。

6.消息可靠性

关于消息可靠性的服务水平,有下面三种级别:
    At most once: 至多一次,消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。
    At least once: 至少一次,消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
    Exactly once:恰好一次,消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。
Kafka、RocketMQ 和 RabbitMQ 以及我们常见的大部分消息队列,能提供的服务水平都是一样的:At least once,也就是至少一次,消息有可能会重复,但可以保证不丢消息。
    Kafka 宣称是支持 Exactly Once 特性的,但是,Kafka 支持的这个“Exactly once”特性,并不是保证我们这个题目中所说的“从消息生产直到消费完成”这一过程中消息不重不丢。它解决的是,在流计算中,用 Kafka 作为数据源,并且将计算结果保存到 Kafka 这种场景下,数据从 Kafka 的某个主题中消费,在计算集群中计算,再把计算结果保存在 Kafka 的其他主题中。这样一个过程中,Kafka 的 Exactly Once 机制,保证每条消息都被恰好计算一次,确保计算结果正确。

7.顺序性

    大部分消息队列都只能保证在分区(队列)上的严格顺序,单调性是指如果已经有一些内容通过哈希分派到了相应的分区中,又有新的分区加入到主题中,哈希的结果应能够保证,原有已分配的内容可以被映射到原有的或者新的分区中去,而不会被映射到旧的分区集合中的其他分区。只要是满足单调性的分片算法,我们就可以按照“先扩容分区 -> 将旧分区中的遗留消息消费完 -> 同时消费所有分区”这样一个方式,确保扩容过程中消息的严格顺序。

8.Kafka 发送消息的可靠性

    Kafka 发送消息的可靠性依靠的是“请求 - 确认”机制,即使是批量发送,这个机制依然可以保证不丢消息,所以没必要把批量大小设置为 1。

9.RocketMQ 的主从模式集群

    由于 RocketMQ 的主从模式集群是不支持自动选举的,一旦主节点宕机,虽然,消费者可以自动切换到从节点继续消费,但生产者就不能再往这个节点上的队列发消息了。所以,为了保证生产的可用性,必须把主题中的队列分布到多个 Broker 上。

10.理解RPC框架

    在 RPC 框架中,最关键的就是理解“桩”的实现原理,桩是 RPC 框架在客户端的服务代理,它和远程服务具有相同的方法签名,或者说是实现了相同的接口。客户端在调用 RPC 框架提供的服务时,实际调用的就是“桩”提供的方法,在桩的实现方法中,它会发请求的服务名和参数到服务端,服务端的 RPC 框架收到请求后,解析出服务名和参数后,调用在 RPC 框架中注册的“真正的服务提供者”,然后将结果返回给客户端。

    通过定义一个接口来解耦调用方和实现。在设计上这种方法称为“依赖倒置原则(Dependence Inversion Principle)”,它的核心思想是,调用方不应依赖于具体实现,而是为实现定义一个接口,让调用方和实现都依赖于这个接口。这种方法也称为“面向接口编程”。

    像 gRPC 这类多语言的 RPC 框架,都是在编译 IDL 的过程中生成桩的源代码,再和业务代码,使用目标语言的编译器一起编译的。而像 Dubbo 这类没有编译过程的 RPC 框架,都是在运行时,利用一些语言动态特性,动态创建的桩。RPC 框架的这种“桩”的设计,其实是一种动态代理设计模式。这种设计模式可以在不修改源码,甚至不需要源码的情况下,在调用链中注入一些业务逻辑。这是一种非常有用的高级技巧,可以用在权限验证、风险控制、调用链跟踪等等很多场景中。

    RPC 框架的服务端主要需要实现下面这两个功能:服务端的业务代码把服务的实现类注册到 RPC 框架中 ;接收客户端桩发出的请求,调用服务的实现类并返回结果。

猜你喜欢

转载自blog.csdn.net/TP89757/article/details/104318511
今日推荐