You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang持续轮询API检测数据变更并存储到Postgres的实现问题咨询

Go 轮询API落库方案修复说明

现有代码的核心缺陷

  • 轮询逻辑提前退出:Poll函数中首次请求完成后直接执行return,后续的抖动休眠、下一轮请求逻辑完全无法触发,整个轮询只会执行一次
  • 资源泄漏:未对response.Body执行关闭操作,持续运行会导致连接耗尽、内存泄漏
  • 错误处理顺序错误:先执行io.ReadAll(response.Body)再判断请求错误,当请求出错时response为nil会直接触发空指针panic
  • 随机种子固定:使用固定值99初始化随机数,每次程序运行生成的抖动序列完全一致,失去抖动避峰的作用
  • 客户端超时配置不合理:HTTP客户端设置了600小时的超时,若单次请求卡住会完全阻塞整个轮询流程
  • main函数语法错误:Go的main函数无返回值,代码中return err不符合语法规范

正确实现思路

你需要实现的是持续轮询+数据变更检测+Postgres落库,因此要对逻辑做拆分:

  1. 轮询控制层维护循环,单次请求异常不终止整个轮询流程
  2. 每次拉取到有效数据后通过回调函数执行比对、落库逻辑,不要阻塞轮询主流程
  3. 维护上一次有效数据的哈希值做变更校验,只有数据发生变更时才执行入库操作,减少无效计算

修复后参考代码

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"io"
	"math/rand"
	"net/http"
	"time"
)

// Poll 持续轮询API,拿到有效数据后调用callback处理
func Poll(req *http.Request, client *http.Client, callback func([]byte) error) error {
	// 用当前时间戳初始化随机数,保证每次运行抖动值不同
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()

	// 维护上一次有效数据的哈希,用于变更检测
	var lastHash string

	for range ticker.C {
		// 每次请求克隆原始req,避免复用导致的Header等字段冲突
		currentReq := req.Clone(req.Context())
		response, err := client.Do(currentReq)
		if err != nil {
			// 单次请求出错打印日志继续下一轮,不要终止轮询
			println("request failed: ", err.Error())
			// 加抖动后进入下一轮
			time.Sleep(time.Duration(r.Int31n(5000)) * time.Millisecond)
			continue
		}

		// 读取数据前先保证Body关闭
		data, err := io.ReadAll(response.Body)
		_ = response.Body.Close()
		if err != nil {
			println("read response failed: ", err.Error())
			time.Sleep(time.Duration(r.Int31n(5000)) * time.Millisecond)
			continue
		}

		// 数据比对逻辑:计算当前数据哈希和上一次对比
		hash := sha256.Sum256(data)
		currentHash := hex.EncodeToString(hash[:])
		if currentHash == lastHash {
			// 数据无变更,直接进入下一轮
			time.Sleep(time.Duration(r.Int31n(5000)) * time.Millisecond)
			continue
		}
		lastHash = currentHash

		// 数据有变更,调用回调函数处理(比如解析后存入Postgres)
		if err = callback(data); err != nil {
			println("process data failed: ", err.Error())
		}

		// 加抖动
		time.Sleep(time.Duration(r.Int31n(5000)) * time.Millisecond)
	}
	return nil
}

func main() {
	// HTTP客户端超时设置为10秒,大于轮询间隔的话没有意义
	client := &http.Client{
		Timeout: 10 * time.Second,
	}
	url := "https://data-exchange-api.vicroads.vic.gov.au/bluetooth_data/links"
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Ocp-Apim-Subscription-Key", "xx")

	// 启动轮询,传入数据处理回调
	err = Poll(req, client, func(data []byte) error {
		// 这里写你的数据解析、存入Postgres的逻辑
		println("new data received, len: ", len(data))
		return nil
	})
	if err != nil {
		panic(err)
	}
}

后续优化建议

  • 可以把上次数据哈希存在持久化存储中,避免程序重启后重复入库相同数据
  • 入库逻辑建议异步执行,避免处理时间过长影响轮询间隔
  • 可添加轮询监控指标,统计请求成功率、数据变更频率等信息

内容的提问来源于stack exchange,提问作者LeCoda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.14 12:57:03