Kafka -- 处理请求
请求协议
Kafka自定义了一组请求协议,用于实现各种各样的交互操作
PRODUCE请求用于生产消息,FETCH请求用于消费消息,METADATA请求用于请求Kafka集群元数据信息
Kafka 2.3总共定义了45种请求格式,所有请求都通过TCP网络以Socket的方式进行通讯
处理请求方案顺序处理实现简单,但吞吐量太差,只适用于请求发送非常不频繁的场景
1234while (true) { Request request = accept(connection); handle(request);}
单独线程处理为每个请求都创建一个新的线程异步处理,完全异步,但开销极大,只适用于请求发送频率很低的场景
12345while (true) { Request request = accept(connection); Thread thread = new Thread(() -> { handle(request); }); thread.start();}
Reactor模式
Reactor ...
Kafka -- 副本
副本机制的优点
提供数据冗余
即使系统部分组件失效,系统依然能够继续运转,增加了整体可用性和数据持久性
提供高伸缩性
支持横向扩展,能够通过增加机器的方式来提升读性能,进而提高读操作吞吐量
改善数据局部性
允许将数据放入与用户地理位置相近的地方,从而降低系统延时
Kafka只能享受副本机制提供数据冗余实现的高可用性和高持久性
副本定义
Kafka主题划分为若干个分区,副本的概念上是在分区层级下定义的,每个分区配置若干个副本
副本:本质上是一个_只能追加写消息的提交日志_
同一个分区下的所有副本保存有相同的消息序列,这些副本分散保存在不同的Broker上,提高了数据可用性
实际生产环境中,每台Broker都可能保存有各个主题不同分区的不同副本
副本角色
Kafka采用基于领导者(Leader-based)的副本机制
副本分为两类:领导者副本(Leader Replica)和追随者副本(Follower Replica)
每个分区在创建时都要选举一个副本,称为领导者副本,其余的副本自动称为追随者副本
追随者副本是不对外提供服务的,所有的读写请求都必须发往领导者副本所在的Brok ...
Java性能 -- JVM堆内存分配
JVM内存分配性能问题
JVM内存分配不合理最直接的表现就是频繁的GC,这会导致上下文切换,从而降低系统的吞吐量,增加系统的响应时间
对象在堆中的生命周期
在JVM内存模型的堆中,堆被划分为新生代和老年代
新生代又被进一步划分为Eden区和Survivor区,Survivor区由From Survivor和To Survivor组成
当创建一个对象时,对象会被优先分配到新生代的Eden区
此时JVM会给对象定义一个对象年轻计数器(-XX:MaxTenuringThreshold)
当Eden空间不足时,JVM将执行新生代的垃圾回收(Minor GC)
JVM会把存活的对象转移到Survivor中,并且对象年龄+1
对象在Survivor中同样也会经历Minor GC,每经历一次Minor GC,对象年龄都会+1
如果分配的对象超过了-XX:PetenureSizeThreshold,对象会直接被分配到老年代
查看JVM堆内存分配
在默认不配置JVM堆内存大小的情况下,JVM根据默认值来配置当前内存大小
在JDK 1.7中,默认情况下新生代和老年代的比例是1:2,可以通过–XX:NewRat ...
Kafka -- 监控消费进度
Consumer Lag
Consumer Lag(滞后程度):消费者当前落后于生产者的程度
Lag的单位是消息数,一般是在主题的级别上讨论Lag,但Kafka是在分区的级别上监控Lag,因此需要手动汇总
对于消费者而言,Lag是最重要的监控指标,直接反应了一个消费者的运行情况
一个正常工作的消费者,它的Lag值应该很小,甚至接近于0,滞后程度很小
如果Lag很大,表明消费者无法跟上生产者的速度,Lag会越来越大
极有可能导致消费者消费的数据已经不在操作系统的页缓存中了,这些数据会失去享有Zero Copy技术的资格
这样消费者不得不从磁盘读取这些数据,这将进一步拉大与生产者的差距
马太效应:_Lag原本就很大的消费者会越来越慢,Lag也会也来越大_
监控LagKafka自带命令
kafka-consumer-groups是Kafka提供的最直接的监控消费者消费进度的工具
也能监控独立消费者的Lag,独立消费者是没有使用消费者组机制的消费者程序,也要配置group.id
消费者组要调用KafkaConsumer.subscribe,独立消费者要调用KafkaConsumer.assign直接消费 ...
Java性能 -- GC
GC机制回收区域
JVM的内存区域中,程序计数器、虚拟机栈、本地方法栈是线程私有,随线程的创建而创建,销毁而销毁
栈中的栈帧随着方法的进入和退出进行入栈和出栈操作,每个栈帧分配多少内存基本是在类结构确定下来时就已知
因此,这三个区域的内存分配和回收都是具有确定性的
堆中的回收主要是对象回收,方法区的回收主要是废弃常量和无用类的回收
回收时机
当一个对象不再被引用,就代表该对象可以被回收
引用计数法:实现简单,判断效率高,但存在循环引用的问题
可达性分析算法:HotSpot VM
引用类型
功能特点
强引用(Strong Reference)
被强引用关联的对象,永远不会被垃圾回收器回收
软引用(Soft Reference)
被软引用关联的对象,只有当系统将要发生内存溢出时,才会去回收软引用关联的对象
弱引用(Weak Reference)
只被弱引用关联的对象,只要发生GC事件,就会被回收
虚引用(Phantom Reference)
被虚引用关联的对象,唯一作用是在这个对象被回收时收到一个系统通知
回收特性
自动性
Java提供了一个系统级的线程来跟踪每一块分配出去 ...
Kafka -- Java消费者管理TCP连接
创建TCP连接
消费者端的主要程序入口是KafkaConsumer,但构建KafkaConsumer实例不会创建任何TCP连接
构建KafkaProducer实例时,会在后台默默地启动一个Sender线程,Sender线程负责Socket连接的创建
在Java构造函数中启动线程,会造成this指针逃逸,是一个隐患
消费者的TCP连接是在调用**KafkaConsumer.poll**方法时被创建的,poll方法内部有3个时机可以创建TCP连接
发起FindCoordinator请求时
消费者组有个组件叫作协调者(Coordinator)
驻留在Broker端的内存中,负责消费者组的组成员管理和各个消费者的位移提交管理
当消费者程序首次启动调用poll方法时,需要向Kafka集群(集群中的任意Broker)发送FindCoordinator请求
社区优化:消费者程序会向集群中当前负载最小的那台Broker发送请求
单向负载评估(非最优解):消费者连接的所有Broker中,谁的待发送请求最少,谁的负载就越小
连接Coordinator时
Broker处理完FindCoordinator请求后, ...
Java性能 -- JIT
编译
前端编译:即常见的**.java文件被编译成.class文件**的过程
运行时编译:机器无法直接运行Java生成的字节码,在运行时,JIT或者解释器会将字节码转换为机器码
类文件在运行时被进一步编译,可以变成高度优化的机器代码
C/C++编译器的所有优化都是在编译期完成的,运行期的性能监控仅作为基础的优化措施是无法进行的
JIT编译器是JVM中运行时编译最重要的部分之一
编译 / 加载 / 执行
类编译
javac:将.java文件编译成.class文件
javap:反编译.class文件,重点关注常量池和方法表集合
常量池主要记录的是类文件中出现的字面量和符号引用
字面量:字符串常量、基本类型的常量
符号引用:类和接口的全限定名、类引用、方法引用、成员变量引用
方法表集合
方法的字节码、方法访问权限、方法名索引、描述符索引、JVM执行指令、属性集合等
类加载
当一个类被创建实例或者被其他对象引用时,JVM如果没有加载过该类,会通过类加载器将**.class文件加载到内存**中
不同的实现类由不同的类加载器加载
JDK中的本地方法类一般由根加载 ...
Kafka -- 多线程消费者
Kafka Java Consumer设计原理
Kafka Java Consumer从Kafka 0.10.1.0开始,KafkaConsumer变成了双线程设计,即用户主线程和心跳线程
用户主线程:启动Consumer应用程序main方法的那个线程
心跳线程:只负责定期给对应的Broker机器发送心跳请求,以标识消费者应用的存活性
引入心跳线程的另一个目的
将心跳频率和主线程调用KafkaConsumer.poll方法的频率分开,解耦真实的消息处理逻辑和消费组成员存活性管理
虽然有了心跳线程,但实际的消息获取逻辑依然是在用户主线程中完成
因此在消费消息的这个层面,依然可以安全地认为KafkaConsumer是单线程的设计
老版本Consumer是多线程的架构
每个Consumer实例在内部为所有订阅的主题分区创建对应的消息获取线程,即Fetcher线程
老版本Consumer同时也是阻塞式的,Consumer实例启动后,内部会创建很多阻塞式的消息获取迭代器
但在很多场景下,Consumer端有非阻塞需求,如在流处理应用中执行过滤、分组等操作就不能是阻塞式的
基于这个原因,社区为新版本Con ...
Java性能 -- JVM内存模型
JVM内存模型
堆
堆是JVM内存中最大的一块内存空间,被所有线程共享,几乎所有对象和数组都被分配到堆内存中
堆被划分为新生代和老年代,新生代又被划分为Eden区和Survivor区(From Survivor + To Survivor)
永久代
在Java 6中,永久代在非堆内存中
在Java 7中,永久代的静态变量和运行时常量池被合并到堆中
在Java 8中,永久代被元空间取代
程序计数器
程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址
Java是多线程语言,当执行的线程数量超过CPU数量时,线程之间会根据时间片轮询争夺CPU资源
当一个线程的时间片用完了,或者其他原因导致该线程的CPU资源被提前抢夺
那么退出的线程需要单独的程序计数器来记录下一条运行的指令
方法区
方法区 != 永久代
HotSpot VM使用了永久代来实现方法区,但在其他VM(Oracle JRockit、IBM J9)不存在永久代一说
方法区只是JVM规范的一部分,在HotSpot VM中,使用了永久代来实现JVM规范的方法区
方法区主要用来存放已被虚拟机加载的类相关信息
类 ...
Kafka -- CommitFailedException
CommitFailedException
CommitFailedException是Consumer客户端在提交位移时出现的不可恢复的严重异常
如果异常是可恢复的瞬时错误,提交位移的API方法是支持自动错误重试的,如commitSync方法
解释
Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member. This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time message processing. You can address this either by increasing the max.poll.interval.ms o ...