kafka提高篇总结

Kafka

继kafka基础之后再来点高级实用的。顺便做一个kafka整理总结。

查看kafka自身维护偏移量:kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list master:9092 --topic flink --time -1

1、kafka自定义分区

分析

步骤:
    1,设计一个子类继承分区父类,重写其中的partition方法,在该方法中定制分区规则
    2,修改producer.properties文件,指定自定的分区类

源码以及效果

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;
import java.util.List;
import java.util.Map;

public class MyPartition implements Partitioner {
    /**
     * 下述方法在消息存储到相应分区之前,都会被回调一次。
     * 原因:消息得找到自己存储的所在。
     *
     * @param topic      主题名
     * @param key        消息的key
     * @param keyBytes   key对应的字节数组
     * @param value      消息的value
     * @param valueBytes value对应的字节数组
     * @param cluster    kafka分布式集群
     * @return
     */
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        //思路:
        //①获得当前主题的分区数
        List<PartitionInfo> partitionInfos = cluster.availablePartitionsForTopic(topic);
        int totalPartitionNum = partitionInfos.size();
        //②关于key
        // a)key≠null, key的hash码值对分区数求余数,余数即为当前消息所对应的分区编号
        //b)key=null, 根据value来计算
        //i)value ≠null, value的hash码值对分区数求余数,余数即为当前消息所对应的分区编号
        //ii)value=null, 直接返回默认的分区编号,如:0.

        if (key != null) {
            return key.hashCode() % totalPartitionNum;
        } else {
            if (value != null) {
                return value.hashCode() % totalPartitionNum;
            } else {
                return 0;
            }
        }
    }

    @Override
    public void close() {
    }
    @Override
    public void configure(Map<String, ?> configs) {
    }
}

2、 消息拦截器

介绍

①在消息发送到kafka分布式集群之前,针对于一些共通的业务处理,建议使用拦截器。如:对每条消息添加一个共通的前缀(如:时间戳),对消息中包含一些反动的信息进行筛选
②还可以监测到消息发送的状态(成功,失败)
③需求:实现一个简单的双interceptor组成的拦截链。第一个interceptor会在消息发送前将时间戳信息加到消息value的最前部;第二个interceptor会在消息发送后更新成功发送消息数或失败发送消息数。
④核心api:ProducerIntercepter

源码以及效果

import org.apache.kafka.clients.producer.*;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Properties;

public class InteceptorUsageDemo {
    public static void main(String[] args) {
        //步骤:
        Producer<Integer, String> producer = null;
        try {
            //①准备Properties的实例,并将资源目录下的配置文件producer.properties中定制的参数封装进去
            Properties properties = new Properties();
          properties.load(InteceptorUsageDemo.class.getClassLoader().getResourceAsStream("producer.properties"));
            //将拦截器封装到properties实例中,作为参数来构建KafkaProducer
            Collection<String> params = new LinkedList<>();
            Collections.addAll(params, "com.intercepter.TimeIntecepter",
                    "com.intercepter.StatusIntecepter");
            properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, params);
            //②KafkaProducer实例的创建
            producer = new KafkaProducer(properties);
            //③通过循环模拟发布多条消息
            for (int i = 1; i <= 10; i++) {
                //a)准备消息
                ProducerRecord<Integer, String> record = new ProducerRecord<>("flink", i, i + "\t→ 走起!呵呵哒哒...");
                //b) 发布消息
                producer.send(record, new Callback() {
                    /**
                     * 当前待发送的消息发送完毕后,下述方法会被回调执行
                     *
                     * @param metadata
                     * @param exception
                     */
                    @Override
                    public void onCompletion(RecordMetadata metadata, Exception exception) {
                        System.out.printf("当前的消息对应的主题是:%s,内容是:%s,所在的分区是:%d,偏移量是:%d%n",
                          metadata.topic(), record.value(), metadata.partition(), metadata.offset());
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //⑤资源释放
            if (producer != null) {
                producer.close();
            }
        }
    }
}
package com.intercepter;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Date;
import java.util.Map;

public class TimeIntecepter implements ProducerInterceptor<Integer, String> {
    /**
     * 每条消息发送之前,下述方法会被执行
     * @param record
     * @return
     */
    @Override
    public ProducerRecord<Integer, String> onSend(ProducerRecord<Integer, String> record) {
        return new ProducerRecord<Integer, String>(record.topic(), record.partition(),
                record.timestamp(), record.key(),
                new Date() + "→" + record.value(), record.headers());
    }
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
    }

    @Override
    public void close() {
    }

    @Override
    public void configure(Map<String, ?> configs) {
    }
}

package com.intercepter;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Map;

public class StatusIntecepter implements ProducerInterceptor<Integer, String> {
    /**
     * 成功消息数
     */
    private int successCnt;

    /**
     * 失败消息数
     */
    private int failureCnt;
    @Override
    public ProducerRecord<Integer, String> onSend(ProducerRecord<Integer, String> record) {
        return record;
    }
    /**
     * 每次发送完毕一条消息,下述的方法会被回调执行
     * Ack: 应答机制
     * @param metadata
     * @param exception
     */
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        //根据参数2来判断消息是否发送成功
        //=null,成功
        //≠null,失败
        if (exception == null) {
            successCnt += 1;
        } else {
            failureCnt += 1;
        }
    }

    @Override
    public void close() {
        //显示结果
        System.out.printf("成功的消息数是:%d,失败的消息数:%d%n", successCnt, failureCnt);
    }
    @Override
    public void configure(Map<String, ?> configs) {
    }
}

1.4、 Kafka与flume的整合

说明

步骤:
1,编辑配置文件
		# flume-kafka.properties: 用来定制agent的各个组件的行为(source,channel,sink)
		############################################
		# 对各个组件的描述说明
		# 其中a1为agent的名字
		# r1是a1的source的代号名字
		# c1是a1的channel的代号名字
		# k1是a1的sink的代号名字
		############################################
		a1.sources = r1
		a1.sinks = k1
		a1.channels = c1

		# 用于描述source的,类型是netcat网络,telnet 
		a1.sources.r1.type = netcat
		# source监听的网络ip地址和端口号
		a1.sources.r1.bind = NODE01
		a1.sources.r1.port = 44444

		# 用于描述channel,在内存中做数据的临时的存储
		a1.channels.c1.type = memory
		# 该内存中最大的存储容量,1000个events事件
		a1.channels.c1.capacity = 1000
		# 能够同时对100个events事件监管事务
		a1.channels.c1.transactionCapacity = 100

		# 用于描述sink,类型是日志格式,用于定制消息发布方的参数
		a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
		a1.sinks.k1.topic = hive
		a1.sinks.k1.brokerList = NODE01:9092,NODE02:9092,NODE03:9092
		a1.sinks.k1.requiredAcks = 1
		a1.sinks.k1.batchSize = 20

		# 将a1中的各个组件建立关联关系,将source和sink都指向了同一个channel
		a1.sources.r1.channels = c1
		a1.sinks.k1.channel = c1

	2,开启flume日志采集服务(后台)~> -Dflume.root.logger=INFO,console
	flume-ng agent  --conf-file flume-kafka.properties --name a1 
	以后台进程的方式启动:
	nohup flume-ng agent  --conf-file flume-kafka.properties --name a1   > /dev/null    2>&1 &
		 
	3,使用netcat向4444端口写入
	  > nc NODE01 44444
	    hello are you ready?
		 _____________
			前提: 
			  需要安装telnet或者是netcat
					运行方式总结:
						一:telnet
							需要在centOS上面安装telnet(注意:在线安装方式 yum install telnet)
							启动flumn-agent
							启动telnet:
								telnet NODE01 44444
								
						二:netcat(注意:在线安装方式 yum install -y nc)
							安装发给大家的nc.xx.rpm
							rpm -ivh nc.xx.rpm-path
							启动flumn-agent
							启动nc进程
								nc NODE01 44444
			 ______________

	4,开启一个kafka消费的进程
	  kafka-console-consumer.sh --topic hive    --zookeeper NODE01:2181,NODE02:2181  --from-beginning

1.5 kafka学后总结

1、Segment的概念:
	一个分区被分成相同大小数据条数不相等的Segment,
	每个Segment有多个index文件和数据文件组成
	
2、数据的存储机制(就是面试题中kafka速度为什么如此之快):
	首先是Broker接收到数据后,将数据放到操作系统的缓存里(pagecache),
	pagecache会尽可能多的使用空闲内存,
	使用sendfile技术尽可能多的减少操作系统和应用程序之间进行重复缓存,
	写入数据的时候使用顺序写入,写入数据的速度可达600m/s

3、Consumer怎么解决负载均衡?(rebalance)
	1)获取Consumer消费的起始分区号
	2)计算出Consumer要消费的分区数量
	3)用起始分区号的hashCode值模余分区数

4、数据的分发策略?
	Kafka默认调用自己分区器(DefaultPartitioner),
	也可以自定义分区器,需要实现Partitioner特质,实现partition方法
	
5、Kafka怎么保证数据不丢失?
	Kafka接收数据后会根据创建的topic指定的副本数来存储,
	也就是副本机制保证数据的安全性
	
6、Kafka的应用:
	①作为消息队列的应用在传统的业务中使用高吞吐、分布式、使得处理大量业务内容轻松自如。
	②作为互联网行业的日志行为实时分析,比如:实时统计用户浏览页面、搜索及其他行为,结合实时处理框架使用实现实时监控,或放到 hadoop/离线数据仓库里处理。
	③作为一种为外部的持久性日志的分布式系统提供服务。主要利用节点间备份数据,文件存储、日志压缩等功能。
	
	——————
	其他应用场景:
		① 企业内部指标
			对于某些时效性要求较高的指标,如预警指标等,必须在数据变化时
			及时计算并发送信息
		② 通信服务运营商
			 对于用户套餐中的剩余量进行监控,如流量,语音通话,短信
		③ 电商行业
			对于吞吐量特别大和数据变动频次较高的应用,如电商网站,必须使
			用实时计算来捕捉用户偏好

7、Kafka组件:
	①每个partition在存储层面是append log文件。新消息都会被直接追加到log文件的尾部,每条消息在log文件中的位置称为offset(偏移量)。
	②每条Message包含了以下三个属性:
	  1° offset	对应类型:long  此消息在一个partition中序号。可以认为offset是partition中Message的id
	  2° MessageSize  对应类型:int。
	  3° data  是message的具体内容。
	③越多的partitions意味着可以容纳更多的consumer,有效提升并发消费的能力。
	④总之:业务区分增加topic、数据量大增加partition (副本数<=broker节点数)。
	
8、实时流处理框架如Storm, Spark Streaming如何实现实时处理的,底层封装了Kafka Stream。
	  若是手动实现实时处理的框架,需要自己使用Kafka Stream 库。
   <dependency>
		<groupId>org.apache.kafka</groupId>
		<artifactId>kafka-streams</artifactId>
		<version>1.0.2</version>
	</dependency>
	
9、维护消息订阅方消费的offset的方式有哪些?
	 ①zookeeper ,参数:--zookeeper 
	 ②kafka集群来维护,参数:--bootstrap-server
		主题名:__consumer_offsets,  默认: 50个分区; 默认的副本数是:1
		若是达到默认的主题__consumer_offsets的分区的ha (高容错),需要在server.properties文件中定制默认的副本数:
		default.replication.factor=3
	 ③手动维护偏移量 (一般使用redis存储偏移量)

 10,几个问题:
		①每次启动一个消费者进程(kafka-console-consumer.sh),是一个单独的进程
		②手动书写的消费者,可以通过参数来定制是从头开始消费,还是接力消费。需要指定flg (main: args[])
		③kafka-console-consumer.sh,每次开启一个消费者进程,有一个默认的消费者组。命名方式是:console-consumer-64328
		④PachCache, SendFile 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值