海量日志收集项目总结(2)logtranfer

海量日志收集项目总结(2)logtranfer

简介

logtranfer的作用是:把日志从kafka消息队列中读出来,发往ES,并连接kibana,做可视化展示。

模块架构为:

mark

项目源码

项目的目录结构

mark

各部分源码

main.go:

package main

import (
   "github.com/wind-zhou/log_transfer/conf"
   "github.com/wind-zhou/log_transfer/es"
   "gopkg.in/ini.v1"
   "fmt"
   "github.com/wind-zhou/log_transfer/kafka"
)

//1.将日志数据取出
   //2.发往ES
var (
   cfg = new(conf.LogTransfer)
)
   func main(){

   	//0. 加载配置文件
      err := ini.MapTo(cfg, "./conf/cfg.ini")
      if err != nil {
         fmt.Println("load ini failed")
         return
      }
      fmt.Println(cfg)


      //初始化es
      err=es.Init(cfg.EsCfg.Address)
      if err != nil {
         fmt.Printf("init es failed err=%v\n",err)
      }

      fmt.Println("init es success")
       //1.初始化kafka
       //1.1 链接kafka
       //1.2 每个分区读数据发往es

   	err=kafka.Init([]string{cfg.KafkaCfg.Address},cfg.KafkaCfg.Topic)
      if err != nil {
         fmt.Printf("init kafka failed err=%v\n",err)
         return
      }
      select {

      }
   }

kafka.go:

package kafka

import (
	"fmt"
	"github.com/Shopify/sarama"
	"github.com/wind-zhou/log_transfer/es"
)


//初始化client 消费者
//从kafka取数据发给es
func Init(address []string,topic string)  error {
	consumer, err := sarama.NewConsumer(address, nil)
	if err != nil {
		fmt.Printf("fail to start consumer, err:%v\n", err)
		return err
	}
	partitionList, err := consumer.Partitions(topic) // 根据topic取到所有的分区
	if err != nil {
		fmt.Printf("fail to get list of partition:err%v\n", err)
		return err
	}
	fmt.Println("分区列表:",partitionList)
	for partition := range partitionList { // 遍历所有的分区
		// 针对每个分区创建一个对应的分区消费者
		pc, err := consumer.ConsumePartition("web_log", int32(partition), sarama.OffsetNewest)
		if err != nil {
			fmt.Printf("failed to start consumer for partition %d,err:%v\n", partition, err)
			return err
		}
		//defer pc.AsyncClose()
		// 异步从每个分区消费信息
		go func(sarama.PartitionConsumer) {
			for msg := range pc.Messages() {
				fmt.Printf("Partition:%d Offset:%d Key:%v Value:%v\n", msg.Partition, msg.Offset, msg.Key, string(msg.Value))
				//发送给es
				ld:=es.LogData{
					Topic: topic,
					Data: string(msg.Value),
				}
				es.SendToEsChan(&ld)//把读取的分区数据发送到一个通道
				if err != nil {
					fmt.Printf("es.SendToEs err=%v\n",err)
					return
				}
			}
		}(pc)
	}
	return  err
}

es.go:

package es

import (
	"context"
	"fmt"
	"github.com/olivere/elastic"
	"strings"

	"time"
)

type LogData struct {
	Topic string `json:"topic"`

	Data string`json:"data"`
}

var (
	client *elastic.Client
	ch =make(  chan *LogData,100000)
)

//初始化es
//准备接受Kafka的数据

func Init(address string)(err error){
	if !strings.HasPrefix(address,"http://"){
		address="http://"+address
	}
	client, err = elastic.NewClient(elastic.SetURL(address))
	if err != nil {
		// Handle error
		return
	}

	fmt.Println("connect to es success")

	go SendToEs()
	return

}


//SendToEs 发送数据到es

func SendToEsChan(msg *LogData) {
	ch <-msg

}

func SendToEs(){
	for{
		select {
			case msg:= <-ch:

				put1, err := client.Index().Index(msg.Topic).Type("xxx").BodyJson(msg).Do(context.Background())
				if err != nil {
					// Handle error
					fmt.Println(err)

				}
				fmt.Printf("Indexed user %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)
		default:
			time.Sleep(time.Second)
		}
	}

}

cfg.go:

package conf

type LogTransfer struct{
	KafkaCfg`ini:"kafka"`
	EsCfg`ini:"es"`
}
type KafkaCfg struct {
	Address string`ini:"address"`
	Topic string`ini:"topic"`
}

type EsCfg struct {
	Address string`ini:"address"`
}

cfg.ini:

[kafka]
address=192.168.43.99:9092
topic=web_log

[es]
address=127.0.0.1:9200

项目分析

这部分的主角其实只有两个:kafka和ES,kibana只是个工具人(因为不需要用go语言操作kibana)。

  • kafka部分需要写一个kafka的消费者,负责从消息队列中按照特定的topic取出日志信息。
  • ES部分需要编写代码,将取出的数据写入ES。

各部分操作都需要初始化,kafka需要知道主机的地址,以及所取日志的topic;ES需要知道主机的地址,因此这些参数可以写入配置文件中,也就是cfg.ini, cfg.go里面声明一些结构体,用于之后配置文件解析。

注:

上面给的源码中,kafka消费者所取的日志topic已经写死到了配置文件中,也就是kafka消费者,只拉取web_log主题的日志,如果想要拉取里面所有的topic日志,可以仿照前面的logagent,加一个etcd模块,用于存储之前存入的topic。

main.go中解析配置文件代码:

mark

kafka消费者:

Kafka消费者的逻辑为:

  1. 传入kafka主机地址,连接上kafka,创建一个消费者的
  2. 传入日志的topic,获取到搞topic所有的分区
  3. 遍历所有的分区,并在每个分区创建一个goroutine,分别去读取日志
  4. 每个goroutine内,将读取的信息发往ES

ES操作:

ES部分的逻辑为:

  1. 传入ES主机的地址,连接ES,并产生一个ES的client
  2. 开启一个goroutine,将数据传入ES的指定的Index,Type。

OK,这部分核心代码就结束了。

一些细节:

  1. 消费者是按topic从kafka读取数据,因此在传入ES时,可以按topic创建Index
  2. 消费者读取的数据为字符串或字符切片。先构造成一个结构体,发送到ES时,会将其转换成 json格式。
  3. main程序最后加了个select{},用于等待,否则主程序会退出

这里还做了一些优化,如kafka拿到数据后,先将其送入一个通道,ES端开启的goroutine是在通道中拿数据并发送。

项目调试

测试环境搭建

  1. 开启kafka

    1. 开启zookeeper :.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties
    2. 开启kafka : .\bin\windows\kafka-server-start.bat .\config\server.properties
  2. 开启etcd

  3. 开启es : bin\elasticsearch.bat

  4. 开启kaibana,连接到ES

    开启kibana步骤:

    1. 修改kibana配置文件kibana.yaml中的内容

    设置ES主机地址:

    mark

    设置中文编码:

    mark

    1. 开启kibana : bin\kibana.bat

    mark

    1. 访问kibana : http://127.0.0.1:5601

    mark

  5. 开启前面写的logagent

    mark

  6. 开启logtranfer

mark

调试

  1. 打开web_log主题日志所在的文件nginx.log , 在里面写入数据

mark

  1. logagent端返回的结果

mark

  1. logtranfer端 返回结果

mark

可见收到的web_log日志被存储到了ES的Index=web_log,Type=xxx中。

  1. 在kibana中索引

mark

后记

到此,这个项目完结,第一次写实战博客,写的很烂,但作为一个开始吧!

本次项目简单学了一些新东西:

ES,kafka,zookeeper,kinbana等。

另外,这个项目是看网上盗版视频缩写,非常的粗糙,logagent和logtranfer两部分并没有很好的衔接起来,两个模块没有整合到在一起,例如logtranfer只读取了logagent中web-log主题日志,没有读取其所有topic日志。

这可以进行优化,大致的思路是,在logagent端每次读取etcd新变化后,记录下该配置,并传递给logtranfer,然后logtranfer端根据此配置去开启或关闭goroutine。

posted @ 2020-05-20 20:47  wind-zhou  Views(302)  Comments(0Edit  收藏  举报