# 监控改造之路3

也是没想到监控系列竟然断更如此之久，要做到笔耕不辍真的挺难，再一次反映自己行动力之差。话说人logseq里面的todo也是越积越多，眼看着2023年就快结束了，小破站虽然没啥人看，但这债，还是得快速还的。好，切入正题，上次写监控还是上次……

上篇提到主机监控，采用的是将主机的node-exporter写入到prometheus配置文件的方式，利用prom配置文件的热更新机制完成，这种方式是比较生硬的，不够优雅。一旦需要监控的主机数量多起来，就会带来一些问题，

1. 每次要读写文件，速度是比较慢的，特别是当配置文件逐渐变大起来时
    
2. 对于问题1，可以将配置文件拆分来缓解，拆分带来的配置的更多的管理和维护成本
    
3. 操作文件意味着写文件的服务/进程需要跟prom的配置文件具体位置强关联，会带来其它问题，比如：读写配置文件的进程或者服务如果是多实例，需要处理资源争抢等问题
    

所以服务发现就有存在的必要的，具体而言，就是将需要监控的目标将给第三方服务来维护和管理，再将第三方服务的地址告诉prom，由prom通过第三方服务来获取需要监控的目标，例如比较流行的consul，以及上文提到的监控目标存在配置文件中，其实也是服务发现的一种。

但是团队的情况有一些复杂，因为早期在项目设计上技术调研上欠缺，导致整个项目引入了过多的中间件，例如zk、kafka、redis等，笔者进入这个团队以后，通过对整个项目的梳理，发现这些中间件并不是必须的，甚至于完全可以去掉，再加之开源竞品仅依赖一个MySQL，团队对于去中件间这件事显得不够有决心，但是对于引入新的中间件却是非常谨慎的。虽然我比较推荐使用consul，但是当时的一些情况，并没有得到支持，于是本着尽可能不引入新的中件间的原则，在架构师的坚持下，使用zk作为服务发现，但其实prom并不支持直接使用zk作为服务发现，不过支持一个叫做`nerve`的服务发现，使用zk作为存储，于是乎，通过走读promtheus的相关代码，使用一些骚操作，将需要监控的主机信息写入zk，以此支持将zk作为promtheus的服务发现，此事就此作罢。

从我的角度，这是一个非常不好的实践，为了之前错误的设计(过多的中间件使用)，要采取一种社区少有人使用的服务发现机制，带来的隐患着实不小。

所以经过一番梳理，我发现平台需要监控的主机信息在主服务(下文以ms指代)中是能查到的，既然主服务中能查到，为何要劳什子地将这些信息写入zk呢？所以答案呼之欲出了，HTTP-based service discovery: ms提供一个查询监控主机信息的HTTP即可，以下是一个简单的示例：

代码示例：

```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type target struct {
    Targets []string          `json:"targets"`
    Labels  map[string]string `json:"labels"`
}

func main() {
    targets := []target{
        {
            Targets: []string{"172.88.1.71:8080", "172.88.1.56:8080"},
            Labels: map[string]string{
                "job": "myapp",
            },
        },
        {
            Targets: []string{"172.18.5.32:10250"},
            Labels: map[string]string{
                "job":              "myapp",
                "__scheme__":       "https",
                "__metrics_path__": "/metrics/cadvisor",
            },
        },
    }

    handler := func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(targets)
    }

    http.HandleFunc("/targets", handler)
    fmt.Println("Starting HTTP server on port 8080...")
    // ip：172.18.4.172
    http.ListenAndServe(":8080", nil)
}
```

相应地，在prometheus.yml中需要添加的配置如下所示：

```yaml
scrape_configs:
  - job_name: myapp
    http_sd_configs:
    - url: http://172.18.4.172:8080/targets
      refresh_interval: 10s
```

这是一个相对接地气的方案，简单而有力。代码改动量也比较小，我比较惊讶的是，团队开发环境的主机不足30台，也就是说需要监控的主机数量不足30台，但这个接口的第一次响应查义时间竟然需要3s以上(http服务发现的方案是我出的，也给出了golang和prom配置示例，接口是其它同事在ms中实现的，ms是java写的) 。呃，又是历史债务么……

---

在如此少的监控目标下，prom的内存战用却意外的高，3G到8G的样子，我很吃惊，于是想用vm(victoriametrics)替换prom，在测试环境作对比，经过近两周的观察，vm完胜，但是却意外发现团队使用prom竟然还是两年前的版本，先前的对比就有些不讲武德了。本着公平的原则，将其升级到当时较新的版本2.45.0，经过一周的观察，监控等量的目标，vm与prom内存占用相当，甚至prom还略低于vm，虽然我是一个“激进”派，但此时，也不得不为prom而感动，一个老项目，能优化至此，实在是没理由不升级到2.45.0，这是一个令我印象深刻的prom版本……

你以为这就完事了？基于一些考量，最近我又谋划着使用vm……
