目录

GO语言开发实践-拨测工具urlooker-pro

拨测工具 urlooker-pro 设计理念及效果 。

动机

我们需要对网页站点进行拨测,并监控站点的可用性、成功率和延迟等相关信息,虽然有很多云厂商有成熟的云拨测产品,但是需要费用,我们这边就想着利用已有的服务器,在上面部署探针,探针从数据库获取需要监控的站点数据,进行拨测,站点数据携带地区的字段,方便我们站点需要在不同地区进行探测的需要。

架构图

/go%E8%AF%AD%E8%A8%80%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5-%E6%8B%A8%E6%B5%8B%E5%B7%A5%E5%85%B7urlooker-pro/urlooker.png
架构图

设计理念

总体来说就是分3点:

  1. 从数据库获取已有的域名信息,字段如下:

    端口字段方便检测自定义端口的站点。

    超时字段方便我们根据每个站点设置不同的超时时间。

    uri自动方便我们探测自定义的url。

    协议方便我们指定 http 还是 https 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    type DomainItem struct {
        Domain     string `json:"domain"`
        Port       int    `json:"port"`
        Timeout    int    `json:"timeout"`
        Uri        string `json:"uri"`
        Region     string `json:"region"`
        Scheme     string `json:"scheme"`
        Ticker     *time.Ticker
    }
    
  2. 如果只是简单的 ping 那没什么意义,我们这边是拿到域名后,针对域名做 A 记录解析,因为如果域名本身有 N 个 A 记录的话,那不做 ip 和域名绑定的探测,是不全面的。每次只会探测到其中一个节点,如果运气不好,每次都避开了有问题的节点,那探测结果是不准确的。

  3. 探测结果通过 prometheus 客户端对外暴露指标,方便我们配置 grafana 大盘展示探测结果及发送告警。

核心代码

具体说明看注释,注释写得很清楚了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
func httpsHostBindIp(domain, urlStr, ip string, timeout int, wg *sync.WaitGroup) {
    defer wg.Done()
    //异常处理
    defer func() {
        //捕获异常
        err := recover()
        if err != nil {
            //条件判断,是否存在异常
            //存在异常,抛出异常
            log.Printf("http请求 %s 出错:%v\n", urlStr, err)
        }
    }()

    if timeout <= 0 || timeout > 10000 {
        timeout = 3000
    }

    urlObj, err := url.Parse(urlStr)
    if err != nil {
        log.Printf("url.Parse %s Error: %v\n", urlStr, err)
    }

    request, _ := http.NewRequest("GET", urlObj.String(), bytes.NewBuffer([]byte{}))
    defer request.Body.Close()
    request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")

    var response *http.Response
    // 一、 绑定固定的IP
    // 1. 先准备一个Dialer对象
    dialer := &net.Dialer{
        Timeout: time.Duration(timeout) * time.Millisecond,
    }
    transport := &http.Transport{
        // 2. 插入特别的改写条件,然后继续利用原先的DialContext逻辑
        DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
            portSlice := strings.Split(addr, ":")
            port := portSlice[1]
            addr = ip + ":" + port
            return dialer.DialContext(ctx, network, addr)
        },
    }

    // 客户端重定向
    client := &http.Client{
        //http客户端重定向
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            if len(via) >= 10 {
                return errors.New("stopped after 10 redirects")
            }
            return nil
        },
        Transport: transport,
        Timeout:   time.Duration(timeout) * time.Millisecond,
    }

    var respCode string
    starttime := time.Now()
    response, err = client.Do(request)
    if err != nil {
        // 判断错误是否为超时
        if urlErr, ok := err.(*url.Error); ok {
            if urlErr.Timeout() {
                log.Printf("探测 %s %s 请求超时:%s\n", urlStr, ip, err)
                respCode = "0"
            } else {
                log.Printf("探测 %s %s 请求时发生其他网络错误:%s\n", urlStr, ip, err)
                respCode = "1"
            }
        } else {
            log.Printf("探测 %s %s 请求时发生非网络错误:%s\n", urlStr, ip, err)
            respCode = "2"
        }
    } else {
        defer response.Body.Close()
        respCode = strconv.Itoa(response.StatusCode)
        log.Printf("探测结果:%s ,IP:%s,状态码 %v\n", urlStr, ip, respCode)
    }
    endtime := time.Since(starttime)
    Observer(domain, urlStr, ip, endtime.Seconds())
    log.Printf("完成时间:%s IP:%s 请求花费了 %.2fs 时间\n", domain, ip, endtime.Seconds())

    IncreaseCounter(domain, urlStr, ip, respCode)
}

效果图

/go%E8%AF%AD%E8%A8%80%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5-%E6%8B%A8%E6%B5%8B%E5%B7%A5%E5%85%B7urlooker-pro/urlooker-result.png
效果图

结束

总体来说开发不难,难的是有没有资源可以使用。不喜勿喷。