入门
简介
kafka(发布订阅模式)是一个分布式的流媒体平台。
应用:消息系统、日志收集、用户行为追踪、流式处理。
特点
高吞吐量(处理消息能力强)、
消息持久化(硬盘顺序存储),
高可靠性(分布式),
高可拓展性。
术语
Broker(kafka集群中的服务器)、
Zookeeper(独立软件,管理集群,kafka也内置了Zookeeper)
Topic(保存消息的位置)、Partition(保存topic的分区)、Offset(消息在分区内存放的索引)
Leader Replica (主副本,可以做响应)、 Follower Replica(随从副本,只负责备份数据)
下载
https://kafka.apache.org/downloads
配置
1、修改Zookeeper.properties配置文件
# 文件保存路径
dataDir=/tmp/zookeeper
2、server.properties 配置log存放位置
log.dirs=/tmp/kafka-logs
启动(windows下)
1、首先启动Zookeeper
启动一个cmd窗口,切换到kafka的目录,输入命令
zookeeper-server-start.bat config\zookeeper.properties
2、接着启动kafka
新启动一个cmd窗口,切换到kafka的目录,输入命令
bin\windows\kafka-server-start.bat config\server.properties
3、创建主题
新启动cmd窗口,切换到kafka的bin目录下输入命令
kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test
命令解释
kafka-topics.bat
–create ip 创建主题的目标。
–replication-factor 创建副本的数量
–partitions 创建分区的数量
–topic 名字 主题名字
–list ----bootstrap-server ip 查询ip服务器的主题。
4、发送消息
kafka-console-producuer.bat --broker-list localhost:9092 --topic test
–broker-list +ip 发送信息的目标
5、消费消息
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test --from-beginning
–from-beginning 读消息从头到尾
SpringBoot整合
1、引入依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.8.2</version>
</dependency>
2、配置Kafka
配置server,consumer
# Kafka配置
spring.kafka.bootstrap-servers=localhost:9092
#消费者组id
spring.kafka.consumer.group-id=test-consumer-group
#是否自动提交偏移量
spring.kafka.consumer.enable-auto-commit=true
#多久提交一次
spring.kafka.consumer.auto-commit-interval=3000
3、Kafka 测试
生产者
kafkaTemplate.send(topic, data);
消费者
@KafkaListener(topics={“test”})
public void handleMessage(ConsumerRecord record){}
测试用例
package com.community.windy;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = WindyCommunityApplication.class)
public class KafkaTests {
@Autowired
KafkaProducer kafkaProducer;
@Test
public void testKafka(){
kafkaProducer.sendMessage("test","你好");
kafkaProducer.sendMessage("test","你多大");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@Component
class KafkaProducer{
@Autowired
private KafkaTemplate kafkaTemplate;
public void sendMessage(String topic,String content) {
kafkaTemplate.send(topic,content);
}
}
@Component
class KafkaConsumer{
@KafkaListener(topics = {"test"})
public void handleMessage(ConsumerRecord record){
System.out.println(record.value());
}
}
4、实际使用
消息生产者
package com.community.windy.event;
import com.alibaba.fastjson.JSONObject;
import com.community.windy.entity.Event;
import netscape.javascript.JSObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
@Component
public class EventProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
//处理事件
public void fireEvent(Event event){
//将事件发送到指定的主题
kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));
}
}
消息消费者
package com.community.windy.event;
import com.alibaba.fastjson.JSONObject;
import com.community.windy.entity.Event;
import com.community.windy.entity.Message;
import com.community.windy.service.MessageService;
import com.community.windy.util.CommunityConstant;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.protocol.types.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class EventConsumer implements CommunityConstant {
private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
@Autowired
private MessageService messageService;
@KafkaListener(topics = {TOPIC_COMMENT,TOPIC_LIKE,TOPIC_FOLLOW})
public void handleCommentMessage(ConsumerRecord record){
if (record==null||record.value()==null){
logger.error("消息内容为空");
return; }
Event event = JSONObject.parseObject(record.value().toString(), Event.class);
if (event==null){
logger.error("消息格式错误");
return; }
//发送站内通知
Message message = new Message();
message.setFromId(SYSTEM_USER_ID);
message.setToId(event.getEntityUserId());
message.setConversationId(event.getTopic());
message.setCreateTime(new Date());
Map<String,Object> content = new HashMap<>();
content.put("userId",event.getUserId());
content.put("entityType",event.getEntityType());
content.put("entityId",event.getEntityId());
if (!event.getData().isEmpty()){
for (Map.Entry<String,Object> entry: event.getData().entrySet()){
content.put(entry.getKey(),entry.getValue());
}
}
//保存数据。
message.setContent(JSONObject.toJSONString(content));
messageService.addMessage(message);
}
}
控制层调用
@Autowired
private EventProducer eventProducer;
@PostMapping(path = "add/{discussPostId}")
public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment){
comment.setUserId(hostHolder.getUser().getId());
comment.setStatus(0);
comment.setCreateTime(new Date());
commentService.addComment(comment);
//触发评论事件
Event event = new Event()
.setTopic(TOPIC_COMMENT)
.setUserId(hostHolder.getUser().getId())
.setEntityId(comment.getEntityId())
.setEntityType(comment.getEntityType())
.setData("postId",discussPostId);
if (comment.getEntityType()==ENTITY_TYPE_POST){
DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId());
event.setEntityUserId(target.getUserId());
}else if (comment.getEntityType() == ENTITY_TYPE_COMMENT){
Comment target = commentService.findCommentById(comment.getTargetId());
event.setEntityUserId(target.getUserId());
}
//发送kafka消息
eventProducer.fireEvent(event);
return "redirect:/discuss/detail/"+discussPostId;
}