
本文介绍使用 time.ticker 在 go 中安全、可控地实现对 rest 接口的定时轮询,涵盖基础结构、错误处理、资源释放及优雅退出等关键实践。
本文介绍使用 time.ticker 在 go 中安全、可控地实现对 rest 接口的定时轮询,涵盖基础结构、错误处理、资源释放及优雅退出等关键实践。
在 Go 应用中周期性调用外部 REST 接口(如 PHP 后端服务)是常见需求,例如监控状态、同步数据或触发下游任务。最简洁且符合 Go 并发模型的方式是结合 time.Ticker 与 goroutine 实现非阻塞轮询。
以下是一个生产就绪的基础模板:
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)
// 示例响应结构体(请根据实际 API 调整)
type ApiResponse struct {
Status string `json:"status"`
Data any `json:"data"`
}
func pollRESTEndpoint(url string, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop() // 确保 ticker 可被 GC 回收(若在长生命周期函数中)
for {
select {
case <-ticker.C:
resp, err := http.Get(url)
if err != nil {
log.Printf("HTTP GET failed: %v", err)
continue // 失败时不中断轮询,等待下次重试
}
defer resp.Body.Close() // ⚠️ 注意:此处 defer 在循环内无效!应改为显式关闭
// 正确做法:立即关闭响应体
if resp.StatusCode != http.StatusOK {
log.Printf("HTTP %d: %s", resp.StatusCode, resp.Status)
continue
}
var result ApiResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Printf("JSON decode failed: %v", err)
continue
}
// ✅ 此处可进行业务处理:存入数据库、发消息、触发事件等
fmt.Printf("Received: %+v\n", result)
case <-time.After(30 * time.Second): // 可选:超时退出示例(仅用于演示)
log.Println("Polling stopped due to timeout.")
return
}
}
}
func main() {
// 启动轮询(建议封装为独立 goroutine,并通过 context 控制生命周期)
go pollRESTEndpoint("http://localhost:8000/api/status", 5*time.Second)
// 模拟主程序运行(实际中可用 signal.Notify + context.WithCancel 实现优雅退出)
select {} // 阻塞主 goroutine
}? 关键注意事项:
- 永远显式关闭 resp.Body:defer resp.Body.Close() 在循环中不可用(defer 会累积),必须在每次迭代末尾直接调用 resp.Body.Close()。
- 错误处理要健壮:网络失败、HTTP 非 2xx 状态、JSON 解析异常均不应导致轮询中断,而应记录日志并继续下一轮。
- 避免硬编码 URL 和间隔:建议通过配置文件或环境变量注入,提升可维护性。
- 引入上下文(context)支持取消:在真实项目中,应将 http.Get 替换为 http.DefaultClient.Do(req) 并传入带超时的 context.Context,以便支持启动/停止控制和请求级超时。
- 考虑限流与退避:若目标服务不稳定,可集成指数退避(如 backoff.Retry)避免雪崩。
✅ 总结:time.Ticker 是实现周期性任务的首选原语,配合 proper error handling、resource cleanup 和 context-aware HTTP 客户端,即可构建高可靠性轮询服务。

















