如何远程写入prometheus存储

本文介绍了如何在不使用Pushgateway的情况下,通过Prometheus的远程写入功能(--enable-feature=remote-write-receiver参数)直接将指标数据推送到Prometheus服务器。这种方法减少了中间转发层,提高了数据写入效率。文章详细阐述了数据格式要求、编码压缩过程,并提供了一个使用Go编写的示例代码,演示了如何构建和发送符合规范的POST请求。
导读prometheus一般都是采用pull方式获取数据,但是有一些情况下,不方便配置exporter,就希望能通过push的方式上传指标数据。

 

简介

prometheus一般都是采用pull方式获取数据,但是有一些情况下,不方便配置exporter,就希望能通过push的方式上传指标数据。

1、可以采用pushgateway的方式,推送到pushgateway,然后prometheus通过pushgateway拉取数据。

2、在新版本中增加了一个参数:--enable-feature=remote-write-receiver,允许远程通过接口/api/v1/write,直接写数据到prometheus里面。

pushgateway在高并发的情况下还是比较消耗资源的,特别是开启一致性检查,高并发写入的时候特别慢。

第二种方式少了一层转发,速度应该比较快。
如何远程写入prometheus存储如何远程写入prometheus存储

接口

可以通过prometheus的http接口/api/v1/write提交数据,这个接口的数据格式有有要求:
使用POST方式提交
需要经过protobuf编码,依赖github.com/gogo/protobuf/proto
可以使用snappy进行压缩,依赖github.com/golang/snappy

步骤:

收集指标名称,时间戳,值和标签
将数据转换成prometheus需要的数据格式
使用proto对数据进行编码,并用snappy进行压缩
通过httpClient提交数据

package prome 
 
import ( 
    "bufio" 
    "bytes" 
    "context" 
    "io" 
    "io/ioutil" 
    "net/http" 
    "net/url" 
    "regexp" 
    "time" 
 
    "github.com/gogo/protobuf/proto" 
    "github.com/golang/snappy" 
    "github.com/opentracing-contrib/go-stdlib/nethttp" 
    opentracing "github.com/opentracing/opentracing-go" 
    "github.com/pkg/errors" 
    "github.com/prometheus/common/model" 
    "github.com/prometheus/prometheus/pkg/labels" 
    "github.com/prometheus/prometheus/prompb" 
) 
 
type RecoverableError struct { 
    error 
} 
 
type HttpClient struct { 
    url     *url.URL 
    Client  *http.Client 
    timeout time.Duration 
} 
 
var MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`) 
 
type MetricPoint struct { 
    Metric  string            `json:"metric"` // 指标名称 
    TagsMap map[string]string `json:"tags"`   // 数据标签 
    Time    int64             `json:"time"`   // 时间戳,单位是秒 
    Value   float64           `json:"value"`  // 内部字段,最终转换之后的float64数值 
} 
 
func (c *HttpClient) remoteWritePost(req []byte) error { 
    httpReq, err := http.NewRequest("POST", c.url.String(), bytes.NewReader(req)) 
    if err != nil { 
        return err 
    } 
    httpReq.Header.Add("Content-Encoding", "snappy") 
    httpReq.Header.Set("Content-Type", "application/x-protobuf") 
    httpReq.Header.Set("User-Agent", "opcai") 
    httpReq.Header.Set("X-Prometheus-Remote-Write-Version", "0.1.0") 
    ctx, cancel := context.WithTimeout(context.Background(), c.timeout) 
    defer cancel() 
 
    httpReq = httpReq.WithContext(ctx) 
 
    if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { 
        var ht *nethttp.Tracer 
        httpReq, ht = nethttp.TraceRequest( 
            parentSpan.Tracer(), 
            httpReq, 
            nethttp.OperationName("Remote Store"), 
            nethttp.ClientTrace(false), 
        ) 
        defer ht.Finish() 
    } 
 
    httpResp, err := c.Client.Do(httpReq) 
    if err != nil { 
        // Errors from Client.Do are from (for example) network errors, so are 
        // recoverable. 
        return RecoverableError{err} 
    } 
    defer func() { 
        io.Copy(ioutil.Discard, httpResp.Body) 
        httpResp.Body.Close() 
    }() 
 
    if httpResp.StatusCode/100 != 2 { 
        scanner := bufio.NewScanner(io.LimitReader(httpResp.Body, 512)) 
        line := "" 
        if scanner.Scan() { 
            line = scanner.Text() 
        } 
        err = errors.Errorf("server returned HTTP status %s: %s", httpResp.Status, line) 
    } 
    if httpResp.StatusCode/100 == 5 { 
        return RecoverableError{err} 
    } 
    return err 
} 
 
func buildWriteRequest(samples []*prompb.TimeSeries) ([]byte, error) { 
 
    req := &prompb.WriteRequest{ 
        Timeseries: samples, 
    } 
    data, err := proto.Marshal(req) 
    if err != nil { 
        return nil, err 
    } 
    compressed := snappy.Encode(nil, data) 
    return compressed, nil 
} 
 
type sample struct { 
    labels labels.Labels 
    t      int64 
    v      float64 
} 
 
const ( 
    LABEL_NAME = "__name__" 
) 
 
func convertOne(item *MetricPoint) (*prompb.TimeSeries, error) { 
    pt := prompb.TimeSeries{} 
    pt.Samples = []prompb.Sample{{}} 
    s := sample{} 
    s.t = item.Time 
    s.v = item.Value 
    // name 
    if !MetricNameRE.MatchString(item.Metric) { 
        return &pt, errors.New("invalid metrics name") 
    } 
    nameLs := labels.Label{ 
        Name:  LABEL_NAME, 
        Value: item.Metric, 
    } 
    s.labels = append(s.labels, nameLs) 
    for k, v := range item.TagsMap { 
        if model.LabelNameRE.MatchString(k) { 
            ls := labels.Label{ 
                Name:  k, 
                Value: v, 
            } 
            s.labels = append(s.labels, ls) 
        } 
    } 
 
    pt.Labels = labelsToLabelsProto(s.labels, pt.Labels) 
    // 时间赋值问题,使用毫秒时间戳 
    tsMs := time.Unix(s.t, 0).UnixNano() / 1e6 
    pt.Samples[0].Timestamp = tsMs 
    pt.Samples[0].Value = s.v 
    return &pt, nil 
} 
 
func labelsToLabelsProto(labels labels.Labels, buf []*prompb.Label) []*prompb.Label { 
    result := buf[:0] 
    if cap(buf) < len(labels) { 
        result = make([]*prompb.Label, 0, len(labels)) 
    } 
    for _, l := range labels { 
        result = append(result, &prompb.Label{ 
            Name:  l.Name, 
            Value: l.Value, 
        }) 
    } 
    return result 
} 
 
func (c *HttpClient) RemoteWrite(items []MetricPoint) (err error) { 
    if len(items) == 0 { 
        return 
    } 
    ts := make([]*prompb.TimeSeries, len(items)) 
    for i := range items { 
        ts[i], err = convertOne(&items[i]) 
        if err != nil { 
            return 
        } 
    } 
    data, err := buildWriteRequest(ts) 
    if err != nil { 
        return 
    } 
    err = c.remoteWritePost(data) 
    return 
} 
 
func NewClient(ur string, timeout time.Duration) (c *HttpClient, err error) { 
    u, err := url.Parse(ur) 
    if err != nil { 
        return 
    } 
    c = &HttpClient{ 
        url:     u, 
        Client:  &http.Client{}, 
        timeout: timeout, 
    } 
    return 
} 

测试

prometheus启动的时候记得加参数--enable-feature=remote-write-receiver

package prome 
 
import ( 
    "testing" 
    "time" 
) 
 
func TestRemoteWrite(t *testing.T) { 
    c, err := NewClient("http://localhost:9090/api/v1/write", 10*time.Second) 
    if err != nil { 
        t.Fatal(err) 
    } 
    metrics := []MetricPoint{ 
        {Metric: "opcai1", 
            TagsMap: map[string]string{"env": "testing", "op": "opcai"}, 
            Time:    time.Now().Add(-1 * time.Minute).Unix(), 
            Value:   1}, 
        {Metric: "opcai2", 
            TagsMap: map[string]string{"env": "testing", "op": "opcai"}, 
            Time:    time.Now().Add(-2 * time.Minute).Unix(), 
            Value:   2}, 
        {Metric: "opcai3", 
            TagsMap: map[string]string{"env": "testing", "op": "opcai"}, 
            Time:    time.Now().Unix(), 
            Value:   3}, 
        {Metric: "opcai4", 
            TagsMap: map[string]string{"env": "testing", "op": "opcai"}, 
            Time:    time.Now().Unix(), 
            Value:   4}, 
    } 
    err = c.RemoteWrite(metrics) 
    if err != nil { 
        t.Fatal(err) 
    } 
    t.Log("end...") 
} 

使用go test进行测试

go test -v 

总结

这个方法也是在看夜莺v5的代码的时候发现的,刚好有需要统一收集redis的监控指标,刚好可以用上,之前用pushgateway写的实在是慢。Linux就该这么学

### 使用Python向Prometheus写入数据 为了实现通过Python将数据写入Prometheus,通常有两种主要方法: #### 方法一:利用Pushgateway中间件 由于Prometheus本身设计为拉取(pull)模式而不是推送(push),因此直接向其发送指标并不常见。然而,官方提供了`pushgateway`作为辅助工具来支持临时性作业的数据提交。 当采用这种方式时,应用程序可以通过HTTP请求把时间序列数据推送到`pushgateway`实例上,之后Prometheus会定期从该网关抓取最新的度量值[^1]。 下面是一个简单的例子展示怎样借助`requests`库完成这一操作: ```python import requests job_name = 'example_job' metric_name = 'custom_metric_total' value = 42 instance_ip = "localhost" port = 9091 # Default port for pushgateway url = f'http://{instance_ip}:{port}/metrics/job/{job_name}' payload = f'# TYPE {metric_name} counter\n{metric_name} {value}\n' response = requests.post(url, data=payload) if response.status_code != 200: raise Exception(f'Failed to send metric: {response.text}') ``` 这段脚本构建了一个自定义的PromQL风格字符串表示单个计数器类型的度量,并将其POST至本地运行着的`pushgateway`服务端口处。 #### 方法二:Remote Write API (实验性质) 另一种更为先进的方案涉及使用Prometheus所提供的远程读/写API特性之一——即所谓的“remote_write”。这允许外部系统按照特定协议格式批量上传样本点给Prometheus服务器处理并存储下来[^3]。 对于希望实施这种方法的应用开发者来说,则需遵循Prometheus所规定的Protobuf消息结构编码规则准备待传输的内容体。具体而言就是参照`.proto`文件定义组装成相应的字节流形式再经由HTTPS通道递交过去。 这里给出一段示意性的代码片段用于阐述概念而非实际执行: ```python from prometheus_client import CollectorRegistry, GaugeMetricFamily, generate_latest import grpc from prompb.remote_pb2_grpc import RemoteWriteStub from prompb.types_pb2 import TimeSeries, LabelPair, Sample def create_sample(name, value, labels=None): ts = TimeSeries() lp = LabelPair(name="__name__".encode(), value=name.encode()) ts.labels.extend([lp]) if isinstance(labels, dict): for k, v in labels.items(): lpair = LabelPair(name=k.encode(), value=str(v).encode()) ts.labels.append(lpair) sample = Sample(value=value) ts.samples.extend([sample]) return ts channel = grpc.insecure_channel('your_prometheus_server_address') stub = RemoteWriteStub(channel) registry = CollectorRegistry(auto_describe=True) gauge = GaugeMetricFamily( name='my_custom_gauge', documentation='An example gauge written via remote write.', unit='', samples=[create_sample('my_custom_gauge', 7)] ) data = bytes(generate_latest(registry)) request = stub.Write(data=data) print(request) ``` 请注意上述示例依赖于第三方包如`grpcio`以及对应版本匹配好的`.proto`描述符文件集才能正常工作。此外考虑到此接口仍处于试验阶段,在生产环境中部署前务必充分测试验证稳定性与兼容性问题。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值