Kafka部件Producer和Consumer 一些原理和运行

1.ACK应答机制

producer将数据写入Kafka时,producer通过ACK应答机制确认数据是否到达Kafka,具体设置参数如下:

/*
 * 0 无需反馈
 * 1 leader反馈
 * -1 leader和所有follower反馈
 */
prop.put(ProducerConfig.ACKS_CONFIG,"1");

 2.ISR机制

     当数据导入Brock后,leader会先让数据落盘,然后follow们会跟着落盘,这其中数据落盘会有快慢之分。当leader炸了时,follow要上去顶替leader,越快将数据落盘的follow里面的数据相较其他follow来说就越完整,这个快的follow就最好顶替leader,我们将快的follow标记进一个组,就是ISR组。ISR组里面可以有多少个follow也是可以通过修改文件设置的,理论来说ISR里面follow越多,数据落盘读取越安全

   这又引出另一个问题,当我们设置ACK应答为-1,leader和ISR的follow都要确认数据才算落盘,如果ISR设置过大,那数据传输越慢,但是ISR设置太小数据可能会不安全。所谓鱼和熊掌不可兼得,数据传输速度和安全相互制约

3.producer用线程池进行数据提交

pom导入包:

<dependency>
  <groupId>org.apache.kafka</groupId>
  <artifactId>kafka-clients</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
  <groupId>org.apache.kafka</groupId>
  <artifactId>kafka_2.11</artifactId>
  <version>2.0.0</version>
</dependency>
<dependency>
  <groupId>org.apache.kafka</groupId>
  <artifactId>kafka-streams</artifactId>
  <version>2.0.0</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-client</artifactId>
  <version>1.2.0</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-server</artifactId>
  <version>1.2.0</version>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>3.7.0</version>
</dependency>
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.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MyProducer3 {
    public static void main(String[] args) {
        //创建一个固定大小为5的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //循环100次,提交100个任务
        for(int i=0;i<100;i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    //创建一个Properties对象,用于存储Kafka生产者的配置信息
                    Properties prop =new Properties();
                    //设置Kafka集群的地址
                    prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.52.146:9092");
                    //设置key的序列化类
                    prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
                    //设置value的序列化类
                    prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
                    //设置ack的值为1,表示只要有一个副本确认了消息就认为消息发送成功
                    prop.put(ProducerConfig.ACKS_CONFIG,"1");
                    //设置重试次数为3次
                    prop.put(ProducerConfig.RETRIES_CONFIG,"3");  //重试3次
                    //创建KafkaProducer对象
                    KafkaProducer<String, String> Producer = new KafkaProducer<>(prop);
                    //循环100000次,发送100000条消息
                    //可以替换为外部消息推送
                    for (int j=0;j<100000;j++){
                        //生成消息
                        String msg = Thread.currentThread().getName()+" "+j;
                        System.out.printf(msg);
                        //创建ProducerRecord对象,指定topic和消息内容
                        ProducerRecord<String,String>record=new ProducerRecord<>("batchmsg",msg);
                        //发送消息
                        Producer.send(record);
                    }
                }
            });
        }
        //停止接收新的任务
        executorService.shutdown();
        try {
            //等待所有任务执行完毕
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
        }
        //输出“生成数据结束”
        System.out.printf("“生成数据结束");
    }
}

 4.Kafka数据落盘

Kafka集群内部大概可以这样看

每个host可以创建几个broker,,集群里面不同broker创建相同的topic,一个broker里面可以创建几个topic(主题)相当于我们把消息的不同类别进行了分区,分区里面创建partition(分区),如p1,p2,p3,在p1里可能放的是a数据块的leader,p2里面放的是b数据块的follow等等,那host2的broker2里面的p1就是a数据块的follow,p2就是b数据块的leader等等,通过将不同的leader和follow数据块放在不同的broker里面,当其中一个broker炸了,另外的broker里面的follow可以接替leader,实现了数据的安全性

5.consumer自动偏移量(offset)重置策略

consumer在消费数据时,从Kafka集群拿取消息根据偏移量可以设置拿取的策略,设置代码如:

prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");

"latest":表示当Kafka中没有找到消费者的偏移量时,消费者将从分区中最新的消息(即分区末尾)开始读取。这意味着消费者会错过所有在它开始消费之前生产的消息。

"none":如果找不到消费者组的偏移量,则抛出异常给消费者。

"earliest":表示当Kafka中没有找到消费者的偏移量时,或者偏移量不再有效时(比如因为数据已被删除),消费者将从分区中最早的消息开始读取。这意呀着消费者会错过在启动后到它开始消费之间生产的所有消息。

6.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.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;

import static java.time.Duration.ofMillis;

public class MyConsumer2 {
    public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.52.146:9092");
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class);
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
        prop.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"500");
        prop.put(ConsumerConfig.GROUP_ID_CONFIG,"MyGroup1");
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
         for (int i=0;i<3;i++){         //一个消费者组有多个消费者,同时topic主题也有多个分区,producer会给每个分区分配一个消费者
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     KafkaConsumer<String,String > consumer = new KafkaConsumer<>(prop);
                     consumer.subscribe(Collections.singletonList("news"));
                     while (true){
                     ConsumerRecords<String,String>records=consumer.poll(Duration.ofMillis(100));
                     for (ConsumerRecord<String,String> record:records) {
                         long offset = record.offset();   //获取消息的偏移量
                         String topic = record.topic(); //获取消息的主题名
                         int partition = record.partition(); //获取消息的分区号
                         String value = record.value(); //获取消息的value值
                         String key = record.key();//获取消息的key值
                         long timestamp = record.timestamp(); //获取消息的时间戳
                         //打印消息
                         System.out.printf("offset:%s,topic:%s,partition:%s,value:%s,key:%s,timestamp:%s%n", offset, topic, partition, value, key, timestamp);
                         consumer.commitAsync();
                     }}
                 }
             }).start();
         }
    }
}

 7.consumer炸了后重新连接broker解决代码

当consumer炸了以后,同一个组的consumer会接替炸了的consumer消费信息,当炸了的consumer恢复正常后,consumer如何重新连接到broker继续消费信息,因为要考虑到consumer炸了之后其他consumer还会消费炸了的consumer的消息,当炸了的恢复后,要将它连接到当前consumer消费到的位置,使用seek方法将redis存储到的位置根据偏移量进行纠正,代码如下(我将Kafka连接到redis存储数据)

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.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
public class MyConsumer3 {
    public static void main(String[] args) {
        RedisJDBC redisJDBC = new RedisJDBC();
        final  String GroupId = "MyGroup1";
        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.52.146:9092");
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
        prop.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"500");
        prop.put(ConsumerConfig.GROUP_ID_CONFIG,"MyGroup1");
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(prop);创建KafkaConsumer对象
        consumer.subscribe(Collections.singleton("news")); //订阅主题

        Set<TopicPartition> assignments =null;//Set是一个不包含重复元素的集合

        while(assignments==null||assignments.size()==0) { //让Kafka消费者不断轮询(poll)主题,以获取新的消息,轮询间隔为100毫秒。
            consumer.poll(Duration.ofMillis(100));
            assignments=consumer.assignment();

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }

            List<Devop> devops = RedisJDBC.getDevopByGroupName("news", GroupId);//获取consumer在redis上获取的消息数据位置

            for (TopicPartition topicPartition:assignments){
                if (devops.isEmpty()||devops == null){
                    break;
                }
                int tpNum=topicPartition.partition();

                for (Devop devop:devops){
                    if(tpNum==devop.getPartition()){
                        consumer.seek(new TopicPartition(devop.getTopic(),devop.getPartition()),devop.getOffset());
                    }
                }
            }
        }


        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            for (ConsumerRecord<String, String> record : records) {
                long offset = record.offset();   //获取消息的偏移量
                String topic = record.topic(); //获取消息的主题名
                int partition = record.partition(); //获取消息的分区号


                redisJDBC.Save(new Devop(topic,GroupId,partition,offset));

                String value = record.value(); //获取消息的value值
                String key = record.key();//获取消息的key值
                long timestamp = record.timestamp(); //获取消息的时间戳
                //打印消息
       
                System.out.printf(Thread.currentThread().getName()+"===="+topic + "====" + offset + "====" + partition + "=====" + value + "====" + key + "====" + record.timestamp());
                consumer.commitAsync();
            }
        }

    }
}
好的,以下是KafkaProducer和Consumer代码示例(使用Java语言): Producer代码示例: ```java import org.apache.kafka.clients.producer.*; import java.util.Properties; public class KafkaProducerExample { public static void main(String[] args) { String topicName = "test-topic"; // 指定topic名称 Properties props = new Properties(); // 创建Properties对象 props.put("bootstrap.servers", "localhost:9092"); // 指定Kafka Broker地址 props.put("acks", "all"); // 指定Producer写入的确认方式 props.put("retries", 0); // 如果发送失败,Producer重新发送的次数 props.put("batch.size", 16384); // 批处理大小 props.put("linger.ms", 1); // 等待时间,如果数据达到batch.size就不等待 props.put("buffer.memory", 33554432); // 缓冲区大小 // 创建Producer对象 Producer<String, String> producer = new KafkaProducer<>(props); // 发送数据 for (int i = 0; i < 10; i++) producer.send(new ProducerRecord<>(topicName, Integer.toString(i), Integer.toString(i))); System.out.println("Message sent successfully"); producer.close(); } } ``` Consumer代码示例: ```java import org.apache.kafka.clients.consumer.*; import org.apache.kafka.common.TopicPartition; import java.util.Arrays; import java.util.Properties; public class KafkaConsumerExample { public static void main(String[] args) { String topicName = "test-topic"; // 指定topic名称 String groupName = "test-group"; // 指定consumer group名称 Properties props = new Properties(); // 创建Properties对象 props.put("bootstrap.servers", "localhost:9092"); // 指定Kafka Broker地址 props.put("group.id", groupName); // 指定consumer group props.put("enable.auto.commit", "true"); // 是否自动提交offset props.put("auto.commit.interval.ms", "1000"); // 自动提交offset的时间间隔 props.put("session.timeout.ms", "30000"); // session超时设置 props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); // key反序列化器 props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); // value反序列化器 // 创建Consumer对象 KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props); // 订阅topic consumer.subscribe(Arrays.asList(topicName)); // 循环读取消息 while (true) { ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println("offset = " + record.offset() + ", key = " + record.key() + ", value = " + record.value()); // 手动提交offset TopicPartition tp = new TopicPartition(record.topic(), record.partition()); OffsetAndMetadata oam = new OffsetAndMetadata(record.offset(), "no metadata"); consumer.commitSync(Arrays.asList(new OffsetCommitRequest.TopicAndPartition[]{new OffsetCommitRequest.TopicAndPartition(tp, oam)})); } } } } ``` 希望这个代码示例对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值