SpringBoot 集成Apache Kafak 消息队列
👆关注微信公众号,获取更多编程内容
Kafka is a distributed,partitioned,replicated commit logservice。它提供了类似于JMS的特性,但是在实现上完全不同,此外它并不是JMS规范的实现。kafka对消息保存时根据Topic进行归类,发送消息者成为Producer,消息接受者成为Consumer,此外kafka集群有多个kafka实例组成,每个实例()成为broker。无论是kafka集群,还是producer和consumer都依赖于zookeeper来保证系统可用性集群保存一些meta信息。
基本概念
1. Topic
一个Topic可以认为是一类消息的总称.每个Topic将被分成若干个partition(区域) 每个partition是以append log的形式保存的,任何发布到partition的消息都会以追加到log的形式保存到文件的尾部,每条消息的则以偏移量offset作为其存储位置,offset 唯一标记一条消息.
由于Kafka并未完成规范的实现JMS,所以在和JMS标准实现,如ActiveMQ上的区别,ActiveMQ的消息在被C消费后,该消息会立即被删除,Kafka则会根据配置保存一段时间,到期后在删除文件,释放空间,这么做的目的为了减少IO的开销,那么offset以及C和P的维护管理则交给了Zookeeper
2. Distribution
一个Topic可以被分为若干个区域,这些若干分块,则被保存在多个Server(Kafka的集群中的一个实例,也可以称之为Brokers),每个Server负责该partition的读写操作,同时可以配置每个partition的备份数目,kafka会自动的在多个Server中备份。
所以在replicated方案中,每个partition会有一个leader,来管理其他备份,若该leader失效,则会自动选取新的leader.
使用场景
上文中提到Kafak没有很好地实现JMS规范,同样的Kafka也没有实现JMS中的事务向操作以及ACK机制(消息确认机制)所以根据这些特性决定了Kafka只能在某些场景,比如日志统计,操作记录等方面大展身手.
服务搭建
Zookeeper集群搭建
这里我是用了Docker搭建Zookeeper集群,非常的方便,我把docker-compose文件记录如下:
version: '3.1'
services:
zoo1:
image: zookeeper:latest
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo2:
image: zookeeper:latest
hostname: zoo2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=0.0.0.0:2888:3888 server.3=zoo3:2888:3888
zoo3:
image: zookeeper:latest
hostname: zoo3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=0.0.0.0:2888:3888
直接使用docker-compose up -d 运行即可,如果不会使用,则可以 下载 https://github.com/zhoutao825638/Dockerfiles.git 代码,打开zookeeper文件夹,执行install.sh 脚本即可.
Kafka服务搭建
kafka这里则没有使用集群搭建的方式,有兴趣的朋友可以尝试搭建一下,这里我们直接下载kafka的安装包,
http://kafka.apache.org/downloads 解压,修改config/server.properties 文件
- 移除配置 #host.name=localhost 的注释标记 #
- 修改log.dirs=xxxx 其中xxxx指定为你的文件系统的存在文件夹地址
- 修改zookeeper.connect为localhost:2181/kafka
启动启动Kafka即可,命令如下
# nohup 表示后台运行
# >> /dev/null 表示把输入的日志重定向到null设备中
nohup bin/kafka-server-start.sh config/server.properties >> /dev/null &
操作代码
项目依赖
下面我们构建Spring应用,Gradle文件如下,使用maven的朋友可以自行转换为pom.xml文件:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.kafka:spring-kafka-test'
}
Ps: 使用lombok插件可以自动生成set、get方法以及log对象,如果不是用的话,下面的set和get以及log请自行修改
消息发送和接收
- 消息发送的实体
package com.seven.demo.kafka.data;
import java.util.Date;
import lombok.Builder;
import lombok.Data;
@Data
public class Message {
private Long id;
private String msg;
private Date sendDate;
@Override
public String toString() {
return "Message{" + "id=" + id + ", msg='" + msg + '\'' + ", sendDate=" + sendDate + '}';
}
}
- 消息发送者
package com.seven.demo.kafka;
import com.seven.demo.kafka.data.Message;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.json.GsonBuilderUtils;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class KafkaSender {
public static final String TOPIC = "SIMPLE_TOPIC";
@Autowired private KafkaTemplate<String, String> kafkaTemplate;
public void send() {
Message message = new Message();
message.setId(System.currentTimeMillis());
message.setMsg(UUID.randomUUID().toString());
message.setSendDate(new Date());
log.info("Send Message : {}", message);
kafkaTemplate.send(TOPIC, message.toString());
}
}
非常简单的代码,注入KafkaTemplate,然后使用发送即可,这里使用toString方法发送,实际项目中可使用GSON或者JackSON序列化工具进行序列化发送。
- 消息接受者
package com.seven.demo.kafka;
import com.seven.demo.kafka.data.Message;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class KafkaReceiver {
@KafkaListener(topics = {KafkaSender.TOPIC})
public void listen(ConsumerRecord<?, ?> record) {
Optional<?> value = Optional.ofNullable(record.value());
if (value.isPresent()) {
Object o = value.get();
log.info("接收到消息:" + o);
}
}
}
核心为@Kafka注解,接收方法参数为ConsumerRecord,这里的Optional是JDK8的新特性,可以防止出现空指针,有兴趣的朋友可以去了解下.
- 项目配置
kafka的默认端口为9092
spring:
kafka:
bootstrap-servers: 127.0.0.1:9092
producer:
retries: 0
batch-size: 16384
buffer-memory: 3554432
consumer:
group-id: test-group-id
auto-offset-reset: earliest
enable-auto-commit: true
auto-commit-interval: 100
- 接口调用
package com.seven.demo.kafka;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SendController {
@Autowired KafkaSender kafkaSender;
@GetMapping("/send")
public String sendMessageToKafka() {
kafkaSender.send();
return "Send Message Is OK!";
}
}
- 实际效果
Send Message : Message{id=1551582388333, msg='aeb2c3a4-38bd-4ac9-a6d5-37a868f7d6ca', sendDate=Sun Mar 03 11:06:28 CST
接收到消息:Message{id=1551582388333, msg='aeb2c3a4-38bd-4ac9-a6d5-37a868f7d6ca', sendDate=Sun Mar 03 11:06:28 CST 2019}
Send Message : Message{id=1551582388620, msg='296ff7b9-a116-4af5-ad18-a29b92c55e56', sendDate=Sun Mar 03 11:06:28 CST
接收到消息:Message{id=1551582388620, msg='296ff7b9-a116-4af5-ad18-a29b92c55e56', sendDate=Sun Mar 03 11:06:28 CST 2019}
总结
总的来说在SpringBoot 和Docker 的加持下,集成Kafka变得异常简单,但是很多核心的改变还需要以后去慢慢了解,这就是简单实用Kafka消息队列的代码示例
本文作者: 燕归来
本文链接: https://blog.zhoutao123.com/archives/1551586012192
版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!