Spring-Kafka(學習筆記2020.3.24)
前言:
Spring-Kafka是將核心Spring概念應用於基於Kafka的消息傳遞解決方案的開發。它提供了一個
“模板”
作爲發送消息的高級抽象。它還爲帶有@KafkaListener
註釋和“偵聽器容器”的消息驅動的POJO提供支持。這些庫促進了依賴注入和聲明式的使用。簡單明瞭,叫是進行封裝了複雜的操作,提供模板方式簡化了操作方式,使用起來更加簡單。
特徵
KafkaTemplate
KafkaMessageListenerContainer
@KafkaListener
KafkaTransactionManager
spring-kafka-test
嵌入式kafka服務器的jar
先前條件:您必須安裝並運行Apache Kafka
1. 快速入門
xml方式
1.1 引入依賴:
<dependencies>
<!-- spring-kafka依賴 -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.4.4.RELEASE</version>
</dependency>
</dependencies>
使用Spring Boot時,忽略版本,Boot將自動引入與您的Boot版本兼容的正確版本
生產者配置
1.2 編寫生產者Producer
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1.定義producer的參數 -->
<bean id="producerProperties" class="java.util.HashMap">
<constructor-arg>
<map>
<!-- brokers 地址-->
<entry key="bootstrap.servers" value="localhost:9092" />
<!-- 組id 生產者是什麼分組, 消費者也要是相同 -->
<entry key="group.id" value="mykafka" />
<!-- 發送失敗重試次數 -->
<entry key="retries" value="1" />
<!-- 批處理條數: -->
<entry key="batch.size" value="16384" />
<!--批處理延遲時間上限:即1ms過後,不管是否達到批處理數,都直接發送一次請求 -->
<entry key="linger.ms" value="1" />
<!-- 即32MB的批處理緩衝區 -->
<entry key="buffer.memory" value="33554432" />
<!-- 即所有副本都同步到數據時send方法才返回, 以此來完全判斷數據是否發送成功, 理論上來講數據不會丟失 -->
<entry key="acks" value="all" />
<entry key="key.serializer"
value="org.apache.kafka.common.serialization.StringSerializer" />
<entry key="value.serializer"
value="org.apache.kafka.common.serialization.StringSerializer" />
</map>
</constructor-arg>
</bean>
<!-- 2.創建kafkatemplate需要使用的producerfactory bean -->
<bean id="producerFactory"
class="org.springframework.kafka.core.DefaultKafkaProducerFactory">
<!-- 使用參數初始化工廠 -->
<constructor-arg>
<ref bean="producerProperties" />
</constructor-arg>
</bean>
<!-- 3.創建kafkatemplate bean,使用的時候,只需要注入這個bean,即可使用template的send消息方法 -->
<bean id="KafkaTemplate" class="org.springframework.kafka.core.KafkaTemplate">
<constructor-arg ref="producerFactory" />
<constructor-arg name="autoFlush" value="true" />
<property name="defaultTopic" value="defaultTopic" />
<!-- 發送信息返回的結果進行監聽 可選 -->
<property name="producerListener" ref="MyProducerListener"/>
</bean>
<!-- 4.我的生產者監聽類 監聽發送結果 - 可選 -->
<bean id="MyProducerListener" class="com.zhihao.resultlistener.KafkaProducerListener"/>
</beans>
1.3 生產者監聽類
監聽發送結果的
KafkaProducerListener
public class KafkaProducerListener implements ProducerListener<String,String> {
@Override
public void onSuccess(ProducerRecord<String, String> producerRecord, RecordMetadata recordMetadata) {
System.out.println("發送成功了"+producerRecord.value());
}
@Override
public void onError(ProducerRecord<String, String> producerRecord, Exception exception) {
System.out.println("發送失敗了"+producerRecord.value());
System.out.println("發送失敗了"+exception.getMessage());
}
}
消費者配置
1.4 編寫消費者Consumer
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 1.定義consumer的參數 -->
<bean id="consumerProperties" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="bootstrap.servers" value="localhost:9092"/>
<!-- 組id 生產者是什麼分組, 消費者也要是相同 -->
<entry key="group.id" value="mykafka"/>
<!-- 啓用自動提交 -->
<entry key="enable.auto.commit" value="true"/>
<!-- 自動提交間隔 -->
<entry key="auto.commit.interval.ms" value="1000"/>
<!-- 在使用Kafka的組管理時,用於檢測消費者故障的超時 -->
<entry key="session.timeout.ms" value="15000"/>
<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>
</bean>
<!-- 2.創建consumerFactory bean -->
<bean id="consumerFactory" class="org.springframework.kafka.core.DefaultKafkaConsumerFactory">
<constructor-arg>
<ref bean="consumerProperties"/>
</constructor-arg>
</bean>
<!-- 3.我的實際執行消息消費的類 -->
<bean id="MyMessageListenerConsumerService" class="com.zhihao.KafkaConsumerServer"/>
<!-- 4.消費者容器屬性配置 -->
<bean id="MyConsumerContainerConfig" class="org.springframework.kafka.listener.ContainerProperties">
<!-- 訂閱 mykafkaDemo 主題(topic) -->
<constructor-arg value="mykafkaDemo"/>
<!-- 選擇我的監聽類進行消費 -->
<property name="messageListener" ref="MyMessageListenerConsumerService"/>
</bean>
<!-- 5.創建多線程Kafka消息監聽容器,執行doStart()方法 -->
<!-- consumerFactory+containerProperties -> messageListenerContainer 容器配置(topics)+ 消息監聽器,構造一個併發消息監聽容器,並執行初始化方法doStart -->
<bean id="messageListenerContainer" class="org.springframework.kafka.listener.ConcurrentMessageListenerContainer" init-method="doStart" >
<constructor-arg ref="consumerFactory" />
<constructor-arg ref="MyConsumerContainerConfig" />
<!-- 併發數 -->
<property name="concurrency" value="5" />
</bean>
<!-- 創建單線程Kafka消息監聽器容器 -->
<!-- <bean id="MyConsumerMessageListenerContainer" class="org.springframework.kafka.listener.KafkaMessageListenerContainer"-->
<!-- init-method="doStart">-->
<!-- <constructor-arg ref="consumerFactory"/>-->
<!-- <constructor-arg ref="MyConsumerContainerConfig"/>-->
<!-- </bean>-->
</beans>
1.5 消費者監聽類
MessageListener接口實現時,當消費者拉取消息之後,消費完成會自動提交offset,即enable.auto.commit爲true時
public class KafkaConsumerServer implements MessageListener<String, String> {
/**
* 接收消息後自動提交offset 需要配置開啓enable-auto-commit: true
*
* @param message
* @return void
* @author: zhihao
* @date: 2020/3/23
*/
@Override
public void onMessage(ConsumerRecord<String, String> message) {
String topic = message.topic();
System.out.println(topic);
System.out.println("--------topic1--------");
System.out.println(message.key());
System.out.println("---------key1-------");
System.out.println(message.value());
}
}
1.6 進行測試
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-kafka-producer.xml","classpath:spring-kafka-consumer.xml"})
public class SpringKafka {
@Autowired
private KafkaTemplate<String,String> kafkaTemplate;
@Test
public void sendMessage() throws InterruptedException {
ListenableFuture<SendResult<String, String>> mykafkaDemo = null;
for (int i = 0; i < 100; i++) {
mykafkaDemo = kafkaTemplate.send("mykafkaDemo", "name" + i, "love" + i);
}
//可選添加回調,而不使用監聽類
//mykafkaDemo.addCallback();
Thread.sleep(5000);
}
}
1.7 手動提交消費者監聽類
使用AcknowledgeMessageListener時,當消費者消費一條消息之後,不會自動提交offset,需要手動ack,即enable.auto.commit爲false時,適合使用此接口
如果業務較重,且offset自動提交時,出現消費異常或者消費失敗的情況,消費者容易丟失消息,所以需要採用手動提交offset的方式。
先在消費者配置文件屬性中關閉自動提交:
<!-- 啓用自動提交 --> <entry key="enable.auto.commit" value="false"/> <!-- 在 4.消費者容器屬性配置 加上屬性--> <property name="ackMode" value="MANUAL_IMMEDIATE"/>
/**
* @Author: zhihao
* @Date: 2020/3/23 16:05
* @Description: 實現的是確認消息監聽器
* @Versions 1.0
**/
public class KafkaConsumerServer implements AcknowledgingMessageListener<String, String> {
/**
* 接收消息手動提交
*
* @param message
* @param acknowledgment
* @return void
* @author: zhihao
* @date: 2020/3/23
*/
@Override
public void onMessage(ConsumerRecord<String, String> message, Acknowledgment acknowledgment) {
String topic = message.topic();
System.out.println(topic);
System.out.println("--------topic--------");
System.out.println(message.key());
System.out.println("---------key-------");
System.out.println(message.value());
//最終提交確認接收到消息 手動提交 offset
acknowledgment.acknowledge();
}
屬性說明
- RECORD
>>>
每處理一條commit一次 - BATCH(
默認
)>>>
每次poll的時候批量提交一次,頻率取決於每次poll的調用頻率 - TIME
>>>
每次間隔ackTime的時間去commit - COUNT
>>>
累積達到ackCount次的ack去commit - COUNT_TIME
>>>
ackTime或ackCount哪個條件先滿足,就commit - MANUAL
>>>
listener負責ack,但是背後也是批量上去 - MANUAL_IMMEDIATE
>>>
listner負責ack,每調用一次,就立即commit
2. 註解方式
使用註解方式, 並設置成功消費的迴應!
2.1 引入依賴
<!-- spring-kafka依賴 -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.4.4.RELEASE</version>
</dependency>
使用Spring Boot時,忽略版本,Boot將自動引入與您的Boot版本兼容的正確版本
2.2編寫生產者配置類ProducerConfig
@Configuration
@EnableKafka //啓用註解kafka
public class ProducerConfig {
/**
* 註冊kafka模板
*
* @return org.springframework.kafka.core.KafkaTemplate<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2020/3/24
*/
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
/**
* 註冊生產者工廠,並配置生產者配置
*
* @return org.springframework.kafka.core.ProducerFactory<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2020/3/24
*/
@Bean
public ProducerFactory<String, String> producerFactory() {
HashMap<String, Object> configs = new HashMap<>();
//bootstrapServers爲Kafka生產者的地址
configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
//確認配置
configs.put(ProducerConfig.ACKS_CONFIG, "all");
//重試次數
configs.put(ProducerConfig.RETRIES_CONFIG, 1);
//批處理數量,每當將多個記錄發送到同一分區時,生產者將嘗試將記錄一起批處理成更少的請求。這有助於提高客戶端和服務器的性能
configs.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
//批處理延遲時間上限:即10ms過後,不管是否達到批處理數,都直接發送一次請求
configs.put(ProducerConfig.LINGER_MS_CONFIG, 10);
//緩衝區內存配置 32MB
configs.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
//key與value序列化方式
configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
DefaultKafkaProducerFactory<String,String> kvDefaultKafkaProducerFactory = new DefaultKafkaProducerFactory<>(configs);
return kvDefaultKafkaProducerFactory;
}
/**
* 創建kafka主題
*
* @param
* @return org.apache.kafka.clients.admin.NewTopic
* @author: zhihao
* @date: 2020/3/24
*/
/*@Bean
public NewTopic foo() {
//第一個是參數是topic名字,第二個參數是分區個數,第三個是topic的複製因子個數
//當broker個數爲1個時會創建topic失敗,
//提示:replication factor: 2 larger than available brokers: 1
//只有在集羣中才能使用kafka的備份功能
return new NewTopic("mykafkaDemo", 1, (short) 1);
}*/
}
2.3 編寫消費者配置ConsumerConfig
類
@Configuration
@EnableKafka //啓用註解kafka
public class ConsumerConfig {
/**
* 默認的卡夫卡消費工廠,並配置消費者配置
*
* @return org.springframework.kafka.core.DefaultKafkaConsumerFactory<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2020/3/24
*/
@Bean
public DefaultKafkaConsumerFactory<String, String> defaultKafkaConsumerFactory() {
Map<String,Object> properties = new HashMap<>();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
//費者羣組ID,發佈-訂閱模式,即如果一個生產者,多個消費者都要消費,那麼需要定義自己的羣組,同一羣組內的消費者只有一個能消費到消息
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"Mykafka");
//自動提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
//提交間隔
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
//會話超時MS配置
properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000");
//自動偏移重設
//earliest:當各分區下有已提交的offset時,從提交的offset開始消費;無提交的offset時,從頭開始消費
//latest:當各分區下有已提交的offset時,從提交的offset開始消費;無提交的offset時,消費新產生的該分區下的數據
//none:topic各分區都存在已提交的offset時,從offset後開始消費;只要有一個分區不存在已提交的offset,則拋出異常
//exception:直接拋出異常
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
//key與value序列化方式
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class);
//設置批量參數,一次調用返回的最大記錄數
properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1000);
DefaultKafkaConsumerFactory<String, String> DefaultKafkaConsumerFactory = new DefaultKafkaConsumerFactory<>(properties);
return DefaultKafkaConsumerFactory;
}
/**
* 併發Kafka偵聽器容器工廠
*
* @return org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2020/3/24
*/
@Bean("kafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(defaultKafkaConsumerFactory());
factory.setConcurrency(3);
//批處理偵聽器
factory.setBatchListener(true);
//設置輪詢超時
factory.getContainerProperties().setPollTimeout(3000);
//設置回覆模板
factory.setReplyTemplate(this.kafkaTemplate());
//手動提交確認模式
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
return factory;
}
/**
* 註冊監聽類到容器,可以使用註解方式註冊(這裏是的demo沒配置包掃描)
*
* @return com.zhihao.AnnotationMessage
* @author: zhihao
* @date: 2020/3/24
*/
@Bean
public AnnotationListenerMessage annotationListenerMessage(){
return new AnnotationListenerMessage();
}
@Bean
public ResultListenerMessage resultListenerMessage(){
return new ResultListenerMessage();
}
}
配置類上需要
@EnableKafka
註釋才能在Spring託管Bean上檢測@KafkaListener
註解
2.4 編寫消費監AnnotationListenerMessage
聽類進行消費
public class AnnotationListenerMessage {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 消費消息
* <p>
* 消費者分組的組名爲Mykafka
* 監聽的主題是mykafkaDemo
* @SendTo(value = "result")可選, 進行監聽消費成功後的轉發,參數是另一個監聽類的主題
* </p>
*
* @param message
* @param acknowledgment
* @author: zhihao
* @date: 2020/3/24
*/
@KafkaListener(groupId = "Mykafka",topics = "mykafkaDemo")
@SendTo(value = "result") //可選
public void myAnnotationMessage(String message,Acknowledgment acknowledgment){
logger.info("接收消息: {}", message);
//最終提交確認接收到消息 手動提交 offset
acknowledgment.acknowledge();
}
}
2.5 編寫成功消費監聽類ResultListenerMessage
可選操作!
public class ResultListenerMessage {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 監聽result主題
*
* @param message
* @param acknowledgment
* @author: zhihao
* @date: 2020/3/24
*/
@KafkaListener(topics = "result")
public void myAnnotationMessage(String message, Acknowledgment acknowledgment){
logger.info("成功消費消息: {}", message);
//最終提交確認接收到消息 手動提交 offset
acknowledgment.acknowledge();
}
}
2.6 發送複雜的消息 (另一篇文章)
目前只發送了簡單的字符串類型的消息,我們可以自定義消息轉換器來發送複雜的消息。
1.定義一個entity對象(省略)
- 修改生產者工廠序列化方式
- 修改模板發送泛型
@Bean
public ProducerFactory<String, Message> producerFactory() {
...............
//value使用kafka提供的JsonSerializer
configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
//修改爲Object
DefaultKafkaProducerFactory<String,Message> kvDefaultKafkaProducerFactory = new DefaultKafkaProducerFactory<>(configs);
return kvDefaultKafkaProducerFactory;
}
//kafka模板類型也修改爲Message
@Bean
public KafkaTemplate<String, Message> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
發送測試類修改爲:
@Autowired
private KafkaTemplate<String,Message> kafkaTemplate;
@Test
public void sendMessage() throws InterruptedException {
ListenableFuture<SendResult<String, Message>> mykafkaDemo = null;
//int i = 1;
for (int i = 0; i < 10; i++) {
mykafkaDemo = kafkaTemplate.send("mykafkaDemo", "key" + i, new Message("love", String.valueOf(i)));
final int j = i;
//添加回調......
Thread.sleep(1000);
}
}
2.7 接收復雜消息
1.修改消費者工廠序列化方式
2.修改偵聽器容器工廠泛型
@Bean
public DefaultKafkaConsumerFactory<String, Message> defaultKafkaConsumerFactory() {
//..............
//Map容器序列化方式註釋,使用構造方法的
DefaultKafkaConsumerFactory<String, Message> DefaultKafkaConsumerFactory
= new DefaultKafkaConsumerFactory<>(properties,new StringDeserializer(),new JsonDeserializer<>(Message.class));
return DefaultKafkaConsumerFactory;
}
//---------------------------------------------------------
@Bean("kafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, Message> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Message> factory = new ConcurrentKafkaListenerContainerFactory<>();
//.........
return factory;
}
接收監聽類修改爲:
@KafkaListener(groupId = "Mykafka",topics = "mykafkaDemo")
public String myAnnotationMessage(Message message,
@Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) String key,
Acknowledgment acknowledgment){
logger.info("接收消息message: {}", message.toString());
logger.info("接收消息key: {}", key);
//最終提交確認接收到消息 手動提交 offset
acknowledgment.acknowledge();
//返回成功消費信息
return message.getMessage();
}
注意事項:
構造消費者工廠使用集合指定序列化
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
會序列化失敗。發送的對象沒有無參構造方法時候,會序列化失敗!
3. @KafkaListener
詳解
3.1 還可以同時監聽來自多個Topic的消息:
@KafkaListener(topics = "mykafkaDemo1, mykafkaDemo2")
3.2 還可以通過@KafkaListener
來指定只接收來自特定分區的消息:
@KafkaListener(groupId = "Mykafka", topicPartitions = @TopicPartition(topic = "mykafkaDemo", partitionOffsets = { @PartitionOffset(partition = "1", initialOffset = "0") })) public void listen(String message, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) { logger.info("接收消息: {},partition:{}", message, partition); }
如果不需要指定
initialOffset
,上面代碼可以簡化爲:
@KafkaListener(groupId = "Mykafka", topicPartitions = @TopicPartition(topic = "mykafkaDemo", partitions = { "0", "1" }))
您可以在
partitions
或partitionOffsets
屬性中指定每個分區,但不能兩個都指定。
3.3containerFactory
屬性指定使用其他容器工廠(例如創建多個自動提交的容器工廠)
3.4 通過@Header
註解獲取其他信息
@KafkaListener(id = "qux", topicPattern = "myTopic1")
public void listen(@Payload String foo,
@Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) Integer key, //收到的消息key
@Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition,//收到的分區ID
@Header(KafkaHeaders.RECEIVED_TOPIC) String topic, //收到的主題
@Header(KafkaHeaders.RECEIVED_TIMESTAMP) long ts //收到的時間戳
) {
以上例子都是發送非阻塞(異步),**如果需要阻塞(同步)**調用
send().get()
4.消息過濾器
我們可以爲消息監聽添加過濾器來過濾一些特定的信息。我們在消費者配置類
KafkaConsumerConfig
的kafkaListenerContainerFactory
方法裏配置過濾規則:
/**
* 併發Kafka偵聽器容器工廠
*
* @return org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2020/3/24
*/
@Bean("kafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
.......
//設置過濾信息
factory.setRecordFilterStrategy(new RecordFilterStrategy<String, String>() {
@Override
public boolean filter(ConsumerRecord<String, String> consumerRecord) {
//信息中value包含love669就返回true過濾不進行消費
return consumerRecord.value().contains("love669");
}
});
return factory;
}