解决的问题
kafka单个消费者多线程顺序消费
组成
Config
// Config the config is the base configuration for initiating a new group.
type Config struct {
// Size merge size
Size int
// Num merge goroutine num
Num int
// Ticker duration of submit merges when no new message
Ticker time.Duration
// Chan size of merge chan and done chan
Chan int
}
配置信息: Size: 每一个 worker 接收的 chan 队列容量, 默认为1024
Num: worker 的数量
Ticker: 定时器,定时提交已处理完的消息, 默认5s
Chan: 已完成的或已合并的chan 容量,默认与Size一样大小
message
type message struct {
next *message
data *databus.Message
object interface{}
done bool
}
消费的消息,为kafka接收到消息的一层封装
next: 下一条消息, 在merge的时候会将新的消息放到他的后面,并置为last
data: kafka中的消息
object: 结构体定义,用于umarsal msg
done: 是否已处理
Group
// Group group.
type Group struct {
c *Config
head, last *message
state int
mu sync.Mutex
mc []chan *message // merge chan
dc chan []*message // done chan
qc chan struct{} // quit chan
msg <-chan *databus.Message
New func(msg *databus.Message) (interface{}, error)
Split func(msg *databus.Message, data interface{}) int
Do func(msgs []interface{})
pool *sync.Pool
}
可以理解为一个消费者组
c: 上面的Config配置,用于初始化
head, last: 已处理到的消息,用于最后的commit
state: group 的状态,开启,关闭
mc: worker num 个chan, 每个chan为Size的容量,用于给worker发送消息
dc: 已经处理完的消息队列
qc: 用于通知各个goruntine退出
msg: 主要的用于kafka的chan
New: func 用于返回实现了message interface的对象
Split: 再次 sharding 的函数
Do: func 具体处理消息的逻辑
pool: message pool
核心方法
consumeproc 预处理
func (g *Group) consumeproc() {
var (
ok bool
err error
msg *databus.Message
)
for {
select {
case <-g.qc:
return
case msg, ok = <-g.msg:
if !ok {
g.Close()
return
}
}
// marked head to first commit
m := g.message()
m.data = msg
if m.object, err = g.New(msg); err != nil {
g.freeMessage(m)
continue
}
g.mu.Lock()
if g.head == nil {
g.head = m
g.last = m
} else {
g.last.next = m
g.last = m
}
g.mu.Unlock()
g.mc[g.Split(m.data, m.object)%g.c.Num] <- m
}
}
此处并不会开始处理消息的业务逻辑,而是去更新group的处理链表,将kafka的消息进行封装后,推送到各个worker的chan中
mergeproc 真实的处理消息业务
func (g *Group) mergeproc(mc <-chan *message) {
ticker := time.NewTicker(time.Duration(g.c.Ticker))
msgs := make([]interface{}, 0, g.c.Size)
marks := make([]*message, 0, g.c.Size)
for {
select {
case <-g.qc:
return
case msg := <-mc:
msgs = append(msgs, msg.object)
marks = append(marks, msg)
if len(msgs) < g.c.Size {
continue
}
case <-ticker.C:
}
if len(msgs) > 0 {
g.Do(msgs)
msgs = make([]interface{}, 0, g.c.Size)
}
if len(marks) > 0 {
g.dc <- marks
marks = make([]*message, 0, g.c.Size)
}
}
}
此处起了一个定时器,5s 去处理消息的业务逻辑,并推送到done队列中
如果堆积的消息达到阀值,则会开始处理消息的业务,并推送到done队列中
commit 提交最小offset
func (g *Group) commitproc() {
commits := make(map[int32]*databus.Message)
for {
select {
case <-g.qc:
return
case done := <-g.dc:
// merge partitions to commit offset
for _, d := range done {
d.done = true
}
g.mu.Lock()
for g.head != nil && g.head.done {
cur := g.head
commits[cur.data.Partition] = cur.data
g.head = cur.next
g.freeMessage(cur)
}
g.mu.Unlock()
for k, m := range commits {
m.Commit()
delete(commits, k)
}
}
}
}
从done队列中取出message, 并将其标记为done
遍历group的消息链表,并且为done的记录,直到最后一条,这一条即为最小的offset, 进行commit提交