github.com/erda-project/erda-infra@v1.0.9/providers/kafka/producer.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  	"encoding/json"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/confluentinc/confluent-kafka-go/kafka"
    23  	"github.com/recallsong/go-utils/reflectx"
    24  
    25  	"github.com/erda-project/erda-infra/base/logs"
    26  	writer "github.com/erda-project/erda-infra/pkg/parallel-writer"
    27  )
    28  
    29  // Message .
    30  type Message struct {
    31  	Topic *string
    32  	Data  []byte
    33  	Key   []byte
    34  }
    35  
    36  // StringMessage .
    37  type StringMessage struct {
    38  	Topic *string
    39  	Data  string
    40  }
    41  
    42  // ProducerConfig .
    43  type ProducerConfig struct {
    44  	Topic       string `file:"topic" env:"KAFKA_P_TOPIC" desc:"topic"`
    45  	Parallelism uint64 `file:"parallelism" env:"KAFKA_P_PARALLELISM" default:"4" desc:"parallelism"`
    46  	Batch       struct {
    47  		Size    uint64        `file:"size" env:"KAFKA_P_BATCH_SIZE" default:"100" desc:"batch size"`
    48  		Timeout time.Duration `file:"timeout" env:"KAFKA_P_BUFFER_TIMEOUT" default:"30s" desc:"timeout to flush buffer for batch write"`
    49  	} `file:"batch"`
    50  	Shared  bool                   `file:"shared" default:"true" desc:"shared producer instance"`
    51  	Options map[string]interface{} `file:"options" desc:"options"`
    52  }
    53  
    54  // ProducerOption .
    55  type ProducerOption interface {
    56  	errHandler() func(error) error
    57  }
    58  
    59  type producerOption struct{ _eh func(error) error }
    60  
    61  func (p *producerOption) errHandler() func(error) error { return p._eh }
    62  
    63  // WithAsyncWriteErrorHandler .
    64  func WithAsyncWriteErrorHandler(eh func(error) error) ProducerOption {
    65  	return &producerOption{_eh: eh}
    66  }
    67  
    68  func newProducer(servers string, extra map[string]interface{}, log logs.Logger) (*kafka.Producer, error) {
    69  	kc := kafka.ConfigMap{"go.batch.producer": true}
    70  	if extra != nil {
    71  		for k, v := range extra {
    72  			kc[k] = v
    73  		}
    74  	}
    75  	kc["bootstrap.servers"] = servers
    76  	kp, err := kafka.NewProducer(&kc)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	go consumeEvents(kp, log)
    81  	return kp, err
    82  }
    83  
    84  func consumeEvents(kp *kafka.Producer, log logs.Logger) {
    85  	for e := range kp.Events() {
    86  		switch ev := e.(type) {
    87  		case *kafka.Message:
    88  			if ev.TopicPartition.Error != nil {
    89  				log.Errorf("Kafka delivery failed: %v", ev.TopicPartition)
    90  			}
    91  		}
    92  	}
    93  	log.Debugf("exit kafka events consumer")
    94  }
    95  
    96  type sharedProducer struct {
    97  	lock     sync.Mutex
    98  	instance *kafka.Producer
    99  	refs     int
   100  	log      logs.Logger
   101  }
   102  
   103  func (p *sharedProducer) release() error {
   104  	p.lock.Lock()
   105  	defer p.lock.Unlock()
   106  	if p.refs == 0 {
   107  		return nil
   108  	}
   109  	p.refs--
   110  	if p.refs == 0 {
   111  		p.instance.Close()
   112  	}
   113  	return nil
   114  }
   115  
   116  func (p *sharedProducer) get(servers string, extra map[string]interface{}) (*kafka.Producer, error) {
   117  	p.lock.Lock()
   118  	defer p.lock.Unlock()
   119  	if p.refs == 0 {
   120  		kp, err := newProducer(servers, extra, p.log)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		p.instance = kp
   125  	}
   126  	p.refs++
   127  	return p.instance, nil
   128  }
   129  
   130  type producer struct {
   131  	kp    *kafka.Producer
   132  	close func() error
   133  	topic string
   134  }
   135  
   136  func (p *producer) ProduceChannelSize() int {
   137  	return len(p.kp.ProduceChannel())
   138  }
   139  
   140  func (p *producer) EventsChannelSize() int {
   141  	return len(p.kp.Events())
   142  }
   143  
   144  func (p *producer) Write(data interface{}) error {
   145  	return p.publish(data)
   146  }
   147  
   148  func (p *producer) WriteN(data ...interface{}) (int, error) {
   149  	for i, item := range data {
   150  		err := p.publish(item)
   151  		if err != nil {
   152  			return i, err
   153  		}
   154  	}
   155  	return len(data), nil
   156  }
   157  
   158  func (p *producer) publish(data interface{}) error {
   159  	var (
   160  		bytes []byte
   161  		key   []byte
   162  	)
   163  	topic := &p.topic
   164  	switch val := data.(type) {
   165  	case *Message:
   166  		if val.Topic != nil {
   167  			topic = val.Topic
   168  		}
   169  		bytes = val.Data
   170  		key = val.Key
   171  	case *StringMessage:
   172  		if val.Topic != nil {
   173  			topic = val.Topic
   174  		}
   175  		bytes = reflectx.StringToBytes(val.Data)
   176  	case []byte:
   177  		bytes = val
   178  	case string:
   179  		bytes = reflectx.StringToBytes(val)
   180  	default:
   181  		data, err := json.Marshal(data)
   182  		if err != nil {
   183  			return err
   184  		}
   185  		bytes = data
   186  	}
   187  	p.kp.ProduceChannel() <- &kafka.Message{
   188  		Value:          bytes,
   189  		Key:            key,
   190  		TopicPartition: kafka.TopicPartition{Topic: topic, Partition: kafka.PartitionAny},
   191  	}
   192  	return nil
   193  }
   194  
   195  func (p *producer) Close() error {
   196  	return p.close()
   197  }
   198  
   199  func (s *service) NewProducer(c *ProducerConfig, options ...ProducerOption) (writer.Writer, error) {
   200  	var eh writer.ErrorHandler = s.producerError
   201  	for _, item := range options {
   202  		if item != nil && item.errHandler() != nil {
   203  			eh = item.errHandler()
   204  		}
   205  	}
   206  	if c.Shared {
   207  		kp, err := s.p.producer.get(s.p.Cfg.Servers, s.p.Cfg.Producer.Options)
   208  		if err != nil {
   209  			return nil, err
   210  		}
   211  		return writer.ParallelBatch(func(uint64) writer.Writer {
   212  			return &producer{
   213  				kp:    kp,
   214  				close: s.p.producer.release,
   215  				topic: c.Topic,
   216  			}
   217  		}, c.Parallelism, c.Batch.Size, c.Batch.Timeout, eh), nil
   218  	}
   219  	kp, err := newProducer(s.p.Cfg.Servers, c.Options, s.log)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	return writer.ParallelBatch(func(uint64) writer.Writer {
   224  		return &producer{
   225  			kp: kp,
   226  			close: func() error {
   227  				kp.Close()
   228  				return nil
   229  			},
   230  			topic: c.Topic,
   231  		}
   232  	}, c.Parallelism, c.Batch.Size, c.Batch.Timeout, eh), nil
   233  }
   234  
   235  func (s *service) producerError(err error) error {
   236  	s.log.Errorf("fail to write kafka: %s", err)
   237  	return nil // skip error
   238  }
   239  
   240  func (s *service) ProduceChannelSize() int {
   241  	s.p.producer.lock.Lock()
   242  	defer s.p.producer.lock.Unlock()
   243  	if s.p.producer.instance == nil {
   244  		return 0
   245  	}
   246  	return len(s.p.producer.instance.ProduceChannel())
   247  }
   248  
   249  func (s *service) ProduceEventsChannelSize() int {
   250  	s.p.producer.lock.Lock()
   251  	defer s.p.producer.lock.Unlock()
   252  	if s.p.producer.instance == nil {
   253  		return 0
   254  	}
   255  	return len(s.p.producer.instance.Events())
   256  }