2018-06-20 11:48:56 +08:00
|
|
|
|
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
2018-06-15 18:31:23 +08:00
|
|
|
|
//
|
|
|
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
|
|
|
// If a copy of the MIT was not distributed with this file,
|
|
|
|
|
// You can obtain one at https://gitee.com/johng/gf.
|
|
|
|
|
|
2018-06-20 11:48:56 +08:00
|
|
|
|
// Kafka Client.
|
2018-06-15 18:31:23 +08:00
|
|
|
|
package gkafka
|
|
|
|
|
|
|
|
|
|
import (
|
2018-10-23 16:56:25 +08:00
|
|
|
|
"gitee.com/johng/gf/g/os/glog"
|
2018-10-22 11:13:00 +08:00
|
|
|
|
"gitee.com/johng/gf/third/github.com/Shopify/sarama"
|
|
|
|
|
"gitee.com/johng/gf/third/github.com/johng-cn/sarama-cluster"
|
2018-12-17 13:02:18 +08:00
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
2018-06-15 18:31:23 +08:00
|
|
|
|
)
|
|
|
|
|
|
2018-06-21 15:51:23 +08:00
|
|
|
|
var (
|
|
|
|
|
// 当使用Topics方法获取所有topic后,进行过滤忽略的topic,多个以','号分隔
|
|
|
|
|
ignoreTopics = map[string]bool {
|
|
|
|
|
"__consumer_offsets" : true,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2018-06-15 18:31:23 +08:00
|
|
|
|
// kafka Client based on sarama.Config
|
|
|
|
|
type Config struct {
|
2018-06-20 21:00:50 +08:00
|
|
|
|
GroupId string // group id for consumer.
|
|
|
|
|
Servers string // server list, multiple servers joined by ','.
|
|
|
|
|
Topics string // topic list, multiple topics joined by ','.
|
|
|
|
|
AutoMarkOffset bool // auto mark message read after consumer message from server
|
2018-06-15 18:31:23 +08:00
|
|
|
|
sarama.Config
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Kafka Client(Consumer/SyncProducer/AsyncProducer)
|
|
|
|
|
type Client struct {
|
2018-06-15 18:50:47 +08:00
|
|
|
|
Config *Config
|
2018-06-15 18:31:23 +08:00
|
|
|
|
consumer *cluster.Consumer
|
2018-06-21 13:56:10 +08:00
|
|
|
|
rawConsumer sarama.Consumer
|
2018-06-15 18:31:23 +08:00
|
|
|
|
syncProducer sarama.SyncProducer
|
|
|
|
|
asyncProducer sarama.AsyncProducer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Kafka Message.
|
|
|
|
|
type Message struct {
|
|
|
|
|
Value []byte
|
|
|
|
|
Key []byte
|
|
|
|
|
Topic string
|
|
|
|
|
Partition int
|
|
|
|
|
Offset int
|
2018-06-20 21:00:50 +08:00
|
|
|
|
client *Client
|
|
|
|
|
consumerMsg *sarama.ConsumerMessage
|
2018-06-15 18:31:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// New a kafka client.
|
2018-06-20 11:48:56 +08:00
|
|
|
|
func NewClient(config *Config) *Client {
|
2018-06-19 19:32:36 +08:00
|
|
|
|
return &Client {
|
|
|
|
|
Config : config,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New a default configuration object.
|
|
|
|
|
func NewConfig() *Config {
|
|
|
|
|
config := &Config{}
|
|
|
|
|
config.Config = *sarama.NewConfig()
|
2018-06-15 18:31:23 +08:00
|
|
|
|
|
2018-06-15 18:50:47 +08:00
|
|
|
|
// default config for consumer
|
2018-06-19 19:32:36 +08:00
|
|
|
|
config.Consumer.Return.Errors = true
|
|
|
|
|
config.Consumer.Offsets.Initial = sarama.OffsetOldest
|
|
|
|
|
config.Consumer.Offsets.CommitInterval = 1 * time.Second
|
2018-06-15 18:31:23 +08:00
|
|
|
|
|
2018-06-15 18:50:47 +08:00
|
|
|
|
// default config for producer
|
2018-06-19 19:32:36 +08:00
|
|
|
|
config.Producer.Return.Errors = true
|
|
|
|
|
config.Producer.Return.Successes = true
|
|
|
|
|
config.Producer.Timeout = 5 * time.Second
|
2018-06-20 21:00:50 +08:00
|
|
|
|
|
|
|
|
|
config.AutoMarkOffset = true
|
2018-06-19 19:32:36 +08:00
|
|
|
|
return config
|
2018-06-15 18:31:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close client.
|
|
|
|
|
func (client *Client) Close() {
|
2018-07-30 10:29:48 +08:00
|
|
|
|
if client.rawConsumer != nil {
|
|
|
|
|
client.rawConsumer.Close()
|
|
|
|
|
}
|
2018-06-15 18:31:23 +08:00
|
|
|
|
if client.consumer != nil {
|
|
|
|
|
client.consumer.Close()
|
|
|
|
|
}
|
|
|
|
|
if client.syncProducer != nil {
|
|
|
|
|
client.syncProducer.Close()
|
|
|
|
|
}
|
|
|
|
|
if client.asyncProducer != nil {
|
|
|
|
|
client.asyncProducer.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-21 13:56:10 +08:00
|
|
|
|
// Get all topics from kafka server.
|
2018-10-23 18:13:29 +08:00
|
|
|
|
// 这里创建独立的消费客户端获取topics,获取完之后销毁该客户端对象。
|
2018-06-21 13:56:10 +08:00
|
|
|
|
func (client *Client) Topics() ([]string, error) {
|
2018-10-23 18:13:29 +08:00
|
|
|
|
if c, err := sarama.NewConsumer(strings.Split(client.Config.Servers, ","), &client.Config.Config); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
} else {
|
|
|
|
|
if topics, err := c.Topics(); err == nil {
|
|
|
|
|
for k, v := range topics {
|
|
|
|
|
if _, ok := ignoreTopics[v]; ok {
|
|
|
|
|
topics = append(topics[ : k], topics[k + 1 : ]...)
|
|
|
|
|
}
|
2018-06-21 15:51:23 +08:00
|
|
|
|
}
|
2018-10-23 18:13:29 +08:00
|
|
|
|
c.Close()
|
|
|
|
|
return topics, nil
|
|
|
|
|
} else {
|
|
|
|
|
return nil, err
|
2018-06-21 15:51:23 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-21 13:56:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-19 15:08:33 +08:00
|
|
|
|
// 初始化内部消费客户端
|
|
|
|
|
func (client *Client) initConsumer() error {
|
2018-06-15 18:31:23 +08:00
|
|
|
|
if client.consumer == nil {
|
2018-07-26 11:01:17 +08:00
|
|
|
|
config := cluster.NewConfig()
|
|
|
|
|
config.Config = client.Config.Config
|
|
|
|
|
config.Group.Return.Notifications = false
|
2018-06-15 18:50:47 +08:00
|
|
|
|
c, err := cluster.NewConsumer(strings.Split(client.Config.Servers, ","), client.Config.GroupId, strings.Split(client.Config.Topics, ","), config)
|
2018-06-15 18:31:23 +08:00
|
|
|
|
if err != nil {
|
2018-09-19 15:08:33 +08:00
|
|
|
|
return err
|
2018-06-15 18:31:23 +08:00
|
|
|
|
} else {
|
|
|
|
|
client.consumer = c
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-19 15:08:33 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 标记指定topic 分区开始读取位置
|
|
|
|
|
func (client *Client) MarkOffset(topic string, partition int, offset int, metadata...string) error {
|
|
|
|
|
if err := client.initConsumer(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
meta := ""
|
|
|
|
|
if len(metadata) > 0 {
|
|
|
|
|
meta = metadata[0]
|
|
|
|
|
}
|
|
|
|
|
client.consumer.MarkPartitionOffset(topic, int32(partition), int64(offset), meta)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Receive message from kafka from specified topics in config, in BLOCKING way, gkafka will handle offset tracking automatically.
|
|
|
|
|
func (client *Client) Receive() (*Message, error) {
|
|
|
|
|
if err := client.initConsumer(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2018-07-26 11:01:17 +08:00
|
|
|
|
errorsChan := client.consumer.Errors()
|
|
|
|
|
notifyChan := client.consumer.Notifications()
|
|
|
|
|
messageChan := client.consumer.Messages()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case msg := <- messageChan:
|
|
|
|
|
if client.Config.AutoMarkOffset {
|
|
|
|
|
client.consumer.MarkOffset(msg, "")
|
|
|
|
|
}
|
|
|
|
|
return &Message {
|
|
|
|
|
Value : msg.Value,
|
|
|
|
|
Key : msg.Key,
|
|
|
|
|
Topic : msg.Topic,
|
|
|
|
|
Partition : int(msg.Partition),
|
|
|
|
|
Offset : int(msg.Offset),
|
|
|
|
|
client : client,
|
|
|
|
|
consumerMsg : msg,
|
|
|
|
|
}, nil
|
|
|
|
|
|
|
|
|
|
case err := <-errorsChan:
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2018-06-15 18:31:23 +08:00
|
|
|
|
|
2018-07-26 11:01:17 +08:00
|
|
|
|
case <-notifyChan:
|
|
|
|
|
}
|
2018-06-20 21:00:50 +08:00
|
|
|
|
}
|
2018-06-15 18:31:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send data to kafka in synchronized way.
|
|
|
|
|
func (client *Client) SyncSend(message *Message) error {
|
|
|
|
|
if client.syncProducer == nil {
|
2018-06-15 18:50:47 +08:00
|
|
|
|
if p, err := sarama.NewSyncProducer(strings.Split(client.Config.Servers, ","), &client.Config.Config); err != nil {
|
2018-06-15 18:31:23 +08:00
|
|
|
|
return err
|
|
|
|
|
} else {
|
|
|
|
|
client.syncProducer = p
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-15 18:50:47 +08:00
|
|
|
|
for _, topic := range strings.Split(client.Config.Topics, ",") {
|
2018-06-15 18:31:23 +08:00
|
|
|
|
msg := messageToProducerMessage(message)
|
|
|
|
|
msg.Topic = topic
|
|
|
|
|
if _, _, err := client.syncProducer.SendMessage(msg); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-23 14:55:52 +08:00
|
|
|
|
// Send data to kafka in asynchronized way(concurrent safe).
|
2018-06-15 18:31:23 +08:00
|
|
|
|
func (client *Client) AsyncSend(message *Message) error {
|
|
|
|
|
if client.asyncProducer == nil {
|
2018-06-15 18:50:47 +08:00
|
|
|
|
if p, err := sarama.NewAsyncProducer(strings.Split(client.Config.Servers, ","), &client.Config.Config); err != nil {
|
2018-06-15 18:31:23 +08:00
|
|
|
|
return err
|
|
|
|
|
} else {
|
|
|
|
|
client.asyncProducer = p
|
2018-10-23 16:56:25 +08:00
|
|
|
|
go func(p sarama.AsyncProducer) {
|
|
|
|
|
errors := p.Errors()
|
|
|
|
|
success := p.Successes()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case err := <-errors:
|
|
|
|
|
if err != nil {
|
|
|
|
|
glog.Error(err)
|
|
|
|
|
}
|
|
|
|
|
case <-success:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}(client.asyncProducer)
|
2018-06-15 18:31:23 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-15 18:50:47 +08:00
|
|
|
|
for _, topic := range strings.Split(client.Config.Topics, ",") {
|
2018-07-26 11:01:17 +08:00
|
|
|
|
msg := messageToProducerMessage(message)
|
2018-06-15 18:31:23 +08:00
|
|
|
|
msg.Topic = topic
|
|
|
|
|
client.asyncProducer.Input() <- msg
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert *gkafka.Message to *sarama.ProducerMessage
|
|
|
|
|
func messageToProducerMessage(message *Message) *sarama.ProducerMessage {
|
|
|
|
|
return &sarama.ProducerMessage {
|
|
|
|
|
Topic : message.Topic,
|
|
|
|
|
Key : sarama.ByteEncoder(message.Key),
|
|
|
|
|
Value : sarama.ByteEncoder(message.Value),
|
|
|
|
|
Partition : int32(message.Partition),
|
|
|
|
|
Offset : int64(message.Offset),
|
|
|
|
|
}
|
|
|
|
|
}
|