概要
在调用第三方 api 的时候, 基本都有访问限速的限制条件. 第三方的 api 有多个的时候, 就不太好控制访问速度, 常常会导致 http 429(too many requests) 然后就会有一段时间的禁止访问.
为了应对这种限速的情况, 通过一个简单的请求队列来控制访问的速度, 之后基本没遇到过 http 429 了.
实现思路
首先, 每个请求包装成一个 requestparam 的 struct, 其中包含请求的地址,类型,参数以及 response 的 channel.
发送请求的时候, 只要将 requestparam 放入请求队列中即可, 请求完成后, 将 response 放入对应的 channel 中.
整个代码实现很简单:
package util
import (
"fmt"
apiclient "gitee.com/wangyubin/gutils/api_client"
"gitee.com/wangyubin/gutils/logger"
)
// request 包含的内容
type requestparam struct {
api string
method string
jsonreq interface{}
resp chan []byte
}
// 请求队列, 本质是一个channel
type requestqueue struct {
queue chan requestparam
}
var queue *requestqueue
// 获取队列
func getqueue() *requestqueue {
return queue
}
// 初始化队列
func initrequestqueue(size int) {
queue = &requestqueue{
queue: make(chan requestparam, size),
}
}
// 将请求放入队列
func (rq *requestqueue) enqueue(p requestparam) {
rq.queue <- p
}
// 请求队列服务, 一直等待接受和处理请求
func (rq *requestqueue) run() {
lg := logger.getlogger()
for p := range rq.queue {
var resp []byte
var err error
switch p.method {
case "get":
resp, err = apiclient.getjson(p.api, p.jsonreq)
case "post":
resp, err = apiclient.postjson(p.api, p.jsonreq)
default:
err = fmt.errorf("wrong type of method(%s)\n", p.method)
}
if err != nil {
lg.err(err).msg("access api error: " + p.api)
continue
}
if p.resp != nil {
p.resp <- resp
close(p.resp)
}
}
lg.info().msg("request queue finished!")
}
这里的请求是用了我自己封装的 apiclient, 可以根据实际情况替换.
在我的应用场景里, 只要 api 顺序访问就不会出现 http 429 了, 如果这样觉得速度太快的的话, 可以尝试在 run() 函数中加入一些时间间隔.
func (rq *requestqueue) run() {
lg := logger.getlogger()
for p := range rq.queue {
time.sleep(1 * time.second)
// ... 省略的代码 ...
}
lg.info().msg("request queue finished!")
}
使用方法
使用很简单, 首先启动, 然后每个调用的地方将 requestparam 放入队列并等待 response 即可.
启动队列服务
func main() {
// init request queue and start queue service
util.initrequestqueue(100)
queue := util.getqueue()
defer close(queue.queue)
go queue.run()
// 其他启动代码
}
使用队列服务
func request(param1 string, param2 int) error {
api := "http://xxxx.com"
api = fmt.sprintf("%s?period=%s&size=%d", api, param1, param2)
queue := util.getqueue()
param := util.requestparam{
api: api,
method: "get",
resp: make(chan []byte, 1),
}
queue.enqueue(param)
var respdata struct {
status string `json:"status"`
data []model.data `json:"data"`
}
var err error
for resp := range param.resp {
err = json.unmarshal(resp, &respdata)
if err != nil {
lg.err(err).msg("unmarshal json error")
return err
}
}
fmt.println(respdata)
return err
}