github.com/erda-project/erda-infra@v1.0.9/providers/kafka/batch_reader.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kafka
    16  
    17  import (
    18  	"time"
    19  
    20  	"github.com/confluentinc/confluent-kafka-go/kafka"
    21  )
    22  
    23  // BatchReader .
    24  type BatchReader interface {
    25  	ReadN(buf []interface{}, timeout time.Duration) (int, error)
    26  	Confirm() error
    27  	Close() error
    28  }
    29  
    30  // BatchReaderConfig .
    31  type BatchReaderConfig struct {
    32  	Topics  []string               `file:"topics" desc:"topics"`
    33  	Group   string                 `file:"group" desc:"consumer group id"`
    34  	Options map[string]interface{} `file:"options" desc:"options"`
    35  }
    36  
    37  // BatchReaderOption .
    38  type BatchReaderOption interface{}
    39  
    40  // WithReaderDecoder .
    41  func WithReaderDecoder(dec Decoder) BatchReaderOption {
    42  	return BatchReaderOption(dec)
    43  }
    44  
    45  // Decoder .
    46  type Decoder func(key, value []byte, topic *string, timestamp time.Time) (interface{}, error)
    47  
    48  func (s *service) NewBatchReader(cfg *BatchReaderConfig, options ...BatchReaderOption) (BatchReader, error) {
    49  	var dec Decoder
    50  	for _, opt := range options {
    51  		switch v := opt.(type) {
    52  		case Decoder:
    53  			dec = v
    54  		}
    55  	}
    56  	if dec == nil {
    57  		dec = func(key, value []byte, topic *string, timestamp time.Time) (interface{}, error) {
    58  			return value, nil
    59  		}
    60  	}
    61  	kc := convertToConfigMap(mergeMap(s.p.Cfg.Comsumer.Options, cfg.Options))
    62  	return newKafkaReader(s.p.Cfg.Servers, cfg.Group, cfg.Topics, kc, dec)
    63  }
    64  
    65  func newKafkaReader(servers, group string, topics []string, kc kafka.ConfigMap, dec Decoder) (BatchReader, error) {
    66  	kc["bootstrap.servers"] = servers
    67  	kc["group.id"] = group
    68  	kc["enable.auto.offset.store"] = false
    69  	kc["enable.auto.commit"] = false
    70  	delete(kc, "auto.offset.reset")
    71  	delete(kc, "auto.commit.interval.ms")
    72  	return &kafkaBatchReader{
    73  		kc:     kc,
    74  		topics: topics,
    75  		decode: dec,
    76  	}, nil
    77  }
    78  
    79  type kafkaBatchReader struct {
    80  	kc       kafka.ConfigMap
    81  	topics   []string
    82  	consumer *kafka.Consumer
    83  	decode   Decoder
    84  }
    85  
    86  func (r *kafkaBatchReader) ReadN(buf []interface{}, timeout time.Duration) (int, error) {
    87  	if r.consumer == nil {
    88  		consumer, err := kafka.NewConsumer(&r.kc)
    89  		if err != nil {
    90  			return 0, err
    91  		}
    92  		err = consumer.SubscribeTopics(r.topics, nil)
    93  		if err != nil {
    94  			consumer.Close()
    95  			return 0, err
    96  		}
    97  		r.consumer = consumer
    98  	}
    99  	size := len(buf)
   100  	var offset int
   101  	maxWaitTimer := time.NewTimer(timeout * 3)
   102  	defer maxWaitTimer.Stop()
   103  loop:
   104  	for {
   105  		if offset >= size {
   106  			break
   107  		}
   108  		select {
   109  		case <-maxWaitTimer.C:
   110  			break loop
   111  		default:
   112  		}
   113  		msg, err := r.consumer.ReadMessage(timeout)
   114  		if err != nil {
   115  			if kerr, ok := err.(kafka.Error); ok {
   116  				if kerr.Code() == kafka.ErrTimedOut {
   117  					return offset, nil
   118  				}
   119  			}
   120  			r.Close()
   121  			return offset, err
   122  		}
   123  
   124  		data, err := r.decode(msg.Key, msg.Value, msg.TopicPartition.Topic, msg.Timestamp)
   125  		if err != nil {
   126  			// ingore decode error
   127  			continue
   128  		}
   129  
   130  		_, err = r.consumer.StoreOffsets([]kafka.TopicPartition{msg.TopicPartition})
   131  		if err != nil {
   132  			return offset, err
   133  		}
   134  		buf[offset] = data
   135  		offset++
   136  	}
   137  	return offset, nil
   138  }
   139  
   140  func (r *kafkaBatchReader) Confirm() error {
   141  	if r.consumer != nil {
   142  		_, err := r.consumer.Commit()
   143  		if kerr, ok := err.(kafka.Error); ok {
   144  			if kerr.Code() == kafka.ErrNoOffset {
   145  				return nil
   146  			}
   147  		}
   148  		return err
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func (r *kafkaBatchReader) Close() error {
   155  	consumer := r.consumer
   156  	if consumer != nil {
   157  		consumer.Unsubscribe()
   158  		err := consumer.Close()
   159  		r.consumer = nil
   160  		return err
   161  	}
   162  	return nil
   163  }
   164  
   165  // CommittedOffsets .
   166  func (r *kafkaBatchReader) CommittedOffsets() ([]kafka.TopicPartition, error) {
   167  	consumer := r.consumer
   168  	if consumer == nil {
   169  		return nil, nil
   170  	}
   171  	ps, err := consumer.Assignment()
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	return consumer.Committed(ps, 30*1000)
   176  }
   177  
   178  // CommittedOffsets .
   179  func CommittedOffsets(r BatchReader) ([]kafka.TopicPartition, error) {
   180  	bw, ok := r.(*kafkaBatchReader)
   181  	if ok {
   182  		return bw.CommittedOffsets()
   183  	}
   184  	return nil, nil
   185  }