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();
}
}
}
}
2180

被折叠的 条评论
为什么被折叠?



