Kafka

概述

Apache Kafka是一个分布式的流数据平台,代表三层含义:

  • Publish/Subscribe: 消息队列系统 MQ(Message Queue)
  • Process: 流数据的实时处理(Stream Process)
  • Store: 流数据会以一种安全、容错冗余存储机制存放到分布式集群中

架构

Kafka架构

  • Cluster: kafka支持一到多个服务构成的分布式集群,每一个服务实例称为Broker
  • Topic: 某一个分类的消息的集合,如:订单的topic、商品的topic等
  • Partition: 一个Topic有若干个分区(Partition)构成,分区的数量在创建Topic时手动指定
  • Replication: 分区副本,是Partition的冗余备份分区,当Partition不可用时,ZooKeeper会自动将Replication(Follower)分区升级为Partition(Leader)分区
  • Offset: 分区中的Record的位置标示,每一个消费者都会记录自己的消费位置(offset)

安装

  1. 修改网卡
# 修改网卡
vim /etc/sysconfig/network-scripts/ifcfg-ens37
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens37
DEVICE=ens37
ONBOOT=yes
IPADDR=192.168.24.11

# 重启网卡服务
service network restart
  1. 安装java环境
  2. 安装zookeeper
# 解压zk
tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz -C /home/software

# 拷贝
cd /home/software/apache-zookeeper-3.6.0-bin/conf
cp zoo_sample.cfg zoo.cfg

# 修改配置文件,单机模式下默认配置即可
vim zoo.cfg

# 启动zk
bin/zkServer.sh  start conf/zoo.cfg
QuorumPeerMain # zk进程

# 关闭zk
bin/zkServer.sh  stop conf/zoo.cfg
  1. 安装Kafka
# 解压
tar -zxvf kafka_2.13-2.4.1.tgz -C /home/software/

# 修改配置文件,如果是集群安装同样需要这些配置
vim /home/software/kafka_2.13-2.4.1/config/server.properties
# 如果是集群,broker.id按照节点改变即可
broker.id=0
listeners=PLAINTEXT://:9092
log.dirs=/home/software/kafka_2.13-2.4.1/data
zookeeper.connect=zk01:2181,zk02:2181,zk03:2181

# 启动服务
bin/kafka-server-start.sh -daemon config/server.properties
Kafka # kafka进程

# 关闭服务
bin/kafka-server-stop.sh config/server.properties

# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

基本使用

# 创建topic
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --create --topic testTopic --partitions 1 --replication-factor 1

# 展示topic列表
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --list

# 描述topic
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --topic testTopic --describe

# 删除topic
bin/kafka-topics.sh --bootstrap-server 192.168.24.11:9092 --topic testTopic --delete

查看消费者组

# 查看消费者组列表
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list

# 查看消费者组消费情况
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group g1

在这里插入图片描述

生产者和消费者

# 生产者
bin/kafka-console-producer.sh --broker-list 192.168.24.11:9092 --topic testTopic

# 消费者
bin/kafka-console-consumer.sh --bootstrap-server 192.168.24.11:9092 --topic testTopic --from-beginning

Java API

Maven依赖

<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.2.0</version>
</dependency>

生产者

package kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ProducerTest
 * @Description: 生产者
 * @Author Lyb
 * @Date 2020/7/6
 **/
public class ProducerTest extends Thread {

    private static String topic = "testTopic";
    private int messageNumToSend = 1000;

    public ProducerTest(){}

    public ProducerTest(int messageNumToSend){
        this.messageNumToSend = messageNumToSend;
    }


    public Producer createProducer(){
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.24.11:9092");
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);

        return new KafkaProducer<String,String>(prop);
    }

    @Override
    public void run() {
        Producer producer =createProducer();
        int i = 0;
        while(i<1000){
            producer.send(new ProducerRecord<Integer, String>(topic, "message:"+i++));
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
            close(producer);
    }

    public void close(Producer producer){
        producer.close();
    }

    public static void main(String[] args) {
        ProducerTest producerTest = new ProducerTest(100);
        producerTest.run();
    }
}

消费者

package kafka.consumer;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

/**
 * @ClassName ConsumerTest
 * @Description: 消费者
 * @Author Lyb
 * @Date 2020/7/6
 **/
public class ConsumerTest extends Thread{
    private String topic;

    public ConsumerTest(String topic){
        super();
        this.topic =topic;
    }

    @Override
    public void run() {
        Properties prop = createPropertes();
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

        KafkaConsumer<String,String> consumer = new KafkaConsumer(prop);

        consumer.subscribe(Collections.singletonList(topic));

        while(true){
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(100));
            for (ConsumerRecord<String, String> record : records) {
                System.out.println(record.key()+" " + record.value() + " " + record.offset());
            }
        }

    }
    private Properties createPropertes(){
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.24.11:9092");
        properties.put("zookeeper.connect", "192.168.24.11:2181");
        properties.put("group.id", "g1");
        return properties;
    }

    public static void main(String[] args) {
        new ConsumerTest("testTopic").start();
    }
}

高级部分

偏移量控制

Kafka消费者在订阅Topic时,会自动拉取Topic中新产生的数据。首次消费时使用默认的偏移量消费策略lastest(最新)

偏移量消费策略:

  • lastest: 如果有已提交的offset,从已提交的offset之后消费消息。如果无提交的offset,从最后的offset之后消费数据
  • earliest: 如果有已提交的offset,从已提交的offset之后消费消息。如果无提交的offset,从最早的offset消费消息
// 注意:此配置项 修改偏移量消费策略的默认行为 
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");

Kafka消费者消费位置offset,默认采用自动提交的方式,将消费位置提交保存到特殊Topic__consumer_offsets

自动提交策略:

// 默认自动提交消费的位置offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
// 默认每隔5秒提交一次消费位置
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,5000);

通常情况需要手动提交消费位置:

为什么需要手动提交消费位置(offset)的原因?
原因:如果自动提交消费位置,有可能在进行业务处理时出现错误,会造成数据没有被正确处理。
手动提交消费位置,可以保证数据一定能够被完整的正确处理。

// 关闭消费位置offset的自动提交功能
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);

// 手动提交消费位置
consumer.commitSync();

消费方式

订阅
// 订阅(消费)Topic所有的分区
consumer.subscribe(Arrays.asList("t3"));
指定消费分区
// 指定消费Topic的特定分区
consumer.assign(Arrays.asList(new TopicPartition("testTopic",0)));
重置消费位置
consumer.assign(Arrays.asList(new TopicPartition("testTopic",0)));
// 重置消费位置
consumer.seek(new TopicPartition("testTopic",0),1);
消费者组

组内分发,组外广播

生产者的批量发送
kafka生产者产生的多条数据共享同一个连接,发送保存到Kafka集群,这种操作方式称为Batch(批处理)。

批处理相比于传统的发送方式,资源利用率更为高效,是一种比较常用的生产者优化策略。
使用方式
# 生产者方 添加如下配置项即可
# 两个条件 满足其一即可
batch.size = 16384Bytes  16kb// 缓冲区大小
linger.ms = 毫秒值    // 缓冲区中数据的驻留时长 
// java代码
prop.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
prop.put(ProducerConfig.LINGER_MS_CONFIG,2000);
生产者幂等操作

幂等:指的多次操作,影响结果是一致的,这种操作方式就被成为幂等操作。使用Kafka生产者幂等操作的原因,kafka生产者在重试发送生产数据时,多次重试操作只会在Kafka的分区队列的末尾写入一条记录

ack三种策略:
在这里插入图片描述

properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true); // 开启幂等操作支持
properties.put(ProducerConfig.ACKS_CONFIG,"all");  // ack时机 -1或者all 所有  1 leader  0 立即应答
properties.put(ProducerConfig.RETRIES_CONFIG,5);   // 重复次数
properties.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 3000); // 请求超时时间
kafka的事务

数据库事务: 一个连接中多个操作不可分割,是一个整体,要么同时成功,同时失败。
Kafka的事务:类似于数据库事务,每一个事务操作都需要一个唯一的事务ID(Transaction-ID),并且事务默认的隔离级别为READ_UNCOMMITTEDREAD_COMMITTED

生产者事务

生产者事务: Kakfka生产者生产的多条数据是一个整体,不可分割,要么同时写入要么同时放弃

要求
  • kafka生产者提供唯一的事务ID
  • 必须开启kafka的幂等性支持
事务操作
  • 初始化事务
  • 开启事务
  • 正确操作 提交事务
  • 操作失败 回滚事务
生产者API
package kafka.producer;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.UUID;

/**
 * @ClassName ProducerTransaction
 * @Description: 生产者事务
 * @Author Lyb
 * @Date 2020/7/14
 **/
public class ProducerTransaction {
    public static void main(String[] args) {
        //1. 准备Kafka生产者配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.24.11:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);

        // 事务ID, 唯一不可重复
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, UUID.randomUUID().toString());
        // 开启幂等操作支持
        properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        properties.put(ProducerConfig.ACKS_CONFIG,"all");  // ack时机 -1或者all 所有  1 leader  0 立即应答
        properties.put(ProducerConfig.RETRIES_CONFIG,5);   // 重复次数
        properties.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 3000); // 请求超时时间

        //2. 创建kafka生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        // 初始化事务
        producer.initTransactions();

        // 开启事务
        producer.beginTransaction();

        try {
            //3. 生产记录并将其发布
            for (int i = 50; i < 60; i++) {
                if(i == 56) {
                	// 人为制造错误
                    int m = 1/0;
                }
                // key不为null  第一种策略
                ProducerRecord<String, String> record = new ProducerRecord<>("testTopic", UUID.randomUUID().toString(),"Hello Kafka"+i);
                // key为null 轮询策略
                producer.send(record);
            }
            // 提交事务
            producer.commitTransaction();
        } catch (Exception e) {
            e.printStackTrace();
            // 取消事务
            producer.abortTransaction();
        } finally {
            //4. 释放资源
            producer.flush();
            producer.close();
        }
    }
}
消费者API
// 其余代码 一致
// 修改消费者默认的事务隔离级别
properties.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG,"read_committed");
生产消费并存事务(consume-transform-produce)

指消费和生产处于同一个事务环境中,要么消费生产同时成功,要么同时失败

要求
  • kafka生产者提供唯一的事务ID
  • 必须开启kafka的幂等性支持
  • 关闭offset的自动提交功能
  • 不能调用手动提交的方法,如: consumer.commitSync();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值