Kafka——NWU_LK

Kafka

定义

Kafka 是一种高吞吐量、分布式、基于发布/订阅的消息系统,主要用于大数据实时处理领域。最初由 LinkedIn 公司开发,使用 Scala 语言编写,目前是 Apache 的开源项目。

消息队列作用
  • 解耦:允许独立的拓展或者修改两边的处理过程,只要确保它们遵守同样的接口
  • 可恢复性:即时系统中处理消息的进程挂掉,消息队列中的消息也不会丢失,可以在系统恢复后被继续处理
  • 缓冲:有助于控制和优化数据流经过系统的速度,解决生产者和消费者处理速度不一致的问题,一般生产大于消费需要解决
  • 削峰
  • 异步通信
消息队列两种模式
  • 点对点模式:消费者主动获取数据
  • 发布/订阅模式:订阅模式,消费者被动的收取数据或主动的拉取数据,而kafka基于消费者主动拉取数据
Kafka中的相关概念

Kafka中发布订阅的对象是topic。可以为每类数据创建一个topic,把向topic发布消息的客户端称作producer,从topic订阅消息的客户端称作consumer。Producers和consumers可以同时从多个topic读写数据。一个kafka集群由一个或多个broker服务器组成,它负责持久化和备份具体的kafka消息。

  • Broker(代理者 ):Kafka集群中的机器 /服务被称为broker, 是一个物理概念。一个Kafka节点就是一个broker,多个broker可以组成一个Kafka集群。注意:一台节点上可以有多个broker,这和HDFS上一个节点是一个namenode或者datanode有所区别。一台机器上的broker数量由server.properties的数量决定。

  • Topic:一类消息,消息存放的目录即主题,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能够同时负责多个topic的分发。一个broker可以容纳多个topic,因此在一台broker上一个topic可以分leader和follower,follower负责备份。

  • Partition:topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列,producer端发送的message必须指定是发送到哪个topic,但是不需要指定topic下的哪个partition,因为kafka会把收到的message进行load balance,均匀的分布在这个topic下的不同的partition上。每个分区只能被同一个消费者组里的一个消费者消费。在数据的产生和消费过程中,不需要关注具体存储的Partition在哪个Broker上,只需要指定 Topic 即可,由 Kafka负责将数据和对应的 Partition关联上。 一般来说,一个Topic的Partition数量大于等于Broker的数量,可以提高吞吐率;同一个Partition的Replica尽量分散到不同的机器,做到高可用。

  • Partition Replica:每个partition可以在其他的Kafka broker节点上存副本,以便某个Kafka broker节点宕机不会影响这个Kafka集群。存replica副本的方式是按照Kafka broker的顺序存。例如有5个Kafka broker节点,某个topic有3个partition,每个partition存2个副本,那么partition1存broker1,broker2,partition2存broker2,broker3。。。以此类推(replica副本数目不能大于Kafka broker节点的数目,否则报错。这里的replica数其实就是partition的副本总数,其中包括一个leader,其他的就是copy副本)。这样如果某个broker宕机,其实整个Kafka内数据依然是完整的。但是,replica副本数越高,系统虽然越稳定,但是回来带资源和性能上的下降;replica副本少的话,也会造成系统丢数据的风险。

  • Segment:partition物理上由多个segment组成,每个Segment存着message信息,文件分为index和log,通过index文件可以定位log中数据的信息

  • Message (消息 ):传递的数据对象,主要由四部分构成(offset(偏移量 )、key、value、 timestamp(插入时间 ),消息和数据不是一个概念,消息是对数据的封装,其中的value部分是可以看成是数据。

  • Producer : 生产message发送到topic

  • Consumer : 订阅topic消费message, consumer作为一个线程来消费

  • Consumer Group:一个Consumer Group包含多个consumer, 这个是预先在配置文件中配置好的。各个consumer(consumer 线程)可以组成一个组(Consumer group ),partition中的每个message只能被组(Consumer group ) 中的一个consumer(consumer 线程 )消费,如果一个message可以被多个consumer(consumer 线程 ) 消费的话,那么这些consumer必须在不同的组。Kafka不支持一个partition中的message由两个或两个以上的consumer thread来处理,即便是来自不同的consumer group的也不行。如果觉得效率不高的时候,可以加partition的数量来横向扩展,那么再加新的consumer thread去消费。这样没有锁竞争,充分发挥了横向的扩展性,吞吐量极高。这也就形成了分布式消费的概念。

Kafka命令
  • 启动:kafka-server-start.sh [-daemon] config/server.properties
  • 创建topic:kafka-topics.sh --create --zookeeper zk地址 --topic 名称 --partitions 分区数 --replication-factor 副本数
  • 查看topic:kafka-topics.sh --list --zookeeper zk地址 --topic 名称
  • 删除topic:kafka-topics.sh --delete --zookeeper zk地址 --topic 名称
  • 查看topic详细信息:kafka-topics.sh --describe --topic 名称 --zookeeper zk地址
  • 命令行启动生产者:bin/kafka-console-producer.sh --broker-list kafka地址:9092 --topic 名称
  • 命令行启动消费者:bin/kafka-console-consumer.sh --topic 名称 --zookeeper zk地址 [--from-beginning]
  • 命令行启动消费者:bin/kafka-console-consumer.sh --topic 名称 --bootstrap-server 生产者地址 [--from-beginning]
ISR

leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护。如果一个flower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除。

生产者

Kafka producer 发送message不用维护message的offsite信息,因为这个时候,offsite就相当于一个自增id,producer就尽管发送message就好了。而且Kafka与AMQ不同,AMQ大都用在处理业务逻辑上,而Kafka大都是日志,所以Kafka的producer一般都是大批量的batch发送message,向这个topic一次性发送一大批message,load balance到一个partition上,一起插进去,offsite作为自增id自己增加就好。并且producer是先把message发送到partition leader,再由leader发送给其他partition follower。

  • 生产者分区策略:默认是将key的哈希值与分区数取余得到分区号,但代码中可以指定分区号。

  • 数据可靠性保证:为保证producer发送的数据能可靠的到达指定的topic,topic的每个partition收到producer发送的消息后,都需要向producer发送ack,如果producer收到ack就会进行下一轮的发送。多少个副本同步完成后发送ack?kafka选择了下面的第二种,但是问题是如果有一台机器很慢甚至挂掉的话那么leader需要一直等待同步完成之后才能返回ack,因此就需要ISR。当ISR中所有Replica都向Leader发送ACK时,leader才commit。

方案优点缺点
半数以上完成同步就发送ack延迟低选举新leader时,容忍n台节点故障,需要2n+1个副本
全部同步完成,才能发送ack选举新leader,容忍n台节点的故障,需要n+1个副本延迟高
  • ack应答机制:对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的follower全部接收成功。Kafka提供了三种可靠性级别,用户可以根据可靠性和延迟的要求进行权衡,选择设置acks来配置。
说明
0生产者不等待broker的ack,可能丢失数据,但不会重复
1等待broker的partition的leader落盘后回复ack,如果follower未同步之前leader发生故障,会造成数据丢失
2leader和其他一个follower成功的时候,broker就返回成功,无论其他的partition follower是否写成功
-1等待follower落盘后回复ack,如果同步完成但是leader未回复ack之前会造成数据重复,但是kafka后续版本引入了幂等性,共同形成了Exactly Once语义,因此不会重复。

发送流程
Kafka的producer发送消息采用的是异步的方式。消息发送的过程中,涉及到了两个线程——main线程和Sender线程,以及一个线程共享变量——recordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker,但是只有数据积累到batch.size后才会一起发送,如果数据在设定的时间linger.time内未达到batch.size,sender会发送数据。
在这里插入图片描述
代码

public static void main(String[] args) {
    Properties properties=new Properties();
    properties.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
    properties.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
    properties.put("bootstrap.servers","kafka服务器的地址");
    properties.put("acks","all");  //应答机制
    properties.put("retries",3);   //重试次数
    properties.put("batch.size",16384);  //sender发送拉取的范围
    properties.put("linger.ms",1);  //sender等待时间
    properties.put("buffer.memory",33554432);//recordAccumulator缓冲区大小
    properties.put("partition.class","com.lk.partition。myPartition");//recordAccumulator缓冲区大小

    //创建KafkaProducer 实例
    KafkaProducer<String,String> producer=new KafkaProducer<String, String>(properties);
    //构建待发送的消息,可以设置分区等
    ProducerRecord<String,String> record=new ProducerRecord<String, String>("topic","hello Kafka!");
    try {
        //尝试发送消息
        producer.send(record);
        //打印发送成功
        System.out.println("send success from producer");
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        //关闭生产者客户端实例
        producer.close();
    }
}

自定义partition

class myPartition implements Partitioner{
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic);
        int size = partitionInfos.size();
        return key.toString().hashCode()%size;
    }
}

拦截器:可以对发送者的消息进行处理后再发送到broker

class MyInterceptor implements ProducerInterceptor<String,String>{
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> producerRecord) {
        //编写处理的逻辑
        return null;
    }
    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {
        //发送到broker后的后续逻辑,比如获取成功或失败的条数
    }
    @Override
    public void close() {
        //关闭时的逻辑
    }
    @Override
    public void configure(Map<String, ?> map) {}
}

引入拦截器只需要在配置文件中引入:prop.put("interceptor.classes",自定义的拦截器列表)

消费者

kafka的消费者采用pull的模式,不足之处是如果kafka没有数据,消费者可能一直循环,一直返回空数据。针对这一点,kafka的消费者会在消费数据时传入一个时间,如果当前没有数据,则会在一定时间后再获取。Consumer处理partition里面的message的时候是o(1)顺序读取的。所以必须维护着上一次读到哪里的offsite信息。high level API,offset存于Zookeeper中,low level API的offset由自己维护。一般来说都是使用high level api的。其次,Kafka默认是读完message先commmit再处理message,autocommit默认是true,这时候就会更新offsite+1,一旦处理失败,offsite已经+1,这个时候就会丢message;也可以配置成读完消息处理再commit,这种情况下consumer端的响应就会比较慢的,需要等处理完才行。

分区分配策略:一种是RoundRobin、一种是range。RoundRobin不适用于消费者组中的消费者订阅了不同topic的情况,range是首先选出一个消费者组中订阅了该topic的消费者,然后给这些消费者按批分发消息,比如一共7条消息分给三个消费者,那么第一个消费者获得3条信息,第二个和第三个消费者获取2条信息,但range会有分配不均衡的问题。

  • Consumer Rebalance的触发条件:(1)Consumer增加或删除会触发 Consumer Group的Rebalance(2)Broker的增加或者减少都会触发 Consumer Rebalance

消费策略:当启动一个consumer group去消费一个topic的时候,无论topic里面有多个少个partition,无论我们consumer group里面配置了多少个consumer thread,这个consumer group下面的所有consumer thread一定会消费全部的partition;即便这个consumer group下只有一个consumer thread,那么这个consumer thread也会去消费所有的partition。因此,最优的设计就是,consumer group下的consumer thread的数量等于partition数量,这样效率是最高的。我们在设定consumer group的时候,只需要指明里面有几个consumer数量即可,无需指定对应的消费partition序号,consumer会自动进行rebalance。

代码

public static void main(String[] args) {
        /*判断是否指定消费的topic*/
        if(args.length == 0){
            System.out.println("Enter topic name");
            return;
        }
        String topicName = args[0].toString();
        Properties props = new Properties();
        props.put("bootstrap.servers", "192.168.12.34:9092");
        props.put("acks", "all");
        props.put("retries", 0);
        /*将单个消费者分配给组*/
        props.put("group.id", "test");
        //如果值为true,则为偏移启用自动提交,否则不提交。也可以手动提交,分为同步或异步。
        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");
        // latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,不从头开始消费
        // earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
        // earliest 加上props.put("group.id", UUID.randomUUID().toString());可以实现 --from-beginning 功能
        props.put("auto.offset.reset", "earliest");
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
        /*Kafka Consumer subscribes list of topics here.Kafka使用者在这里订阅主题列表。*/
        consumer.subscribe(Arrays.asList(topicName));
        /*print the topic name 打印topic 名称*/
        System.out.println("Subscribed to topic " + topicName);
        /*长时间监听和消费*/
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(100);
            for (ConsumerRecord<String, String> record : records) {
                /* print the offset,key and value for the consumer records.打印消费者记录的偏移量、键和值。*/
                System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());
            }
        }
    }

自定义存储offset

consumer.subscribe(Arrays.asList(topicName), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> collection) {}
    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> collection) {}
});
Message
  • message状态:在Kafka中,消息的状态被保存在consumer中,broker不会关心哪个消息被消费了被谁消费了,只记录一个offset值(指向partition中下一个要被消费的消息位置),这就意味着如果consumer处理不好的话,broker上的一个消息可能会被消费多次。
  • message持久化:Kafka中会把消息持久化到本地文件系统中,并且保持o(1)极高的效率。我们众所周知IO读取是非常耗资源的性能也是最慢的,这就是为了数据库的瓶颈经常在IO上,需要换SSD硬盘的原因。但是Kafka作为吞吐量极高的MQ,却可以非常高效的message持久化到文件。这是因为Kafka是顺序写入o(1)的时间复杂度,速度非常快。也是高吞吐量的原因。由于message的写入持久化是顺序写入的,因此message在被消费的时候也是按顺序被消费的,保证partition的message是顺序消费的。一般的机器,单机每秒100k条数据。
  • message有效期:Kafka会长久保留其中的消息,以便consumer可以多次消费,当然其中很多细节是可配置的。

Kafka提供3种消息传输一致性语义:最多1次,最少1次,恰好1次。

  • 最少1次 At least once:可能会重传数据,有可能出现数据被重复处理的情况;
  • 最多1次 At most once:可能会出现数据丢失情况;
  • 恰好1次 Exactly once:并不是指真正只传输1次,只不过有一个机制。确保不会出现“数据被重复处理”和“数据丢失”的情况。
Kafka的leader选举

Kakfa Broker集群受Zookeeper管理。所有的Kafka Broker节点一起去Zookeeper上注册一个临时节点,因为只有一个Kafka Broker会注册成功,其他的都会失败,所以这个成功在Zookeeper上注册临时节点的这个Kafka Broker会成为Kafka Broker Controller,其他的Kafka broker叫Kafka Broker follower。(这个过程叫Controller在ZooKeeper注册Watch)。这个Controller会监听其他的Kafka Broker的所有信息,如果这个kafka broker controller宕机了,在zookeeper上面的那个临时节点就会消失,此时所有的kafka broker又会一起去Zookeeper上注册一个临时节点,因为只有一个Kafka Broker会注册成功,其他的都会失败,所以这个成功在Zookeeper上注册临时节点的这个Kafka Broker会成为Kafka Broker Controller,其他的Kafka broker叫Kafka Broker follower。例如:一旦有一个broker宕机了,这个kafka broker controller会读取该宕机broker上所有的partition在zookeeper上的状态,并选取ISR列表中的一个replica作为partition leader(如果ISR列表中的replica全挂,选一个幸存的replica作为leader; 如果该partition的所有的replica都宕机了,则将新的leader设置为-1,等待恢复,等待ISR中的任一个Replica“活”过来,并且选它作为Leader;或选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader),这个broker宕机的事情,kafka controller也会通知zookeeper,zookeeper就会通知其他的kafka broker。

Kafka 事务机制

Kafka 从 0.11 版本开始支持了事务机制。Kafka 事务机制支持了跨分区的消息原子写功能。具体来说,Kafka 生产者在同一个事务内提交到多个分区的消息,要么同时成功,要么同时失败。这一保证在生产者运行时出现异常甚至宕机重启之后仍然成立。

  • producer的事务:为了实现跨分区跨会话的事务,需要引入一个全局唯一的transactionID,并将producer获得的pid和transactionID绑定。这样producer重启后就可以通过正在进行的transactionID获得原来的pid。为了管理transaction,Kafka引入了新的组件transaction coordinator。Producer就是通过和transaction coordinator交互获得了transactionID对应的任务状态。transaction coordinator还负责将事务所有写入Kafka的一个内部topic,这样即使整个服务重启,由于事务状态得以保存,事务还可以恢复继续进行。
Kafka监控

Eagle略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值