项目实现借鉴了go-stash,也是从kafka读取数据然后写入es
唯一不同的点是在写入es前会插入到指定索引,索引可以自定义生成
代码解读
一 从启动函数入手
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
proc.SetTimeToForceQuit(c.GracePeriod)
group := service.NewServiceGroup()
defer group.Stop()
for _, processor := range c.Clusters {
client, err := elastic.NewClient(
elastic.SetSniff(false),
elastic.SetURL(processor.Output.ElasticSearch.Hosts...),
elastic.SetBasicAuth(processor.Output.ElasticSearch.Username, processor.Output.ElasticSearch.Password),
)
logx.Must(err)
handle := es.NewHandle(client, processor.Output.ElasticSearch.Index, processor.Output.ElasticSearch)
for _, k := range toKqConf(processor.Input.Kafka) {
group.Add(kq.MustNewQueue(k, handle))
}
}
group.Start()
这是go_zero的配置加载的一个方法,直接可以拿过来用
conf.MustLoad(*configFile, &c)
- 在从kafka取数据的过程被打包成了一个服务,为了提升效率,就将多个服务打包成一个服务组,一次启动一个服务组就可以启动多个服务,下面看下如何实现的
返回一个服务组,这个服务组具有start和stop的方法,可以通过start方法启动这个服务组,能明白这个意思就行
group := service.NewServiceGroup()
defer group.Stop()
group.Start()
type (
// Starter is the interface wraps the Start method.
Starter interface {
Start()
}
// Stopper is the interface wraps the Stop method.
Stopper interface {
Stop()
}
// Service is the interface that groups Start and Stop methods.
Service interface {
Starter
Stopper
}
// A ServiceGroup is a group of services.
// Attention: the starting order of the added services is not guaranteed.
ServiceGroup struct {
services []Service
stopOnce func()
}
)
// NewServiceGroup returns a ServiceGroup.
func NewServiceGroup() *ServiceGroup {
sg := new(ServiceGroup)
sg.stopOnce = syncx.Once(sg.doStop)
return sg
}
服务组启动的过程发生了什么?我们从启动一个服务组开始说起
启动服务组的代码如下
// Start starts the ServiceGroup.
// There should not be any logic code after calling this method, because this method is a blocking one.
// Also, quitting this method will close the logx output.
func (sg *ServiceGroup) Start() {
proc.AddShutdownListener(func() {
log.Println("Shutting down...")
sg.stopOnce()
})
sg.doStart()
}
// doStart 方法
func (sg *ServiceGroup) doStart() {
routineGroup := threading.NewRoutineGroup()
for i := range sg.services {
//循环拿到每一个服务
service := sg.services[i]
// RunSafe开启一个安全的go协程
//和使用关键词go开启一个协程不同的是
//RunSafe内部做了recover处理(defer rescue.Recover())
routineGroup.RunSafe(func() {
//启动service
//每个service都继承了Starter的接口
service.Start()
})
}
routineGroup.Wait()
}
从kafka取出数据的方式是借助了go-queue,只需要配置config.yaml,然后传入需要处理的handle函数,这个函数开发者可以自定义其中的数据业务
example:
var c kq.KqConf
conf.MustLoad("./config/config.yaml", &c)
// WithHandle: 具体的处理msg的logic
// 这也是开发者需要根据自己的业务定制化
// 返回的q(kafkaQueues)继承了Start()和Stop()方法
q := kq.MustNewQueue(c, kq.WithHandle(func(k, v string) error {
fmt.Printf("%s=> %s\n", k, v)
return nil
}))
defer q.Stop()
q.Start()
kafkaQueue实现了一下接口
type MessageQueue interface {
Start()
Stop()
}
func (q *kafkaQueue) Start() {
q.startConsumers()
q.startProducers()
q.producerRoutines.Wait()
close(q.channel)
q.consumerRoutines.Wait()
}
- 在实现了Start和Stop功能后,启动后数据如何进行处理
ConsumeHandler是一个接口,只要实现这个接口就可以自定义处理数据的业务逻辑
MustNewQueue(c KqConf, handler ConsumeHandler, opts ...QueueOption) queue.MessageQueue {}
先看下es的handle.go
//初始化一个handle
func NewHandle(client *elastic.Client, index string, c config.EsConf) *InsertDoc {
writer := &InsertDoc{
client: client,
index: index,
}
//批量执行,提升效率
// 执行的条件是1、达到指定的chunk大小 2、刷新的间隔时间
writer.inserter = executors.NewChunkExecutor(writer.execute, executors.WithChunkBytes(c.MaxChunkBytes))
return writer
}
实现了ConsumeHandler接口
func (m *InsertDoc) Consume(_, val string) error {
err := m.CreateIndex(m.index)
if err != nil {
return err
}
var d map[string]interface{}
if err := jsoniter.Unmarshal([]byte(val), &d); err != nil {
return err
}
bs, err := jsoniter.Marshal(d)
if err != nil {
return err
}
return m.write(m.index, string(bs))
}
// 写入批量执行器
func (m *InsertDoc) write(index, val string) error {
return m.inserter.Add(valueWithIndex{
index: index,
val: val,
}, len(val))
}