GuzzleHttp 并发发起 http 请求
系统中需要调用百度翻译接口做多语言支持,百度翻译对于不同的认证给予了不同的权益,通用文本翻译API标准版、高级版、尊享版的不同服务权益如下:
标准版 | 高级版 | 尊享版 |
---|---|---|
QPS=1 支持28个语种互译 单次最长请求1000字符 免费调用量5万字符/月 |
QPS=10 支持 28个语种互译 单次最长请求6000字符 免费调用量100万字符/月 |
QPS=100 支持200+语种互译 单次最长请求6000字符 免费调用量200万字符/月 |
最初的实现是依次循环调用接口,组装请求参数、调用翻译接口、解析翻译结果,调试下来每秒种只能调用 5 ~ 6 次接口,高级版的 QPS = 10 都跑不满,尊享版的 QPS = 100 完全没有用武之地,系统中新支持一套语言包时,整体翻译下来需要数个小时。
考虑通过 GuzzleHttp 的并发请求进行优化,优化后可以根据不同的权益等级设置不同的并发请求数量,特别是对于尊享版本的 QPS=100,可以大幅提升翻译速度。
本地先做了模拟测试,首先部署一个 POST 请求的接口,接口的限制频率为 10次/s,将请求参数直接返回:
type TransData struct {
Q string `json:"q" form:"q"`
From string `json:"from" form:"from"`
To string `json:"to" form:"to"`
AppId string `json:"app_id" form:"app_id"`
Salt string `json:"salt" form:"salt"`
Sign string `json:"sign" form:"sign"`
}
func RegisterAPIRoutes(r *gin.Engine) {
var v1 *gin.RouterGroup
v1 = r.Group("/v1")
v1.Use(middlewares.LimitIP("10-S"))
{
v1.POST("/trans", func(c *gin.Context) {
transData := TransData{}
if err := c.ShouldBind(&transData); err != nil {
fmt.Println(err.Error())
return
}
// 没什么业务逻辑,所以加了 200ms 的延迟响应
time.Sleep(time.Millisecond * 200)
c.JSON(http.StatusOK, transData)
})
}
}
先按之前单条循环的方式调用接口,传入的参数按照百度翻译接口的参数组装:
$url = 'http://localhost:8000/api/v1/trans';
$client = new Client();
foreach (range(0, 9) as $i) {
$text = '测试' . $i;
$appId = 'app_id';
$appKey = 'key';
$salt = random_int(1000, 9999);
$data = [
'q' => $text,
'from' => 'zh',
'to' => 'en',
'appid' => $appId,
'salt' => $salt,
'sign' => md5($appId . $text . $salt . $appKey)
];
$response = $client->request('POST', $url, [
'http_errors' => false,
'form_params' => $data,
]);
$status = $response->getStatusCode();
echo 'status = ' . $status . ' ---> ';
if ($status === 200) {
$body = $response->getBody()->getContents();
$data = json_decode($body, true);
echo 'i = ' . $i . ', time = ' . time() . ', 请求成功, q = ' . $data['q'] . PHP_EOL;
} else {
echo 'i = ' . $i . ', time = ' . time() . ', 请求失败' . PHP_EOL;
}
}
10 次接口请求用时 2 秒多,1 秒内可以完成请求 4 次左右(和之前在系统内调试的 5 ~ 6 次差不多),执行结果:
status = 200 ---> i = 0, time = 1717747910, 请求成功, q = 测试0
status = 200 ---> i = 1, time = 1717747910, 请求成功, q = 测试1
status = 200 ---> i = 2, time = 1717747911, 请求成功, q = 测试2
status = 200 ---> i = 3, time = 1717747911, 请求成功, q = 测试3
status = 200 ---> i = 4, time = 1717747911, 请求成功, q = 测试4
status = 200 ---> i = 5, time = 1717747911, 请求成功, q = 测试5
status = 200 ---> i = 6, time = 1717747912, 请求成功, q = 测试6
status = 200 ---> i = 7, time = 1717747912, 请求成功, q = 测试7
status = 200 ---> i = 8, time = 1717747912, 请求成功, q = 测试8
status = 200 ---> i = 9, time = 1717747912, 请求成功, q = 测试9
Time: 00:02.100, Memory: 34.00 MB
再改为并发的方式发起请求:
$url = 'http://localhost:8000/api/v1/trans';
$appId = 'app_id';
$appKey = 'key';
$promises = [];
$client = new Client();
foreach (range(0, 9) as $i) {
$text = '测试' . $i;
$salt = random_int(1000, 9999);
$data = [
'q' => $text,
'from' => 'zh',
'to' => 'en',
'appid' => $appId,
'salt' => $salt,
'sign' => md5($appId . $text . $salt . $appKey)
];
$promises[] = $client->postAsync($url, [
'http_errors' => false,
'form_params' => $data,
]);
}
$responses = Utils::unwrap($promises);
foreach (range(0, 9) as $i) {
$status = $responses[$i]->getStatusCode();
echo 'status = ' . $status . ' ---> ';
if ($status === 200) {
$body = $responses[$i]->getBody()->getContents();
$data = json_decode($body, true);
echo 'i = ' . $i . ', time = ' . time() . ', 请求成功, q = ' . $data['q'] . PHP_EOL;
} else {
echo 'i = ' . $i . ', time = ' . time() . ', 请求失败' . PHP_EOL;
}
}
10 次接口请求用时不到 1 秒,大幅减少了等待时间,执行结果:
status = 200 ---> i = 0, time = 1717748427, 请求成功, q = 测试0
status = 200 ---> i = 1, time = 1717748427, 请求成功, q = 测试1
status = 200 ---> i = 2, time = 1717748427, 请求成功, q = 测试2
status = 200 ---> i = 3, time = 1717748427, 请求成功, q = 测试3
status = 200 ---> i = 4, time = 1717748427, 请求成功, q = 测试4
status = 200 ---> i = 5, time = 1717748427, 请求成功, q = 测试5
status = 200 ---> i = 6, time = 1717748427, 请求成功, q = 测试6
status = 200 ---> i = 7, time = 1717748427, 请求成功, q = 测试7
status = 200 ---> i = 8, time = 1717748427, 请求成功, q = 测试8
status = 200 ---> i = 9, time = 1717748427, 请求成功, q = 测试9
Time: 00:00.309, Memory: 34.00 MB
将并发的数量由 10 改为 15,(两次循环处的 9 改为 14):
// foreach (range(0, 9) as $i) {
foreach (range(0, 14) as $i) {
执行结果也符合 10次/s 的频率限制:
status = 200 ---> i = 0, time = 1717750047, 请求成功, q = 测试0
status = 200 ---> i = 1, time = 1717750047, 请求成功, q = 测试1
status = 200 ---> i = 2, time = 1717750047, 请求成功, q = 测试2
status = 200 ---> i = 3, time = 1717750047, 请求成功, q = 测试3
status = 200 ---> i = 4, time = 1717750047, 请求成功, q = 测试4
status = 200 ---> i = 5, time = 1717750047, 请求成功, q = 测试5
status = 200 ---> i = 6, time = 1717750047, 请求成功, q = 测试6
status = 200 ---> i = 7, time = 1717750047, 请求成功, q = 测试7
status = 200 ---> i = 8, time = 1717750047, 请求成功, q = 测试8
status = 200 ---> i = 9, time = 1717750047, 请求成功, q = 测试9
status = 429 ---> i = 10, time = 1717750047, 请求失败
status = 429 ---> i = 11, time = 1717750047, 请求失败
status = 429 ---> i = 12, time = 1717750047, 请求失败
status = 429 ---> i = 13, time = 1717750047, 请求失败
status = 429 ---> i = 14, time = 1717750047, 请求失败
Time: 00:00.310, Memory: 34.00 MB
接口频率限制改为 100次/s
v1.Use(middlewares.LimitIP("100-S"))
并发数量也改为 100 次:
foreach (range(0, 99) as $i) {
100 次的接口请求用时 1 秒钟:
status = 200 ---> i = 0, time = 1717750526, 请求成功, q = 测试0
status = 200 ---> i = 1, time = 1717750526, 请求成功, q = 测试1
status = 200 ---> i = 2, time = 1717750526, 请求成功, q = 测试2
status = 200 ---> i = 3, time = 1717750526, 请求成功, q = 测试3
.
.
.
status = 200 ---> i = 94, time = 1717750526, 请求成功, q = 测试94
status = 200 ---> i = 95, time = 1717750526, 请求成功, q = 测试95
status = 200 ---> i = 96, time = 1717750526, 请求成功, q = 测试96
status = 200 ---> i = 97, time = 1717750526, 请求成功, q = 测试97
status = 200 ---> i = 98, time = 1717750526, 请求成功, q = 测试98
status = 200 ---> i = 99, time = 1717750526, 请求成功, q = 测试99
Time: 00:00.951, Memory: 36.00 MB
标签:status,200,http,请求,GuzzleHttp,---,并发,测试,time From: https://www.cnblogs.com/zhpj/p/18237543/guzzlehtp-and-issue-an-http-request-z1xzemg