go-transport项目解读

111 阅读3分钟
项目实现借鉴了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))
}

go-transport地址