基本概念
为什么要使用消息队列?
- 应用解耦。各系统间耦合性太强,彼此直接关联在一起;若业务变更,会造成较大的维护成本。使用MQ作为消息中间件,子系统订阅MQ,使整体系统解耦,从而降低维护成本;
- 流量削峰。一般指业务数据太大,超过数据库的写入极限,若直接写入库中,会导致系统宕机;将业务消息传递到MQ,数据库按合适的速度消费MQ,即可避免流量过大导致系统出现故障的问题;
- 异步处理。将原本串行运算的子任务,使之同时接受MQ的消息,并做并行计算,从而加快系统运行速度;
Kafka其实就是个中间件,一些程序向Kafka的某些topic发送数据,被称之为生产者Producer;一些程序读取kafka特定topic上的数据,被称之为Consumer。从这个意义上说,其实Kafka与数据库的功能有很多相似的地方。
常用资料如下,最好的学习方式当然是在使用中学习:
Kafka中的专有名词:
- Kfaka:由领英开源的一种可扩展、低延迟、高吞吐量的分布式消息系统,目的是作为系统解耦、缓冲、异步的工具。是大数据时代应用最为广泛的消息中间件;
- Topic:一种存储消息的逻辑概念,可以认为是一个消息集合。物理上来说,不同topic的消息是分开存储的;按照业务逻辑,将消息存储在不同的区域,这些区域被称之为topic。例如城市topic、日期topic等;
- Producer:生产者,将外部系统的数据发送到Kafka的指定topic上;
- Consumer:消费者,使用Kafka的指定topic上的数据;
- Broker:一台Kafka Server就是一个Broker节点,一个Kafka集群由多个Broker组成,一个Broker存储多个topics;
- Partition:一种存储消息的物理概念,对于用户来说是不可见的。每个Topic可以划分为至少一个分区,每个分区中的消息是不同的;Partition存在的意义,是便于Kafka集群的水平扩展:当数据量很大时,单个broker会成为性能的瓶颈,因此将topic的数据分别存储在多个broker的多个partition中,可以做大吞吐量的增大。如果分区规则十分合理,那么消息会均匀的存储在多个分区上面,从而实现Kafka的负载均衡;1 个消费组包含 1 个 consumer,消费多个 partition 的情况,消费完一个 partition 会消费另一个 partition。offset 只是标记 partition 中数据顺序,不同 partition 间的 offset 没有关系;
- Offset:新增消息到每个主题的partition当中时,每条消息会以append log的方式追加的写入分区,同时会增加一个long类型的数字Offset,它是消息在该分区内部的唯一编号,从而保证消息在分区内部的有序。各个Consumer控制和设置其在该partition下消费到offset位置,这样下次可以以该offset位置开始进行消费;
- Replicas:副本,每个topic创建时都会生成副本,默认为1;集群节点大于3时,一般设置为3;其次为了保证高可用,每个分区都会有一定数量的副本replicas。这样如果有部分服务器不可用,副本所在的服务器就会接替上来,保证应用的持续性;
- Leader:假如有N个副本。其中一个replica为leader,其他都为follower,leader处理partition的所有读写请求,于此同时,follower会被动定期的去复制leader上的数据;
- Follower:
- Isr:leader会追踪和维护ISR中所有follower的滞后状态。如果滞后太多(数量滞后和时间滞后两个维度,replica.lag.time.max.ms和replica.lag.max.message可配置),leader会把该replica从ISR中移除。被移除ISR的replica一直在追赶leader;
- Group:Kafka实现单播和广播两种消息模型的手段。对于同一个topic,每个Group都可以拿到相同的全部数据。多个Consumer组成一个Consumer Group,同样也是逻辑上的概念;一个Consumer只能属于一个Consumer Group。Consumer Group保证其订阅的Topic的每个分区只被分配给此Consumer Group中的一个消费者处理。如果不同Consumer Group订阅了同一Topic,Consumer Group彼此之间不会干扰。这样,如果要实现一个消息可以被多个消费者同时消费(“广播”)的效果,则将每个消费者放人单独的一个Consumer Group;如果要实现一个消息只被一个消费者消费(“独占”)的效果,则将所有的Consumer放入一个Consumer Group中。在Kafka官网的介绍中,将Consumer Group称为“逻辑上的订阅者”( logical subscriber),从这个角度看,是有一定道理的。
- 消息:Kafka中传递数据的最小单位;
- Key:<k, v>对的键,可选项,可传空值;
- Value:<k, v>对的值,可选项,可传空值;
- Streams:从kafka topic的读取消息,并进行转换,然后发送到另一个topic上;
- Connector:将Kafka连接到外部程序,从外部数据直接拉取数据,或将数据从kafka发送到外部系统中;
- AdminClient:管理、监视Kafka topics/ broker / other object;
- Kafka支持的组件:Spark/ Flink/ Storm/ Flume/ Hive/ ElasticSearch/ HBase/
常用命令
安装流程
windows可安装Kafka Tool,用于kafka消息的可视化。
# 下面演示的是在单节点安装Kafka的详细教程
# 安装Java,因为Kafka的启动依赖于Java
yum install java-1.8.0-openjdk.x86_64
# 下载Kafka
wget https://mirror-hk.koddos.net/apache/kafka/2.7.0/kafka_2.12-2.7.0.tgz
# 解压
tar -zxvf /root/software/kafka_2.12-2.7.0.tgz
# 启动Zookeeper。其中,Zookeeper可以单独安装,也可以使用Kafka安装目录中默认的。这里的路径需要自己修改
nohup /root/kafka_2.12-2.7.0/bin/zookeeper-server-start.sh /root/kafka_2.12-2.7.0/config/zookeeper.properties &
# 检查Zookeeper是否正常运行,查询后台是否存在其进程
ps aux | grep zookeeper
# 启动Kafka
nohup /root/kafka_2.12-2.7.0/bin/kafka-server-start.sh /root/kafka_2.12-2.7.0/config/server.properties &
# 安装JPS,它可以显示Linux下所有的Java进程
yum install java-1.8.0-openjdk-devel.x86_64
# 查看Kafka是否启动
jps
# 添加Kafka命令到环境变量,方便快速启动
vim /etc/profile # 打开配置文件
export PATH="/root/kafka_2.12-2.7.0/bin:$PATH" # 增加一行
source /etc/profile # 变量生效
常用命令
# Tip: Linux配置不同,其命令与官网也不相同
# IP地址更换为Kafka安装的IP,即下面命令中的 `localhost` 需要更换
# Kafka安装目录:[IP_address]/home/devsa_dev/kafka_2.11-1.0.0
# Kafka版本:Kafka未提供kafka version命令,可观察kafka安装文件名;例如,kafka_2.11-1.0.0,2.11是Scala版本,1.0.0是Kafka版本
# 查看所有topics;2181是zookeeper端口号,可在home/devsa_dev/zookeeper-3.4.6/conf/zoo.cfg中修改
kafka-topics.sh --list --zookeeper localhost:2181
kafka-topics.sh --list --zookeeper localhost:2181 | head -n 10 # 查看前10个topics
kafka-topics.sh --list --zookeeper localhost:2181 | tail -n 10 # 查看后10个topics
kafka-topics.sh --list --zookeeper localhost:2181 | grep "yangsong" # 查看名称包含yangsong的topic
# 查看指定topic的信息
kafka-topics.sh --describe --zookeeper localhost:2181 --topic <topic_name>
# 创建topic
kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic <topic_name>
# 删除topic
kafka-topics.sh --delete --zookeeper localhost:2181 --topic <topic_name>
# 显示所有consumer group
kafka-consumer-groups.sh --list --bootstrap-server localhost:9092
# 查看特定consumer group的信息
kafka-consumer-groups.sh --describe --bootstrap-server localhost:9092 --group <group_name>
# 启动生产者;9092是kafka的监听端口,可在kafka/config/server.properties中修改
kafka-console-producer.sh --broker-list localhost:9092 --topic <topic_name>
# 启动消费者,2181为zookeeper的监听端口
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic <topic_name> --from-beginning # 新版方法;9092是kafka监听的端口号
# 查询topic最小的offset
kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list localhost:9092 -topic <topic_name> --time -2
# 查询topic最大的offset
kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list localhost:9092 -topic <topic_name> --time -1
# 查看topic消费进度
kafka-consumer-groups.sh --describe --group <group_name> --zookeeper localhost:2181
# 在[localhost]节点上开启zookeeper;输入jps后,若出现QuorumPeerMain,说明zookeeper已开启
zkServer.sh start
# 启动kafka server;输入jps后,若出现Kafka,说明Kafka已开启
kafka-server-start.sh config/server.properties # 单节点启动方式,当前界面启动
# 集群启动方式
kafka-server-start.sh -daemon config/server.properties # 单节点启动方式,后台启动,推荐
# 集群启动方式
# 关闭kafka;kafka的打开和关闭都需要数十秒的时间延迟;正常开启Kafka之后,就不要关闭了,因为作业需要一直运行
kafka-server-stop.sh
Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo0408</artifactId>
<version>1.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- kafka常用的maven依赖 -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<!-- 使打包的jar文件包含了主函数的入口 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>kafka_producer_demo0407</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Kafka Demo: Producer简单例子
注意,idea的Java程序打包有两种方式:
- 胖包,参考博客:https://blog.csdn.net/weixin_42089175/article/details/89113271;这种方式将程序所需的依赖全都注入到jar包里面,耗费空间、但是稳定,不易报错;
- 瘦包,参考博客:https://jingyan.baidu.com/article/15622f24d673befdfdbea557.html;这种方式基于Maven打包,将依赖对应的链接注入jar包中,占用空间小、但是容易出现缺少第三方包的报错;
打包后,将jar包提交到集群中运行即可:java -jar <jar_name>
。
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
public class kafka_producer_demo0407 {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("bootstrap.servers", "localhost:9092"); // Broker的IP和端口
properties.put("ack", "all"); // 所有follower都响应了才认为消息提交成功,即"committed"
properties.put("retries", 0); // retries = MAX 无限重试,直到你意识到出现了问题
properties.put("batch.size", 16384);
properties.put("linger.ms", 1);
properties.put("buffer.memory", 33554432);
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<String, String>(properties);
String topic = "mesong0407";
// 以下为业务逻辑,将业务所需数据装配为<k, v>形式,然后发送给指定的topic
String msg_key;
String msg_value;
for (int i = 0; i < 3; i++) {
msg_key = Integer.toString(i); // Kafka的key可以指定为null
msg_value = Integer.toString(i);
producer.send(new ProducerRecord<String, String>(topic, msg_key, msg_value));
System.out.println("发送消息到Kafka");
}
producer.close();
}
}
Kafka Demo: Consumer简单例子
import java.util.Arrays;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
public class demo {
public static void main(String[] args)
{
Properties properties = new Properties();
properties.put("bootstrap.servers", "localhost:9092"); // Broker的IP和端口
properties.put("group.id", "yangsong"); // 指定Consumer GroupId
properties.put("enable.auto.commit", "true");
properties.put("auto.commit.interval.ms", "1000");
properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties); // 实例化一个Consumer对象
consumer.subscribe(Arrays.asList("yangsong.test")); // Consumer订阅该topic,可以订阅多个topic
while (true)
{
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n" // 输出该topic中新增消息记录的偏移量、key、value
, record.offset() // 消息的偏移量
, record.key() // 消息的键
, record.value()); // 消息的值
}
}
// consumer.close();
}
}
踩坑记录
- Q:
zkServer.sh start
启动zookeeper时,报错log为Permission denied FAILED TO WRITE PID
- A:表示此时所用账号没有写入权限,先
sudo su
切换root账号,然后chmod a+xwr zookeeper-3.4.6/
增加写入权限,再开启zookeeper即可;
- Q:启动kafka时,日志报错:
kafka.common.KafkaException: Failed to acquire lock on file .lock in /tmp/kafka-logs. A Kafka instance in another process or thread is using this directory
- A:先
jps
查看进程,kill掉已开启的kafka和zookeeper。然后重新开启zookeeper进程、kafka即可。非root用户,有时候无法通过jps
命令查看所有后台进程;
- Q:开启kafka生产者后,在会话框输入信息后报错log:
WARN [Producer clientId=console-producer] Connection to node -1 could not be established. Broker may not be available
- A:检查kafka/config/server.properties中的listeners,输入listeners指的host和端口号,或者直接输入真实IP;
经验点
- Kafka默认端口为9092,一般最好不要修改,因为至少要修改5个配置文件,比较麻烦;
- Kafka自带了Zookeeper,在单节点情况下,不用另外安装zookeeper即可使用;
- 开发环境、测试环境、生产环境的Java版本尽量保持一致,否则会出现版本不同导致的报错;