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落库,因此要对逻辑做拆分:
- 轮询控制层维护循环,单次请求异常不终止整个轮询流程
- 每次拉取到有效数据后通过回调函数执行比对、落库逻辑,不要阻塞轮询主流程
- 维护上一次有效数据的哈希值做变更校验,只有数据发生变更时才执行入库操作,减少无效计算
修复后参考代码
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
相关产品推荐
相关产品推荐

