问题:
为什么要使用消息队列?
什么是幂等性?
kafka是什么?
学习kafka的学习的重点是什么?
简单介绍一下kafka的架构组成。
消费模式有几种?他们有什么优缺点?
如何搭建一个kafka服务?
kafka的消息发送流程是什么?
向Broker发送消息有几种方式,他们的特点是什么?
为什么要使用消息队列?
业务组件之间需要解耦。
业务QPS不稳定,容易出现波峰的情况,比如:双十一,微博的热点话题。
信息只需要发送,不需要确定是否处理成功。比如:骚扰短信,服务器状态数据发送,日志信息发送,埋点信息等。
什么是幂等性?
幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
如何保证幂等性?
通过唯一ID,每一个操作都有一个唯一ID,在Redis和数据库中通过唯一ID去重。
kafka是什么?
总体说明:
Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。
版权声明:本段内容为优快云博主「昙花逐月」的原创文章。
原文链接: https://blog.youkuaiyun.com/wanghailan1818/article/details/125166287
kafka的特性:多个生产者和多个消费者,可持久化操作,高吞吐量,可伸缩性,实时性,容错性。
实时性:延迟低。
kafka使用的是一套基于TCP的自定义的传输协议。
学习kafka的学习的重点是什么?
重点是各种参数的作用的配置方法,合适的参数配置才能提高kafka的效率。这里注意kafka的配置项有的在producer上,有的在kafka服务中。
简单介绍一下kafka的架构组成。
业务总体一般由三部分组成,product(生产者)+kafka cluster(kafka集群)+consumer(消费者)。
kafka cluster包括多个broker和注册中心zookeeper。
broker(中间件)是一个kafka的实例。
broker中包括多个topic(消息主题),每一个topic可以理解为一个逻辑通道。
topic中包括多个Partition(分区),每一个Partition可以理解为一个topic通道的实例通道,也可以理解为完成负载功能。负责具体做事:
一般消息会直接发送到partition中。
每一个partition中绑定一种服务的一个实例,就是说可以绑定多个不同服务的实例,但一个服务只能绑定一个实例。
partition比服务实例多,不同partition可以对应同一个服务实例。
服务实例比partition多,部分服务实例不能提供消费服务。
partition负责按规则发送信息。
topic主要负责保存服务信息,以及规则信息。partition中负责保存服务的实例信息,可以根据topic的发送方式发送。
如果同一个服务有多个实例,会组成一个消费者组,就是consumer group。这里主要是保证一个消息只能被消费一次。
一个cluster中,同一个topic会有多个Replication。其中一个是leader负责处理业务,剩余的为follower,负责冗余数据,保证容错。follower在leader down后会自动重新选举出leader。Replication一般不会超过10个,如果broker少的话,可能多少个broker会有多少个Replication。
重点参数
topic中的partition数量,应当尽量和消费端实例数量一致或者比实例数量多,因为如果实例数量多于partition数量,有部分实例会接不到任务。
Replication的数量,一般采用和broker的数量一致。
消费模式有几种?他们有什么优缺点?
有两种,推模式和拉模式。
推模式是kafka会主动将消息推给消费者。好处是消息处理及时,但不能保证消费平稳,可能将波峰直接推给消费者。
拉模式是消费者从kafka获取消息。好处是消费者可以根据自己的情况合理的接收数据,坏处是消息处理有一定的延迟。还有一个坏处是不知道消息中间件中消息的情况,即使没有消息也会不断查询浪费资源。
kafka是拉模式。使用了长轮询降低请求次数来降低无数据时造成的资源消耗。
如何搭建一个kafka服务?
使用docker-compose,脚本网上找。(有模板,但不能发 o(╥﹏╥)o)。
kafka的消息发送流程是什么?
producer向cluster发送message需要经过4个线程,分别是producer的main线程 ,producer的sender,cluster的selector线程和kafka实例中broker中的main线程。
先说producer的main线程,经过interceptor(拦截器)、seri序列化器、partitioner(分区器)后将数据放到内存缓存池中对应的Dqueue(数据队列)中。
一般一个分区会对应一个数据队列。
分区器中默认选择sticky partiton(粘性分区)策略,就是先都发向同一个数据队列中,等队列中数据满足发送条件(比如默认满足16k)将队列中的数据发送出去。等上一个队列开始发送数据后,新的消息会放到另一个数据队列中,直到继续等符合条件后发送。按该规则循环处理。
内存缓存池的总大小默认是32M。
接下来producer的sender线程会从内存缓存池中获取到所有的message,然后将负荷条件的message组成一个ProducerBatch,之后将ProducerBatch包装成ClientRequest通过NetWorkClient发送给kafka集群。
Producerbatch发送的条件包括包大小和等待时间,默认大小为16K,默认时间是0ms,两者满足一个就可以发送了。
kafka集群中的controller,会通过selector线程接收ClientRequest,之后将ClientRequest转发给对应的broker中的topic。
topic中的leader接到数据后回首先会同步数据到其他的follower,待一半以上的follower ack后,leader储存并返回储存成功。这里像基于CP原则。
topic完成处理后返回ack给NetWorkClient,然后NetWorkClient会将对应的ClientRequest删除,之后将对应的ProducerBatch中的message删除。
如果没有ack,NetWorkClient会尝试多次重新发送,次数是int值得最大值。
重点参数
ProducerBatch中的大小(配置项:batch.size,ProducerConfig:BATCH_SIZE_CONFIG)和发送等待时间(配置项:linger.ms,ProducerConfig:LINGER_MS_CONFIG)。
如果数据量比较大,可以增加大小,加长发送的等待时间,降低网络IO消耗。
如果数据量少,可以降低发送等待时间。
内存缓冲池的大小(配置项:buffer.memory,ProducerConfig:BUFFER_MEMORY_CONFIG)。这里有个问题:如果数据在cluster处理不及时的话会将数据堆积在内存缓冲池中,如果内存缓存池满了的话,Producer的main进程就会阻塞。
这里的阻塞时间也可以配置(配置项:max.block.ms,ProducerConfig:MAX_BLOCK_MS_CONFIG)。
分区器的分区策略,(配置项是:partitioner.class,ProducerConfig:PARTITIONER_CLASS_CONFIG)。默认策略是粘性策略。还有其他的比如:
如果指定了key,会依据key的哈希值和分区数量取余获取分区,目的是为了保证一组信息的顺序。这里可以再次论证最好分区数量和消费实例数量一致。
如果指定了分区,当然就可以发到指定的分区上。一般不使用该方式,可能出现所有消息都发到同一个分区上的情况。如果需要的话建议使用业务ID作为key,即上一种分区方式。
当然还可以选择自定义的分区策略。自己编写就行了。
代码示例
建立kafka服务,使用docker。新建topic,命令如下:
sh bin/kafka-topics.sh --zookeeper zookeeper:2181 --create --topic test --partitions 3 --replication-factor 3
创建了一个叫test的topic,指定了3个partition和3个replication。
采用maven构建项目,引入了springboot,pom文件如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
</dependencies>
main测试方法代码如下:
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
public class MainHandler {
public static void main(String[] args) throws InterruptedException {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"xxx.xxx.xxx.xxx:9091,xxx.xxx.xxx.xxx:9092,xxx.xxx.xxx.xxx:9093");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
KafkaProducer<String, Object> producer = new KafkaProducer<String, Object>(properties);
for (int i = 0; i < 10; i++) {
ProducerRecord<String, Object> record = new ProducerRecord<>("test", "第" + i + "条消息。");
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
System.out.println("完成发送,topic:" + recordMetadata.topic() + ",partition:" + recordMetadata.partition()
+ ",offset:" + recordMetadata.offset() + ",msg:" + record.value());
}
});
}
System.out.println("发送结束!");
producer.flush();
producer.close();
}
}
没有消费端,在kafka中启动自带的消费监听器。命令如下:
sh bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
执行结果如下,信息并不是连续发送的,猜测是使用了不同的partition的。
第0条消息。
第1条消息。
第2条消息。
第3条消息。
第4条消息。
第7条消息。
第5条消息。
第8条消息。
第6条消息。
第9条消息。
重点参数
发送代码中又引入序列化方式的配置,就是代码中的ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG和ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG。上文提到kafka中使用的传输协议是自定义的,所以需要指定序列化方法,错误的序列化方法会直接导致异常,正确的序列化方法有可能提高传输效率。注意:producer和consumer序列化的方式要一致。
向Broker发送消息有几种方式,他们的特点是什么?
三种方式:发送并忘记,同步发送,异步发送。
发送并忘记,只管发不管结果,规则感觉像UDP。例如:发送骚扰短信。追求发送的速度,对发送结果无要求。代码如下:
KafkaProducer<String, Object> producer = new KafkaProducer<String, Object>(properties);
for (int i = 0; i < 10; i++) {
ProducerRecord<String, Object> record = new ProducerRecord<>("test", /*"key",*/"第" + i + "条消息。");
producer.send(record);
}
同步发送,消息按顺序发送,一个消息处理完成,下一个消息才能发送。代码如下:
KafkaProducer<String, Object> producer = new KafkaProducer<String, Object>(properties);
for (int i = 0; i < 10; i++) {
ProducerRecord<String, Object> record = new ProducerRecord<>("test", /*"key",*/"第" + i + "条消息。");
RecordMetadata recordMetadata = producer.send(record).get();
System.out.println("完成发送,topic:" + recordMetadata.topic() + ",partition:" + recordMetadata.partition()
+ ",offset:" + recordMetadata.offset() + ",msg:" + record.value());
}
异步发送,常用的发送方式。代码就是上文中的示例代码。