At present, there are several commonly used Golang Kafka clients, each of which has its own advantages and disadvantages
Client name | Advantages and disadvantages |
---|---|
sarama | The number of users is relatively large, but it is relatively difficult to use, with better performance |
confluent-kafka-go | The encapsulation of Kafka in C language has strong performance, but depends on librdkafka |
kafka-go | The operation is simple, but the performance is poor. The common dual core CPU machine in the production environment only processes about 300 pieces per second |
healer | The operation is very simple, and the performance is similar to that of sarama. This product is the work of a big bull of Ctrip. At present, the number of users in the community is small, and there is no corresponding support and maintenance |
After a simple test in the above several client retest environments, through comparison, sarama is finally used as the Kfaka client of the production environment.
At present, we see that some examples of Kafka topic specified group online are completed by using sarama cluster. Currently, sarama also supports specified consumer group.
Consumer Group is a logical concept. It is Kafka's means to implement two message models: unicast and broadcast. The data of the same topic will be broadcast to different groups; only one worker in the same group can get the data. In other words, for the same topic, each group can get all the same data, but the data can only be consumed by one of the workers after entering the group. The workers in a group can be implemented by multithreading or multiprocessing, or process can be distributed on multiple machines. The number of workers usually does not exceed the number of partitions, and it is better to keep an integer multiple relationship between them, because Kafka assumes that a partition can only be consumed by one worker (in the same group)
Example of sarama producer
package main import ( "fmt" "github.com/Shopify/sarama" ) /* Initialize NewConfig configuration sarama.NewConfig Create producer sarama.NewSyncProducer Create message sarama.ProducerMessage Send message client.SendMessage */ func main() { config := sarama.NewConfig() config.Producer.RequiredAcks = sarama.WaitForAll config.Producer.Partitioner = sarama.NewRandomPartitioner config.Producer.Return.Successes = true msg := &sarama.ProducerMessage{} msg.Topic = "TestTopic" msg.Value = sarama.StringEncoder("this is a test") client, err := sarama.NewSyncProducer([]string{"172.0.0.1:9092"}, config) if err != nil { fmt.Println("producer close, err:", err) return } defer client.Close() pid, offset, err := client.SendMessage(msg) if err != nil { fmt.Println("send message failed,", err) return } fmt.Printf("pid:%v offset:%v\n", pid, offset) }
Operation result:
pid:0 offset:0
Run again:
pid:0 offset:1
Run again:
pid:0 offset:
sarama consumer example
Example 1: do not specify group for consumption
package main import ( "fmt" "strings" "sync" "github.com/Shopify/sarama" ) var ( wg sync.WaitGroup ) func main() { //Create consumer consumer, err := sarama.NewConsumer(strings.Split("192.168.1.125:9092", ","), nil) if err != nil { fmt.Println("Failed to start consumer: %s", err) return } //Set partition partitionList, err := consumer.Partitions("nginx_log") if err != nil { fmt.Println("Failed to get the list of partitions: ", err) return } fmt.Println(partitionList) //Cyclic partition for partition := range partitionList { pc, err := consumer.ConsumePartition("nginx_log", int32(partition), sarama.OffsetNewest) if err != nil { fmt.Printf("Failed to start consumer for partition %d: %s\n", partition, err) return } defer pc.AsyncClose() wg.Add(1) go func(pc sarama.PartitionConsumer) { defer wg.Done() for msg := range pc.Messages() { fmt.Printf("Partition:%d, Offset:%d, Key:%s, Value:%s", msg.Partition, msg.Offset, string(msg.Key), string(msg.Value)) fmt.Println() } }(pc) } //time.Sleep(time.Hour) wg.Wait() consumer.Close() }
Example 2: designated group for consumption
package main import ( "context" "flag" "log" "os" "os/signal" "strings" "sync" "syscall" "github.com/Shopify/sarama" ) // Sarama configuration options var ( brokers = "" version = "" group = "" topics = "" assignor = "" oldest = true verbose = false ) func init() { flag.StringVar(&brokers, "brokers", "", "Kafka bootstrap brokers to connect to, as a comma separated list") flag.StringVar(&group, "group", "", "Kafka consumer group definition") flag.StringVar(&version, "version", "2.1.1", "Kafka cluster version") flag.StringVar(&topics, "topics", "", "Kafka topics to be consumed, as a comma seperated list") flag.StringVar(&assignor, "assignor", "range", "Consumer group partition assignment strategy (range, roundrobin, sticky)") flag.BoolVar(&oldest, "oldest", true, "Kafka consumer consume initial offset from oldest") flag.BoolVar(&verbose, "verbose", false, "Sarama logging") flag.Parse() if len(brokers) == 0 { panic("no Kafka bootstrap brokers defined, please set the -brokers flag") } if len(topics) == 0 { panic("no topics given to be consumed, please set the -topics flag") } if len(group) == 0 { panic("no Kafka consumer group defined, please set the -group flag") } } func main() { log.Println("Starting a new Sarama consumer") if verbose { sarama.Logger = log.New(os.Stdout, "[sarama] ", log.LstdFlags) } version, err := sarama.ParseKafkaVersion(version) if err != nil { log.Panicf("Error parsing Kafka version: %v", err) } /** * Construct a new Sarama configuration. * The Kafka cluster version has to be defined before the consumer/producer is initialized. */ config := sarama.NewConfig() config.Version = version switch assignor { case "sticky": config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategySticky case "roundrobin": config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobin case "range": config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRange default: log.Panicf("Unrecognized consumer group partition assignor: %s", assignor) } if oldest { config.Consumer.Offsets.Initial = sarama.OffsetOldest } /** * Setup a new Sarama consumer group */ consumer := Consumer{ ready: make(chan bool), } ctx, cancel := context.WithCancel(context.Background()) client, err := sarama.NewConsumerGroup(strings.Split(brokers, ","), group, config) if err != nil { log.Panicf("Error creating consumer group client: %v", err) } wg := &sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() for { if err := client.Consume(ctx, strings.Split(topics, ","), &consumer); err != nil { log.Panicf("Error from consumer: %v", err) } // check if context was cancelled, signaling that the consumer should stop if ctx.Err() != nil { return } consumer.ready = make(chan bool) } }() <-consumer.ready // Await till the consumer has been set up log.Println("Sarama consumer up and running!...") sigterm := make(chan os.Signal, 1) signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM) select { case <-ctx.Done(): log.Println("terminating: context cancelled") case <-sigterm: log.Println("terminating: via signal") } cancel() wg.Wait() if err = client.Close(); err != nil { log.Panicf("Error closing client: %v", err) } } // Consumer represents a Sarama consumer group consumer type Consumer struct { ready chan bool } // Setup is run at the beginning of a new session, before ConsumeClaim func (consumer *Consumer) Setup(sarama.ConsumerGroupSession) error { // Mark the consumer as ready close(consumer.ready) return nil } // Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited func (consumer *Consumer) Cleanup(sarama.ConsumerGroupSession) error { return nil } // ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages(). func (consumer *Consumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { // NOTE: // Do not move the code below to a goroutine. // The `ConsumeClaim` itself is called within a goroutine, see: // https://github.com/Shopify/sarama/blob/master/consumer_group.go#L27-L29 for message := range claim.Messages() { log.Printf("Message claimed: value = %s, timestamp = %v, topic = %s", string(message.Value), message.Timestamp, message.Topic) session.MarkMessage(message, "") } return nil }
$ go run main.go -brokers="127.0.0.1:9092" -topics="sarama" -group="example"