标题
一、为什么需要消息系统
1.解耦:
允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
2.冗余:
消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
3.扩展性:
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。
4.灵活性 & 峰值处理能力:
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
5.可恢复性:
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
6.顺序保证:
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性)
7.缓冲:
有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
8.异步通信:
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
二、kafka 架构
如下图:
kafka 相关名词解释如下
1.producer:
消息生产者,发布消息到 kafka 集群的终端或服务。
2.broker:
kafka 集群中包含的服务器。
3.topic:
每条发布到 kafka 集群的消息属于的类别,即 kafka 是面向 topic 的。
4.partition:
partition 是物理上的概念,每个 topic 包含一个或多个 partition。kafka 分配的单位是 partition。
5.consumer:
从 kafka 集群中消费消息的终端或服务。
6.Consumer group:
high-level consumer API 中,每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 Consumer 消费,但可以被多个 consumer group 消费。
7.replica:
partition 的副本,保障 partition 的高可用。
8.leader:
replica 中的一个角色, producer 和 consumer 只跟 leader 交互。
9.follower:
replica 中的一个角色,从 leader 中复制数据。
10.controller:
kafka 集群中的其中一个服务器,用来进行 leader election 以及 各种 failover。
12.zookeeper:
kafka 通过 zookeeper 来存储集群的 meta 信息。
docker安装kafka这里就不演示了,非常简单
进入kafka容器内部,执行命令如下,创建主题test
–create 创建主题的动作指令
–zookeeper 47.93.130.192:2181 指定了kafka所连接的zookeeper的地址
–replication-factor 1 指定了副本因子
–partitions 1 指定了分区个数
–topic test 指定了所要创建主题的名称
可以看到test主题已被创建
入门代码
生产者代码
package com.wangtao.test1;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class Producer {
private static final String brokerList="47.93.130.192:9092";
private static final String topic="test";
public static void main(String[] args) {
Properties props = new Properties();
//设置key序列化器
props.put("key.serializer", StringSerializer.class.getName());
//设置value序列化器
props.put("value.serializer", StringSerializer.class.getName());
//设置集群地址
props.put("bootstrap.servers", brokerList);
props.put("acks", "all");
//设置重试次数
props.put("retries", 10);
props.put("batch.size", 16384);
KafkaProducer<String, String> producer=new KafkaProducer<String, String>(props);
ProducerRecord<String, String> record = new ProducerRecord<String, String>(topic, "kafka-demo", "hello kafka");
try {
/* Future<RecordMetadata> send = producer.send(record);
RecordMetadata recordMetadata = send.get();
System.out.println("topic:"+recordMetadata.topic());
System.out.println("分区:"+recordMetadata.partition());
System.out.println("offset:"+recordMetadata.offset());*/
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if(e==null){
System.out.println("topic:"+recordMetadata.topic());
System.out.println("分区:"+recordMetadata.partition());
System.out.println("offset:"+recordMetadata.offset()); }
}
});
} catch (Exception e) {
e.printStackTrace();
}
producer.close();
}
}
消费者代码
package com.wangtao.test1;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class Consumer {
private static final String brokerList="47.93.130.192:9092";
private static final String groupid="group.demo";
private static final String topic="test";
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", brokerList);
props.put("group.id", groupid);
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("session.timeout.ms", "30000");
props.put("auto.offset.reset", "earliest");
props.put("key.deserializer", StringDeserializer.class.getName());
props.put("value.deserializer", StringDeserializer.class.getName());
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
consumer.subscribe(Collections.singletonList(topic));
// consumer.assign(Arrays.asList(new TopicPartition(topic,0)));
while (true){
ConsumerRecords<String, String> record=consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> record1 : record) {
System.out.println("=======receive: key = " + record1.key() + ", value = " + record1.value()+" offset==="+record1.offset());
}
}
}
}
启动生产者和消费者,消费者窗口看到以下运行截图,说明消息已被消费
下面演示与springboot整合,非常简单
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wangtao</groupId>
<artifactId>kafka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>kafka</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.kafka.bootstrap-servers=47.93.130.192:9092
spring.kafka.producer.transaction-id-prefix=kafka_tx.
消费者与生产者代码写在一起了,注意其中指明了参数传error是会抛异常(这里开启了kafka的事务)
package com.wangtao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class KafkaApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(KafkaApplication.class);
public static void main(String[] args) {
SpringApplication.run(KafkaApplication.class, args);
}
@RequestMapping("/index")
public String index() {
return "hello,kafka";
}
@Autowired
private KafkaTemplate template;
private static final String topic = "test";
@RequestMapping("/send/{input}")
public String sendToKafka(@PathVariable String input){
//template.send(topic, input);
template.executeInTransaction(t->{
t.send(topic, input);
if ("error".equals(input)) {
throw new RuntimeException("input is error");
}
t.send(topic, input+"eee");
return true;
});
return "send success"+input;
}
@RequestMapping("/send2/{input}")
@Transactional(rollbackFor = RuntimeException.class)
public String sendToKafkaTran(@PathVariable String input){
//template.send(topic, input);
template.send(topic, input);
if ("error".equals(input)) {
throw new RuntimeException("input is error");
}
template.send(topic, input+"author");
return "send success"+input;
}
@KafkaListener(id="",topics = topic,groupId = "group.demo")
public void listener(String input){
LOGGER.info("收到消息:[{}]", input);
}
}
正常情况
错误情况
以上是kafka入门demo,其他请自行探索