package spark_guohang
import java.util.Properties
import kafka.common.TopicAndPartition
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.streaming.kafka010.OffsetRange
import org.apache.zookeeper.Watcher.Event.EventType
import org.apache.zookeeper.ZooDefs.Ids
import org.apache.zookeeper.{CreateMode, WatchedEvent, Watcher, ZooKeeper}
//摘选自国航demo中在zookeeper中维护kafka的offset,①-④连接zk(initZK递归自己) 监听节点(process) 设置配置信息(chgFlag已更改,如过zk没有开通则调用initzk再一次在设置一次)⑤-⑥为读取offset维护offset
//zk所在地 zk监听的目录节点 zk的通知机制,即继承watch类后实现process方法里面的参数为events即可获得所有改动过后的数据https://blog.youkuaiyun.com/zkp_java/article/details/82711810
class ZookeeperWatchDemo(zkQuorum:String,zkCfgNode:String) extends Watcher {
var zk: ZooKeeper =_ //这种_的用法代表使用默认值,引用数据类型为null
var timeout:Int=100000
var conf:Properties=new Properties()
/*④try:此时拿到的为节点的状态和数据 但是为二进制所以需要转为string类型,然后将此数据上的 " 变更为 ,然后按照|切割
*利用map将里面的参数配置取出来并按=来切拿取0设置k和拿取1设置v,(此时需要设置conf定义为默认空值),此时我们将配置全部设置上了
* 然后调用chgFlag方法设为true代表我们已经设置上配置信息了
* catch:如果没有设置成功则睡三秒,如果zk的状态是关闭的或者没有连接的状态,先关闭一下zk 然后在初始化zk,最后在调用一次initzk(直到成功配置上信息)
*getdata为二进制数组 第一个参数是监听的目录节点,第二参数为是否监听此节点,第三个参数为节点状态
*/
def refreshConf: Unit ={
/*
zk.getDate获取到zkCfgNode的相关数据,是一个byte[].那么肯定是诸如"/zk/hadoop/.../.../"之类的玩意。让后将所有的"替换掉。
DEFAULT_DELIMITER应该是个常量。。。应是个`,`。最后遍历split后的数组。
*/
try {
new String(zk.getData(zkCfgNode, false, null)).replaceAll("\"", "")
.split("\\|").map(f => conf.setProperty(f.split("=")(0), f.split("=")(1)))
this.chgFlag(true) // "/testzwd","offsetZknode=/kafkaoffsetd|zkTimeout=10000l"
}catch
{
case e:Exception=>{
Thread.sleep(3000)
if(zk.getState==ZooKeeper.States.CLOSED || zk.getState==ZooKeeper.States.NOT_CONNECTED){
zk.close()
initZk()
this.refreshConf
}
}
}
}
/**①连接zk服务
*try:刚开始zk肯定是没有开启状态 所以必然是null状态和没有存活状态 但是还是要先判断严谨!先锁一下当前线程
* 先判断zk是否为空 如果为空则关闭zk(以防万一),重新建立连接(newzk),然后监听指定的目录变化
* 只要zk的状态为非连接状态,则睡一下3000毫秒,再一次连接直到成功
* catch:抛出zk连接异常后3秒继续初始化,关掉zk服务递归自己(initzk)
*/
private def initZk(): Unit ={
try {
if (zk == null || !zk.getState.isAlive) this synchronized {
if (zk == null) zk.close()
zk = new ZooKeeper(zkQuorum, timeout, this)//zk所在地,zk会话时间,监听节点的事件
zk.exists(zkCfgNode, true)
while (zk.getState ne ZooKeeper.States.CONNECTED) {
Thread.sleep(3000)
}
}
}catch{
case e:Exception=>{
Thread.sleep(3000)
if(zk.getState==ZooKeeper.States.CLOSED || zk.getState==ZooKeeper.States.NOT_CONNECTED){
zk.close()
initZk()
}
}
}
}
/**③
* @param ischange 这里的property是传给哪里了??
*/
def chgFlag(ischange:Boolean): Unit ={
conf.setProperty("ischange",ischange.toString)
}
/**②监听事件数据变动并及时刷新
* @param watchedEvent 为监听的事件数据变动
* 这个类为实现监听节点的变动
* watchedEvent.getType: 当前的状态
* EventType.NodeDataChanged 事件的类型为 节点信息变动
* zk.exists 返回监听此路径上节点信息的变动 true为监听 false为不监听
*
*/
override def process(watchedEvent: WatchedEvent): Unit = {
if(watchedEvent.getType==EventType.NodeDataChanged){
zk.exists(zkCfgNode,true)
this.refreshConf
}
}
/**⑤读取topic中的offset-->readOffsets方法是获得offset的字符串,然后取offsetRangesStrOpt里面匹配我们想要的结果
*先获取到ofsetzknode的地址(在zk的所在位置) 然后拼接字符串将地址与主题组在一起(offsetTopic),
* 用来获取zk中kafka目录的数据(offset)然后返回给offsetRangesStrOpt变量
* offsetRangesStrOpt变量匹配 只要是有值就匹配到offsetsRangesStr 没有值就会匹配none,然后offsetsRangesStr其实就是offset的数据
* 按照,切割在按照:切割,匹配一个array的数组Array(partitionStr, offsetStr),将其变成一个map类型的输出给offsets,
* 到了这里我们就读取到top的offset的所有信息了
*
* @param topic
* @return
*/
def readOffsets(topic:String):Option[Map[TopicAndPartition,Long]]={
val offsetParent=conf.getProperty("offsetZknode")
val offsetTopic=offsetParent+"/"+topic
val offsetRangesStrOpt={
val offset=zk.getData(offsetTopic,false,null)
if(offset==null)None//如果为空 返回空
else Some(new String(offset))//如果有值返回值 推测可能是byte
}
offsetRangesStrOpt match { //offsetsRangesStrOpt的模式匹配,其实就是offset内的数据
case Some(offsetsRangesStr) => //如果匹配到Some。Some是scala中的一个类,该类代表有东西返回...... 这里的offsetsRangesStr其实就是offset a中的一个类,该类代表有东西返回。。。。
// "/testzwd","offsetZknode=/kafkaoffsetd|zkTimeout=10000l"
val offsets = offsetsRangesStr.split(",") //关于这个逻辑,你应该去自己试试看。自己写个zk的类,然后看看kafka在zk中的数据格式是什么样子的,会更加好理解。
.map(s => s.split(":"))
.map { case Array(partitionStr, offsetStr) => (TopicAndPartition(topic, partitionStr.toInt) -> offsetStr.toLong) }
.toMap //看逻辑,会获取到([topic,partitionid],offset)
Some(offsets) //返回offsets
case None => //什么都没有匹配到的话就返回None
None
}
}
/**
* ⑥这个逻辑是将streaming消费者的offset更新到zk中
* 将offsetrange为我已经使用过得偏移量offset,取出分区和offset的起始,如果此节点为null说明此分区没有offset需要创建一个
* 创建的参数为 offset的地址,数据,List<ACL>,是否为临时节点
* 如果此分区中有则设置此分区.
* @param topic
* @param offsetsRanges
*/
def saveOffsets(topic:String,offsetsRanges:Array[OffsetRange]): Unit ={
val offsetParent= conf.getProperty("offsetZknode")
val offsetToptic=offsetParent+"/"+topic
val offsetsRangesStr = offsetsRanges.map(offsetRange=>s"${offsetRange.partition}:${offsetRange.fromOffset}").mkString(",")
if(null == zk.exists(offsetsRangesStr,false)){
zk.create(offsetToptic,null,Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT)
}
zk.setData(conf.getProperty("offsetZknode"),Bytes.toBytes(offsetsRangesStr),zk.exists(conf.getProperty("offsetZknode"),false).getVersion)
}
}
zk维护kafkaoffset
最新推荐文章于 2022-12-28 21:23:48 发布
由于未提供博客具体内容,暂无法生成包含关键信息的摘要。
2278

被折叠的 条评论
为什么被折叠?



