github.com/m3db/m3@v1.5.0/src/msg/consumer/consumer.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package consumer 22 23 import ( 24 "net" 25 "sync" 26 "time" 27 28 "go.uber.org/zap" 29 30 "github.com/m3db/m3/src/msg/generated/proto/msgpb" 31 "github.com/m3db/m3/src/msg/protocol/proto" 32 "github.com/m3db/m3/src/x/clock" 33 xio "github.com/m3db/m3/src/x/io" 34 xtime "github.com/m3db/m3/src/x/time" 35 36 "github.com/uber-go/tally" 37 ) 38 39 type listener struct { 40 net.Listener 41 42 opts Options 43 msgPool *messagePool 44 m metrics 45 } 46 47 // NewListener creates a consumer listener. 48 func NewListener(addr string, opts Options) (Listener, error) { 49 if opts == nil { 50 opts = NewOptions() 51 } 52 lis, err := net.Listen("tcp", addr) 53 if err != nil { 54 return nil, err 55 } 56 mPool := newMessagePool(opts.MessagePoolOptions()) 57 mPool.Init() 58 return &listener{ 59 Listener: lis, 60 opts: opts, 61 msgPool: mPool, 62 m: newConsumerMetrics(opts.InstrumentOptions().MetricsScope()), 63 }, nil 64 } 65 66 func (l *listener) Accept() (Consumer, error) { 67 conn, err := l.Listener.Accept() 68 if err != nil { 69 return nil, err 70 } 71 72 return newConsumer(conn, l.msgPool, l.opts, l.m, NewNoOpMessageProcessor()), nil 73 } 74 75 type metrics struct { 76 messageReceived tally.Counter 77 messageDecodeError tally.Counter 78 ackSent tally.Counter 79 ackEncodeError tally.Counter 80 ackWriteError tally.Counter 81 // the duration between the producer sending the message and the consumer reading the message. 82 receiveLatency tally.Histogram 83 // the duration between the consumer reading the message and sending an ack to the producer. 84 handleLatency tally.Histogram 85 } 86 87 func newConsumerMetrics(scope tally.Scope) metrics { 88 return metrics{ 89 messageReceived: scope.Counter("message-received"), 90 messageDecodeError: scope.Counter("message-decode-error"), 91 ackSent: scope.Counter("ack-sent"), 92 ackEncodeError: scope.Counter("ack-encode-error"), 93 ackWriteError: scope.Counter("ack-write-error"), 94 receiveLatency: scope.Histogram("receive-latency", 95 // 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.2s, 2.4s, 4.8s, 9.6s 96 tally.MustMakeExponentialDurationBuckets(time.Millisecond*10, 2, 11)), 97 handleLatency: scope.Histogram("handle-latency", 98 // 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.2s, 2.4s, 4.8s, 9.6s 99 tally.MustMakeExponentialDurationBuckets(time.Millisecond*10, 2, 11)), 100 } 101 } 102 103 type consumer struct { 104 sync.Mutex 105 106 opts Options 107 mPool *messagePool 108 encoder proto.Encoder 109 decoder proto.Decoder 110 w xio.ResettableWriter 111 conn net.Conn 112 113 ackPb msgpb.Ack 114 closed bool 115 doneCh chan struct{} 116 wg sync.WaitGroup 117 m metrics 118 messageProcessor MessageProcessor 119 } 120 121 func newConsumer( 122 conn net.Conn, 123 mPool *messagePool, 124 opts Options, 125 m metrics, 126 mp MessageProcessor, 127 ) *consumer { 128 var ( 129 wOpts = xio.ResettableWriterOptions{ 130 WriteBufferSize: opts.ConnectionWriteBufferSize(), 131 } 132 133 rwOpts = opts.DecoderOptions().RWOptions() 134 writerFn = rwOpts.ResettableWriterFn() 135 ) 136 137 return &consumer{ 138 opts: opts, 139 mPool: mPool, 140 encoder: proto.NewEncoder(opts.EncoderOptions()), 141 decoder: proto.NewDecoder( 142 conn, opts.DecoderOptions(), opts.ConnectionReadBufferSize(), 143 ), 144 w: writerFn(newConnWithTimeout(conn, opts.ConnectionWriteTimeout(), time.Now), wOpts), 145 conn: conn, 146 closed: false, 147 doneCh: make(chan struct{}), 148 m: m, 149 messageProcessor: mp, 150 } 151 } 152 153 func (c *consumer) Init() { 154 c.wg.Add(1) 155 go func() { 156 c.ackUntilClose() 157 c.wg.Done() 158 }() 159 } 160 func (c *consumer) process(m Message) { 161 c.messageProcessor.Process(m) 162 } 163 164 func (c *consumer) Message() (Message, error) { 165 m := c.mPool.Get() 166 m.reset(c) 167 if err := c.decoder.Decode(m); err != nil { 168 c.mPool.Put(m) 169 c.m.messageDecodeError.Inc(1) 170 return nil, err 171 } 172 if m.Metadata.SentAtNanos > 0 { 173 c.m.receiveLatency.RecordDuration(xtime.Since(xtime.UnixNano(m.Metadata.SentAtNanos))) 174 } 175 c.m.messageReceived.Inc(1) 176 return m, nil 177 } 178 179 // This function could be called concurrently if messages are being 180 // processed concurrently. 181 func (c *consumer) tryAck(m msgpb.Metadata) { 182 c.Lock() 183 if c.closed { 184 c.Unlock() 185 return 186 } 187 c.ackPb.Metadata = append(c.ackPb.Metadata, m) 188 ackLen := len(c.ackPb.Metadata) 189 if ackLen < c.opts.AckBufferSize() { 190 c.Unlock() 191 return 192 } 193 c.trySendAcksWithLock(ackLen) 194 c.Unlock() 195 } 196 197 func (c *consumer) ackUntilClose() { 198 flushTicker := time.NewTicker(c.opts.AckFlushInterval()) 199 defer flushTicker.Stop() 200 201 for { 202 select { 203 case <-flushTicker.C: 204 c.tryAckAndFlush() 205 case <-c.doneCh: 206 c.tryAckAndFlush() 207 return 208 } 209 } 210 } 211 212 func (c *consumer) tryAckAndFlush() { 213 c.Lock() 214 if ackLen := len(c.ackPb.Metadata); ackLen > 0 { 215 c.trySendAcksWithLock(ackLen) 216 } 217 c.w.Flush() 218 c.Unlock() 219 } 220 221 // if acks fail to send the client will retry sending the messages. 222 func (c *consumer) trySendAcksWithLock(ackLen int) { 223 err := c.encoder.Encode(&c.ackPb) 224 log := c.opts.InstrumentOptions().Logger() 225 c.ackPb.Metadata = c.ackPb.Metadata[:0] 226 if err != nil { 227 c.m.ackEncodeError.Inc(1) 228 log.Error("failed to encode ack. client will retry sending message.", zap.Error(err)) 229 return 230 } 231 _, err = c.w.Write(c.encoder.Bytes()) 232 if err != nil { 233 c.m.ackWriteError.Inc(1) 234 log.Error("failed to write ack. client will retry sending message.", zap.Error(err)) 235 c.tryCloseConn() 236 return 237 } 238 if err := c.w.Flush(); err != nil { 239 c.m.ackWriteError.Inc(1) 240 log.Error("failed to flush ack. client will retry sending message.", zap.Error(err)) 241 c.tryCloseConn() 242 return 243 } 244 c.m.ackSent.Inc(int64(ackLen)) 245 } 246 247 func (c *consumer) tryCloseConn() { 248 if err := c.conn.Close(); err != nil { 249 c.opts.InstrumentOptions().Logger().Error("failed to close connection.", zap.Error(err)) 250 } 251 } 252 253 func (c *consumer) Close() { 254 c.Lock() 255 if c.closed { 256 c.Unlock() 257 return 258 } 259 c.closed = true 260 c.Unlock() 261 262 close(c.doneCh) 263 c.wg.Wait() 264 c.conn.Close() 265 } 266 267 type message struct { 268 msgpb.Message 269 270 mPool *messagePool 271 c *consumer 272 } 273 274 func newMessage(p *messagePool) *message { 275 return &message{mPool: p} 276 } 277 278 func (m *message) Bytes() []byte { 279 return m.Value 280 } 281 282 func (m *message) Ack() { 283 m.c.tryAck(m.Metadata) 284 if m.mPool != nil { 285 m.mPool.Put(m) 286 } 287 } 288 289 func (m *message) reset(c *consumer) { 290 m.c = c 291 resetProto(&m.Message) 292 } 293 294 func (m *message) ShardID() uint64 { 295 return m.Metadata.Shard 296 } 297 298 func (m *message) SentAtNanos() uint64 { 299 return m.Metadata.SentAtNanos 300 } 301 302 func resetProto(m *msgpb.Message) { 303 m.Metadata.Id = 0 304 m.Metadata.Shard = 0 305 m.Value = m.Value[:0] 306 } 307 308 type connWithTimeout struct { 309 net.Conn 310 311 timeout time.Duration 312 nowFn clock.NowFn 313 } 314 315 func newConnWithTimeout(conn net.Conn, timeout time.Duration, nowFn clock.NowFn) connWithTimeout { 316 return connWithTimeout{ 317 Conn: conn, 318 timeout: timeout, 319 nowFn: nowFn, 320 } 321 } 322 323 func (conn connWithTimeout) Write(p []byte) (int, error) { 324 if conn.timeout > 0 { 325 conn.SetWriteDeadline(conn.nowFn().Add(conn.timeout)) 326 } 327 return conn.Conn.Write(p) 328 }