Kafka -- 重平衡
触发重平衡
组成员数量发生变化 – 最常见
订阅主题数量发生变化
订阅主题的分区数发生变化
通知
重平衡过程是通过消费者的心跳线程通知到其它消费者实例的
Kafka Java消费者需要定期地发送心跳请求到Broker端的协调者,表明它还活着
在Kafka 0.10.1.0之前,发送心跳请求是在消费者主线程完成的,即调用poll方法的那个线程
弊端
消息处理逻辑是也在主线程完成的
一旦消息处理消耗了很长时间,心跳请求将无法及时发送给协调者,导致协调者误以为消费者已死
从Kafka 0.10.1.0开始,社区引入了单独的心跳线程
重平衡的通知机制是通过心跳线程来完成的
当协调者决定开启新一轮重平衡后,会将REBALANCE_IN_PROGRESS封装进心跳请求的响应中
当消费者实例发现心跳响应中包含REBALANCE_IN_PROGRESS,就知道重平衡要开始了,这是重平衡的通知机制
heartbeat.interval.ms的真正作用是控制重平衡通知的频率
消费者组状态机
状态
描述
Empty
组内没有任何成员,但消费者组可能存在已提交的位移数据,而且这些位移尚未过期
Dea ...
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规范的方法区
方法区主要用来存放已被虚拟机加载的类相关信息
类 ...