k8s与监控--改造telegraf的buffer实现

改造telegraf的buffer实现

前言

最近在使用telegraf的场景中,要求数据在程序意外终止的时候不丢失。按照telegraf最初的原始实现,在running_output内部维护了两个buffer,分别是metrics和failMetrics。这两个buffer是基于go中channel实现的。因为没有持久化机制,在乎外退出的时候,存在丢失数据的风险。因此这篇文章主要讲述以前telegraf保证数据安全的一些措施和咱们对代码的一些优化。git

telegraf关于数据安全的处理办法

关于两个buffer,定义在running_output.go的struct中。github

// RunningOutput contains the output configuration
type RunningOutput struct {
    Name              string
    Output            telegraf.Output
    Config            *OutputConfig
    MetricBufferLimit int
    MetricBatchSize   int

    MetricsFiltered selfstat.Stat
    MetricsWritten  selfstat.Stat
    BufferSize      selfstat.Stat
    BufferLimit     selfstat.Stat
    WriteTime       selfstat.Stat

    metrics     *buffer.Buffer
    failMetrics *buffer.Buffer

    // Guards against concurrent calls to the Output as described in #3009
    sync.Mutex
}

这个两个buffer的大小提供了配置参数能够设置。redis

metrics:           buffer.NewBuffer(batchSize),
failMetrics:       buffer.NewBuffer(bufferLimit),

顾名思义。metrics存放要发送到指定output的metric,而failMetrics存放发送失败的metric。固然失败的metrics会在telegraf重发机制下再次发送。json

if ro.metrics.Len() == ro.MetricBatchSize {
        batch := ro.metrics.Batch(ro.MetricBatchSize)
        err := ro.write(batch)
        if err != nil {
            ro.failMetrics.Add(batch...)
        }
    }

在向metrics增长metrics的时候,作是否达到批量发送的数量,若是达到就调用发送方法。固然还有定时的解决方案,若是一直没有达到MetricBatchSize,也会在必定时间后发送数据。具体实现代码在agent.go中安全

ticker := time.NewTicker(a.Config.Agent.FlushInterval.Duration)
    semaphore := make(chan struct{}, 1)
    for {
        select {
        case <-shutdown:
            log.Println("I! Hang on, flushing any cached metrics before shutdown")
            // wait for outMetricC to get flushed before flushing outputs
            wg.Wait()
            a.flush()
            return nil
        case <-ticker.C:
            go func() {
                select {
                case semaphore <- struct{}{}:
                    internal.RandomSleep(a.Config.Agent.FlushJitter.Duration, shutdown)
                    a.flush()
                    <-semaphore
                default:
                    // skipping this flush because one is already happening
                    log.Println("W! Skipping a scheduled flush because there is" +
                        " already a flush ongoing.")
                }
            }()

在程序接受到中止信号后,程序会首先flush剩下的数据到output中,而后退出进程。这样能够保证必定的数据安全。网络

基于redis实现buffer的持久化

在持久化机制的选型中,优先实现redis。自己redis性能高,并且具有完善的持久化。
具体的实现架构以下:
图片描述
将原buffer中功能抽象出buffer.go接口。
具体代码:数据结构

package buffer

import (
    "github.com/influxdata/telegraf"
    "github.com/influxdata/telegraf/internal/buffer/memory"
    "github.com/influxdata/telegraf/internal/buffer/redis"
)

const (
    BufferTypeForMemory = "memory"
    BufferTypeForRedis  = "redis"
)

type Buffer interface {
    IsEmpty() bool
    Len() int
    Add(metrics ...telegraf.Metric)
    Batch(batchSize int) []telegraf.Metric
}

func NewBuffer(mod string, size int, key, addr string) Buffer {
    switch mod {
    case BufferTypeForRedis:
        return redis.NewBuffer(size, key, addr)
    default:
        return memory.NewBuffer(size)
    }
}

而后分别内存和redis实现了Buffer接口。
其中NewBuffer至关于一个工厂方法。
固然在后期能够实现基于file和db等buffer实现,来知足不一样的场景和要求。架构

redis实现buffer的要点

因为要知足先进先出的要求,选择了redis的list数据结构。redis中的list是一个字符串list,因此telegraf中metric数据接口要符合序列化的要求。好比属性须要可导出,即public。因此这点须要改动telegraf对于metric struct的定义。另外能够选择json或是msgpack等序列化方式。咱们这边是采用的json序列化的方式。app

结语

改造之后,能够根据本身的需求经过配置文件来决定使用channel或是redis来实现buffer。各有优劣,内存实现的话,性能高,受到的依赖少。而redis这种分布式存储,决定了数据安全,可是性能会有必定的损耗,毕竟有大量的序列化和反序列化以及网络传输,固然依赖也增长了,取决于redis的可靠性,建议redis集群部署。dom

相关文章
相关标签/搜索