读kafka生产端源码,窥kafka设计之道(上)

这篇具有很好参考价值的文章主要介绍了读kafka生产端源码,窥kafka设计之道(上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. kafka 高吞吐之道-------异步提交批量发送

简约的发送接口----后面隐藏着并不简单的设计

kafka发送消息的接口非常简约,在简约的表面上,其背后却并不简单。先看下发送接口

kafkaProducer.send(new ProducerRecord(topic,msg), new Callback() {
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
                if (null != exception) { //消息发送失败,记个日志
                    log.error("send to kafka error {}",exception);
                }
            }
        });

正常情况下,调用kafkaProducer.send方法,由业务线程执行(比如tomcat的业务线程);业务线程在执行send代码后,就直接返回;这个send方法的本质:其实是把消息写入到了本地内存中,并没有发生网络IO。

什么时候会把内存中缓存的消息发给broker集群了?
是kafka的Sender Thread来负责发送的(代码类名就叫 Sender);Sender 线程会把内存中的数据,重新组织:把属于同一个broker的多条消息,打包成一个ProduceRequest;通过Sender Threader,把多条数据一次网络IO批量发给到对应的broker。

整个过程如下图:
读kafka生产端源码,窥kafka设计之道(上),kafka,kafka,分布式,架构,中间件

图1 消息的生成和发发送过程

从图中可看到:业务线程投递消息到内存是同步的。但这一过程速度非常快。到底有多快?可以借鉴多年前Google的Jeff Dean 在Engineer All-Hands Meeting上 对I/O延迟的测试数据
读kafka生产端源码,窥kafka设计之道(上),kafka,kafka,分布式,架构,中间件

图2 IO延迟

写内存延迟在100纳秒;写网络延迟在20,000纳秒;两种延迟相差200倍;kafka提供的send接口,翻译过来是发送的意思;这个表面的“发送”接口, 理论上调用百W次,只花费了1秒钟。

为什么kafka发消息可以使用“异步批量”方式了?而没有选择“同步单条”方式了?

推测有以下2个主要原因

原因1:异步,消息中间件自带的好处之一
使用消息中间件的好处之一是异步 (另外两个是解耦,削峰)。即发送消息和消费消息不是一个同步的过程;很有可能生产者发送消息成功了,而消费者消费消息失败了;并且生产消息和消费消息可以是在不同的时间点和不同机器上发生的。从消息产生和消费的整体流程上来看,不需要同步。 kafka生产端能够使用这种方式,和他的应用场景是密切相关的,即天生就是异步的。

原因2:通过减少网络次数和传输的元数据方式,整体提高了发送端的吞吐量

假设生产端产生一条消息,立即同步发送给服务端;相比较攒一波再发送;必定会增加网络I/O次数,
加重了网络传输的资源消耗。

这让我想到了亲身项目经历:当时有一个IOT项目;需要在30秒内,完成10W设备的连接,并且在连接完后,需要把设备版本号进行入库操作。当时服务端采用的是连接一个设备,存一次当前设备版本到库里;这种方式在测试环境进行压测情况下,应用响应奇慢无比和宕机没啥区别了,mysql数据库的cpu比平常高了70%; 后来采用这种异步批量提交版本到数据库的方式;应用轻松应对10W个设备同时发起连接,并保存版本号到数据库;而mysql的cpu也只比平常高了1%;

攒一波的本质,带来的最终效果是一次IO发送多条消息;而且这种批量打包消息还不是针对某个topic 分区的,也不是针对某个topic的,而是针对broker维度的,即kafka生产端的IO线程,会在发送消息的时候,会重新计算每条消息对应的是那个broker,然后把发往同一个broker的消息在打包成一个ProduceRequest;带来的最终好处:把多个topic,多个分区的数据打包发往一个broker时只需一次网络I/O

2. 无锁化的线程模型----性能提高的利器

从前面的图1,我们发现;在一个应用中,其实只用了一个IO单线程,对集群所有的broker进行网络IO通信;而采用IO单线程的方式。有以下优点:

简化编程模型

使用单线程管理网络 I/O 可以简化编程模型,避免了多线程编程中的并发和同步问题。开发人员可以专注于业务逻辑的实现,而不必过多关注线程间的同步和数据一致性问题。

降低资源消耗,提高性能

在传统的BIO网络编程模型中,通常用每个连接一个线程或进程的方式进行处理;见下图:

读kafka生产端源码,窥kafka设计之道(上),kafka,kafka,分布式,架构,中间件

图3 传统BIO模型

在这种方式里,线程数和网络连接数成线性关系。 而线程是一种资源;过多的线程会导致占用更多的内存并且管理线程本身也会有资源开销,过多线程产生的上下文切换,也会导致系统性能直线下降

kafka采用单线程 异步非阻塞的方式管理网络 I/O,自身通过事件循环的机制,高效地处理并发请求,避免了线程切换和阻塞操作的开销。

想想在java界鼎鼎大名的网络编程框架netty,不就是这种无锁化的IO线程设计吗? 1个线程管理多个TCP连接的读写操作。

保证消息发送的顺序性

Kafka 是一个分布式消息系统,消息的顺序性对于一些业务场景非常重要(比如订单状态的变化,需要通知到下游进行对应的业务处理)。使用单线程管理网络 I/O 可以保证消息的发送顺序,因为所有的发送操作都在同一个线程中进行,不会出现并发发送导致消息顺序混乱的情况。 注:kafka的消息顺序,只保证在broker维度是顺序的,不保证消息在多个broker间是顺序的,这和kafka是个分布式多分区特性有关。

3. 良好的封装 -----开箱即用,减少使用者心智

1、生产端只需要7行代码,就可接入kafka,并且具备消息发送能力

Map<String,Object> produceConfigMap = new HashMap<String,Object>();
produceConfigMap.put("bootstrap.servers",KafkaConfig.getInstance().getProperty("bootstrap.servers"));
produceConfigMap.put("retries",3);
produceConfigMap.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
produceConfigMap.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
produceConfigMap.put("acks","all");
KafkaProducer kafkaProducer = new KafkaProducer(produceConfigMap);

2、IO线程封装:生产者端和kafka broker集群所有的网络交互,无需上层关心,即和中间件进行网络交互的业务,交给sdk 的IO线程就OK了

3、元数据的更新,由sdk内部完成,对客户端的外部使用者并不可见。
不仅做到了元数据的自动更新;还做到了元数据更新后,客户端的重连,消息的重试,
生产端消息尽量不丢
; leader分区迁移或者leader分区切换时,客户端自动感知和切换(这个高级功能是kafka整体高可用的前提);前段时间在做leader分区迁移和切换时;生产端是秒级感应;在leader分区切换后,生产端通过自动重试的机制把消息发给了新分区,保证了消息的不丢和kafka的生产端高可用

4. 扩展性考虑

生产端扩展性考虑,主要有两点:

第一点:通过拦截器,在消息发送之前,统一的做点什么事;是不是感觉到有点熟悉的面向“AOP”编程味
比如:在实现ProducerInterceptor接口,在onSend方法里;对所有消息发送记录以打印日志的方式进行单独记录

第二点:从消息对象到网络传输的字节,即序列化方式,和反序列化方式;可自行定义实现;
比如你觉得原生的字符串方式的序列化不好,序列化后的字节数太大,加重了网络传输的负载;你可以使用业界优秀的protobuf,hessian,kryo等二进制的序列化方式;只需实现序列化接口,反序列化接口

5. 其它好玩的事情

在看kafka生产端源码时,除了发现有很多设计上的优点以外,还发现其在使用底层技术上也很有考究:
比如:

不走寻常路的生产者消费者模型

kafka消息生成和发送,其本质是一个生产者消费者模型;而生产者消费者模型,我们一般用什么做队列了?首先想到的是并发包里的BlockingQueue做队列;
为啥了?
第一:BlockingQueue底层帮我们解决了并发访问的问题;
第二:绝大多数现有讲解生产者消费者模型的课程都会用此队列,已经形成了使用此模型的第一联想位;

但kafka出乎个人意料之外的使用了双端队列;why?

内存的循环使用----延迟GC的频繁执行

减少因为发送消息时ByteBuffer内存的频繁申请和回收,降低JVM GC的频繁执行。如何做到的?

上面个问题,留到下篇文章中去聊吧

总结

如果要设计一款sdk去连接某个中间件,我希望能做到向kafka客户端一样的优秀;这些优秀的设计包括高性能线程模型的思考,能够提升吞吐量并且符合自身业务场景的异步批量提交;内存的重复使用避免频繁的GC;良好的封装性降低使用者心智负担;优雅的扩展性让用户能深度定制关键节点。

不知道你在使用kafka客户端,或者了解的第三方客户端时,有哪些优秀的设计;欢迎评论区留言,共同学习和交流
原创不易,欢迎大家点赞,收藏,转发 三暴击 ^^文章来源地址https://www.toymoban.com/news/detail-572417.html

到了这里,关于读kafka生产端源码,窥kafka设计之道(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 分布式 - 消息队列Kafka:Kafka生产者发送消息的3种方式

    不管是把Kafka作为消息队列、消息总线还是数据存储平台,总是需要一个可以往Kafka写入数据的生产者、一个可以从Kafka读取数据的消费者,或者一个兼具两种角色的应用程序。 Kafka 生产者是指使用 Apache Kafka 消息系统的应用程序,它们负责将消息发送到 Kafka 集群中的一个或多

    2024年02月13日
    浏览(36)
  • 读kafka生产端源码,窥kafka设计之道(上)

    1. kafka 高吞吐之道-------异步提交批量发送 简约的发送接口----后面隐藏着并不简单的设计 kafka发送消息的接口非常简约,在简约的表面上,其背后却并不简单。先看下发送接口 正常情况下,调用kafkaProducer.send方法,由 业务线程执行 (比如tomcat的业务线程);业务线程在执行

    2024年02月16日
    浏览(23)
  • Kafka的分布式架构与高可用性

    一开始我们就说过Kafka是一款开源的高吞吐、分布式的消息队列系统,那么今天我们就来说下它的分布式架构和高可用性以及双/多中心部署。 以下是 Kafka 的软件架构,整个 Kafka 体系结构由 Producer、Consumer、Broker、ZooKeeper 组成。Broker 又由 Topic、分区、副本组成。 详细可以参

    2024年02月10日
    浏览(27)
  • 分布式系统架构设计之分布式缓存技术选型

    随着互联网业务的快速发展,分布式系统已经成为了解决大规模并发请求、高可用性、可扩展性等问题的重要手段。在分布式系统中,缓存作为提高系统性能的关键技术,能够显著降低数据库负载、减少网络延迟、提高数据访问速度。当面对大量并发请求时,如果每次都直接

    2024年02月03日
    浏览(45)
  • 【系统架构】分布式系统架构设计

    分布式系统是指由多个计算机节点组成的一个系统,这些节点通过网络互相连接,并协同工作完成某个任务。 与单个计算机相比,分布式系统具有更高的可扩展性、可靠性和性能等优势,因此广泛应用于大规模数据处理、高并发访问、分布式存储等领域。 分布式系统的设计

    2024年02月15日
    浏览(36)
  • 阿里架构师分享分布式架构笔记文档:Nginx+Redis+ZK+Kafka+MQ等

    Nginx 是一款非常优秀的开源软件,工作需要,研究了很久一段时间的 Nginx 源码,在研究学习的过程中收益颇多。作为高性能服务器的代表,为了追求极致的高性能,在许多方面,Nginx 的源码实现都可以称得上是典范。 市面上真正适合学习的Nginx资料太少,有的书或资料虽然讲

    2024年02月10日
    浏览(39)
  • 架构设计-分布式ID

    1.不要用主键ID作为业务单号的唯一标识,因为一是数据同步麻烦,第二一旦业务数据扩张涉及到分库分表则数据维护麻烦,因为此时主键ID容易造成重复 。 2.对于有相似属性的业务ID如直播或者录播ID存储在业务表中的一个字段,一旦程序员哪天状态不好忘记区分类型,就很

    2024年02月03日
    浏览(77)
  • 分布式系统架构设计之分布式数据存储的扩展方式、主从复制以及分布式一致性

    在分布式系统中,数据存储的扩展是为了适应业务的增长和提高系统的性能。分为水平扩展和垂直扩展两种方式,这两种方式在架构设计和应用场景上有着不同的优势和局限性。 水平扩展是通过增加节点或服务器的数量来扩大整个系统的容量和性能。在数据存储领域,水平扩

    2024年02月03日
    浏览(51)
  • 分布式系统架构设计之分布式数据存储的安全隐私和性能优化

    在前面分布式系统部分,有对安全性做过介绍,如前面所述,在分布式系统中,确保系统的安全性和隐私是至关重要的。安全性关注系统的防护措施,而隐私是关注用户的个人信息保护。 身份认证:确保用户和系统组件的身份是合法的,通过通过密码、令牌或证书实现 授权

    2024年02月02日
    浏览(43)
  • 【架构设计】单体软件分布式化思考

    单体软件是历史悠久的软件架构形态,以下是一个简单的前后端分离的单体架构的 web 软件。 单体软件采用分布式方案部署,是根据需求而定的。 为了满足不同场景下的需求,单体软件中的客户端、代理层、服务、数据库,都可以以多个副本联合起来,提供服务的方式部署,

    2024年01月18日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包