基本概念
kafka的三大作用
- 消息系统:与其他消息中间件一样,kafka具有系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、可恢复性等功能,除此之外还包含消息顺序性和回溯消费的功能。
- 存储系统:将消息持久化到磁盘。
- 流式处理平台:不仅为每个流行的流式处理框架提供可靠的数据来源,还提供了一个完整的流式处理类库,比如窗口、连接、变换和聚合等操作。
组成
kafka体系架构包含:若干Producer、若干Broker、若干Consumer以及一个Zookeeper
Producer:生产者,也就是发送消息的一方。生产者负责创建消息,然后将其投递到kafka中。
Consumer:消费者,也就是接收消息的一方。消费者负责连接到kafka并接收消息,进而进行相应的业务逻辑处理。
Broker:服务代理节点。一个或多个Broker组成一个kafka集群。
主题与分区
主题(Topic):kafka中的消息是以主题为单位进行归类,发送到kafka集群中的消息都要指定一个主题,而消费者负责订阅主题并进行消费。
分区(Partition): 一个主题可以细分为多个分区,但一个分区只属于单个主题。同一主题下的不同分区包含的消息是不同的。可以将分区在存储层看做是一个可追加的日志文件,消息被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。分区可以分布在不同的broker上,也就是一个主题可以横跨多个broker,以此来提供比单个broker更强大的性能。分区引入多副本(Replica)机制,通过增加副本数量提升容灾能力。副本遵从一主多从,leader副本负责处理读写请求,follower副本负责和leader同步消息。但同步具有一定的滞后性、
分区中的所有副本统称为AR(Assigned Replicas),与leader副本保持一定程度同步的副本组成ISR(In-Sync Replicas),滞后过多的副本组成OSR(Out-of-Sync Replicas)。这个一定程度可以通过参数配置。ISR还与HW(High Watermark ,高水位)以及LEO(Log End Offset)
偏移量offset:消息在分区中的唯一标识,保证消息在分区中的顺序性,但并不跨区。
HW:标识了一个特定的消息偏移量,消费者只能拉取到这个offset之前的消息。
LEG:标志当前日志文件中下一条待写入消息的offset。
分区ISR集合中每个副本都会维护自身的LEG。其中最小的LEG即为HW。
生产者
负责向kafka发送消息
public class KafkaProducerAnalysis{
public static final String brokerList = "localhost:9092";
public static final String topic = "topic-demo";
public static Properties initConfig(){
Properties props = new Properties();
props.put("bootstaro.servers", brekerList);
props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
props.put("client.id","producer.client.id.demo");
return props
}
public static void main(String[] args){
Properties props = initConfig();
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
ProducerRecord<String, String> record = new ProducerRecord<>(topic, "Hello,kafka!");
try{
producer.send(record);
}catch (Exception e){
e.printStatckTrace();
}
}
}
上述代码中,消息对象ProducerRecord并不是单纯意义上的消息,它包含了多个属性。如下:
public class ProducerRecord<K, V> {
private final String topic; //主题
private final Integer partition;//分区号
private final Headers headers; //消息头部
private final K key;
private final V value;
private final Long timestamp; //消息的时间戳
}
创建对象
在创建kafka实例之前,需要配置相应的参数,如上述创建代码中,initConfig()函数中有三个参数是必填的。
- bootstaro.servers:指定生产者客户端连接Kafka集群所需的broker地址清单,具体的内容格式为:
host1:port1,host2:port2
。但不需要写上所有的broker,因为生产者会从指定的broker中找到其他broker信息,但为了减免宕机造成的影响,至少设置两个以上的broker地址信息。 - key.serializer
- value.serializer
这两个参数用来指定key、value在序列化操作时的序列化器,无默认值,且必须填写序列化器的全限定名。
上述initConfig可以通过引入ProducerConfig简化为
public static Properties initConfig(){
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVICES_CONFIG, brekerList);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
props.put(ProducerConfig.CLIENT_ID_CONFIG,"producer.client.id.demo");
return props
}
KafkaProducer<String, String> producer = new KafkaProducer<>(props);\
//这是用来创建一个生产者实例,KafkaProducer是线程安全的,可以再多个线程中共享单个KafkaProducer实例
发送消息
创建完对象后就需要构建消息,也就是ProducerRecord对象。它有多个构造方法,但key和value是必填项。如果现有构造方法不满足需求,还可以自行添加更过。
发送消息有三种形式:
- 发后即忘(fire-and-forget)
producer.send(record);
只管发送,不关心消息是否正确到达。性能最好,但可靠性最差 - 同步(sync)
producer.send(record).get();
send方法本身是异步的,而get用来阻塞等待kafka响应,知道消息发送成功或者出现异常。 - 异步(async)
一般在send方法里指定一个Callback回调函数
producer.send(record,new Callback(){
@Override
public void onCompletion(RecordMetadata metadata, Exception exception){
if(exception != null){
exception.printStackTrace();
}else{
System.out.println(metadata.topic() + "-" + metadata.partition() + ":" + metadata.offset())
}
}
})
值得一说的是,send方法的的返回并不是void类型,而是Future<RecordMetadata>
类型。 Future表示一个任务的生命周期,并提供了相应的方法来判断任务是否已经完成或者取消,以及获取任务的结果和取消任务等。
最后,在发送完所有消息之后,需要调用close()方法来回收所占用的资源。
producer.close();
//带超时时间
producer.close(long timeout, TimeUnit timeUnit)
close()方法会阻塞等待之前所有的发送请求完成后再关闭KafkaProducer,但如果设定了时间,就只会等待timeout时间内来完成请求处理,然后强行退出。
序列化
生产者需要通过序列化器把对象转换成字节数组才能通过网络发送给kafka。同样地,消费者需要通过反序列化器把从kafka中收到的字节数组转换成相对应的对象。