学习来源
尚硅谷
作用
1 异步,发送方发送完数据可以直接结束,而不用等服务器响应,提高效率
2 解耦,发送方和服务器之间接触绑定,使代码更清晰
3 削峰,消息队列有缓存功能,服务器可以根据自己的处理能力,而策略性地获取消息队列里的数据
先决条件
Linux NAT配置,Zookeeper集群,JDK配置,Kafka服务端安装
maven kafka 3.0 kafka-client
架构
生产者 ->缓存 -> broker -> 缓存-> 消费者
生产者模块
生产者流程
假设来了一个较大的对象,首先根据分区策略一批一批往Queue(也就是图中分区缓存)里面放入,满足一定推送条件,select线程会往broker推送。如果这一批和之前顺序正常,就会落盘,然后根据确认策略返回ack,顺序不正常,就会先放置而不落盘。一个分区对应的broker缓存最多可以存五批,如果没有到5批,允许select线程继续发送,如果到达了5批,就必须等待ack才能继续发送。
配置文件
bootstrap.server 选择消息队列主机,一般为了高可靠性,会选择两台及以上
serializer key/value 用于把传输数据给序列化成字节数组,需要key型和value型都配置
batch_size 批次大小,达到大小会直接发送
linger_ms 等待时长,达到时间会直接发送
compression 序列化后压缩形式,gzip、snappy、lz4 和 zstd
buffer_memory 缓存大小
partitioner 注册自定义策略,策略类需要实现partitioner接口
分区传输策略
如果指定分区,直接选择
如果没有指定,有key,使用hash选择
如果不指定,没有key,完全传输完一个分区之后才到下一个
除此之外,也可以自己重写策略类,并装入properties中
send传输
调用send方法,可以选择回调与否
kafkaProducer.send(new ProducerRecord<>("first",
"atguigu " + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null){
System.out.println(" 主题: " +
metadata.topic() + "->" + "分区:" + metadata.partition()
);
}else {
e.printStackTrace();
}
}
});
demo
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
public class CustomProducerParameters {
public static void main(String[] args) throws
InterruptedException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
"hadoop102:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
// batch.size:批次大小,默认 16K
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// linger.ms:等待时间,默认 0
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// RecordAccumulator:缓冲区大小,默认 32M:buffer.memory
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,
// compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4 和 zstd
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
// 3. 根据之前属性创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<String, String>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","atguigu " + i));
}
// 5. 关闭资源
kafkaProducer.close();
}
}
确认策略
ack = 0,-1,1
ack = 0,生产者发送数据就不管了,可靠性差,效率低
ack = 1,会等待Leader提交后的应答,可以给一些允许个别丢失的数据设置,比如一些普通日志
ack = -1,会等待全部副本提交后等待应答,一般数据级别
完全可靠条件为: ack = -1,isr > 1,partition > 1
数据顺序问题
幂等性保证数据会话内不重复,通过pid(Producer ID),分区号,序列号来标识唯一批,PID是每台生产者生产时被分配的,如果pid和序列号组合已经出现过,就会去掉这个数据
而对于数据顺序问题
broker模块
服役和退役
先新建一个脚本,写入要更新的主题和它的brokerId
然后使用指令创造随机方案,满意后使用指令重新编排主机
达到服役和退役的效果,只需要在brokerId中删除或增加
Zookeeper的作用
ids 保存所有在线的brokerId
controller 保存目前Leader信息
Leader选举
AR=ISR+OSR
ISR是保证能和Leader同步的主机,包括Leader。OSR是不能和Leader同步的
Leader是AR中最前面,且在ISR中的节点
它会将自己的节点数据保存在zookeeper的controller模块
AR顺序
一开始是1234,然后会开始隔一个变成2341,然后再隔一个3412
一致性的保证
只有Leader可以读写,由它完成同步任务
通过HW和LEO(Log End Offset)来保证故障后的一致性,LEO是完成了落盘的数据最后offset的位置+1,也就是正在读取的位置,HW是全部副本最小的LEO
如果Leader故障,Follower会成为Leader,然后以自己设置为新的HW,如果目前HW高于自己,所有主机都会丢弃这部分,如果低于自己,就会跟上Leader
如果follower故障,follower重新上线时,会根据目前HW,把自己上次HW数据多读的数据都丢弃,然后去同步Leader的数据
文件策略
删除或压缩
删除有根据时间删除或者根据大小删除,一般是根据时间
压缩就是把相同key的数据合并,合并之后,会保留最后一次的offset,所以如果要取某个offset之后的数据,会往后找
高速传输
零拷贝-直接从内核缓存读到网卡,而不经过应用层,把数据处理交给消费者和生产者
追加读,每次读到log后面,这样可以保证顺序读而减少磁头移动时间
消费者模块
流程
先通过id%50获得自己的coodinate,向coordinate申请加入组
使用随机算法获得一个消费者作为Leader,并告知coodinate
coodinate把这个topic的分区情况告诉Leader
Leader制定好分配策略,将策略发给coordinate
coordinate发给组内所有consumer
consumer根据策略读取数据
如果消费者一段时间不应答Leader,或者一段时间没有读完数据,会被判定为无效消费者,那么就会把它剔除出消费者组,并重新分配它负责分区。后续如果还有任务也不会再分配给它,除非重新加入。
在读的过程中broker会随着consumer的读取进度移动offset,并且在固定一个时间段后就提交一次
消费组
每个消费者组都有自己的groupId,有的只有一个groupId,有的却有很多个,同一组的消费者不能消费其它消费者已经消费的分区,如果消费者少,那么一个消费者会负责多个分区,如果消费者多,那么会有空闲的消费者。
分区分配策略
range
根据分区数/消费者数=每个消费者需要读取分区数,如果除不尽,就将余数n分给前n个消费者
它会先把消费者A要完成的分区全给A,再到B,比如 1,2,3 给A;4,5,6 给B
如果某个消费者被判定为无效消费者,会把它的所有负责分区交给下一个消费者
缺点是在有余数时,前n个消费者任务重于其余消费者,这种行为被称为数据倾斜。
并且失败的消费者会让下一个消费者压力骤增
轮询
将分区一个一个分给消费者,比如1给A,2给B,3给C,4给A,按顺序一直给
如果某个消费者被判定为无效消费者,会把它的所有负责分区一个一个交给其余消费者
在生产环境用得较多
sticky
根据分区数/消费者数=每个消费者需要读取分区数,如果除不尽,就将余数n分给前n个消费者
和分区不同的是,它会随机将每个分区给某个消费者,比如将1号随机给B,又将2号随机给A,直到消费者满了才会停止随机到该消费者
如果某个消费者被判定为无效消费者,会把它的所有负责分区随机交给其余消费者
数据读取方式方式
推:固定速率,对于kafka不合适
拉:根据客户端自身传输速率来选择,可能造成长期io阻塞等待,但比较适合
offset设置
kafka0.9后,offset以<groupId,topicId,partition>
保存在系统主题中
默认5秒自动提交一次
可以更改为手动提交,也可以更改自动提交频率