仿牛客网第五章
Kafaka消息队列框架、不用框架也可以解决消息队列问题,利用阻塞队列产生消息系统
阻塞队列比较简单、理解了阻塞队列,可以更加直观的理解Kafaka底层逻辑
一、阻塞队列
Java自带的API
- BlockingQueue【接口】
- 解决线程通信的问题。
- 阻塞方法:put、take。
- 生产者消费者模式
- 生产者:产生数据的线程。
- 消费者:使用数据的线程。
- 实现类
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue、SynchronousQueue、DelayQueue等。
面试常问:写一个生产者消费者实现
public class Test {
public static void main(String[] args) {
BlockingQueue<String> queue = new LinkedBlockingDeque<>(10);
Producer p = new Producer(queue);
Consumer c = new Consumer(queue);
new Thread(p,"producer").start();
new Thread(c,"consumer").start();
}
}
class Consumer implements Runnable {
private BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while(true){
Thread.sleep(20);
System.out.println("消费者消费了:" + queue.take());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Producer implements Runnable{
private BlockingQueue<String> queue;
public Producer(BlockingQueue<String> queue){
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
String tmp = "a product " + i + " from:" + Thread.currentThread().getName();
System.out.println("生产者生产了:" + tmp);
queue.put(tmp);
Thread.sleep(20);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、Kafka入门
Kafka 性能最好的消息队列服务器,能够处理TB级异步消息函数
服务器自动给某些用户发消息,系统消息
数据量大、频率高
-
Kafka简介
- Kafka是一个分布式的流媒体平台。
- 应用:消息系统、日志收集、用户行为追踪、流式处理。
-
Kafka特点
-
高吞吐量、消息持久化、高可靠性、高扩展性。
将消息存在硬盘上、而不是简单的存在内存里,容量大:可持久化存储、可存储海量数据
对硬盘读取的效率取决于读取硬盘的方式,若对硬盘进行顺序读取,而不是随机存取,性能甚至由于内存读取 性能高
分布式的集群部署==》高可靠性、高扩展性
目前最为流行、性能最好的消息队列框架
-
-
Kafka术语
- Broker、Zookeeper
- Topic、Partition、Offset
- Leader Replica 、Follower Replica
消息队列实现的方式:①点对点的方式 ②发布订阅模式
Kafka术语解释
- Broker:Kafka的服务器
- Zookeeper:管理集群【不是kafaka组件、是一个单独的软件,Kafaka内置Zookeeper】
- Topic:点对点模式中每个消费者拿到的消息都不同,发布订阅模式中消费者可能拿到同一份消息。Kafka采用发布订阅模式,生产者把消息发布到的空间(位置)就叫Topic【类似于文件夹】
- Partition:是对Topic位置的分区,如下图:【一个主题可以分为多个分区】
- Offset:就是消息在分区中的索引
- Leader Replica:主副本,可以处理请求
- Follower Replica:从副本,只是用作备份
Kafka相关链接:官网
windows下更改配置
1.config目录下zookeeper.properties修改
2.server.properties
window下使用Kafka
1.启动Zookeeper
2.启动Kafka
启动完成后就会出现我们设置的文件夹
3.创建主题
查看所有主题判断是否创建成功
4.往主题上发送消息
发两条消息
5.消费者接收消息
三、Spring整合Kafka
- 引入依赖
- spring-kafka
- 配置Kafka
- 配置server、consumer
- 访问Kafka
- 生产者
kafkaTemplate.send(topic, data); - 消费者
@KafkaListener(topics = {“test”})
public void handleMessage(ConsumerRecord record) {}
- 生产者
导包
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
application.properties中配置
测试一波
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class KafkaTest {
@Autowired
KafkaProducer kafkaProducer;
@Autowired
KafkaConsumer kafkaConsumer;
@Test
public void testKafka(){
kafkaProducer.sendMessage("test","hello world");
kafkaProducer.sendMessage("test","I love java");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@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());
}
}
生产者主动发消息,消费者被动接收消息
四、发送系统通知
频繁操作、保证性能==》Kafaka消息队列
可以定义三种不同的主题、一旦事件发生,将消息包装到队列中。消费者继续处理该业务的后续过程、生产者可以继续别的业务
生产者和消费者处理业务是并发的,可以同时处理事务【异步】
在Kafaka的基础上,以事件进行封装
- 触发事件
- 评论后,发布通知
- 点赞后,发布通知
- 关注后,发布通知
- 处理事件
- 封装事件对象
- 开发事件的生产者
- 开发事件的消费者
封装事件对象
public class Event {
//张三给李四点赞---userId是张三,entityUserId是李四
private String topic;
private int userId;
private int entityType;
private int entityId;
private int entityUserId;
//额外的数据放在Map中,具备一定的扩展性
private Map<String,Object> data = new HashMap<>();
public String getTopic() {
return topic;
}
public Event setTopic(String topic) {
//改造,set主题之后,返回当前对象,方便set其它数据
//因为参数很多,最好不用参数构造器
this.topic = topic;
return this;
}
public int getUserId() {
return userId;
}
public Event setUserId(int userId) {
this.userId = userId;
return this;
}
public int getEntityType() {
return entityType;
}
public Event setEntityType(int entityType) {
this.entityType = entityType;
return this;
}
public int getEntityId() {
return entityId;
}
public Event setEntityId(int entityId) {
this.entityId = entityId;
return this;
}
public int getEntityUserId() {
return entityUserId;
}
public Event setEntityUserId(int entityUserId) {
this.entityUserId = entityUserId;
return this;
}
public Map<String, Object> getData() {
return data;
}
public Event setData(String key,String object) {
this.data.put(key,object);
return this;
}
}
- 注意set方法的修改是为了可以类似sb.append(" ").append(“1”);的操作
- 注意setData方法的修改
开发事件的生产者
新建event包
@Component
public class EventProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
//处理事件,本质就是发送消息
public void fireEvent(Event event){
//将事件发送到指定的主题
kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));
}
}
开发事件的消费者
@Component
public class EventConsumer implements CommunityContant {
//记日志
private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
//消息最终是要往message表中插入数据的
@Autowired
private MessageService messageService;
@KafkaListener(topics = {
TOPIC_LIKE,TOPIC_COMMENT,TOPIC_FOLLOW})
public void handleCommentMessage(ConsumerRecord record){
if(record==null||record.value()==null){
logger.error("消息的内容为空");
return;
}
//将JSON字符串 解析为对象
Event event = JSONObject.parseObject(record.value().toString(),Event.class);
if(event==null){
logger.error("消息格式错误");
return;
}
//发送站内通知,主要是构造message对象
Message message = new Message();
//User表中id为1代表系统用户
message.setFromId(SYSTEM_ID);
message.setToId(event.getEntityUserId());
message.setConversationId(event.getTopic());//存的是主题
message.setCreateTime(new Date()