You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
3.3 KiB
177 lines
3.3 KiB
2 years ago
|
package batcher
|
||
|
|
||
|
import (
|
||
|
"Open_IM/pkg/common/log"
|
||
|
"context"
|
||
|
"errors"
|
||
|
"hash/crc32"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrorNotSetFunction = errors.New("not set do function")
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
DefaultSize = 100
|
||
|
DefaultBuffer = 100
|
||
|
DefaultWorker = 5
|
||
|
DefaultInterval = time.Second
|
||
|
)
|
||
|
|
||
|
type DoFuntion func(ctx context.Context, val map[string][]interface{})
|
||
|
type Option func(c *Config)
|
||
|
type Config struct {
|
||
|
size int //Number of message aggregations
|
||
|
buffer int //The number of caches running in a single coroutine
|
||
|
worker int //Number of coroutines processed in parallel
|
||
|
interval time.Duration //Time of message aggregations
|
||
|
}
|
||
|
|
||
|
func newDefaultConfig() *Config {
|
||
|
return &Config{
|
||
|
size: DefaultSize,
|
||
|
buffer: DefaultBuffer,
|
||
|
worker: DefaultWorker,
|
||
|
interval: DefaultInterval,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type Batcher struct {
|
||
|
config Config
|
||
|
Do func(ctx context.Context, val map[string][]interface{})
|
||
|
Sharding func(key string) int
|
||
|
chans []chan *msg
|
||
|
wait sync.WaitGroup
|
||
|
}
|
||
|
type msg struct {
|
||
|
key string
|
||
|
val interface{}
|
||
|
}
|
||
|
|
||
|
func NewBatcher(fn DoFuntion, opts ...Option) *Batcher {
|
||
|
b := &Batcher{}
|
||
|
b.Do = fn
|
||
|
config := newDefaultConfig()
|
||
|
for _, o := range opts {
|
||
|
o(config)
|
||
|
}
|
||
|
b.chans = make([]chan *msg, b.config.worker)
|
||
|
for i := 0; i < b.config.worker; i++ {
|
||
|
b.chans[i] = make(chan *msg, b.config.buffer)
|
||
|
}
|
||
|
return b
|
||
|
}
|
||
|
func WithSize(s int) Option {
|
||
|
return func(c *Config) {
|
||
|
c.size = s
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func WithBuffer(b int) Option {
|
||
|
return func(c *Config) {
|
||
|
c.buffer = b
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func WithWorker(w int) Option {
|
||
|
return func(c *Config) {
|
||
|
c.worker = w
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func WithInterval(i time.Duration) Option {
|
||
|
return func(c *Config) {
|
||
|
c.interval = i
|
||
|
}
|
||
|
}
|
||
|
func (b *Batcher) Start() error {
|
||
|
if b.Do == nil {
|
||
|
return ErrorNotSetFunction
|
||
|
}
|
||
|
if b.Sharding == nil {
|
||
|
b.Sharding = func(key string) int {
|
||
|
hasCode := int(crc32.ChecksumIEEE([]byte(key)))
|
||
|
return hasCode % b.config.worker
|
||
|
}
|
||
|
}
|
||
|
b.wait.Add(len(b.chans))
|
||
|
for i, ch := range b.chans {
|
||
|
go b.merge(i, ch)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (b *Batcher) Add(key string, val interface{}) error {
|
||
|
ch, msg := b.add(key, val)
|
||
|
select {
|
||
|
case ch <- msg:
|
||
|
default:
|
||
|
return ErrFull
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (b *Batcher) add(key string, val interface{}) (chan *msg, *msg) {
|
||
|
sharding := b.Sharding(key) % b.opts.worker
|
||
|
ch := b.chans[sharding]
|
||
|
msg := &msg{key: key, val: val}
|
||
|
return ch, msg
|
||
|
}
|
||
|
|
||
|
func (b *Batcher) merge(idx int, ch <-chan *msg) {
|
||
|
defer b.wait.Done()
|
||
|
|
||
|
var (
|
||
|
msg *msg
|
||
|
count int
|
||
|
closed bool
|
||
|
lastTicker = true
|
||
|
interval = b.opts.interval
|
||
|
vals = make(map[string][]interface{}, b.opts.size)
|
||
|
)
|
||
|
if idx > 0 {
|
||
|
interval = time.Duration(int64(idx) * (int64(b.opts.interval) / int64(b.opts.worker)))
|
||
|
}
|
||
|
ticker := time.NewTicker(interval)
|
||
|
for {
|
||
|
select {
|
||
|
case msg = <-ch:
|
||
|
if msg == nil {
|
||
|
closed = true
|
||
|
break
|
||
|
}
|
||
|
count++
|
||
|
vals[msg.key] = append(vals[msg.key], msg.val)
|
||
|
if count >= b.opts.size {
|
||
|
break
|
||
|
}
|
||
|
continue
|
||
|
case <-ticker.C:
|
||
|
if lastTicker {
|
||
|
ticker.Stop()
|
||
|
ticker = time.NewTicker(b.opts.interval)
|
||
|
lastTicker = false
|
||
|
}
|
||
|
}
|
||
|
if len(vals) > 0 {
|
||
|
ctx := context.Background()
|
||
|
b.Do(ctx, vals)
|
||
|
vals = make(map[string][]interface{}, b.opts.size)
|
||
|
count = 0
|
||
|
}
|
||
|
if closed {
|
||
|
ticker.Stop()
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (b *Batcher) Close() {
|
||
|
for _, ch := range b.chans {
|
||
|
ch <- nil
|
||
|
}
|
||
|
b.wait.Wait()
|
||
|
}
|