springboot 接入 kafka 组件 xml方式

1. kafka相关简介

1.1 kafka是什么

kafka是一个高吞吐量,分布式的消息队列系统,把数据从一个系统搬运到其它系统。同时它还提供一段时间内的数据存储,以及按索引或者时间戳索引的数据检索

1.2 kafka的作用

流量削峰(请求徒增,后端无法承受)
多系统之间解耦(不同语言的系统不需要写客户端互相通信)
流式处理(大数据系统的数据输入和输出源)
日志接收和处理等(应用→kafka→logstash→es/hbase)

1.3 topic和partition(分区)关系

在这里插入图片描述
topic不保证数据的先后顺序,但是partition(分区)是保证数据写入的先后顺序,一个topic可以有一个分区,也可以有多个分区

1.4 kafka消费组的概念

在这里插入图片描述
consumer group是一组相同逻辑的机器/线程。他们订阅同一个topic时,会分别消费不同的partition,保证同一条消息在一个consumer group中不会被消费多次。

consumer group和partition的概念主要是为了增加消费端的并发消费能力,以提升消费速度。

在一个group中,一个consumer可以消费多个partition。但是一个partition只会被一个consumer消费。

一般而言可以认为consumer就是一个kafka client机器上的线程,一个consumer group可能包含n台有m个线程的kafka client机器。那么当这个group消费一个有n*m个partition的topic时,每个线程正好消费一个partition,性能最好。

2 SpringBoot接入kafka组件

2.1 相关依赖

		<dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

2.2 优势

通过本文可以实现对kafka消费端消息的统一拦截处理,以及相关的消息分发

2.3 kafka消费工厂重写

public class GenericKafkaConsumerFactory<K, V> extends DefaultKafkaConsumerFactory<K, V> {

    public GenericKafkaConsumerFactory(Map<String, Object> configs, boolean broadcastGroup)
            throws NoSuchFieldException, IllegalAccessException {
        super(configs);
        //主要是为了一些特殊的业务场景准备,有部分业务场景同一个topic的消息需要被同一个业务组的应用消费
        //比如异步通知其他pod同应用更新内存数据,此时就需要再分组后添加ip地址来生成一个唯一分组,保证可以消费到
     	if(broadcastGroup){
            Object groupId = configs.get(ConsumerConfig.GROUP_ID_CONFIG);
            Assert.notNull(groupId, "group.id is empty");
            groupId = groupId.toString() + "_" + NetUtils.getLocalHost();
            //修改kafka分组
            try {
                Class<DefaultKafkaConsumerFactory> defaultKafkaConsumerFactoryClass = DefaultKafkaConsumerFactory.class;
                Field kafkaConfigs = defaultKafkaConsumerFactoryClass.getDeclaredField("configs");
                kafkaConfigs.setAccessible(true);
                Map<String, Object> updateConfigs = (Map<String, Object>) kafkaConfigs.get(this);
                updateConfigs.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
            } catch (Exception e) {
                log.error("kafka消费端分组随机数设置失败",e);
            }
        }

    }
}

2.4 kafka消息监听容器实现

public class GenericKafkaMessageListenerContainer implements InitializingBean {

    GenericKafkaMessageListenerContainer kafkaMessageListenerContainer = null;

    List<ContainerProperties> containerPropertiesList = new ArrayList<>();

    GenericKafkaListener genericKafkaListener;

    protected List<KafkaConsumerConfig> consumerConfigs;

    DefaultKafkaConsumerFactory factory = null;

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("------- 初始化kafka监听容器");
        for(KafkaConsumerConfig consumerConfig : consumerConfigs){
        	//设置kafka监听器监听的topic
            ContainerProperties containerProperties = new ContainerProperties(consumerConfig.getTopic());
            KafkaMessageListenerContainer container = new KafkaMessageListenerContainer(factory, containerProperties);
            //将业务消费端配置加入到监听器中
            genericKafkaListener.addConsumerConfig(consumerConfig);
            container.setupMessageListener(genericKafkaListener);
            container.start();
        }
        log.info("------- 初始化kafka监听容器完成");
    }
}

public class KafkaConsumerConfig {

    private String topic;

    private String key; //messagekey 过滤暂不支持

    private IMqKafkaBizConsumer consumer;

 }
public interface IMqKafkaBizConsumer<T> {

    /**
     * 消息成功消费返回null,否则返回data
     *
     * @param data
     * @return 返回true表示消费成功,false表示消费失败,但是抛弃这条消息不做处理。抛出异常会启动重投机制。
     */
    boolean consumber(String topic, T data, int partition, long offset, String key) throws Exception;

}
统一监听器

监听实现,如果有监听的topic有消息,统一会回调onMessage方法,此时我们需要根据之前的topic和自定义consumer的映射,找到consumer将消息交由他处理

public class GenericKafkaListener<T> implements MessageListener<String, T> {

    protected Map<String, KafkaConsumerConfig> consumerConfigMap	= new ConcurrentHashMap<>();

	//加入KafkaConsumer配置
    public void addConsumerConfig(KafkaConsumerConfig kafkaConsumerConfig){
        consumerConfigMap.put(kafkaConsumerConfig.getTopic(), kafkaConsumerConfig);
    }

    public void onMessage(ConsumerRecord<String, T> record){
        // 处理收到的消息
        System.out.println("Received message  "+ record.topic()+"  : " + record.value());
        String topic = record.topic();
        KafkaConsumerConfig consumerConfig = consumerConfigMap.get(topic);
        IMqKafkaBizConsumer consumer = consumerConfig.getConsumer();
        boolean t = false;
        try {
        	//根据topic找到对应的业务consumer,将topic消息交由对应的consumer处理
            t = consumer.consumber(record.topic(), record.value(), record.partition(), record.offset(),
                    record.key());
            if (!t) {
                log.warn("[topic:" + record.topic() + "],消费失败,但业务允许放弃:" + record.value().toString());
            }
        } catch (Exception e) {
            //报错是否允许直接跳过
            if (consumerConfig.isIgnore()) {
                return;
            } else {
                throw new RuntimeException(
                        "-1,[topic:" + record.topic() + "],异常重试:" + record.value().toString() + "kafka_error1:", e);
            }
        }
        return;
    }
}

3. 生产端xml配置

<bean id="kafkaProducerFactory" class="org.springframework.kafka.core.DefaultKafkaProducerFactory">
    <constructor-arg>
      <map>
        <entry key="bootstrap.servers" value="${kafka.bootstrap.servers}"/>
        <entry key="retries" value="5"/>
        <entry key="batch.size" value="16384"/>
        <entry key="linger.ms" value="2"/>
        <entry key="buffer.memory" value="33554432"/>
        <entry key="key.serializer" value="org.apache.kafka.common.serialization.StringSerializer"></entry>
        <entry key="value.serializer" value="org.apache.kafka.common.serialization.StringSerializer"></entry>
<!--        <entry key="use.sasl" value="${kafka.use.sasl:true}"/>-->
<!--        <entry key="servers.sasl" value="${kafka.biz.servers.sasl}"/>-->
      </map>
    </constructor-arg>
  </bean>

  <bean id="kafkaTemplate" class="org.springframework.kafka.core.KafkaTemplate">
    <constructor-arg ref="kafkaProducerFactory"/>
    <constructor-arg name="autoFlush" value="true"/>
  </bean>

之后在代码中注入kafkaTemplate 就可以使用其调用

kafkaTemplate.send(topic, message);

4. 消费端xml配置以及使用

<bean id="consumerFactory"
        class="com.nuwa.retransmission.infrastructure.kafka.GenericKafkaConsumerFactory" >
    <constructor-arg>
      <map>
        <entry key="bootstrap.servers" value="${kafka.bootstrap.servers}" />
        <entry key="enable.auto.commit" value="false" />
        <entry key="auto.commit.interval.ms" value="3000"/>
        <entry key="session.timeout.ms" value="60000"/>
        <entry key="request.timeout.ms" value="61000"/>
        <entry key="group.id" value="nuwa-retransmission"/>
        <entry key="key.deserializer"
               value="org.apache.kafka.common.serialization.StringDeserializer" />
  
      <entry key="value.deserializer"
              value="org.apache.kafka.common.serialization.StringDeserializer" />
      </map>
    </constructor-arg>
    <constructor-arg name="broadcastGroup" value="true"></constructor-arg>
  </bean>

  <bean id="messageListenerContainer" class="com.nuwa.retransmission.infrastructure.kafka.GenericKafkaMessageListenerContainer">
    <property name="factory" ref="consumerFactory"/>
    <property name="kafkaTemplate" ref="kafkaTemplate"/>
    //配置Kafka统一监听器
    <property name="genericKafkaListener" ref="genericKafkaListener"/>
    //消费端配置信息
    <property name="consumerConfigs">
      <list>
        <bean class="com.basic.mq.domain.KafkaConsumerConfig">
          //监听的topic
          <property name="topic" value="topic2"/>
          //监听topic回调的consumer
          <property name="consumer">
            <bean id="eventConsumer" class="com.nuwa.retransmission.service.impl.AppAndApiAuthSyncConsumer"/>
          </property>
        </bean>
      </list>
    </property>
  </bean>
  
  <bean id="genericKafkaListener" class="com.nuwa.retransmission.infrastructure.kafka.GenericKafkaListener"/>

消费端代码

public class AppAndApiAuthSyncConsumer implements IMqKafkaBizConsumer<String> {

    private static final Logger LOGGER = LoggerFactory.getLogger(AppAndApiAuthSyncConsumer.class);




    @Override
    public boolean consumber(String topic, String kafkaMqData, int i, long l, String s1) {
        String oldName = Thread.currentThread().getName();
            Thread.currentThread().setName("Thread_AppAndApiAuthSyncConsumer_" + UUID.randomUUID());
            LOGGER.info("topic {}, receive {}", topic, data);


        return true;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值