0x0 需求

  消费Kafka的日志并写入ElasticSearch供查询

0x1 依赖库

golang版Kafka客户端 https://github.com/Shopify/sarama

golang版ElasticSearch客户端  https://github.com/elastic/go-elasticsearch

0x2 实现

总共分3部分

1、Kafka消费者

// LogJson json格式
type LogJson struct {
Tag string `json:"tag"`
Level string `json:"level"`
File string `json:"file"`
Time time.Time `json:"@timestamp"`
Message string `json:"message"`
} type taskProcessor interface {
AddTask(key string, val []byte)
} // MyConsumer 可关闭的带任务处理器的消费者
type MyConsumer struct {
processor taskProcessor
ctx context.Context
} // NewMyConsumer 构造
func NewMyConsumer(p taskProcessor, ctx context.Context) *MyConsumer {
c := &MyConsumer{
processor: p,
ctx: ctx,
} return c
} // Setup 启动
func (consumer *MyConsumer) Setup(s sarama.ConsumerGroupSession) error {
log.Printf("[main] consumer.Setup memberID=[%s]", s.MemberID())
return nil
} // Cleanup 当退出时
func (consumer *MyConsumer) Cleanup(s sarama.ConsumerGroupSession) error {
log.Printf("[main] consumer.Cleanup memberID=[%s]", s.MemberID())
return nil
} // ConsumeClaim 消费日志
func (consumer *MyConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { for {
select {
case message, ok := <-claim.Messages():
if !ok {
return nil
}
js := &LogJson{}
if err := json.Unmarshal(message.Value, js); nil != err {
fmt.Fprintf(os.Stderr, "[MyConsumer] ConsumeClaim json.Unmarshal err=[%s] topic=[%s] key=[%s] val=[%s]\n", err.Error(), message.Topic, message.Key, string(message.Value))
} else {
index := fmt.Sprintf("%s-%s", message.Topic, js.Time.Format("2006.01.02"))
consumer.processor.AddTask(index, message.Value)
session.MarkMessage(message, "")
} case <-consumer.ctx.Done():
return nil
}
} return nil
}

2、插入ElasticSearch的Worker

package elastic_worker

import (
"context"
"encoding/json"
"fmt"
"log"
"runtime"
"sync"
"time" "github.com/olivere/elastic"
) // Config 配置
type Config struct {
MaxMessage int `xml:"max_msg"` // 最大缓冲
WorkerNum int `xml:"worker_number"` // 线程个数
BatchSize int `xml:"batch_size"` // 每个批次最大条数
TickTime int `xml:"tick_millisecond"` // 处理频率
} type task struct {
key string
val []byte
} // Worker 消息处理器
type Worker struct {
msgQ chan *task
client *elastic.Client
wg sync.WaitGroup
config *Config
} // NewWorker 构造
func NewWorker(client *elastic.Client, cfg *Config) *Worker {
w := &Worker{
client: client,
config: cfg,
msgQ: make(chan *task, cfg.MaxMessage),
} return w
} // Run 开工
func (w *Worker) Run(ctx context.Context) { // 线程数
thread := w.config.WorkerNum
if thread <= {
thread = runtime.NumCPU()
} // ticker
tickTime := time.Duration(w.config.TickTime) * time.Millisecond
if tickTime <= {
tickTime = time.Duration() * time.Millisecond
} // 启动
for i := ; i < thread; i++ {
w.wg.Add()
time.Sleep(tickTime / time.Duration(thread))
go func(idx int) { // 构造一个service,server可以反复使用
service := w.client.Bulk()
service.Refresh("wait_for")
defer service.Reset() log.Printf("[elastic_worker] worker[%d] start", idx)
defer w.wg.Done() // ticker
ticker := time.NewTicker(tickTime)
defer ticker.Stop() LOOP:
for {
select {
case <-ctx.Done():
log.Printf("[elastic_worker] worker[%d] is quiting", idx)
// 要把通道里的全部执行完才能退出
for {
if num := w.process(service); num > {
log.Printf("[elastic_worker] worker[%d] process batch [%d] when quiting", idx, num)
} else {
break LOOP
}
time.Sleep(tickTime)
} case <-ticker.C:
if num := w.process(service); num > {
log.Printf("[elastic_worker] worker[%d] process batch [%d] ", idx, num)
}
}
} log.Printf("[elastic_worker] worker[%d] stop", idx)
}(i)
}
} // AddTask 添加任务,goroutine safe
func (w *Worker) AddTask(key string, val []byte) {
t := &task{
key: key,
val: val,
}
w.msgQ <- t
} // process 处理任务
func (w *Worker) process(service *elastic.BulkService) int {
//service.Reset() // 每个批次最多w.config.BatchSize个
LOOP:
for i := ; i < w.config.BatchSize; i++ {
// 有任务就加到这个批次,没任务就退出
select {
case m := <-w.msgQ:
req := elastic.NewBulkIndexRequest().Index(m.key).Type("doc").Doc(json.RawMessage(m.val))
service.Add(req)
default:
break LOOP
}
} total := service.NumberOfActions()
if total > {
if resp, err := service.Do(context.Background()); nil != err {
panic(err)
} else {
if resp.Errors {
for _, v := range resp.Failed() {
fmt.Println("service.Do failed", v)
}
panic("resp.Errors")
}
}
} return total
} // Close 关闭 需要外面的context关闭,和等待msgQ任务被执行完毕
func (w *Worker) Close() {
w.wg.Wait()
if n := len(w.msgQ); n > {
log.Printf("[elastic_worker] worker Close remain msg[%d]", n)
}
}

3、main.go

package main

import (
"context"
"encoding/xml"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time" "consumer"
"elastic_worker" "github.com/Shopify/sarama"
"github.com/olivere/elastic"
) // Consumer Consumer配置
type ConsumerConfig struct {
Topic []string `xml:"topic"`
Broker string `xml:"broker"`
Partition int32 `xml:"partition"`
Replication int16 `xml:"replication"`
Group string `xml:"group"`
Version string `xml:"version"`
} // Config 配置
type Config struct {
Consumer ConsumerConfig `xml:"consumer"`
ElasticURL string `xml:"elastic_url"`
Filters []string `xml:"filter"`
Worker elastic_worker.Config `xml:"elastic_worker"`
} var (
configFile = "" // 配置路径
initTopic = false
listTopic = false
delTopic = ""
cfg = &Config{}
web = ""
) func init() {
flag.StringVar(&configFile, "config", "cfg.xml", "config file ")
flag.BoolVar(&initTopic, "init", initTopic, "create topic")
flag.BoolVar(&listTopic, "list", listTopic, "list topic")
flag.StringVar(&delTopic, "del", delTopic, "delete topic")
} var (
elasticClient *elastic.Client
) func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) defer time.Sleep(time.Second) // 获取host名字
hostName, err := os.Hostname()
if nil != err {
hostName = "[beats]"
} // 加载配置
if contents, err := ioutil.ReadFile(configFile); err != nil {
panic(err)
} else {
if err = xml.Unmarshal(contents, cfg); err != nil {
panic(err)
}
} // sarama的logger
sarama.Logger = log.New(os.Stdout, fmt.Sprintf("[%s]", hostName), log.LstdFlags) // 指定kafka版本,一定要支持kafka集群
version, err := sarama.ParseKafkaVersion(cfg.Consumer.Version)
if err != nil {
panic(err)
}
config := sarama.NewConfig()
config.Version = version
config.Consumer.Offsets.Initial = sarama.OffsetOldest
config.ClientID = hostName // 工具
if tool(cfg, config) {
return
} else {
initTopic = true
tool(cfg, config)
} // 启动elastic客户端
urls := strings.Split(cfg.ElasticURL, ",")
if cli, err := elastic.NewClient(elastic.SetURL(urls...)); err != nil {
panic(err)
} else {
elasticClient = cli
// ping检查
if ret, _, err := elasticClient.Ping(urls[]).Do(context.Background()); nil != err { panic(err)
} else {
log.Printf("elasticClient.Ping %+v", ret)
} defer elasticClient.Stop()
} // ctx
ctx, cancel := context.WithCancel(context.Background()) // Worker
worker := elastic_worker.NewWorker(elasticClient, &cfg.Worker)
worker.Run(ctx)
defer worker.Close() // kafka consumer client
kafkaClient, err := sarama.NewConsumerGroup(strings.Split(cfg.Consumer.Broker, ","), cfg.Consumer.Group, config)
if err != nil {
panic(err)
} consumer := consumer.NewMyConsumer(worker, ctx)
go func() {
for {
select {
case <-ctx.Done():
return
default:
err := kafkaClient.Consume(ctx, cfg.Consumer.Topic, consumer)
if err != nil {
log.Printf("[main] client.Consume error=[%s]", err.Error())
time.Sleep(time.Second)
}
}
}
}() // os signal
sigterm := make(chan os.Signal, )
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM) //time.Sleep(time.Second * 4)
sig := <-sigterm
log.Printf("[main] os sig=[%v]", sig) cancel()
log.Printf("[main] cancel")
if err := kafkaClient.Close(); nil != err {
log.Printf("[main] kafkaClient close error=[%s]", err.Error())
} log.Printf("[main] beats quit")
} func tool(cfg *Config, config *sarama.Config) bool {
if initTopic || listTopic || len(delTopic) > {
ca, err := sarama.NewClusterAdmin(strings.Split(cfg.Consumer.Broker, ","), config)
if nil != err {
panic(err)
} if len(delTopic) > { // 删除Topic
if err := ca.DeleteTopic(delTopic); nil != err {
panic(err)
}
log.Printf("delete ok topic=[%s]\n", delTopic)
} else if initTopic { // 初始化Topic
if detail, err := ca.ListTopics(); nil != err {
panic(err)
} else {
for _, v := range cfg.Consumer.Topic {
if d, ok := detail[v]; ok {
if cfg.Consumer.Partition > d.NumPartitions {
if err := ca.CreatePartitions(v, cfg.Consumer.Partition, nil, false); nil != err {
panic(err)
}
log.Println("alter topic ok", v, cfg.Consumer.Partition)
} } else {
if err := ca.CreateTopic(v, &sarama.TopicDetail{NumPartitions: cfg.Consumer.Partition, ReplicationFactor: cfg.Consumer.Replication}, false); nil != err {
panic(err)
}
log.Println("create topic ok", v)
}
}
}
} // 显示Topic列表
if detail, err := ca.ListTopics(); nil != err {
log.Println("ListTopics error", err)
} else {
for k := range detail {
log.Printf("[%s] %+v", k, detail[k])
}
} if err := ca.Close(); nil != err {
panic(err)
} return true
}
return false
}

0x3 配置文件

<?xml version="1.0" encoding="utf-8"?>
<config> <consumer>
<!-- Kafka cluster -->
<broker>127.0.0.1:</broker> <!-- topic 可以配多个-->
<topic>top1</topic>
<topic>top2</topic> <!-- Kafka 分组 -->
<group>test-group</group> <!-- Kafka 版本 -->
<version>2.2.</version> <!-- partition 个数,开consumer个数不能超过这个 -->
<partition></partition> <!-- 副本因子 -->
<replication></replication>
</consumer> <elastic_url>http://127.0.0.1:9200</elastic_url> <elastic_worker>
<!-- 最大缓冲 这个小点可以防止崩溃导致丢失太多-->
<max_msg></max_msg> <!-- 线程个数 -->
<worker_number></worker_number> <!-- 每个批次最大数量 -->
<batch_size></batch_size> <!-- 处理频率(毫秒) -->
<tick_millisecond></tick_millisecond>
</elastic_worker> </config>

0x4 注意

1、如果你的ElasticSearch集群的配置足够高,你可以修改配置文件里的<worker_number>1</worker_number>给Worker开多协程,否则还是单协程性能更高一些。

2、可以适当调整<batch_size>1024</batch_size>每个批次的数量来提升写入性能。

3、如果报这个错误  EsRejectedExcutionException,说明ES性能扛不住了,需要提升配置,降低写入量。

[Golang] 消费Kafka的日志提交到ElasticSearch的更多相关文章

  1. kafka日志同步至elasticsearch和kibana展示

    kafka日志同步至elasticsearch和kibana展示 一 kafka consumer准备 前面的章节进行了分布式job的自动计算的概念讲解以及实践.上次分布式日志说过日志写进kafka, ...

  2. JavaWeb项目架构之Kafka分布式日志队列

    架构.分布式.日志队列,标题自己都看着唬人,其实就是一个日志收集的功能,只不过中间加了一个Kafka做消息队列罢了. kafka介绍 Kafka是由Apache软件基金会开发的一个开源流处理平台,由S ...

  3. storm消费kafka实现实时计算

    大致架构 * 每个应用实例部署一个日志agent * agent实时将日志发送到kafka * storm实时计算日志 * storm计算结果保存到hbase storm消费kafka 创建实时计算项 ...

  4. Spark streaming消费Kafka的正确姿势

    前言 在游戏项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark streaming从kafka中不 ...

  5. 基于Flume+LOG4J+Kafka的日志采集架构方案

    本文将会介绍如何使用 Flume.log4j.Kafka进行规范的日志采集. Flume 基本概念 Flume是一个完善.强大的日志采集工具,关于它的配置,在网上有很多现成的例子和资料,这里仅做简单说 ...

  6. Spark Streaming消费Kafka Direct方式数据零丢失实现

    使用场景 Spark Streaming实时消费kafka数据的时候,程序停止或者Kafka节点挂掉会导致数据丢失,Spark Streaming也没有设置CheckPoint(据说比较鸡肋,虽然可以 ...

  7. 【转】flume+kafka+zookeeper 日志收集平台的搭建

    from:https://my.oschina.net/jastme/blog/600573 flume+kafka+zookeeper 日志收集平台的搭建 收藏 jastme 发表于 10个月前 阅 ...

  8. ELK+kafka构建日志收集系统

    ELK+kafka构建日志收集系统   原文  http://lx.wxqrcode.com/index.php/post/101.html   背景: 最近线上上了ELK,但是只用了一台Redis在 ...

  9. 消费阿里云日志服务SLS

    此文档只关心消费接入,不关心日志接入,只关心消费如何接入,可直接跳转到[sdk消费接入] SLS简介 日志服务: 日志服务(Log Service,简称 LOG)是针对日志类数据的一站式服务,在阿里巴 ...

随机推荐

  1. php的选择排序

    往前. <?php /** * 选择排序 * 工作原理是每次从待排序的元素中的第一个元素设置为最小值, * 遍历每一个没有排序过的元素,如果元素小于现在的最小值, * 就将这个元素设置成为最小值 ...

  2. python正则表达式(4)--search方法

    1.re.search函数 re.search 扫描整个字符串并返回第一个成功的匹配,如果匹配失败search()就返回None. (1)函数语法: re.search(pattern, string ...

  3. 剑指Offer_编程题-003 - 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList

    如题 (总结) 首节点也存放了值,所以ListNode t = listNode; 直接从头开始遍历即可. 简单题目,但是构建的时候出了点问题,毕竟需要自己简单测测. 掌握链表的构建方法, 还要根据题 ...

  4. 推荐系统(recommender systems):均值归一化(mean normalization)

    均值归一化可以让算法运行得更好. 现在考虑这样一个情况:一个用户对所有的电影都没有评分,即上图所示 的Eve用户.现在我们要学习特征向量(假设n=2) 以及用户5的向量θ(5),因为用户Eve没有对任 ...

  5. python基础知识字符串与元祖

    https://blog.csdn.net/hahaha_yan/article/details/78905495 一.字符串的类型 ##表示字符串: 'i like the world' " ...

  6. springMVC(2)

    SpringMVC_JSR303数据校验 1.需要加入hibernate validator验证框架 2.在springMVC配置文件中添加<mvc:annotation-driven/> ...

  7. Spring AOP中JoinPoint的用法

    Spring JoinPoint的用法 JoinPoint 对象 JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的 ...

  8. IMP self _cmd

    The only way to circumvent dynamic binding is to get the address of a method and call it directly as ...

  9. Daily consumption

    Bill record, standard of living, record every consumption, income, expenditure, manage your own life

  10. 趋势投资tz-proj springcloud (vue redis)

    https://github.com/deadzq/tz-test-1 https://github.com/deadzq/tz-test-api-1 https://github.com/deadz ...