Loading

GuzzleHttp 并发发起 http 请求

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(); // GuzzleHttp\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(); // GuzzleHttp\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); // GuzzleHttp\Promise\Utils
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

posted @ 2024-06-07 17:10  zhpj  阅读(106)  评论(0)    收藏  举报