多種MQ的探究-Kafka
Kafka
原理
簡介:
kafka是一個支持高吞吐量的分佈式消息服務。
特點:
kafka依賴於zookeeper進行節點註冊。
kafka集羣:
kafka集羣與zk類似,leader擁有最新最全的信息,然後複製信息到從節點上。
springboot2.0版本集成的kafka2.x,消息offsets放置在zk節點topics上。
優缺點:
採用分區機制,吞吐量高,擁有ack容錯機制,支持集羣負載均衡,對日誌文件的記錄比較好。
但由於高效率的原因,kafka對死信隊列、延遲隊列等方面不如其他mq。
原理
kafka集羣節點註冊到zk上,服務加載初始化時加載kafka集羣信息,然後進行消息處理。
kafka與zk類似,存在leader與follower,並實時監測服務狀態。
模式:
kafka----broker1(topic1…topicn)–consumer
…
kafka----brokern(topic1…topicn)–consumer
詳細:
kafka的topic會依據配置分區數量進行分區(partition),消息producer後push進入broker的各個分區中有序排列(offset)。消息消費監聽到當前分區有自己的消息進來,然後進行處理,並記錄offset。
producer
消息推送首先獲取節點分片情況,然後進行序列化處理
clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
...
serializedKey = keySerializer.serialize(record.topic(), record.headers(), record.key());
serializedValue = valueSerializer.serialize(record.topic(), record.headers(), record.value());
我們可以在application配置文件中配置序列化:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
然後獲取傳入的分片id、時間戳等信息創建分片對象,然後創建producer進行提交:
ProducerBatch batch = new ProducerBatch(tp, recordsBuilder, time.milliseconds());
FutureRecordMetadata future = Utils.notNull(batch.tryAppend(timestamp, key, value, headers, callback, time.milliseconds()));
consumer
消息消費有兩種方式:1-繼承函數接口實現onMessage() 2-註冊監聽@KafkaListener,兩種都是通過監聽器來實現,consumer通過poll分片來獲取消費對象。消息監聽後會進行註冊:
public class KafkaListenerEndpointRegistrar implements BeanFactoryAware, InitializingBean {
private final List<KafkaListenerEndpointDescriptor> endpointDescriptors = new ArrayList<>();
private KafkaListenerEndpointRegistry endpointRegistry;
...
通過bean工廠來管理監聽器,當監聽到消息時會即時進行處理消費。
與其他mq監聽並無二致。
安裝
環境:centos7
下載
1.從官方服務器上下載
2.直接在服務器上下載
例如:wget http://mirror.bit.edu.cn/apache/kafka/1.0.0/kafka_2.11-1.0.0.tgz
然後解壓 tar -zxvf xxx.gz
配置
kafka擁有四種配置:
server.properties
作爲服務的主配置,其中可配置信息如下:
- listeners=PLAINTEXT://192.168.99.128:9092 監聽器監聽的ip與端口,需要放開,否則無法識別連接
- num.partitions=1 kafka的分區,默認1個區,僅支持一個consumer,如果需要一個主題多消費者則需要放開分區,例如num.partitions=5
- log.dirs=/tmp/kafka-logs 日誌存放路徑
- zookeeper.connect=192.168.99.128:2181 kafka依賴的zookeeper的連接地址(一般使用自己的zk)
- 等等…
producer.properties
消息生產者的配置信息,主要的配置有bootstrap.servers,默認爲當前機器單節點
consumer.properties
消息消費者,與生產者一致,其中配置了consumer.group-id作爲分組id
zookeeper.properties
單機zk的配置,如果使用外置zk則不需要配置
啓動
執行:./bin/kafka-server-start.sh -daemon ./config/server.properties
jps查詢運行的應用:
停止:./bin/kafka-server-stop.sh
應用
採用springboot集成kafka模式進行開發
服務
1.kafka組件
將kafka服務作爲獨立組件jar來實現
引入依賴:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置producer
@Service("kafkaProducer")
public class KafkaProducer
{
@Resource
private KafkaTemplate kafkaTemplate;
public Boolean sendMessage(String topic, Object message)
{
try
{
kafkaTemplate.send(topic, message);
} catch (Exception e)
{
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}
2.業務
引入kafka組件包
主題:
public static final String KAFKA_MY_TOPIC = "MY_KAFKA_TOPIC";
public static final String KAFKA_MY_TOPIC_BAK = "MY_KAFKA_TOPIC_BAK";
服務啓動後zk中可見:
說明:__consumer_offsets偏移量爲消費者已消費的元數據,存儲在消費者分區上,是在config配置中的num.partitions決定的,默認50個,0~49
消息推送:
@PostMapping("/sendBatchKafka")
@ResponseBody
public String sendBatchKafka(@RequestBody String request)
{
kafkaProducer.sendMessage(KafkaTopic.KAFKA_MY_TOPIC, request);
Map<String, String> params = (Map<String, String>) JSONObject.parse(request);
String context = params.get("context");
params.put("context", context + "123");
kafkaProducer.sendMessage(KafkaTopic.KAFKA_MY_TOPIC_BAK, JSONObject.toJSONString(params));
return "success";
}
消息消費:
@KafkaListener(topics = {KafkaTopic.KAFKA_MY_TOPIC})
public void onMessage(String message)
{
logger.info("KafkaConsumer主題消息:" + message);
}
@KafkaListener(topics = {KafkaTopic.KAFKA_MY_TOPIC, KafkaTopic.KAFKA_MY_TOPIC_BAK}, containerFactory = KafkaConfig.TOPIC_FACTORY)
public void dealMessage(String message)
{
logger.info("KafkaBatchConsumer主題消息(多主題):" + message);
}
調用推送接口,即可發送消息,多主題都可以接收。
單消費配置:
spring:
kafka:
bootstrap-servers: 192.168.99.128:9092
consumer:
group-id: test-consumer-group
enable-auto-commit: true
listener:
missing-topics-fatal: false
producer:
batch-size: 4096
說明:kafka默認單消費者,選取最新加載的消費者進行消費,如下:
此時查看zk,則可發現只有一個分區:
如果我們需要的就是一對一模式,那麼在code的時候注意規範,完全可當做點對點來實現。
多消費者模式
但如果我們需要一對多模式,那麼就需要進行調整:
spring:
kafka:
bootstrap-servers: 192.168.99.128:9092
consumer:
group-id: test-consumer-group
enable-auto-commit: true
listener:
missing-topics-fatal: false
concurrency: 5
type: batch
producer:
batch-size: 4096
listener.type改爲batch批量模式
listener.concurrency線程數調整爲依據server.properoties中的num.partitions=5來配置。
missing-topics-fatal默認會校驗不存在的topic,此處false,否則需要在啓動時創建,不然報錯。
topic設置分區信息即可,默認1個分區(會依據規則找最新註冊消費者消費),分區數不能大於配置的分區數:
@Bean(name = "MY_KAFKA_TOPIC")
public NewTopic myTopic()
{
return new NewTopic(KAFKA_MY_TOPIC, 5, (short)2);
}
可見按照配置設置了5個分區:
詳細說明:
此處採用的是依據消費者的數量進行消息分區推送,因爲分區數>=消費者數,那麼自定義一個全局線程安全的容器來存放消費者的數量,故推送方法做出變化:
protected static Map<String, Integer> consumerCache = new ConcurrentHashMap<String, Integer>();
Integer concurrency = consumerCache.get(topic);
for (int i=0; i<concurrency; i++)
{
kafkaTemplate.send(topic, i, groupId , message);
}
說明:多消費者模式包含單消費,單主題默認分區爲1個id爲0。
測試:
這樣三個消費者就都能收到消息了。
此處推送爲一個topic對應多consumer模式,另一種是多topic對應多consumer模式,需要消息根據不同的topic進行推送,如果幾千條就需要更多的topic,有點得不償失。
mq-kafka-code源碼
微信公衆號:像是風