définition:
- Kafka est une file d'attente de messages par défaut de publication/abonnement distribuée
- Il s'agit d'une plate-forme de streaming d'événements distribués open source couramment utilisée pour les pipelines de données, l'analyse de flux, l'intégration de données et les applications critiques.
Modèle de consommation:
- Les consommateurs en mode point à point (moins utilisé)
extraient activement les données et effacent les messages après leur réception.
- Modèle de publication/abonnement :
les producteurs envoient les messages vers les files d'attente et les consommateurs s'abonnent aux messages dont ils ont besoin.
concept de base:
- Producteur : producteur de messages
- Consommateur : consommateur
- Consommateur : groupe de consommateurs de groupe, les consommateurs ayant le même ID de groupe de consommateurs constituent un groupe de consommateurs ; un consommateur consomme également pour un groupe de consommateurs.
- Courtier : serveur Kafka
- Sujet : sujet du message, classification des données
- Partition : Partition, un Tpoic se compose de plusieurs partitions
- Réplique : Réplique, chaque partition correspond à plusieurs répliques
- Leader : La copie contient le leader et le suiveur ; la production et la consommation sont uniquement destinées au leader.
Processus d'envoi du producteur :
- producteur -> envoyer (producteurRecord) -> intercepteur d'interception -> sérialiseur sérialiseur -> partitionneur de partitionnement
- Lorsque les données sont accumulées
batch.size
, l'expéditeur enverra les données ; la valeur par défaut est 16 Ko.- Si les données n'atteignent pas batch.size, l'expéditeur attendra
linger.ms
le temps défini puis enverra les données. Unité ms. Valeur par défaut0ms
, indiquant l'absence de délaicompression.type
Méthode de compression des donnéesRecordAccumulator
Taille du tampon, par défaut 32 m- mode de réponse
ack
- 0 : Une fois que le producteur a envoyé les données, il n'est pas nécessaire d'attendre la réponse des données.
- 1 : Les données envoyées par le producteur, le leader répond après réception des données
- 1 : tous les leaders répondent après avoir collecté les données de tous les autres nœuds
Logique générale de consommation :
Groupe de consommateurs (CG) :
groupid
Des consommateurs identiques forment un groupe de consommateurs- Chaque consommateur du groupe de consommateurs est responsable de la consommation des données de différentes partitions. Une partition ne peut être consommée que par un seul consommateur dans un groupe.
- Les groupes de consommateurs n’ont aucune influence les uns sur les autres
- Lorsque le nombre de groupes de consommateurs est supérieur au nombre de partitions, il y aura
闲置
coordinator
: Aide à réaliser初始化
la somme des groupes de consommateurs分区的分配
- Chaque nœud en possède un
coordinator
. Engroupid % 50
sélectionnantcoordinator
le nœud 50, le nombre de partitions est _consumer_offset.- 1%50 = 1,
_consumer_offset
le numéro sur la partitioncoordinator
est le leadercoordinator
Sélectionnez au hasard un consommateur dans le groupe de consommateurs pour devenir le leader. Le leader formulera un plan de consommation et le rendra au groupe de consommateurscoordinator
, puiscoordinator
attribuera la technologie de consommation à d'autres consommateurs.coordinator
心跳
Le temps de rétention chez le consommateur3秒
,45秒 超时
- supprimera le consommateur et déclenchera再平衡
- Le temps de consommation du consommateur est trop long. Par défaut
5分钟
, le déclencheur du consommateur sera supprimé.再平衡
Processus de consommation :
- Créer un client de connexion réseau grand public
ConsumerNetworkClient
pour interagir avec kafka- Initialisation de la demande de consommation : à chaque lot
最小抓取大小
, les données n'atteignent pas le délai d'attente de 500 ms et la limite supérieure de la taille des données capturées- Envoyer une demande de consommation-》rappel onSuccess(), extraire des données-》Mettez-les dans la file d'attente des messages par lots
- Les consommateurs consomment les données de la file d'attente des messages dans chaque lot (500 éléments) -》Désérialisation-》Intercepteur-》Traitement des données
Plan de consommation (stratégie d'allocation de partition) Range par défaut + CooperativeSticky :
- Plage : pour
每一个topic
trier les partitions de sujet et les consommateurs de messages, déterminez le nombre de partitions que chaque messager consomme en fonction du nombre de partitions/nombre de consommateurs, en excluant les consommateurs précédents inépuisables qui consomment davantage.容易产生数据倾斜
- RoundRobin : stratégie de partitionnement d'interrogation,
针对所有topic
répertorie toutes les partitions de sujet et les consommateurs, les trie en fonction du hashcode et轮询算法
alloue des partitions aux consommateurs- Sticky : Sticky (lors de l'exécution d'une nouvelle allocation, essayez d'être aussi proche que possible du dernier résultat d'allocation), essayez d'abord d'être aussi uniforme que possible et attribuez des partitions au hasard aux consommateurs.
- CooperativeSticky : Sticky des collaborateurs, la stratégie de Sticky est la même, mais prend en charge le rééquilibrage coopératif. Les consommateurs peuvent continuer à consommer à partir de partitions qui n'ont pas été réaffectées.
déplacement décalé : marque la position de consommation
- <0,9 : Il est maintenu dans zookeeper
- Après la version 0.9 : les compensations sont conservées dans une rubrique intégrée : _consumer_offsets
- Utilisez la méthode clé-valeur pour stocker les données, clé : groupid + sujet + numéro de partition
- offset
自动提交
: Par défaut, le décalage est automatiquement soumis toutes les 5 secondes,默认
ce qui est vrai- offset
手动提交
: lors de la consommation, soumettre manuellement l'offset
- Synchronisation : attendez que l'offset soit soumis avec succès avant de consommer le suivant
- Asynchrone : pas d'attente, consommation directe, pas de mécanisme de nouvelle tentative après échec
- Spécifiez la consommation compensée :
earliest
: Réinitialiser automatiquement le décalage au décalage le plus ancien --from-beginninglatest
(Par défaut) : recharger automatiquement le décalage jusqu'au dernier décalagenono
: lève une exception au consommateur si le décalage précédent du groupe de consommateurs n'est pas trouvé.
//设置自动提交offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
//自动提交时间 5s
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"5000");
//offset 手动提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);
KafkaConsumer kafkaConsumer = new KafkaConsumer<String,String>(properties);
//定义主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
//订阅
kafkaConsumer.subscribe(topics);
while (true){
ConsumerRecords<String,String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
if (CollectionUtil.isNotEmpty(consumerRecords)){
for (ConsumerRecord<String, String> record : consumerRecords) {
System.out.println(record);
}
}
//手动提交offset
kafkaConsumer.commitAsync();
}
Consommation à heure précisée :
//查询对应分区
Set<TopicPartition> partitions = kafkaConsumer.assignment();
//保证分区分配方案定制完毕
while (partitions.size()==0){
kafkaConsumer.poll(Duration.ofSeconds(1));
partitions=kafkaConsumer.assignment();
}
//把时间转换成对应的 offset
Map<TopicPartition,Long> map = new HashMap<>(6);
Map<TopicPartition,Long> offsetmap = kafkaConsumer.offsetsForTimes(map);
for (TopicPartition topicPartition : partitions) {
//一天前
offsetmap.put(topicPartition,System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
}
Map<TopicPartition, OffsetAndTimestamp> offsetsForTimeMap = kafkaConsumer.offsetsForTimes(offsetmap);
for (TopicPartition partition : partitions) {
OffsetAndTimestamp timestamp = offsetsForTimeMap.get(partition);
kafkaConsumer.seek(partition,timestamp.offset());
}
Mécanisme de stockage de fichiers Kafka :
Topic
C'est un concept logique etpartition
un concept physique, et chacunpartition
correspond à unlog文件
. Le fichier journal stocke les données produites par le producteur.- Les données produites par le producteur seront continuellement ajoutées à la fin du fichier journal. Afin d'éviter que le fichier journal ne soit trop volumineux et n'entraîne une faible efficacité de positionnement des données, Kafka adopte un mécanisme de partitionnement et d'indexation.
- Chaque partition est divisée en plusieurs segments
segment
et chaque segment contient des fichiers .index .log .timeindex .snapshot- Ces fichiers se trouvent dans un dossier et la règle de dénomination du dossier est : nom du sujet + numéro de partition en premier-0
- Index fragmenté : environ tous les 4 Ko de données écrites dans le fichier journal, un index sera écrit dans le fichier d'index.
- L'odffset enregistré dans le fichier d'index est
相对offset
, cela peut garantir que l'espace occupé par la valeur de décalage ne sera pas trop grand, de sorte que la valeur de décalage puisse être contrôlée à une taille fixe
Stratégies de nettoyage et de compression des fichiers :
- La durée de stockage des journaux par défaut de Kafka est de 7 jours
- Stratégie de compression : compacte, correspondant à la valeur de la même clé, seule la dernière version est conservée.
Kafka lecture et écriture efficaces :
- Kafka lui-même est un cluster distribué, qui peut utiliser la technologie de partitionnement et présente un degré élevé de parallélisme.
- Utilisé pour lire les données
稀疏索引
, vous pouvez localiser rapidement les données à consommer- Écrivez sur le disque de manière séquentielle. Le producteur de Kafka produit les données qui doivent être écrites
log文件
. Le processus d'écriture est ajouté à la fin du fichier.顺序写
零拷贝
: Les opérations de traitement des données de Kaka sont gérées par les producteurs de Kaka et les consommateurs de Kaka. La couche application Kaka Broker ne se soucie pas des données stockées, il n'est donc pas nécessaire de passer par la couche application et l'efficacité de la transmission est élevée.- Page Cache : Kaka s'appuie fortement sur la fonction PageCache fournie par le système d'exploitation sous-jacent. Lorsqu'il y a une opération d'écriture dans la couche supérieure, le système d'exploitation écrit simplement les données dans PageCache. Lorsqu'une opération de lecture se produit, elle est d'abord recherchée dans PageCache. Si elle ne peut pas être trouvée, elle est lue à partir du disque. En fait, PageCache utilise autant de mémoire libre que possible comme cache disque.
Noms de scripts couramment utilisés :
- commandes liées au sujet :
- Liste des sujets de requête :
sh kafka-topics.sh --bootstrap-server localhost:9092 --list
- Créez un sujet (nom : première partition : 1 réplique et 3 répliques). Le nombre de répliques ne peut pas dépasser le nombre de clusters.
sh kafka-topics.sh --bootstrap-server localhost:9092 --topic first --create --partitions 1 --replication-factor 3
- informations sur le sujet
sh kafka-topics.sh --bootstrap-server localhost:9092 --topic first --describe
- Modifier le nombre de partitions de sujet (ne peut être augmenté que)
sh kafka-topics.sh --bootstrap-server localhost:9092 --topic first --describe --partitions 3
- Nouvelles de la production :
sh kafka-console-producer.sh --bootstrap-server localhost:9092 --topic first
- Consommation Consommation :
sh kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first
sh kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first --from-beginning
Intégration simple de Spring Boot :
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
server:
port: 8200
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
application:
name: @artifactId@
kafka:
bootstrap-servers:
- 192.168.1.250:32010
# 生产配置
producer:
#序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
properties:
linger.ms: 10 #sender 等待事件
#ssl认证配置相关
# sasl.mechanism: PLAIN
# security.protocol: SASL_PLAINTEXT
# sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin";
#缓存区大小 32m
buffer-memory: 33554432
#批次大小 16k
batch-size: 16
# ISR 全部应答
#acks: -1
#事务ID前缀 ,配合 @Transactional ,保证多个消息的原子性
#transaction-id-prefix: "transaction-id-xx"
#消费配置
consumer:
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
#group-id: xiaoshu-1
enable-auto-commit: false
# 从最早消息开始消费,但是消费后,会记录offset、相同 group-id不会再次消费
# offset 是针对每个消费者组
auto-offset-reset: earliest
#批量消费,每次最多消费多少条
#max-poll-records: 50
#ssl认证配置相关
# properties:
# sasl.mechanism: PLAIN
# security.protocol: SASL_PLAINTEXT
# sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin";
listener:
# 手动调用Acknowledgment.acknowledge()后立即提交
ack-mode: manual
#批量消费,配合 @KafkaListener - batch="true"
#type: batch
Production:
@Resource
private KafkaTemplate<String,String> kafkaTemplate;
//@Transactional(rollbackFor = RuntimeException.class),配合 ack配置 实现多条消息发送,原子性
@ApiOperation(value = "推送消息到kafak")
@GetMapping("/sendMsg")
public String sendMsg(String topic,String msg){
kafkaTemplate.send(topic,msg).addCallback(success -> {
if (success==null){
System.out.println("消息发送失败");
return;
}
// 消息发送到的topic
String topicName = success.getRecordMetadata().topic();
// 消息发送到的分区
int partition = success.getRecordMetadata().partition();
// 消息在分区内的offset
long offset = success.getRecordMetadata().offset();
System.out.println("发送消息成功:" + topic + "-" + partition + "-" + offset);
}, failure -> {
System.out.println("发送消息失败:" + failure.getMessage());
});
return "ok";
}
Consommation:
@Configuration
public class KafkaConsumer {
private static final String TOPIC_DLT=".DLT";
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
/**
* 每个分区由消费者组种得一个消费者消费,每个消费者独立
* 分区 -》 消费 、2分区2个消费监听
* @param record
* @param consumer
*/
@KafkaListener(groupId = "group-1", topicPartitions ={
@TopicPartition(topic = "four",partitions = {
"0"})},batch = "false")
public void consumerTopic1(ConsumerRecord<String, String> record, Consumer consumer){
String value = record.value();
String topic1 = record.topic();
long offset = record.offset();
int partition = record.partition();
try {
log.info("收到消息:"+value+"topic:"+topic1+"offset:"+offset+"分区"+partition);
//TODO 异常,推送到 对应死信 ↓
//int i=1/0;
} catch (Exception e) {
System.out.println("commit failed");
kafkaTemplate.send(topic1+TOPIC_DLT,value);
} finally {
consumer.commitAsync();
}
}
@KafkaListener(groupId = "group-1", topicPartitions ={
@TopicPartition(topic = "four",partitions = {
"1"})},batch = "false")
public void consumerTopic2(ConsumerRecord<String, String> record, Consumer consumer){
String value = record.value();
String topic1 = record.topic();
long offset = record.offset();
int partition = record.partition();
try {
log.info("收到消息:"+value+"topic:"+topic1+"offset:"+offset+"分区"+partition);
//TODO 异常,推送到 对应死信 ↓
//int i=1/0;
} catch (Exception e) {
System.out.println("commit failed");
kafkaTemplate.send(topic1+TOPIC_DLT,value);
} finally {
consumer.commitAsync();
}
}
}
/**
* 监听 topic1 ->转发到 topic2
*/
@KafkaListener(topics = {
"topic1"},groupId = "group-4")
@SendTo("topic2")
public String onMessage7(ConsumerRecord<?, ?> record) {
return record.value()+"-转发消息";
}
@KafkaListener(topics = {
"topic2"},groupId = "group-5")
public void onMessage8(ConsumerRecord<?, ?> record) {
System.out.println("收到转发消息"+record.value());
}