公司项目需要对接kafaka,记一次开发环境安装。
一、安装kafaka
创建docker网络
docker network create app-tier --driver bridge
安装zookeeper
因为kafaka依赖于zookeeper运行,所以先安装zookeeper。zookeeper的默认端口为:2181
ALLOW_ANONYMOUS_LOGIN 表示可以不需要密码。
下面为快捷安装指令,生产环境需要将配置、数据文件等文件夹挂载到宿主机。
docker run -d --name zookeeper-server \
--network app-tier \
-e ALLOW_ANONYMOUS_LOGIN=yes \
bitnami/zookeeper:latest
安装Kafka
因为kafaka依赖于zookeeper运行,所以先安装zookeeper。zookeeper的默认端口为:2181
ALLOW_PLAINTEXT_LISTENER 表示任何人可以访问。
KAFKA_CFG_ZOOKEEPER_CONNECT 表示zookeeper的地址
KAFKA_CFG_ADVERTISED_LISTENERS 表示当前主机IP或地址,监听端口
下面为快捷安装指令,生产环境需要将配置、数据文件等文件夹挂载到宿主机。
docker run -d --name kafka-server \
--network app-tier \
-p 9092:9092 \
-e ALLOW_PLAINTEXT_LISTENER=yes \
-e KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper-server:2181 \
-e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.21.131:9092 \
bitnami/kafka:latest
查看kafka容器日志
docker logs -f kafka
可视化管理工具kafka-map安装
docker run -d --name kafka-map \
--network app-tier \
-p 9001:8080 \
-v /opt/kafka-map/data:/usr/local/kafka-map/data \
-e DEFAULT_USERNAME=admin \
-e DEFAULT_PASSWORD=admin123 \
--restart always dushixiang/kafka-map:latest
访问测试
访问地址:http://服务器IP:9001/
DEFAULT_USERNAME:默认账号admin
DEFAULT_PASSWORD:默认密码admin123
Git 地址:https://github.com/dushixiang/kafka-map/blob/master/README-zh_CN.md
二、SpringBoot 项目集成kafaka
依赖导入
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置文件
spring:
kafka:
#集群配置
bootstrap-servers: 192.168.21.131:9092
#生产者配置
producer:
#采用的ack机制
acks: 1
#批量提交的数据大小 16kb
batch-size: 16384
#生产者暂存数据的缓冲区大小
buffer-memory: 33554432
retries: 0
#系列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
#消费者配置
consumer:
#是否自动提交偏移量
enable-auto-commit: true
#消费消息后间隔多长时间提交偏移量
auto-commit-interval: 100
#默认的消费者组,代码中可以热键修改
group-id: test
# earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
# latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
# none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
auto-offset-reset: latest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
测试实体
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @describe 订单类javaBean实体
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Order {
/**
* 订单id
*/
private long orderId;
/**
* 订单号
*/
private String orderNum;
/**
* 订单创建时间
*/
private LocalDateTime createTime;
}
生产者
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import java.time.LocalDateTime;
/**
* @describe 话题的创建类,使用它向kafka中创建一个关于Order的订单主题
*/
@Component
@Slf4j
public class KafkaProvider {
/**
* 消息 TOPIC
*/
private static final String TOPIC = "shopping";
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void sendMessage(long orderId, String orderNum, LocalDateTime createTime) {
// 构建一个订单类
Order order = Order.builder()
.orderId(orderId)
.orderNum(orderNum)
.createTime(createTime)
.build();
// 发送消息,订单类的 json 作为消息体
ListenableFuture<SendResult<String, String>> future =
kafkaTemplate.send(TOPIC, JSONObject.toJSONString(order));
// 监听回调
future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
@Override
public void onFailure(Throwable throwable) {
log.info("## Send message fail ...");
}
@Override
public void onSuccess(SendResult<String, String> result) {
log.info("## Send message success ...");
}
});
}
}
消费者
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
/**
* @create 2022-10-08 1:25
* @describe 通过指定的话题和分组来消费对应的话题
*/
@Component
@Slf4j
public class KafkaConsumer {
@KafkaListener(topics = "shopping", groupId = "group_id") //这个groupId是在yml中配置的
public void consumer(String message) {
log.info("## consumer message: {}", message);
}
}
测试类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class KafakaUtil {
@Autowired
private KafkaProvider kafkaProvider;
public void sendMessage() throws InterruptedException {
//如果这里打印为null,要么是zk或kafka没正常启动,此时进入linux分别查看他们状态接口,另外也需要排查一下你的yum文件配置的kafka的地址,最后排查自己的注解是否引入错误的package
System.out.println("是否为空??+" + kafkaProvider);
// 发送 1000 个消息
for (int i = 0; i < 1000; i++) {
long orderId = i + 1;
String orderNum = UUID.randomUUID().toString();
kafkaProvider.sendMessage(orderId, orderNum, LocalDateTime.now());
}
TimeUnit.MINUTES.sleep(1);
}
}