github.com/m3db/m3@v1.5.0/src/msg/producer/writer/consumer_service_writer.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 writer 22 23 import ( 24 "errors" 25 "fmt" 26 "sync" 27 "time" 28 29 "github.com/m3db/m3/src/cluster/placement" 30 "github.com/m3db/m3/src/msg/producer" 31 "github.com/m3db/m3/src/msg/topic" 32 "github.com/m3db/m3/src/x/watch" 33 34 "github.com/uber-go/tally" 35 "go.uber.org/zap" 36 ) 37 38 var ( 39 acceptAllFilter = producer.FilterFunc( 40 func(m producer.Message) bool { 41 return true 42 }, 43 ) 44 45 errUnknownConsumptionType = errors.New("unknown consumption type") 46 ) 47 48 type initType int 49 50 const ( 51 // failOnError will fail the initialization when any error is encountered. 52 failOnError initType = iota 53 54 // allowInitValueError will not fail the initialization when the initial 55 // value could not be obtained within timeout. 56 // This could be used to initialize a new consumer service writer during 57 // runtime so it allows the consumer service writer to continue waiting 58 // for the placement update in the background. 59 allowInitValueError 60 ) 61 62 type consumerServiceWriter interface { 63 // Write writes a message. 64 Write(rm *producer.RefCountedMessage) 65 66 // Init will initialize the consumer service writer. 67 Init(initType) error 68 69 // Close closes the writer and the background watch thread. 70 Close() 71 72 // SetMessageTTLNanos sets the message ttl nanoseconds. 73 SetMessageTTLNanos(value int64) 74 75 // RegisterFilter registers a filter for the consumer service. 76 RegisterFilter(fn producer.FilterFunc) 77 78 // UnregisterFilter unregisters the filter for the consumer service. 79 UnregisterFilter() 80 } 81 82 type consumerServiceWriterMetrics struct { 83 placementError tally.Counter 84 placementUpdate tally.Counter 85 filterAccepted tally.Counter 86 filterNotAccepted tally.Counter 87 queueSize tally.Gauge 88 } 89 90 func newConsumerServiceWriterMetrics(scope tally.Scope) consumerServiceWriterMetrics { 91 return consumerServiceWriterMetrics{ 92 placementUpdate: scope.Counter("placement-update"), 93 placementError: scope.Counter("placement-error"), 94 filterAccepted: scope.Counter("filter-accepted"), 95 filterNotAccepted: scope.Counter("filter-not-accepted"), 96 queueSize: scope.Gauge("queue-size"), 97 } 98 } 99 100 type consumerServiceWriterImpl struct { 101 sync.Mutex 102 103 cs topic.ConsumerService 104 ps placement.Service 105 shardWriters []shardWriter 106 opts Options 107 logger *zap.Logger 108 109 value watch.Value 110 dataFilter producer.FilterFunc 111 router ackRouter 112 consumerWriters map[string]consumerWriter 113 closed bool 114 doneCh chan struct{} 115 wg sync.WaitGroup 116 m consumerServiceWriterMetrics 117 cm consumerWriterMetrics 118 119 processFn watch.ProcessFn 120 } 121 122 func newConsumerServiceWriter( 123 cs topic.ConsumerService, 124 numShards uint32, 125 opts Options, 126 ) (consumerServiceWriter, error) { 127 ps, err := opts.ServiceDiscovery(). 128 PlacementService(cs.ServiceID(), opts.PlacementOptions()) 129 if err != nil { 130 return nil, err 131 } 132 ct := cs.ConsumptionType() 133 if ct == topic.Unknown { 134 return nil, errUnknownConsumptionType 135 } 136 router := newAckRouter(int(numShards)) 137 w := &consumerServiceWriterImpl{ 138 cs: cs, 139 ps: ps, 140 shardWriters: initShardWriters(router, ct, numShards, opts), 141 opts: opts, 142 logger: opts.InstrumentOptions().Logger(), 143 dataFilter: acceptAllFilter, 144 router: router, 145 consumerWriters: make(map[string]consumerWriter), 146 closed: false, 147 doneCh: make(chan struct{}), 148 m: newConsumerServiceWriterMetrics(opts.InstrumentOptions().MetricsScope()), 149 cm: newConsumerWriterMetrics(opts.InstrumentOptions().MetricsScope()), 150 } 151 w.processFn = w.process 152 return w, nil 153 } 154 155 func initShardWriters( 156 router ackRouter, 157 ct topic.ConsumptionType, 158 numberOfShards uint32, 159 opts Options, 160 ) []shardWriter { 161 var ( 162 sws = make([]shardWriter, numberOfShards) 163 m = newMessageWriterMetrics( 164 opts.InstrumentOptions().MetricsScope(), 165 opts.InstrumentOptions().TimerOptions(), 166 opts.WithoutConsumerScope(), 167 ) 168 mPool messagePool 169 ) 170 if opts.MessagePoolOptions() != nil { 171 mPool = newMessagePool(opts.MessagePoolOptions()) 172 mPool.Init() 173 } 174 for i := range sws { 175 switch ct { 176 case topic.Shared: 177 sws[i] = newSharedShardWriter(uint32(i), router, mPool, opts, m) 178 case topic.Replicated: 179 sws[i] = newReplicatedShardWriter(uint32(i), numberOfShards, router, mPool, opts, m) 180 } 181 } 182 return sws 183 } 184 185 func (w *consumerServiceWriterImpl) Write(rm *producer.RefCountedMessage) { 186 if rm.Accept(w.dataFilter) { 187 w.shardWriters[rm.Shard()].Write(rm) 188 w.m.filterAccepted.Inc(1) 189 return 190 } 191 // It is not an error if the message does not pass the filter. 192 w.m.filterNotAccepted.Inc(1) 193 } 194 195 func (w *consumerServiceWriterImpl) Init(t initType) error { 196 w.wg.Add(1) 197 go func() { 198 w.reportMetrics() 199 w.wg.Done() 200 }() 201 202 updatableFn := func() (watch.Updatable, error) { 203 return w.ps.Watch() 204 } 205 getFn := func(updatable watch.Updatable) (interface{}, error) { 206 update, err := updatable.(placement.Watch).Get() 207 if err != nil { 208 w.m.placementError.Inc(1) 209 w.logger.Error("invalid placement update from kv", zap.Error(err)) 210 return nil, err 211 } 212 w.m.placementUpdate.Inc(1) 213 return update, nil 214 } 215 vOptions := watch.NewOptions(). 216 SetInitWatchTimeout(w.opts.PlacementWatchInitTimeout()). 217 SetInstrumentOptions(w.opts.InstrumentOptions()). 218 SetNewUpdatableFn(updatableFn). 219 SetGetUpdateFn(getFn). 220 SetProcessFn(w.processFn). 221 SetKey(w.opts.TopicName()) 222 w.value = watch.NewValue(vOptions) 223 err := w.value.Watch() 224 if err == nil { 225 return nil 226 } 227 if t == allowInitValueError { 228 if _, ok := err.(watch.InitValueError); ok { 229 w.logger.Warn("invalid placement update, continue to watch for placement updates", 230 zap.Error(err)) 231 return nil 232 } 233 } 234 return fmt.Errorf("consumer service writer init error: %v", err) 235 } 236 237 func (w *consumerServiceWriterImpl) process(update interface{}) error { 238 var ( 239 p = update.(placement.Placement) 240 isSharded = p.IsSharded() 241 ) 242 // Non sharded placement is only allowed for Shared consumption type. 243 if w.cs.ConsumptionType() == topic.Replicated && !isSharded { 244 return fmt.Errorf("non-sharded placement for replicated consumer %s", w.cs.String()) 245 } 246 // NB(cw): Lock can be removed as w.consumerWriters is only accessed in this thread. 247 w.Lock() 248 newConsumerWriters, tobeDeleted := w.diffPlacementWithLock(p) 249 for i, sw := range w.shardWriters { 250 if isSharded { 251 sw.UpdateInstances(p.InstancesForShard(uint32(i)), newConsumerWriters) 252 continue 253 } 254 sw.UpdateInstances(p.Instances(), newConsumerWriters) 255 } 256 oldConsumerWriters := w.consumerWriters 257 w.consumerWriters = newConsumerWriters 258 w.Unlock() 259 go func() { 260 for _, addr := range tobeDeleted { 261 cw, ok := oldConsumerWriters[addr] 262 if ok { 263 cw.Close() 264 } 265 } 266 }() 267 return nil 268 } 269 270 func (w *consumerServiceWriterImpl) diffPlacementWithLock(newPlacement placement.Placement) (map[string]consumerWriter, []string) { 271 var ( 272 newInstances = newPlacement.Instances() 273 newConsumerWriters = make(map[string]consumerWriter, len(newInstances)) 274 toBeDeleted []string 275 ) 276 for _, instance := range newInstances { 277 id := instance.Endpoint() 278 cw, ok := w.consumerWriters[id] 279 if ok { 280 newConsumerWriters[id] = cw 281 continue 282 } 283 cw = newConsumerWriter(instance.Endpoint(), w.router, w.opts, w.cm) 284 cw.Init() 285 newConsumerWriters[id] = cw 286 } 287 288 for id := range w.consumerWriters { 289 if _, ok := newConsumerWriters[id]; !ok { 290 toBeDeleted = append(toBeDeleted, id) 291 } 292 } 293 return newConsumerWriters, toBeDeleted 294 } 295 296 func (w *consumerServiceWriterImpl) Close() { 297 w.Lock() 298 if w.closed { 299 w.Unlock() 300 return 301 } 302 w.closed = true 303 w.Unlock() 304 305 w.logger.Info("closing consumer service writer", zap.String("writer", w.cs.String())) 306 close(w.doneCh) 307 // Blocks until all messages consuemd. 308 var shardWriterWG sync.WaitGroup 309 for _, sw := range w.shardWriters { 310 sw := sw 311 shardWriterWG.Add(1) 312 go func() { 313 sw.Close() 314 shardWriterWG.Done() 315 }() 316 } 317 shardWriterWG.Wait() 318 319 w.value.Unwatch() 320 for _, cw := range w.consumerWriters { 321 cw.Close() 322 } 323 w.wg.Wait() 324 w.logger.Info("closed consumer service writer", zap.String("writer", w.cs.String())) 325 } 326 327 func (w *consumerServiceWriterImpl) SetMessageTTLNanos(value int64) { 328 for _, sw := range w.shardWriters { 329 sw.SetMessageTTLNanos(value) 330 } 331 } 332 333 func (w *consumerServiceWriterImpl) RegisterFilter(filter producer.FilterFunc) { 334 w.Lock() 335 w.dataFilter = filter 336 w.Unlock() 337 } 338 339 func (w *consumerServiceWriterImpl) UnregisterFilter() { 340 w.Lock() 341 w.dataFilter = acceptAllFilter 342 w.Unlock() 343 } 344 345 func (w *consumerServiceWriterImpl) reportMetrics() { 346 t := time.NewTicker(w.opts.InstrumentOptions().ReportInterval()) 347 defer t.Stop() 348 349 for { 350 select { 351 case <-w.doneCh: 352 return 353 case <-t.C: 354 var l int 355 for _, sw := range w.shardWriters { 356 l += sw.QueueSize() 357 } 358 w.m.queueSize.Update(float64(l)) 359 } 360 } 361 }