Kafka集羣搭建
一、概念
- 說明
它是一個分佈式消息系統,由linkedin使用scala編寫,用作LinkedIn的活動流(Activity Stream)和運營數據處理管道(Pipeline)的基礎。具有高水平擴展和高吞吐量。
- 比較
定義解釋:
1、Java 和 scala都是運行在JVM上的語言。
2、erlang和最近比較火的和go語言一樣是從代碼級別就支持高併發的一種語言,所以RabbitMQ天生就有很高的併發性能,但是 有RabbitMQ嚴格按照AMQP進行實現,受到了很多限制。kafka的設計目標是高吞吐量,所以kafka自己設計了一套高性能但是不通用的協議,他也是仿照AMQP( Advanced Message Queuing Protocol 高級消息隊列協議)設計的。
3、事物的概念:在數據庫中,多個操作一起提交,要麼操作全部成功,要麼全部失敗。舉個例子, 在轉賬的時候付款和收款,就是一個事物的例子,你給一個人轉賬,你轉成功,並且對方正常行收到款項後,這個操作纔算成功,有一方失敗,那麼這個操作就是失敗的。
對應消在息隊列中,就是多條消息一起發送,要麼全部成功,要麼全部失敗。3箇中只有ActiveMQ支持,這個是因爲,RabbitMQ和Kafka爲了更高的性能,而放棄了對事物的支持 。
4、集羣:多臺服務器組成的整體叫做集羣,這個整體對生產者和消費者來說,是透明的。其實對消費系統組成的集羣添加一臺服務器減少一臺服務器對生產者和消費者都是無感之的。
5、負載均衡,對消息系統來說負載均衡是大量的生產者和消費者向消息系統發出請求消息,系統必須均衡這些請求使得每一臺服務器的請求達到平衡,而不是大量的請求,落到某一臺或幾臺,使得這幾臺服務器高負荷或超負荷工作,嚴重情況下會停止服務或宕機。
6、動態擴容是很多公司要求的技術之一,不支持動態擴容就意味着停止服務,這對很多公司來說是不可以接受的。
注:
阿里巴巴的Metal,RocketMQ都有Kafka的影子,他們要麼改造了Kafka或者借鑑了Kafka,最後Kafka的動態擴容是通過Zookeeper來實現的。
Zookeeper是一種在分佈式系統中被廣泛用來作爲:分佈式狀態管理、分佈式協調管理、分佈式配置管理、和分佈式鎖服務的集羣。kafka增加和減少服務器都會在Zookeeper節點上觸發相應的事件kafka系統會捕獲這些事件,進行新一輪的負載均衡,客戶端也會捕獲這些事件來進行新一輪的處理。
- 工作原理
二、搭建kafak
搭建kafka集羣首先要搭建zookeeper集羣
- 軟件環境
Ip |
系統類型 |
|
192.168.1.221 |
centos7 |
Server1 |
192.168.1.138 |
centos7 |
Server2 |
192.168.1.89 |
centos7 |
Server3 |
- Kafka需要在zookeeper上運行,所以先搭建zookeeper (每一臺服務器都需要操作)
- 安裝jdk
yum list java*
yum -y install java-1.7.0-openjdk*
-
- . 下載zookeeper
#進入文件夾下
cd /use/local/zookeeper
#快照日誌
mkdir data
#事務日誌
mkdir logs
#命令下載
-
- 修改配置文件
#進入conf目錄
cd /usr/local/zookeeper/apache-zookeeper-3.5.6-bin/conf/
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper/data
dataLogDir=/usr/local/zookeeper/logs
clientPort=2181
server.1=192.168.1.221:2888:3888
server.2=192.168.1.138:2888:3888
server.3=192.168.1.89:2888:3888
#server.1 這個1是服務器的標識也可以是其他的數字, 表示這個是第幾號服務器,用來標識服務器,這個標識要寫到快照目錄下面myid文件裏
#192.168.7.107爲集羣裏的IP地址,第一個端口是master和slave之間的通信端口,默認是2888,第二個端口是leader選舉的端口,集羣剛啓動的時候選舉或者leader掛掉之後進行新的選舉的端口默認是3888
-
- 創建myid文件(分別在每一個機器上)
#server1
echo "1" > /usr/local/zookeeper/data/myid
#server2
echo "2" > /usr/local/zookeeper/data/myid
#server3
echo "3" > /usr/local/zookeeper/data/myid
-
- 啓動(三臺機子都要操作)
#進入到Zookeeper的bin目錄下
cd /opt/zookeeper/zookeeper-3.4.6/bin
#啓動服務(3臺都需要操作)
./zkServer.sh start
#檢查服務器狀態
./zkServer.sh status
#執行命令jps
Jps
QuorumPeerMain
- 搭建kafka集羣
進入目錄下 cd /use/local/kafka
創建消息目錄 mkdir logs,主要存放 kafka消息
在線下載 wget https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.4.0/ kafka_2.13-2.4.0.tgz
解壓 tar -zxvf kafka_2.13-2.4.0.tgz
-
-
- 修改配置文件
-
進入到config目錄
cd /usr/local/kafka/kafka_2.13-2.4.0/config/
編輯server.poperties
vim server.poperties
broker.id=0 #當前機器在集羣中的唯一標識,和zookeeper的myid性質一樣
port=9092 #當前kafka對外提供服務的端口默認是9092
host.name=192.168.1.221 #這個參數默認是關閉的,在0.8.1有個bug,DNS解析問題,失敗率的問題。
num.network.threads=3 #這個是borker進行網絡處理的線程數
num.io.threads=8 #這個是borker進行I/O處理的線程數
log.dirs=/opt/kafka/kafkalogs/ #消息存放的目錄,這個目錄可以配置爲“,”逗號分割的表達式,上面的num.io.threads要大於這個目錄的個數這個目錄,如果配置多個目錄,新創建的topic他把消息持久化的地方是,當前以逗號分割的目錄中,那個分區數最少就放那一個
socket.send.buffer.bytes=102400 #發送緩衝區buffer大小,數據不是一下子就發送的,先回存儲到緩衝區了到達一定的大小後在發送,能提高性能
socket.receive.buffer.bytes=102400 #kafka接收緩衝區大小,當數據到達一定大小後在序列化到磁盤
socket.request.max.bytes=104857600 #這個參數是向kafka請求消息或者向kafka發送消息的請請求的最大數,這個值不能超過java的堆棧大小
num.partitions=1 #默認的分區數,一個topic默認1個分區數
log.retention.hours=168 #默認消息的最大持久化時間,168小時,7天
message.max.byte=5242880 #消息保存的最大值5M
default.replication.factor=2 #kafka保存消息的副本數,如果一個副本失效了,另一個還可以繼續提供服務
replica.fetch.max.bytes=5242880 #取消息的最大直接數
log.segment.bytes=1073741824 #這個參數是:因爲kafka的消息是以追加的形式落地到文件,當超過這個值的時候,kafka會新起一個文件
log.retention.check.interval.ms=300000 #每隔300000毫秒去檢查上面配置的log失效時間(log.retention.hours=168 ),到目錄查看是否有過期的消息如果有,刪除
log.cleaner.enable=false #是否啓用log壓縮,一般不用啓用,啓用的話可以提高性能
zookeeper.connect=192.168.1.221: 2181,192.168.1.138:2181,192.168.1.89:218 #設置zookeeper的連接端口
-
-
- 啓動服務
-
#從後臺啓動Kafka集羣(3臺都需要啓動)
cd /usr/local/kafka/kafka_2.13-2.4.0/bin #進入到kafka的bin目錄
#前臺啓動看看報錯不
./kafka-server-start.sh ../config/server.properties
#後臺啓動
./kafka-server-start.sh -daemon ../config/server.properties
#是否啓動
Jps
QuorumPeerMain
Kafka
三、Springboot測試kafka
1.jar包依賴
<!-- kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<!--swagger2-ui依賴-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
2.yml配置
server:
port: 18888
topinfo:
# kafka集羣配置 ,bootstrap-servers 是必須的
kafka:
# 生產者的kafka集羣地址
bootstrap-servers: 192.168.1.221:9092,192.168.1.138:9092,192.168.1.89:9092
producer:
topic-name: topinfo-01
consumer:
group-id: ci-data
3.yml變實體類
import com.uximt.kaa.kafka.bean.Consumer;
import com.uximt.kaa.kafka.bean.Producer;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "topinfo.kafka")
@Component
public class KafKaConfiguration {
/**
* @Fields bootstrapServer : 集羣的地址
*/
private String bootstrapServers;
private Producer producer;
private Consumer consumer;
public String getBootstrapServers() {
return bootstrapServers;
}
public void setBootstrapServers(String bootstrapServers) {
this.bootstrapServers = bootstrapServers;
}
public Producer getProducer() {
return producer;
}
public void setProducer(Producer producer) {
this.producer = producer;
}
public Consumer getConsumer() {
return consumer;
}
public void setConsumer(Consumer consumer) {
this.consumer = consumer;
}
}
4.topic配置
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.KafkaAdmin;
import java.util.HashMap;
import java.util.Map;
/**
* @date: 2019/12/31 14:36
* @Explanation:
*/
@Configuration
public class KafKaTopicConfig {
@Autowired
private KafKaConfiguration configuration;
/**
*@Description: kafka管理員,委派給AdminClient以創建在應用程序上下文中定義的主題的管理員。
*@return
*/
@Bean
public KafkaAdmin kafkaAdmin() {
Map<String, Object> props = new HashMap<>();
// 配置Kafka實例的連接地址
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, configuration.getBootstrapServers());
KafkaAdmin admin = new KafkaAdmin(props);
return admin;
}
/**
*@Description: kafka的管理客戶端,用於創建、修改、刪除主題等
*@return
*/
@Bean
public AdminClient adminClient() {
return AdminClient.create(kafkaAdmin().getConfig());
}
/**
* @Description: 創建一個新的 topinfo 的Topic,如果kafka中topinfo 的topic已經存在,則忽略。
* @return
*/
@Bean
public NewTopic topinfo() {
// 主題名稱
String topicName = configuration.getProducer().getTopicName();
// 第二個參數是分區數, 第三個參數是副本數量,確保集羣中配置的數目大於等於副本數量
return new NewTopic(topicName, 2, (short) 2);
}
}
5.配置kafka生產者消費者
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import java.util.HashMap;
import java.util.Map;
/**
* @date: 2019/12/31 14:30
* @Explanation: kafka配置類
*/
@Configuration
public class KafKaConfig {
@Autowired
private KafKaConfiguration configuration;
/**
* @Description: 生產者的配置
* @return
*/
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
// 集羣的服務器地址
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, configuration.getBootstrapServers());
// 消息緩存
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 40960);
// 生產者空間不足時,send()被阻塞的時間,默認60s
props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 6000);
// 生產者重試次數
props.put(ProducerConfig.RETRIES_CONFIG, 0);
// 指定ProducerBatch(消息累加器中BufferPool中的)可複用大小
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 4096);
// 生產者會在ProducerBatch被填滿或者等待超過LINGER_MS_CONFIG時發送
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// key 和 value 的序列化
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer");
// 客戶端id
props.put(ProducerConfig.CLIENT_ID_CONFIG, "producer.client.id.topinfo");
return props;
}
/**
* @Description: 生產者工廠
* @return
*/
@Bean
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
/**
* @Description: KafkaTemplate
* @return
*/
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<String, String>(producerFactory());
}
// ------------------------------------------------------------------------------------------------------------
/**
* @Description: 消費者配置
* @return
*/
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<String, Object>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, configuration.getBootstrapServers());
// 消費者組
props.put(ConsumerConfig.GROUP_ID_CONFIG, configuration.getConsumer().getGroupId());
// 自動位移提交
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
// 自動位移提交間隔時間
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 100);
// 消費組失效超時時間
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 10000);
// 位移丟失和位移越界後的恢復起始位置
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
// key 和 value 的反序列化
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
return props;
}
/**
* @Description: 消費者工廠
* @return
*/
@Bean
public ConsumerFactory<String, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
/**
* @Description: kafka 監聽容器工廠
* @return
*/
@Bean
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
// 設置消費者工廠
factory.setConsumerFactory(consumerFactory());
// 要創建的消費者數量(10 個線程併發處理)
factory.setConcurrency(10);
return factory;
}
}
6.swagger配置
package com.uximt.kaa.kafka.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Swagger配置
* @ClassName: Swagger2Config
*/
@Configuration
@EnableSwagger2
@ConditionalOnProperty(name = "enabled", prefix = "swagger", havingValue = "true", matchIfMissing = true)
public class Swagger2Config {
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Springboot Swagger項目文檔")
.version("1.0")
.build();
}
}
7.消費者和生產者實體建立
public class Consumer {
private String groupId;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
}
public class Producer {
private String topicName;
public String getTopicName() {
return topicName;
}
public void setTopicName(String topicName) {
this.topicName = topicName;
}
}
8.建立發送者controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author:hemingzhu
* @date: 2019/12/31 14:38
* @Explanation:
*/
@RestController
@RequestMapping("kafka")
public class TestKafKaProducerController {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@PostMapping("send")
public String send(String name) {
ListenableFuture<SendResult<String, String>> future = kafkaTemplate.send("topinfo", name);
future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
@Override
public void onSuccess(SendResult<String, String> result) {
System.out.println("生產者-發送消息成功:" + result.toString());
}
@Override
public void onFailure(Throwable ex) {
System.out.println("生產者-發送消息失敗:" + ex.getMessage());
}
});
return "test-ok";
}
9.消費者監聽器
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
/**
* @date: 2019/12/31 14:39
* @Explanation:
*/
@Component
public class KafKaConsumer {
private final Logger logger = LoggerFactory.getLogger(KafKaConsumer.class);
/**
* @Description: 可以同時訂閱多主題,只需按數組格式即可,也就是用“,”隔開
* @param record
*/
@KafkaListener(topics = { "topinfo" })
public void receive(ConsumerRecord<?, ?> record) {
logger.info("消費得到的消息---key: " + record.key());
logger.info("消費得到的消息---value: " + record.value().toString());
}
}