1.kafka 基础与安装

基础

Kafka是基于Scala开发的多分区、多副本基于ZooKeeper协调的分布式消息引擎系统。和传统的消息队列/消息中间件不同,如下图,kafka一般当作集群用,支持如下功能:

  • 消息系统:类似传统消息队列(中间件)的功能,常用于系统解耦和异步处理,相比前辈它提供了消息顺序性和回溯消费等功能。
  • 存储系统:Kafka默认将消息顺序保存到磁盘上,其提供了多副本机制,非常适合做长期数据存储系统。
  • 流处理系统:Kafka 0.10版本开始提供流处理功能,适合实时处理场景。

kafka常见应用

如下图,kafka体系结构包含如下三部分:

kafka架构图

  • 生产者(Producer),向集群写入消息
  • 消费者(Consumer),消费集群消息,处理业务逻辑
  • 服务代理节点(broker),一般一台服务器部署一个或多个broker

kafka中消息按照主题(Topic)分区(Partition)来划分,一般每种类型或每个业务逻辑消息划分到一个Topic,生产者按照Topic写入,消费者按照Topic订阅消费。

Topic是逻辑上的概念,对应具体存储,一般一个Topic分为多个Partition存储,这样可以支持很好的伸缩而且提高并发写入。为了保证消息不丢失,Kafka引入常见的副本机制(Replication),每个分区有多个副本(Replica)

副本之间是一主多从的关系,leader分区负责对外读写,follower分区负责异步同步follower消息,follower和leader消息存在一定延时,但最终会一致。当leader分区损坏时,会自动从follower分区选举新的leader分区保证高可用。

结合前面的broker来说,每个broker包含多个Partition,一个Topic具体的分区和副本是尽量均布在不同的broker上的,示意如下。

副本示意图

具体的分区磁盘存储类似如下,每个消息写入添加在原有日志文件(Log)末尾,产生一个对应的offset,称为消息偏移(Offset)

磁盘消息格式

实际消费消息时,消费者只需要保存当前消费的位置即可,称为消费者偏移(Offset),由于消息固化在磁盘上,不同的消费者可分别重复消费,每个消费者维持自己的offset,如下:

消息消费offset

具体每个Log日志的结构如下,实际中follower副本和leader副本存在滞后,与leader副本存在一定程度的滞后(包括leader副本)组成ISR(In-Sync Replicas)。与leader副本滞后太冤的称为OSR(Out-of-Sync Replicas)全量副本(Assigned Replicas)=ISR+OSR,只有ISR中副本在leader失效时有资格选为leader。

LEO(Log End Offset)为当前分区消息写入的最后偏移,HW(High Watermark)俗称高水位,消费者只能消费HW偏移之前的消息,ISR集合中最小的LEO即为HW。

具体消息格式

具体到Topic的消费时,因为每个Topic非为多个分区Partition,因此消费时可以指定一个消费组(Consumer Group),消费组包含多个消费者实例(Consumer Instance),每个消费者对应一个分区或多个Partition的消费,这样可以实现Topic的并行处理,如下。当一个消费者死掉的时候,会自动重平横,选用其他消费者来消费Topic数据,也就是常说的重平横(Reblance)

消费组

安装

环境准备

虚拟机环境配置参考

Java环境配置参考

Zookeeper环境配置参考

kafka包下载位置:https://www.apache.org/dyn/closer.cgi?path=/kafka/2.2.0/kafka_2.11-2.2.0.tgz

自动选择最近镜像,进入后下载对应的bin.tar.gz后缀文件,上传虚拟机~/app目录,tar -zxvf xxx 解压,当前使用版本为kafka_2.11-2.2.0.tgz

参数配置

暂时只需要配置如下参数即可运行集群,优化参数后续专门来讲,在config/server.properties中修改如下参数:

  1. broker.id

broker的编号,多个broker设置不同的编号,这里我们依次设置id为0、1、2。

如果没有设置,那么 Kafka会自动生成一个,和broker.id.geηeration. enable 和 reserved.broker.max.id有关。

  1. host.name

这个参数关联如下两个参数:

* listeners 

broker监听客户端连接的地址列表,配置格式为 protocoll : //hostnamel:portl, protocol2://hostname2:port2 ,多个地址则中间以逗号隔开。其中 protocol 代表协议类型, Kafka 当前支持的协议类型有PLAINTEXT、SSL、SASL_SSL 等,如果未开启安全认证,则使用简单的 PLAINTEXT 即可。hostname代表主机名, port此代表服务端口。如果不指定主机名,则表示绑定默认网卡localhost,这样无法对外提供服务,所以主机名最好不要为 空; 如果主机名是 0.0.0.0, 则表示绑定所有的网卡。

* advertised.listeners

和listeners功能类似,默认为空,主要用于IaaS (Infrastructure as a Service)环境,比如公 有云上的机器通常配备有多块网卡,即包含私网网卡和公网网卡,对于这种情况而言,可以设置 advertised.listeners 参数绑定公网IP供外部客户端使用,而配置 listeners 参数来绑定私网IP地址供 broker 间通信使用。

这里默认为空,绑定网卡localhost,无法对外服务,因此我们可以修改

listeners= PLAINTEXT://wenzhou001:9092

更简单的方法是,我们可以直接指定host.name=wenzhou001来对外提供服务

  1. log.dirs/log.dir

log.dir用来配置单个根目录,而log.dirs 用来配置多个根目录(以逗号分隔),同时配置时log.dirs为准。配置多个目录能够提高并发写入性和实现磁盘故障转移。

提前创建好logs目录,配置如下

log.dirs=/Users/wenzhou/Software/kafka_2.11-2.2.0/logs
  1. zookeeper.connect

broker 要连接的ZooKeeper集群的服务地址,多个地址使用逗号分割,最佳的实践方式是再加一个 chroot路径,这样既可以明确指明该chroot路径下的节点是为 Kafka 所用的,也可以实现多个 Kafka 集群复用一套 ZooKeeper 集群,切记 chroot 只需要写一次,而且是加到最后的,比如

zookeeper.connect=localhost1:2181, localhost2:2181,localhost3:2181/kafka

这里为了简单,直接使用根路径,配置如下

zookeeper.connect=localhost1:2181, localhost2:2181,localhost3:2181

简单使用

开启kafka前,先开启Zookeeper集群,然后运行如下命令,开启Kafka集群

nohup bin/kafka-server-start.sh config/server.properties &

jps此时可以看到进程名为kafka的进程在运行,停止运行操作类似

命令行操作

kafka提供了一些命令脚本,可以快速常见操作Topic,和可以做常见的压测。如下,常见一个名为副本数为3/分区数为4/名为demo的Topic。

bin/kafka-topics.sh --create --topic demo --replication-factor 3 --partitions 4 --bootstrap-server wenzhou001:9092,wenznhou002:9092

如下命令,查看常见的Topic详情

bin/kafka-topics.sh --describe --topic demo  --bootstrap-server wenzhou001:9092,wenzhou002:9092

详情如下,可以看到新建的Topic,有四个分区,分区0/3 leader副本在broker.id=0上,分区1/2 leader副本分别在broker.id=2/1上。由于这里副本和broker数量一致,因此每个broker上都存在对应的副本,因此Isr包含3个broker上分区,第一个为leader分区。

Topic:demo	PartitionCount:4	ReplicationFactor:3	Configs:segment.bytes=1073741824
	Topic: demo	Partition: 0	Leader: 0	Replicas: 0,2,1	Isr: 0,2,1
	Topic: demo	Partition: 1	Leader: 2	Replicas: 2,1,0	Isr: 2,1,0
	Topic: demo	Partition: 2	Leader: 1	Replicas: 1,0,2	Isr: 1,0,2
	Topic: demo	Partition: 3	Leader: 0	Replicas: 0,1,2	Isr: 0,1,2

在一台机器上运行如下命令,打开一个生产者命令行

kafka_2.11-2.2.0]$ bin/kafka-console-producer.sh --topic demo --broker-list wenzhou001:9092,wenzhou002:9092

输入如下

>test1
>test2
>test3

再起一个shell,运行如下命令,监控并输出最新生产的消息,但是已经生产的不会再输出

bin/kafka-console-consumer.sh --topic demo --bootstrap-server wenzhou001:9092,wenzhou002:9092

要想从头开始输出,可添加beginning参数,如下

bin/kafka-console-consumer.sh --topic demo --from-beginning --bootstrap-server wenzhou001:9092,wenzhou002:9092

此时再在生产者输入新的内容,两个shell消费都会输出最新内容。

生成者和消费者编写

这里使用本地集群演示代码生产和消费Topic,如下

生产者

producer的原理是为每个Partition分区维持一个本地缓冲buffer,send的数据先保存在本地buffer,另有一个后台IO线程监测到达一定数量后再批量发送,相关参数包括如下

  • batch.size

本地缓冲buffer的大小,超出这个消息数,会被批量提交

  • buffer.memory

本地所有分区缓冲buffer的内存总上限,超过内存占用,后续写入会阻塞,阻塞超过max.block.ms时间会抛出异常

按时间分片,指定时间内聚合消息批量发送

这三个参数是相互影响的,以最快到达瓶颈的参数来触发限制。

因为发送是异步的,因此producer本身还支持重试和应答控制。acks=all要求所有副本写入完成确认后才算发送成功,这样最慢但是一致性最强。retries强调消息发送失败时自动重试最大次数。

producer是线程安全的,推荐多个线程公用一个producer对象,因为他们都是依赖后台线程发送的,没必要使用多个。

key.serializer和value.serializer指定序列化方式,这里默认使用java序列化。

0.11版本引入了kafka 幂等生产者和生产者事务,这个后续kafka特性一节再详解。

    public static void Generate()
    {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("acks", "all");
		props.put("retries", 0);
		props.put("batch.size", 16384);
		props.put("linger.ms", 1);
		props.put("buffer.memory", 33554432);
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        for (int i = 0; i < 10; i++)
        {
            try {
                producer.send(new ProducerRecord<String, String>("test", Integer.toString(i), "Hello World-"+i));
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        producer.close();
    }
消费者

kafka 消费端API从0.10开始有较大改变,如果是使用之前版本API请参考官方对应版本文档。新版API只需要指定bootstrap.servers即可,不再需要指定Cluster的Zookeeper地址,这里不需要指定全部broker机器,kafka会自动根据指定的bootstrap.servers机器获取全部broker列表,这里最后指定多台,保证一定的冗余性,防止部分机器宕机的情况影响。

原理部分已经说明,Kafka消费端以消费组为单位消费Topic数据,消费组自动实现平衡,broker增减或Consumer增减,均会自动重平衡,group.id指定消费组ID,group.id相同的当作一个消费组。

实际使用,Consumer订阅指定的Topic,每次调用poll获取指定数量的消息数,每条消息中的offset指下一个要处理的消息位移。指定offset的消息处理完后,需要提交committed position,有两种方法,自动提交和手动提交,enable.auto.commit指定开启自动提交,auto.commit.interval.ms指明自动提交的间隔。

自动提交只要poll调用成功,就默认消息处理成功了,此时会自动提交commit position,至于后续消息如何处理存储和是否成功,都不再关心,这样处理简单,但是没法完全保证消息业务
成功处理。

手动提交允许自己控制何时确认提交committed position,保证消息业务完成后再提交, commitSync和commitAsync支持提交本次poll偏移或指定offset position。手动提交允许更加精细的控制,常用于配合Outside Kafka Offset存储实现精确一次性语义控制,和自定义分区绑定特性后续kafka特性一节再详解。

Consumer同样支持失败自动重试,超过Consumer和broker之间的心跳时间session.timeout.ms或Consumer最大处理时间(两次poll调用时间)max.poll.interval.ms,均认为Consumer不可用,会自动重平衡其他Consumer来处理当前数据。另外可用max.poll.records指定每次poll最大消息数,合理的调整此参数可减少max.poll.interval.ms参数。

Consumer是非线程安全的,可每个线程/进程使用一个Consumer对象或者使用线程同步技术,多个线程共享Consumer对象。

    public static void ReadRecords()
    {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "test");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList("test"));
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records)
                System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
        }
    }

原创,转载请注明来自

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值