站点可用性检测是很多运维系统和业务监控模块的核心功能,通过Golang的并发特性可以高效完成多站点的批量检测,同时实现检测结果的统一上报。本文会完整介绍从检测逻辑设计到并发调度再到结果上报的全流程实现。

核心需求与实现思路
站点可用性检测的核心目标是判断目标站点是否能正常响应,通常需要关注响应状态码、响应耗时、是否出现连接错误等指标。如果使用串行方式逐个检测站点,当站点数量较多时耗时会被无限拉长,因此需要使用Golang的并发能力并行处理多个检测任务。
整体实现思路分为三个部分:
- 定义检测结果的结构体,存储站点地址、状态码、耗时、错误信息等内容
- 使用goroutine并行发起每个站点的检测请求,通过channel收集检测结果
- 对收集到的结果进行整理,按照需求上报到指定存储或接口
检测结果结构体定义
首先定义统一的检测结果结构体,方便后续传递和处理数据:
package main
import "time"
// CheckResult 站点检测结果结构体
type CheckResult struct {
URL string // 检测的站点地址
StatusCode int // HTTP响应状态码,0表示请求失败
CostTime time.Duration // 请求耗时
ErrMsg string // 错误信息,为空表示请求成功
}
单站点检测逻辑实现
单站点的检测需要实现HTTP请求发起、超时控制、状态码判断的逻辑,这里使用Golang标准库的net/http实现:
package main
import (
"errors"
"net/http"
"time"
)
// checkSingleSite 检测单个站点的可用性
// url:待检测的站点地址
// timeout:请求超时时间
func checkSingleSite(url string, timeout time.Duration) CheckResult {
result := CheckResult{
URL: url,
}
// 创建带超时的HTTP客户端
client := &http.Client{
Timeout: timeout,
}
// 记录请求开始时间
startTime := time.Now()
// 发起GET请求
resp, err := client.Get(url)
// 计算请求耗时
result.CostTime = time.Since(startTime)
if err != nil {
result.ErrMsg = err.Error()
result.StatusCode = 0
return result
}
defer resp.Body.Close()
// 判断状态码是否为2xx或3xx,认为是可用
if resp.StatusCode >= 200 && resp.StatusCode < 400 {
result.StatusCode = resp.StatusCode
} else {
result.StatusCode = resp.StatusCode
result.ErrMsg = errors.New("状态码不在可用范围").Error()
}
return result
}
并发检测调度实现
使用goroutine和channel实现多站点的并发检测,控制并发数量避免对目标站点造成过大压力:
package main
import (
"sync"
"time"
)
// ConcurrentCheck 并发检测多个站点
// urls:待检测的站点地址列表
// timeout:单个请求超时时间
// maxConcurrent:最大并发数
func ConcurrentCheck(urls []string, timeout time.Duration, maxConcurrent int) []CheckResult {
// 用于接收检测结果
resultChan := make(chan CheckResult, len(urls))
// 用于控制并发数的信号量
sem := make(chan struct{}, maxConcurrent)
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(targetURL string) {
defer wg.Done()
// 获取信号量,控制并发
sem <- struct{}{}
defer func() { <-sem }()
// 执行单站点检测
res := checkSingleSite(targetURL, timeout)
resultChan <- res
}(url)
}
// 等待所有检测任务完成
go func() {
wg.Wait()
close(resultChan)
}()
// 收集所有检测结果
var results []CheckResult
for res := range resultChan {
results = append(results, res)
}
return results
}
检测结果上报实现
检测结果上报可以根据实际需求对接不同的存储或接口,这里以两种方式为例,一种是打印到控制台,另一种是上报到自定义接口:
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// PrintResults 将检测结果打印到控制台
func PrintResults(results []CheckResult) {
fmt.Println("站点可用性检测结果:")
for _, res := range results {
if res.ErrMsg != "" {
fmt.Printf("站点:%s,检测失败,错误信息:%s,耗时:%vn", res.URL, res.ErrMsg, res.CostTime)
} else {
fmt.Printf("站点:%s,检测成功,状态码:%d,耗时:%vn", res.URL, res.StatusCode, res.CostTime)
}
}
}
// ReportToAPI 将检测结果上报到指定接口
// reportURL:上报接口的地址
func ReportToAPI(results []CheckResult, reportURL string) error {
// 将结果序列化为JSON
data, err := json.Marshal(results)
if err != nil {
return err
}
// 发起POST请求上报结果
resp, err := http.Post(reportURL, "application/json", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("上报失败,状态码:%d", resp.StatusCode)
}
fmt.Println("检测结果上报成功")
return nil
}
完整调用示例
将以上模块组合起来,实现一个完整的检测流程:
package main
func main() {
// 待检测的站点列表
sites := []string{
"http://ipipp.com",
"http://127.0.0.1:8080",
"https://www.baidu.com",
}
// 单个请求超时时间设置为3秒
timeout := 3 * time.Second
// 最大并发数设置为5
maxConcurrent := 5
// 执行并发检测
results := ConcurrentCheck(sites, timeout, maxConcurrent)
// 打印检测结果
PrintResults(results)
// 上报结果到指定接口,这里替换为实际上报地址
reportURL := "http://192.168.0.1:9000/report"
_ = ReportToAPI(results, reportURL)
}
注意事项
在实际使用中需要注意几个问题:
- 超时时间需要根据站点的正常响应耗时合理设置,避免误判
- 并发数需要根据目标站点的承受能力调整,避免触发站点的限流策略
- 如果检测的是HTTPS站点,可以自定义http.Transport配置跳过证书校验,但要明确安全风险
- 结果上报时建议增加重试机制,避免上报失败导致数据丢失