github.com/Psiphon-Inc/goarista@v0.0.0-20160825065156-d002785f4c67/kafka/producer/producer.go (about) 1 // Copyright (C) 2016 Arista Networks, Inc. 2 // Use of this source code is governed by the Apache License 2.0 3 // that can be found in the COPYING file. 4 5 package producer 6 7 import ( 8 "expvar" 9 "fmt" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "github.com/Shopify/sarama" 15 "github.com/aristanetworks/glog" 16 "github.com/aristanetworks/goarista/kafka" 17 "github.com/aristanetworks/goarista/kafka/openconfig" 18 "github.com/aristanetworks/goarista/monitor" 19 "github.com/golang/protobuf/proto" 20 ) 21 22 // counter counts the number Sysdb clients we have, and is used to guarantee that we 23 // always have a unique name exported to expvar 24 var counter uint32 25 26 // MessageEncoder defines the encoding from topic, key, proto.Message to sarama.ProducerMessage 27 type MessageEncoder func(string, sarama.Encoder, proto.Message) (*sarama.ProducerMessage, error) 28 29 // Producer forwards messages recvd on a channel to kafka. 30 type Producer interface { 31 Run() 32 Write(proto.Message) 33 Stop() 34 } 35 36 type producer struct { 37 notifsChan chan proto.Message 38 kafkaProducer sarama.AsyncProducer 39 topic string 40 key sarama.Encoder 41 encoder MessageEncoder 42 done chan struct{} 43 wg sync.WaitGroup 44 45 // Used for monitoring 46 histogram *monitor.Histogram 47 numSuccesses monitor.Uint 48 numFailures monitor.Uint 49 } 50 51 // New creates new Kafka producer 52 func New(topic string, notifsChan chan proto.Message, client sarama.Client, key sarama.Encoder, 53 encoder MessageEncoder) ( 54 Producer, error) { 55 if notifsChan == nil { 56 notifsChan = make(chan proto.Message) 57 } 58 kafkaProducer, err := sarama.NewAsyncProducerFromClient(client) 59 if err != nil { 60 return nil, err 61 } 62 63 // Setup monitoring structures 64 histName := "kafkaProducerHistogram" 65 statsName := "messagesStats" 66 if id := atomic.AddUint32(&counter, 1); id > 1 { 67 histName = fmt.Sprintf("%s-%d", histName, id) 68 statsName = fmt.Sprintf("%s-%d", statsName, id) 69 } 70 hist := monitor.NewHistogram(histName, 32, 0.3, 1000, 0) 71 statsMap := expvar.NewMap(statsName) 72 73 p := &producer{ 74 notifsChan: notifsChan, 75 kafkaProducer: kafkaProducer, 76 topic: topic, 77 key: key, 78 encoder: encoder, 79 done: make(chan struct{}), 80 wg: sync.WaitGroup{}, 81 histogram: hist, 82 } 83 84 statsMap.Set("successes", &p.numSuccesses) 85 statsMap.Set("failures", &p.numFailures) 86 87 return p, nil 88 } 89 90 func (p *producer) Run() { 91 p.wg.Add(2) 92 go p.handleSuccesses() 93 go p.handleErrors() 94 95 p.wg.Add(1) 96 defer p.wg.Done() 97 for { 98 select { 99 case batch, open := <-p.notifsChan: 100 if !open { 101 return 102 } 103 err := p.produceNotification(batch) 104 if err != nil { 105 if _, ok := err.(openconfig.UnhandledSubscribeResponseError); !ok { 106 panic(err) 107 } 108 } 109 case <-p.done: 110 return 111 } 112 } 113 } 114 115 func (p *producer) Write(m proto.Message) { 116 p.notifsChan <- m 117 } 118 119 func (p *producer) Stop() { 120 close(p.done) 121 p.kafkaProducer.Close() 122 p.wg.Wait() 123 } 124 125 func (p *producer) produceNotification(protoMessage proto.Message) error { 126 message, err := p.encoder(p.topic, p.key, protoMessage) 127 if err != nil { 128 return err 129 } 130 select { 131 case p.kafkaProducer.Input() <- message: 132 return nil 133 case <-p.done: 134 return nil 135 } 136 } 137 138 // handleSuccesses reads from the producer's successes channel and collects some 139 // information for monitoring 140 func (p *producer) handleSuccesses() { 141 defer p.wg.Done() 142 for msg := range p.kafkaProducer.Successes() { 143 metadata := msg.Metadata.(kafka.Metadata) 144 // TODO: Add a monotonic clock source when one becomes available 145 p.histogram.UpdateLatencyValues(metadata.StartTime, time.Now()) 146 p.numSuccesses.Add(uint64(metadata.NumMessages)) 147 } 148 } 149 150 // handleErrors reads from the producer's errors channel and collects some information 151 // for monitoring 152 func (p *producer) handleErrors() { 153 defer p.wg.Done() 154 for msg := range p.kafkaProducer.Errors() { 155 metadata := msg.Msg.Metadata.(kafka.Metadata) 156 // TODO: Add a monotonic clock source when one becomes available 157 p.histogram.UpdateLatencyValues(metadata.StartTime, time.Now()) 158 glog.Errorf("Kafka Producer error: %s", msg.Error()) 159 p.numFailures.Add(uint64(metadata.NumMessages)) 160 } 161 }