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请自行修改

消息发送和接收

  1. 消息发送的实体
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 + '}';
  }
}

  1. 消息发送者

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序列化工具进行序列化发送。

  1. 消息接受者
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的新特性,可以防止出现空指针,有兴趣的朋友可以去了解下.

  1. 项目配置

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
  1. 接口调用
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!";
  }
}
  1. 实际效果
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 许可协议。转载请注明出处!

posted @ 2019-03-03 12:11  燕归来兮  阅读(699)  评论(0编辑  收藏  举报