闲来无事,大概翻翻kafka源码,就我个人看源码的习惯而言,
1、先到官网上看看功能介绍
2、大概扫扫架构图
3、看看系统启动了哪些重要组件,这样比较容易切入
4、最后就是在使用中带着问题去看源码
由于第一次写文章,各位看官也就手下留个情
源码中找到启动脚本kafka-server-start.sh可以看到调用的是kafka包下的Kafka类,准确说这是scala object类型
这个过程简单明了,就是object类型的fromProps方法产生KafkaServerStartable对象,
同时定义了一个RunTime hook钩子回调函数,它实现了KafkaServerStartable对象的shutdown方法
接下来就执行红框中方法
kafkaServerStartable.startup()
kafkaServerStartable.awaitShutdown()
显然是启动和等待关闭两个方法,接下来重点看下kafkaServerStartable.startup()都做了哪些事情
KafkaServerStartable本身是个代理,具体执行逻辑交给了KafkaServer去做,重点就是startup()、shutdown()
1、定义了两个AtomicBoolean类型的变量判断如果正在关闭服务及已经完成启动则返回
2、原子操作isStartingUp.compareAndSet判断如果正在启动,则不执行canStartup下方函数
在canStartup function中,先设置brokerState.newState(Starting),这里用状态机类维护BrokerStates状态,继承BrokerStates特质在scala中用模式匹配方式替代java的枚举效果,这里穿插下BrokerStates状态机有哪些状态,见下图:
好了接下来回到启动函数执行中,
3、initZkClient(time)进入代码看到主要是构建连接zk的client变量KafkaZkClient,再者就是在zk 上创建PersistentPath
4、getOrGenerateClusterId(zkClient)利用zkClient创建一个base64位加密的uuid作为_clusterId
5、generate brokerId
getBrokerIdAndOfflineDirs(),此方法的逻辑见下图:
解释下上图的代码
1、判断配置中如果不存在brokerId并且配置brokerId是自生成的,则依赖zk生成一个sequence序列作为brokerId
2、根据配置config.logDirs下面对应的brokerMetadataCheckpoints找到多个brokerIdSet,抛异常这就说明了多个broker共享一个logDirs,这肯定会出问题
3、根据配置config.logDirs下面对应的brokerMetadataCheckpoints找到对应的brokerId等于1但和配置中的brokerId不相等,也抛异常
4、除了以上3中情况外如果从meta.properties能找到唯一一个brokerId,则赋值给当前服务节点brokerId,另外这个offlineDirs主要是记录当前节点配
置下的meta.properties无法读取的N个LogDirs,即不正常的目录有哪些,主要给后面的LogManager对象使用,后面再分析继续往下走
如上图所示,这个是启动kafka的动态参数服务,主要利用zk分布式一致性,将动态参数写到zk路径下对所有节点可见
然后根据配置的后台线程数创建一个KafkaScheduler对象并将之启动,KafkaScheduler对象到底起了什么作用呢?这个貌似还挺重要,咱们进去看下,首先定义一个特质Scheduler如下图所示,4个方法,启动、关闭、判断是否启动、schedule,到这儿基本就明白这个特质是用来干什么的了,调度用的,来看看特质实现类KafkaScheduler的内部实现,先上个图看看此类内部结构
分析下KafkaScheduler的内部实现,它有两个重要成员变量,一个是ScheduledThreadPoolExecutor类型的线程池executor,另外一个原子类型的AtomicInteger主要维护KafkaScheduler生命周期状态,
startup()方法用了并发控制synchronized判断KafkaScheduler没有启动的情况下,根据节点配置指定的线程数、线程池执行策略、以及线程池生成ThreadFactory(控制生成线程名称以及是否后台启动)
shutdown()方法控制着线程池的销毁
scheduleOnce()调度一次执行即完成的task,实际实现由schedule()完成
实现过程:首先确保KafkaScheduler没有关闭;利用CoreUtils构建Runnable线程,线程执行体由入参func:()=>Uinit实现,这里也能看到scala函数一等公民带来的好处,编程更灵活了,下来就是JDK线程池实现类的具体实现了,这个大家都很清楚
resizeThreadPool()对外暴露的重置线程池个数
isStarted()同步方法判断线程池是否为空
ensureRunning()判断是否启动
好了以上应该对KafkaScheduler有了一个整体上的认识,今天先到这里了
接着上次的继续分析源码,这一节主要看kafka如何实现指标数据的收集,UML类图方法参数类型除了java基本类型能使用之外,其他类型还不知道怎么添加,要是有知道的给说下,图没法画了,纯汉字描述吧
指标发布接口定义了四种方法,初始化、指标参数更改时、指标移除时、关闭
JmxReporter类实现指标发布接口,LOCK并发安全控制,Map结构mbeas存储JMX MBean name->KafkaMbean的映射关系,
KafkaMbean实现javax.management的DynamicMBean,objectName表示Mbean的名称,metrics存储KafkaMetric名称和实体KafkaMetric对应关系
KafkaMetric实现Metric接口,
KafkaMetric内部维护指标描述类MetricName、锁lock、时间工具Time、MetricConfig指标配置以及MetricValueProvider指标value提供接口实例
从上面的record方法已经清楚的表达了只要发生了记录值,第一会将当前最新时间赋值给lastRecordTime,同时将值传递给List类型的stats,完成avg、min、max等function函数的值更新
add方法是将MetricName或CompoundStat类型最终转化为KafkaMetric对象,register到Metrics和Mapmetrics;//指标名称->KafkaMetric类映射管理
hasExpired用来做超时判断用的
我们来看此环节最后一个指标流转管理类Metrics
Metrics实例变量如上,再来看看此类构造方法
如果设置enableExpiration=true,将会构造一个个数为1的线程池,同时添加一个kafka-metrics-count的监控指标,那ExpireSensorTask中做了什么工作?
简单来说就是迭代所有传感器sensors,判断如果长时间没有record数据并且超时了,则移除当前sensor,再来看下removeSensor
先定义一个childSensors赋值为空搁在上面,
1、如果sensor.remove(name,sesor)移除返回true,说明原map中有维护,前面也说过,sensor中挂了很多Kafka指标对象,则一一解除
2、如果子节点childrenSensors包含sensor也做移除操作,这里看的不是很明白,传感器按照设计布局,将会是个多叉树结构,自身没必要
再关联自身
3、遍历当前sersor的所有父节点,做父子关系解除
4、最后判断如果当前sensor有子节点childSensors,递归编译子节点做removeSensor操作,说直白点就是父节点都删除了,下面挂的所有子节点做 删除操作
到此为止,我们已经明白了上述4行代码表达的含义,我们继续回到kafkaServer类中继续分析