1、kafka是什麼
Apache Kafka是一個開源消息系統,由Scala寫成
Kafka最初是由LinkedIn開發,並於2011年初開源
Kafka是一個分佈式消息隊列:生產者消費者的功能。它提供了類似於JMS的特性,但是在設計實現上完全不同,此外它並不是JMS規範的實現
Kafka對消息保存時根據Topic進行分類,發送消息者稱爲producer,消息接受者稱爲Consumer,此外Kafka集羣有多個Kafka實例組成,每個實例稱爲broker
無論是Kafka集羣還是producer和consumer都依賴於zookeeper集羣保存一些meta信息,來保證系統的可用性
JMS:jms是Java提供的一套技術規範。
可以用來異構系統集成通信,緩解系統瓶頸,提高系統的伸縮性增強系統用戶體驗,使得系統模塊化和組件化變得可行並更加靈活。
類JMS消息隊列,結合JMS中的兩種模式,可以有多個消費者主動拉取數據,在JMS中只有點對點模式纔有消費者主動拉取數據。
kafka是一個生產-消費模型。
01.Producer:生產者
只負責數據生產,生產者的代碼可以集成到任務系統中。 數據的分發策略由producer決定,默認是defaultPartition Utils.abs(key.hashCode) % numPartitions
02.Broker:
當前服務器上的Kafka進程,俗稱拉皮條。只管數據存儲,不管是誰生產,不管是誰消費。在集羣中每個broker都有一個唯一brokerid,不得重複。
03.Topic:
目標發送的目的地,這是一個邏輯上的概念,落到磁盤上是一個partition的目錄。partition的目錄中有多個segment組合(index,log)
一個Topic對應多個partition[0,1,2,3],一個partition對應多個segment組合。一個segment有默認的大小是1G。
每個partition可以設置多個副本(replication-factor 1),會從所有的副本中選取一個leader出來。所有讀寫操作都是通過leader來進行的。
特別強調,和mysql中主從有區別,mysql做主從是爲了讀寫分離,在kafka中讀寫操作都是leader。
04.ConsumerGroup:
數據消費者組,ConsumerGroup可以有多個,每個ConsumerGroup消費的數據都是一樣的。
可以把多個consumer線程劃分爲一個組,組裏面所有成員共同消費一個topic的數據,組員之間不能重複消費。
(在下面代碼配置文件中,可以設置groupID和讀取的位置)
05.zookeeper
依賴集羣保存meta信息(每次讀取到哪的信息)。
2、kafka生產數據時的分組策略
默認是defaultPartition Utils.abs(key.hashCode) % numPartitions
上文中的key是producer在發送數據時傳入的,produer.send(KeyedMessage(topic,myPartitionKey,messageContent))
3、kafka如何保證數據的完全生產
ack機制:broker表示發來的數據已確認接收無誤,表示數據已經保存到磁盤。
0:不等待broker返回確認消息
1:等待topic中某個partition leader保存成功的狀態反饋
-1:等待topic中某個partition 所有副本都保存成功的狀態反饋
4、broker如何保存數據
在理論環境下,broker按照順序讀寫的機制,可以每秒保存600M的數據。主要通過pagecache機制,儘可能的利用當前物理機器上的空閒內存來做緩存。
當前topic所屬的broker,必定有一個該topic的partition,partition是一個磁盤目錄。partition的目錄中有多個segment組合(index,log)
5、partition如何分佈在不同的broker上
int i = 0
list{kafka01,kafka02,kafka03}
for(int i=0;i<5;i++){
brIndex = i%broker;
hostName = list.get(brIndex)
}
6、consumerGroup的組員和partition之間如何做負載均衡
最好是一一對應,一個partition對應一個consumer。
如果consumer的數量過多,必然有空閒的consumer。
算法:
假如topic1,具有如下partitions: P0,P1,P2,P3
加入group中,有如下consumer: C1,C2
首先根據partition索引號對partitions排序: P0,P1,P2,P3
根據consumer.id排序: C0,C1
計算倍數: M = [P0,P1,P2,P3].size / [C0,C1].size,本例值M=2(向上取整)
然後依次分配partitions: C0 = [P0,P1],C1=[P2,P3],即Ci = [P(i * M),P((i + 1) * M -1)]
7、如何保證kafka消費者消費數據是全局有序的
僞命題
如果要全局有序的,必須保證生產有序,存儲有序,消費有序。
由於生產可以做集羣,存儲可以分片,消費可以設置爲一個consumerGroup,要保證全局有序,就需要保證每個環節都有序。
只有一個可能,就是一個生產者,一個partition,一個消費者。這種場景和大數據應用場景相悖。
8.kafka生產數據
import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; import java.util.Properties; import java.util.UUID; /** * 這是一個簡單的Kafka producer代碼 * 包含兩個功能: * 1、數據發送 * 2、數據按照自定義的partition策略進行發送 * KafkaSpout的類 */ public class KafkaProducerSimple { public static void main(String[] args) { //1、指定當前kafka producer生產的數據的目的地 //創建topic可以輸入以下命令,在kafka集羣的任一節點進行創建。 //bin/kafka-topics.sh --create --zookeeper zk01:2181 --replication-factor 1 --partitions 1 --topic test String TOPIC = "orderMq"; //2、讀取配置文件 Properties props = new Properties(); //key.serializer.class默認爲serializer.class props.put("serializer.class", "kafka.serializer.StringEncoder"); //kafka broker對應的主機,格式爲host1:port1,host2:port2 props.put("metadata.broker.list", "kafka01:9092,kafka02:9092,kafka03:9092"); // request.required.acks,設置發送數據是否需要服務端的反饋,有三個值0,1,-1 // 0,意味着producer永遠不會等待一個來自broker的ack,這就是0.7版本的行爲。這個選項提供了最低的延遲,但是持久化的保證是最弱的,當server掛掉的時候會丟失一些數據。 // 1,意味着在leader replica已經接收到數據後,producer會得到一個ack。這個選項提供了更好的持久性,因爲在server確認請求成功處理後,client纔會返回。如果剛寫到leader上,還沒來得及複製leader就掛了,那麼消息纔可能會丟失。 // -1,意味着在所有的ISR都接收到數據後,producer纔得到一個ack。這個選項提供了最好的持久性,只要還有一個replica存活,那麼數據就不會丟失 props.put("request.required.acks", "1"); // 可選配置,如果不配置,則使用默認的partitioner partitioner.class // 默認值:kafka.producer.DefaultPartitioner // 用來把消息分到各個partition中,默認行爲是對key進行hash。 props.put("partitioner.class", "cn.my.storm.kafka.MyLogPartitioner"); // props.put("partitioner.class", "kafka.producer.DefaultPartitioner"); //3、通過配置文件,創建生產者 Producer<String, String> producer = new Producer<String, String>(new ProducerConfig(props)); //4、通過for循環生產數據 for (int messageNo = 1; messageNo < 100000; messageNo++) { // String messageStr = new String(messageNo + "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey," + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發" + // "用來配合自定義的MyLogPartitioner進行數據分發"); // 5、調用producer的send方法發送數據 // 注意:這裏需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行數據分發 producer.send(new KeyedMessage<String, String>(TOPIC, messageNo + "", "appid" + UUID.randomUUID() + "itcast")); } } }
import kafka.producer.Partitioner; import kafka.utils.VerifiableProperties; import org.apache.log4j.Logger; public class MyLogPartitioner implements Partitioner { private static Logger logger = Logger.getLogger(MyLogPartitioner.class); public MyLogPartitioner(VerifiableProperties props) { } public int partition(Object obj, int numPartitions) { return Integer.parseInt(obj.toString())%numPartitions; // return 1; } }
9.kafka消費數據(低階)
import kafka.consumer.Consumer; import kafka.consumer.ConsumerConfig; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.javaapi.consumer.ConsumerConnector; import kafka.message.MessageAndMetadata; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class KafkaConsumerSimple implements Runnable { public String title; public KafkaStream<byte[], byte[]> stream; public KafkaConsumerSimple(String title, KafkaStream<byte[], byte[]> stream) { this.title = title; this.stream = stream; } @Override public void run() { System.out.println("開始運行 " + title); ConsumerIterator<byte[], byte[]> it = stream.iterator(); /** * 不停地從stream讀取新到來的消息,在等待新的消息時,hasNext()會阻塞 * 如果調用 `ConsumerConnector#shutdown`,那麼`hasNext`會返回false * */ while (it.hasNext()) { MessageAndMetadata<byte[], byte[]> data = it.next(); String topic = data.topic(); int partition = data.partition(); long offset = data.offset(); String msg = new String(data.message()); System.out.println(String.format( "Consumer: [%s], Topic: [%s], PartitionId: [%d], Offset: [%d], msg: [%s]", title, topic, partition, offset, msg)); } System.out.println(String.format("Consumer: [%s] exiting ...", title)); } public static void main(String[] args) throws Exception{ Properties props = new Properties(); props.put("group.id", "dashujujiagoushi"); props.put("zookeeper.connect", "zk01:2181,zk02:2181,zk03:2181"); props.put("auto.offset.reset", "largest"); props.put("auto.commit.interval.ms", "1000"); props.put("partition.assignment.strategy", "roundrobin"); ConsumerConfig config = new ConsumerConfig(props); String topic1 = "orderMq"; String topic2 = "paymentMq"; //只要ConsumerConnector還在的話,consumer會一直等待新消息,不會自己退出 ConsumerConnector consumerConn = Consumer.createJavaConsumerConnector(config); //定義一個map Map<String, Integer> topicCountMap = new HashMap<>(); topicCountMap.put(topic1, 3); //Map<String, List<KafkaStream<byte[], byte[]>> 中String是topic, List<KafkaStream<byte[], byte[]>是對應的流 Map<String, List<KafkaStream<byte[], byte[]>>> topicStreamsMap = consumerConn.createMessageStreams(topicCountMap); //取出 `kafkaTest` 對應的 streams List<KafkaStream<byte[], byte[]>> streams = topicStreamsMap.get(topic1); //創建一個容量爲4的線程池 ExecutorService executor = Executors.newFixedThreadPool(3); //創建20個consumer threads for (int i = 0; i < streams.size(); i++) executor.execute(new KafkaConsumerSimple("消費者" + (i + 1), streams.get(i))); } }
10.kafka和zookeeper使用JavaAPI能夠拉取到數據(高階消費)
properties配置文件
###zookeeper\u548ckafka\u914d\u7f6e\u5730\u5740 zk.connect=xxxxx #zk.connect=xxxxx ###kafka\u6d88\u8d39\u7684group\u5fc5\u987b\u8c03\u6574\u4e3a\u72ec\u5360 adinfo.log.group.name=qinbin_ad_interfaceLog_20171218 ###kafka\u7684topic.\u9700\u8981\u548cadstat\u6a21\u5757\u7684kafka topic\u4e00\u81f4 adinfo.log.topic.name=ad_interfaceLog adinfo.log.queue.max=10000 adinfo.log.list.size=1 ###\u4e2d\u95f4\u7ed3\u679c\u4fdd\u5b58\u65e5\u5fd7 adinfo.log.pathFile=E:/opt/realtime/avro/file/ ###\u9ed8\u8ba4\u4e0d\u8981\u52a8 adinfo.statistics.time=120000 adinfo.statistics.commitSize=3000
kafka配置文件(注意groupID)
<?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:int="http://www.springframework.org/schema/integration" xmlns:int-kafka="http://www.springframework.org/schema/integration/kafka" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/integration/kafka http://www.springframework.org/schema/integration/kafka/spring-integration-kafka.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <int:channel id="inputFromAdinfo"> <int:queue/> </int:channel> <int-kafka:inbound-channel-adapter id="kafkaInboundChannelAdinfo" kafka-consumer-context-ref="consumerContextAdinfo" auto-startup="true" channel="inputFromAdinfo" > <int:poller fixed-delay="10" time-unit="MILLISECONDS" max-messages-per-poll="5" /> </int-kafka:inbound-channel-adapter> <bean id="consumerPropertiesAdinfo" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="properties"> <props> <prop key="auto.offset.reset">smallest</prop> <prop key="socket.receive.buffer.bytes">314572</prop> <!-- 5M --> <prop key="fetch.min.bytes">26214</prop><!-- 256k --> <prop key="fetch.message.max.bytes">104857</prop><!-- 3M --> <prop key="fetch.wait.max.ms">5000</prop> <prop key="auto.commit.interval.ms">2000</prop> <prop key="rebalance.backoff.ms">5000</prop> <prop key="rebalance.max.retries">5</prop> </props> </property> </bean> <int-kafka:consumer-context id="consumerContextAdinfo" consumer-timeout="4000" zookeeper-connect="zookeeperConnectAdinfo" consumer-properties="consumerPropertiesAdinfo"> <int-kafka:consumer-configurations> <!-- 需要注意如果兩個線程同時互不相干去消費通一個topic,則需要注意group-id不能重複 --> <int-kafka:consumer-configuration group-id="${adinfo.log.group.name}" max-messages="500"> <int-kafka:topic id="${adinfo.log.topic.name}" streams="1" /> </int-kafka:consumer-configuration> </int-kafka:consumer-configurations> </int-kafka:consumer-context> <int-kafka:zookeeper-connect id="zookeeperConnectAdinfo" zk-connect="${zk.connect}" zk-connection-timeout="6000" zk-session-timeout="6000" zk-sync-time="2000"/> </beans>
然後在spring配置文件中import kafka的配置文件
Java接收:
import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Resource; import org.apache.avro.io.DatumReader; import org.apache.avro.specific.SpecificDatumReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.integration.channel.QueueChannel; import org.springframework.messaging.Message; import com.ElasticSearchServiceImpl; import com.IElasticSearchService; import com.AdInfoRealTimeThread; import com.ConfigUtil; import com.AdInfo; public class AdInfoConsumer { // DatumReader<AdInfo> adInfoDatumReader = new // SpecificDatumReader<AdInfo>(AdInfoOld.getClassSchema(),AdInfo.getClassSchema()); DatumReader<AdInfo> adInfoDatumReader = new SpecificDatumReader<AdInfo>(AdInfo.class); private Logger logger = LoggerFactory.getLogger(AdInfoConsumer.class); @Resource(type = ElasticSearchServiceImpl.class) private IElasticSearchService elasticSearchServiceImpl; @Resource(type = ConfigUtil.class) private ConfigUtil configUtil; private QueueChannel queueChannel; public QueueChannel getQueueChannel() { return queueChannel; } public void setQueueChannel(QueueChannel queueChannel) { this.queueChannel = queueChannel; } private AdInfoRealTimeThread adInfoRealTimeThread; public AdInfoRealTimeThread getAdInfoRealTimeThread() { return adInfoRealTimeThread; } public void setAdInfoRealTimeThread(AdInfoRealTimeThread adInfoRealTimeThread) { this.adInfoRealTimeThread = adInfoRealTimeThread; } public void consumerLog() throws UnsupportedEncodingException { @SuppressWarnings("rawtypes") Message msg; while ((msg = queueChannel.receive()) != null) { // msg = queueChannel.receive(); try { Map<String, Object> map = (Map<String, Object>) msg.getPayload(); Set<Entry<String, Object>> set = map.entrySet(); for (Map.Entry<String, Object> entry : set) { String topic = entry.getKey(); ConcurrentHashMap<Integer, List<byte[]>> messages = (ConcurrentHashMap<Integer, List<byte[]>>) entry .getValue(); Collection<List<byte[]>> values = messages.values(); for (Iterator<List<byte[]>> iterator = values.iterator(); iterator.hasNext();) { List<byte[]> list = iterator.next(); for (byte[] object : list) { String message = new String(object, "UTF-8"); StringBuilder megJson = new StringBuilder(message); megJson.delete(0, megJson.indexOf("=") + 1); // logger.info("json:"+megJson.toString()); // adinfoToSaveES.saveAdLogToEs(megJson.toString()); elasticSearchServiceImpl.executeSearch(configUtil.clusterName,megJson.toString()); //System.out.println(megJson.toString()); } } } } catch (Exception ex) { logger.error("===AdInfoConsumer consumer is exception", ex); } } logger.error("====AdInfoConsumer receive is interrupted===="); } /* * public void consumerLog() throws UnsupportedEncodingException { * * @SuppressWarnings("rawtypes") Message msg; while ((msg = * queueChannel.receive()) != null) { * * try { * * Map<String, Object> map = (Map<String, Object>) msg.getPayload(); * Set<Entry<String, Object>> set = map.entrySet(); for (Map.Entry<String, * Object> entry : set) { // String topic = entry.getKey(); * ConcurrentHashMap<Integer, List<byte[]>> messages = * (ConcurrentHashMap<Integer, List<byte[]>>) entry .getValue(); * Collection<List<byte[]>> values = messages.values(); for * (Iterator<List<byte[]>> iterator = values.iterator(); iterator.hasNext();) { * List<byte[]> list = iterator.next(); for (byte[] object : list) { * * try { Decoder decoder = DecoderFactory.get().binaryDecoder(object, null); * * AdInfo adInfo = adInfoDatumReader.read(null, decoder); * * String json=adInfo.toString(); System.out.println("*************"+json); * //logger.info("json:"+json); //adInfoRealTimeThread.statistics(json); * * } catch (Exception e) { * logger.error("===AdInfoConsumer consumer one is exception", e); } * * * } } } } catch (Exception ex) { * logger.error("===AdInfoConsumer consumer is exception", ex); } } * * logger.error("====AdInfoConsumer receive is interrupted===="); } */ }