kafka 上手指南:單節點

大家好,我叫謝偉,是一名程序員。

今天的主題:kafka 使用指南,單節點版本。

1. 使用場景

如果你是一名后端工程師,設計的應用正常的線上運行,某次秒殺活動,突然間把系統搞崩了,排查系統發現很多的流量沒有處理,導致系統掛了,這個時候有兩種思路: 1. nginx 反向代理,把更多的請求轉發給內部網絡的服務器上進行處理,達到一個負載均衡的目的 2. 使用消息系統,將更多的請求使用中間件“緩存”起來,再從這個系統中不斷的取到緩存的請求,進行進一步的處理。

后者使用到的消息系統,就是kafka 的一個使用場景。

那么什么是 kafka?

kafka 是一個分布式消息系統,目前已定位為分布式流式處理平臺。

簡單的說一個系統A 將消息發給消息系統,一個系統B 再從消息系統中取到消息,進行后續的處理。

常見的用來描述 kafka 應用場景的一個詞是:削峰填谷,削減波峰流量,填充波谷流量,使系統盡量的平滑。

由此得處:kafka 的三個典型應用場景

  • 消息系統
  • 存儲系統
  • 分布式流式處理平臺

消息系統是目前最廣泛的應用;消息傳輸需要存儲起來,供后續系統拉取,故也可以當作存儲系統;拉取消息之后,其實也是供后續系統處理,那么為什么不把數據處理也包含再kafka 系統中?分布式流式處理平臺,大概就是這個意思。

下文陳述最核心的應用:消息系統

2. 基本概念

一條消息由系統A 產生,發往消息系統,系統B 從消息系統中拉取,這其中涉及到很多的概念。

  • 系統A 稱為生產者 producer,目的是發送消息
  • 消息系統稱為 broker,本質是服務進程目的是接受生產者的消息、消費者的消息拉取請求、持久化
  • 系統B 稱為消費者 consumer, 目的是拉取消息系統中的消息

針對生產者、消費者有不同的設置參數,決定了生產者、消費者的不同行為。

生產者要發送消息,首先要知道發往何處,即要知道 broker 的地址,知道 broker 的地址,broker(kafka server) 的設置約束了持久化存儲的地址及其他行為,除此之外,如何區分發的消息的類型不同呢?kafka 系統給這個區分消息的概念取了個邏輯概念:Topic , 即生產者指定的 Topic 不同,存儲的地址就不同。

針對 Topic,簡單的場景是,不斷的往里面發內容,持久化存儲就不斷以追加的模式存儲,簡單場景沒什么問題,問題是消息數據過多的話,不利于系統消費,很簡單的想法,分不同的“文件”追加存儲,把整體規模縮小,這個概念在 kafka 中稱之為分區:partition. 消息可以不斷的以追加的模式不斷的發往分區內,分區有編號,起始位 0 ,消息追加模式存儲在分區內,會給一個編號 offset

消費者從 broker 系統中拉取消息,首先要知道broker 地址,其次需要知道 Topic,更細化的還可以設置哪個分區,哪個偏移量 offset 開始,消費消息。

那消息萬一丟了咋整?一個簡單的做法就是冗余備份:Replication,多份備份,其中有一個是 Leader , 其他的是 follower, leader 的作用是和消息對接,follower 不直接和消息對接,只負責和 leader 對接,不斷的同步數據。

多個 broker 構成 kafka 集群,萬一一個掛了 kafka 系統依靠 zookeeper 進行重新選舉產生新leader。

kafka cluster:


image

kafka topic: 分區概念

image

kafka 集群:

image

3. 客戶端使用

基于上述概念:那么如何構建一個Kafka 服務,完成消息系統呢?

  • 啟動服務進程:broker

偽代碼:

type Broker struct{
    Addr 
    Config
    ...
}
  • 生產者連接 broker

偽代碼:


type Producer struct{
    Config
    Message 
    ...
}

  • 消費者連接 broker

偽代碼

type Consumer strcut{
    Config
    Topic 
    Partitions
    Offset
    ...
}

基本的思路:

  • 啟動kafka服務
  • 系統A 連接服務,發送消息
  • 系統B 連接服務,消費消息

結合官網的示例:如何完成最基本的消息收發。

下載安裝包:kafka_2.12-2.3.0.tgz

  • 2.12 指編譯器版本
  • 2.3.0 指kafka 版本

解壓之后,最重要的有兩目錄:

  • bin : 一系列的腳本,比如啟動 zookeeper 服務,創建 topic,生產者生產消息,消費者消費消息等
zookeeper-server-start.sh
zookeeper-server-stop.sh
kafka-configs.sh
kafka-console-consumer.sh
kafka-console-producer.sh
kafka-consumer-groups.sh
kafka-topics.sh
kafka-server-start.sh
kafka-server-stop.sh
...

  • config: 配置文件:比如配置 zookeeper 端口,配置kafka 日志存儲目錄、對外端口,消息最大容量,保存時常等
zookeeper.properties
server.properties
producer.properties
consumer.properties
...

大概200多個參數吧,不好意思,我記不住。那怎么辦?不學了嗎,那掙不了錢,漲不了工資啊。

基本默認設置,部分按分類設置:

  • zookeeper.properties

kafka 依賴于 zookeeper 分布式協調

dataDir=/tmp/zookeeper
clientPort=2181

記住這個默認的 clientPort=2181

  • server.properties

kafka server 服務

log.dirs=/tmp/kafka-logs //日志存儲目錄
log.retention.hours=168 // 日志存儲時長
broker.id=0 // 默認 broker id,集群方式的 kafka 設置,給每個 broker 編號
listeners=PLAINTEXT://:9092 // 對外提供的服務入口地址
zookeeper.connect=localhost:2181 // ZooKeeper集群地址
...
  • producer.properties

約定消息等的內容

  • consumer.properties

約定消費消息等的內容

配置好配置參數后:

  • 啟動 zookeeper
> bin/zookeeper-server-start.sh config/zookeeper.properties
  • 啟動 kafka 服務進程
> bin/kafka-server-start.sh config/server.properties

創建topic, 查詢 topic 等可以使用:kafka-topics.sh

生產者生產消息可以使用:kafka-console-producer.sh

消費者消費消息可以使用:kafka-console-consumer.sh

當然,這些操作,一般只供測試使用,實際的使用是使用對應變成語言的客戶端。

4. 演示

kafka go版本客戶端:

下載安裝:

go get -u -v github.com/Shopify/sarama

4.1 生產者

系統 A

  • 生產者
type KafkaAction struct {
    DataSyncProducer  sarama.SyncProducer
    DataAsyncProducer sarama.AsyncProducer
}
// 同步方式

func newDataSyncProducer(brokerList []string) sarama.SyncProducer {
    config := sarama.NewConfig()
    config.Producer.RequiredAcks = sarama.WaitForAll // Wait for all in-sync replicas to ack the message
    config.Producer.Retry.Max = 5                    // Retry up to 10 times to produce the message
    config.Producer.Return.Successes = true
    config.Producer.Partitioner = sarama.NewRoundRobinPartitioner
    producer, err := sarama.NewSyncProducer(brokerList, config)
    if err != nil {
        log.Fatalln("Failed to start Sarama producer1:", err)
    }
    return producer

}

// 異步方式
func newDataAsyncProducer(brokerList []string) sarama.AsyncProducer {
    config := sarama.NewConfig()
    sarama.Logger = log.New(os.Stdout, "[KAFKA] ", log.LstdFlags)
    config.Producer.RequiredAcks = sarama.WaitForLocal       // Only wait for the leader to ack
    config.Producer.Compression = sarama.CompressionSnappy   // Compress messages
    config.Producer.Flush.Frequency = 500 * time.Millisecond // Flush batches every 500ms
    config.Producer.Partitioner = sarama.NewRoundRobinPartitioner
    producer, err := sarama.NewAsyncProducer(brokerList, config)
    if err != nil {
        log.Fatalln("Failed to start Sarama producer2:", err)
    }
    go func() {
        for err := range producer.Errors() {
            log.Println("Failed to write access log entry:", err)
        }
    }()
    return producer
}

還記得生產者有一系列配置參數嗎?config 就這這個作用,有默認值,可以自己設置對應的值。

比如:壓縮算法

config.Producer.Compression = sarama.CompressionSnappy

常用的壓縮算法有:

  • gzip
  • snappy
  • lz4
  • zstd

不同的壓縮算法主要在壓縮比和吞吐量不同。

比如分區規則

config.Producer.Partitioner = sarama.NewRoundRobinPartitioner

常用的分區規則:

  • 輪詢機制
  • 隨機分區
  • 按 key 分區

比如:發送消息是否返回成功與否

onfig.Producer.RequiredAcks = sarama.WaitForLocal
  • 消息:生產者只傳遞字節組數據。

接口

type Encoder interface {
    Encode() ([]byte, error)
    Length() int
}

發送的消息需要實現Encoder 接口,即定義的消息結構體需要實現 Encode 和 Length 方法。

type SendMessage struct {
    Method  string `json:"method"`
    URL     string `json:"url"`
    Value   string `json:"value"`
    Date    string `json:"date"`
    encoded []byte
    err     error
}

func (S *SendMessage) Length() int {
    b, e := json.Marshal(S)
    S.encoded = b
    S.err = e
    return len(string(b))
}
func (S *SendMessage) Encode() ([]byte, error) {
    return S.encoded, S.err
}
  • 發送消息
func (K *KafkaAction) Do(v interface{}) {
    message := v.(SendMessage)
    // 發送的消息返回分區和偏移量
    partition, offset, err := K.DataSyncProducer.SendMessage(&sarama.ProducerMessage{
        Topic: TOPIC,
        Value: &message,
    })
    if err != nil {
        log.Println(err)
        return
    }
    value := map[string]string{
        "method": message.Method,
        "url":    message.URL,
        "value":  message.Value,
        "date":   message.Date,
    }
    fmt.Println(fmt.Sprintf("/%d/%d/%+v", partition, offset, value))
}

比如我們按照上面的配置發送消息:topic: topic-golang
partition/offset/value

/0/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/0/1/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/0/2/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/0/3/map[date:12344 method:get5 url:www.baidu.com4 value:da4]

上文只有一個 partition , offset值不斷增加。

創建另外一個 topic, 分10個區。topic: topic-python

在日志中顯示成咋樣的呢?

// cd log.dirs  ; server.properties 中的設置

topic-golang-0
topic-python-0
topic-python-1
topic-python-2
topic-python-3
topic-python-4
topic-python-5
topic-python-6
topic-python-7
topic-python-8
topic-python-9

往 topic-python 中發送日志,分區規則輪詢:

/0/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/1/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/2/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/3/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/4/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/5/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/6/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/7/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/8/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/9/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/0/1/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/1/1/map[date:12344 method:get5 url:www.baidu.com4 value:da4]

輪詢,不斷的往分區內存消息。

4.2 消費者

系統 B

func main() {
    config := sarama.NewConfig()
    config.Consumer.Return.Errors = true
    brokers := []string{"127.0.0.1:9092"}
    master, err := sarama.NewConsumer(brokers, config)
    if err != nil {
        panic(err)
    }
    defer func() {
        if err := master.Close(); err != nil {
            panic(err)
        }
    }()
    _, e := master.Partitions("topic-python")
    if e != nil {
        log.Println(e)
    }
    consumer, err := master.ConsumePartition("topic-python", 0, sarama.OffsetOldest)
    if err != nil {
        panic(err)
    }
    signals := make(chan os.Signal, 1)
    signal.Notify(signals, os.Interrupt)
    doneCh := make(chan struct{})
    go func() {
        for {
            select {
            case err := <-consumer.Errors():
                fmt.Println(err)
            case msg := <-consumer.Messages():
                fmt.Println("Received messages", string(msg.Key), string(msg.Value), msg.Topic)
            case <-signals:
                fmt.Println("Interrupt is detected")
                doneCh <- struct{}{}
            }
        }
    }()
    <-doneCh
}
  • 消費者指定了 topic: topic-python
  • 消費者指定了 partition: 0

還記得生產者向 topic-python 內發送的消息嗎?
partition/offset/value

/0/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/1/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/2/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/3/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/4/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/5/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/6/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/7/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/8/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/9/0/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/0/1/map[date:12344 method:get5 url:www.baidu.com4 value:da4]
/1/1/map[date:12344 method:get5 url:www.baidu.com4 value:da4]

可以看出:partition: 0 中有兩條消息。那么消費者指定了分區,只能消費這兩條消息。

Received messages  {"method":"get5","url":"www.baidu.com4","value":"da4","date":"12344"} topic-python
Received messages  {"method":"get5","url":"www.baidu.com4","value":"da4","date":"12344"} topic-python

4.3 其他

使用 kafka 客戶端 ,那么我們還需要哪些功能?

  • 關于 Topic 的創建、描述、刪除等
  • 消費者組描述等
  • 元信息:metadata
type ClusterAdmin interface {
    CreateTopic(topic string, detail *TopicDetail, validateOnly bool) error
    ListTopics() (map[string]TopicDetail, error)
    DescribeTopics(topics []string) (metadata []*TopicMetadata, err error)
    DeleteTopic(topic string) error
    CreatePartitions(topic string, count int32, assignment [][]int32, validateOnly bool) error
    DeleteRecords(topic string, partitionOffsets map[int32]int64) error
    DescribeConfig(resource ConfigResource) ([]ConfigEntry, error)
    AlterConfig(resourceType ConfigResourceType, name string, entries map[string]*string, validateOnly bool) error
    CreateACL(resource Resource, acl Acl) error
    ListAcls(filter AclFilter) ([]ResourceAcls, error)
    DeleteACL(filter AclFilter, validateOnly bool) ([]MatchingAcl, error)
    ListConsumerGroups() (map[string]string, error)
    DescribeConsumerGroups(groups []string) ([]*GroupDescription, error)
    ListConsumerGroupOffsets(group string, topicPartitions map[string][]int32) (*OffsetFetchResponse, error)
    DeleteConsumerGroup(group string) error
    DescribeCluster() (brokers []*Broker, controllerID int32, err error)
    Close() error
}

關于單節點 kafka 的基本應用就這些。

5. 容器服務

任何提供服務的系統,都可以使用容器版本,kafka 也可以使用容器版本。配置可以使用環境變量的形式設置。

docker-compose.yml

version: '2'
services:
  ui:
    image: index.docker.io/sheepkiller/kafka-manager:latest
    depends_on:
      - zookeeper
    ports:
      - 9000:9000
    environment:
      ZK_HOSTS: zookeeper:2181
  zookeeper:
    image: index.docker.io/wurstmeister/zookeeper:latest
    ports:
      - 2181:2181
  server:
    image: index.docker.io/wurstmeister/kafka:latest
    depends_on:
      - zookeeper
    ports:
      - 9092:9092
    environment:
      KAFKA_OFFSETS_TOPIC_REPLIATION_FACTOR: 1
      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
  • zookeeper 分布式協調系統
  • kafka server Kafka 服務
  • kafka-manager kafka 管理平臺

后續集群版本。

<完>

代碼:https://github.com/wuxiaoxiaoshen/go-thirdparty

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 大致可以通過上述情況進行排除 1.kafka服務器問題 查看日志是否有報錯,網絡訪問問題等。 2. kafka p...
    生活的探路者閱讀 7,612評論 0 10
  • 一、入門1、簡介Kafka is a distributed,partitioned,replicated com...
    HxLiang閱讀 3,365評論 0 9
  • 什么是消息系統? 早期兩個應用程序間進行消息傳遞需要保證兩個應用程序同時在線,并且耦合度很高。為了解決應用程序不在...
    Java小鋪閱讀 1,228評論 0 2
  • 1介紹 Kafka是一個分布式的、可分區的、可復制的消息系統,提供了一個生產者、緩沖區、消費者的模型。 Kafka...
    蟲兒飛ZLEI閱讀 636評論 0 1
  • 古之善為士者,微妙玄通,深不可識。夫唯不可識,故強為之容。豫焉若冬涉川,猶兮若畏四鄰,儼兮其若容,涘兮若冰之將釋,...
    沉醉的文人閱讀 334評論 2 9