CosyVoice 音声合成サービスは、WebSocket プロトコルを使用してリアルタイムのストリーミング通信を行います。高同時実行シナリオでは、リクエストごとに WebSocket 接続を作成および破棄すると、ネットワークとシステムリソースに大きなオーバーヘッドが生じ、接続レイテンシーが発生します。パフォーマンスを最適化し、安定性を確保するために、DashScope ソフトウェア開発キット (SDK) は、接続プールやオブジェクトプールなどのリソース再利用メカニズムを提供します。このドキュメントでは、DashScope Python および Java SDK でこれらの機能を使用して、高同時実行シナリオで CosyVoice サービスを効率的に呼び出す方法について説明します。
中国 (北京) リージョンのモデルを使用するには、中国 (北京) リージョンの API キー ページに移動します
前提条件
Python SDK:オブジェクトプールの最適化
Python SDK は、SpeechSynthesizerObjectPool クラスを使用して、オブジェクトプールの最適化を通じて SpeechSynthesizer オブジェクトを管理および再利用します。
オブジェクトプールが初期化されると、指定された数の SpeechSynthesizer インスタンスが作成され、WebSocket 接続が事前に確立されます。プールからオブジェクトを取得すると、接続が確立されるのを待たずに直接リクエストを送信できます。この方法により、ファーストパケットレイテンシーを効果的に削減できます。タスクが完了し、オブジェクトがプールに返されると、その WebSocket 接続はアクティブなままで、次のタスクに備えます。
実装手順
依存関係のインストール:
pip install -U dashscopeを実行して DashScope の依存関係をインストールします。オブジェクトプールの作成と構成
SpeechSynthesizerObjectPoolを使用してオブジェクトプールのサイズを設定します。この値は、ピーク時の同時実行数の 1.5 倍から 2 倍に設定することを推奨します。オブジェクトプールのサイズは、アカウントのクエリ/秒 (QPS) 制限を超えてはなりません。次のコードを使用して、固定サイズのグローバルなシングルトンオブジェクトプールを作成します。オブジェクトプールが初期化されると、指定された数の
SpeechSynthesizerオブジェクトがすぐに作成され、WebSocket 接続が確立されます。このプロセスには時間がかかります。from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)オブジェクトプールから
SpeechSynthesizerオブジェクトを取得現在使用中のオブジェクト数がプールの最大容量を超えると、システムは新しい
SpeechSynthesizerオブジェクトを作成します。この新しいオブジェクトは再初期化し、新しい WebSocket 接続を確立する必要があります。プールから既存の接続リソースを使用できないため、リソース再利用の恩恵は受けられません。
speech_synthesizer = connectionPool.borrow_synthesizer( model='cosyvoice-v3-flash', voice='longanyang', seed=12382, callback=synthesizer_callback )音声合成の実行
SpeechSynthesizerオブジェクトの `call` または `streaming_call` メソッドを呼び出して、音声合成を実行します。SpeechSynthesizerオブジェクトを返す音声合成タスクが完了したら、
SpeechSynthesizerオブジェクトを返して、後続のタスクで再利用できるようにします。未完了または失敗したタスクのオブジェクトは返さないでください。
connectionPool.return_synthesizer(speech_synthesizer)
完全なコード
# !/usr/bin/env python3
# Copyright (C) Alibaba Group. All Rights Reserved.
# MIT License (https://opensource.org/licenses/MIT)
import os
import time
import threading
import dashscope
from dashscope.audio.tts_v2 import *
USE_CONNECTION_POOL = True
text_to_synthesize = [
'Sentence 1: Welcome to the Alibaba Cloud speech synthesis service.',
'Sentence 2: Welcome to the Alibaba Cloud speech synthesis service.',
'Sentence 3: Welcome to the Alibaba Cloud speech synthesis service.',
]
connectionPool = None
if USE_CONNECTION_POOL:
print('creating connection pool')
start_time = time.time() * 1000
connectionPool = SpeechSynthesizerObjectPool(max_size=3)
end_time = time.time() * 1000
print('connection pool created, cost: {} ms'.format(end_time - start_time))
def init_dashscope_api_key():
'''
# DashScope API キーを設定します。詳細については、
# https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md をご参照ください。
'''
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # DASHSCOPE_API_KEY 環境変数から API キーを読み込みます
else:
dashscope.api_key = '<your-dashscope-api-key>' # API キーを手動で設定します
def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id):
global USE_CONNECTION_POOL, connectionPool
'''
# 指定されたテキストをストリーミングモードで音声合成し、非同期で呼び出し、合成された音声をリアルタイムで再生します。
# 詳細については、https://www.alibabacloud.com/help/document_detail/2712523.html をご参照ください。
'''
complete_event = threading.Event()
# 結果を処理するためのコールバックを定義します
class Callback(ResultCallback):
def on_open(self):
# オブジェクトプールを使用する場合、タスク開始後に on_open が呼び出されます。
self.file = open(f'result_{task_id}.mp3', 'wb')
print(f'[task_{task_id}] start')
def on_complete(self):
print(f'[task_{task_id}] speech synthesis task completed successfully.')
complete_event.set()
def on_error(self, message: str):
print(f'[task_{task_id}] speech synthesis task failed, {message}')
def on_close(self):
# オブジェクトプールを使用する場合、タスク終了後に on_close が呼び出されます。
print(f'[task_{task_id}] finished')
def on_event(self, message):
# print(f'recv speech synthsis message {message}')
pass
def on_data(self, data: bytes) -> None:
# プレーヤーに送信します
# 音声をファイルに保存します
self.file.write(data)
# 音声合成のコールバックを呼び出します
synthesizer_callback = Callback()
# 音声合成器を初期化します
# voice、format、sample_rate などの合成パラメーターをカスタマイズできます
if USE_CONNECTION_POOL:
speech_synthesizer = connectionPool.borrow_synthesizer(
model='cosyvoice-v3-flash',
voice='longanyang',
seed=12382,
callback=synthesizer_callback
)
else:
speech_synthesizer = SpeechSynthesizer(model='cosyvoice-v3-flash',
voice='longanyang',
seed=12382,
callback=synthesizer_callback)
try:
speech_synthesizer.call(text)
except Exception as e:
print(f'[task_{task_id}] speech synthesis task failed, {e}')
if USE_CONNECTION_POOL:
# 接続プールを使用しているときにタスクが失敗した場合は、手動でシンセサイザーの接続を閉じる必要があります。
speech_synthesizer.close()
return
print('[task_{}] Synthesized text: {}'.format(task_id, text))
complete_event.wait()
print('[task_{}][Metric] requestId: {}, first package delay ms: {}'.format(
task_id,
speech_synthesizer.get_last_request_id(),
speech_synthesizer.get_first_package_delay()))
if USE_CONNECTION_POOL:
connectionPool.return_synthesizer(speech_synthesizer)
# main 関数
if __name__ == '__main__':
init_dashscope_api_key()
task_thread_list = []
for task_id in range(3):
thread = threading.Thread(
target=synthesis_text_to_speech_and_play_by_streaming_mode,
args=(text_to_synthesize[task_id], task_id))
task_thread_list.append(thread)
for task_thread in task_thread_list:
task_thread.start()
for task_thread in task_thread_list:
task_thread.join()
if USE_CONNECTION_POOL:
connectionPool.shutdown()リソース管理と例外処理
タスクの成功:音声合成タスクが正常に完了した場合、
connectionPool.return_synthesizer(speech_synthesizer)を呼び出してSpeechSynthesizerオブジェクトをプールに返し、再利用できるようにする必要があります。重要未完了または失敗したタスクの
SpeechSynthesizerオブジェクトは返さないでください。タスクの失敗:SDK 内部の例外またはビジネスロジックの例外によってタスクが中断された場合、
speech_synthesizer.close()を呼び出して、基盤となる WebSocket 接続を手動で閉じる必要があります。すべての音声合成タスクが完了したら、
connectionPool.shutdown()を呼び出してオブジェクトプールを閉じます。`TaskFailed` エラーが発生した場合、追加の処理は必要ありません。
Java SDK:接続とオブジェクトプールの最適化
Java SDK は、組み込みの接続プールとカスタムオブジェクトプールを使用して連携し、最適なパフォーマンスを提供します。
接続プール:SDK は OkHttp3 接続プールを統合して、基盤となる WebSocket 接続を管理および再利用します。これにより、ネットワークハンドシェイクのオーバーヘッドが削減されます。この機能はデフォルトで有効になっています。
オブジェクトプール:
commons-pool2に基づいて実装されたこのプールは、事前に接続が確立されたSpeechSynthesizerオブジェクトのセットを維持します。プールからオブジェクトを取得することで、接続設定のレイテンシーがなくなり、ファーストパケットレイテンシーが大幅に削減されます。
実装手順
依存関係の追加
プロジェクトのビルドツールに基づいて、`dashscope-sdk-java` と `commons-pool2` を依存関係構成ファイルに追加します。
以下のセクションでは、Maven と Gradle の構成を示します。
Maven
Maven プロジェクトの
pom.xmlファイルを開きます。<dependencies>タグ内に次の依存関係情報を追加します。
<dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <!-- 'the-latest-version' をバージョン 2.16.9 以降に置き換えます。関連するバージョン番号は https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java で確認できます --> <version>the-latest-version</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <!-- 'the-latest-version' を最新バージョンに置き換えます。関連するバージョン番号は https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 で確認できます --> <version>the-latest-version</version> </dependency>pom.xmlファイルを保存します。mvn clean installやmvn compileなどの Maven コマンドを使用して、プロジェクトの依存関係を更新します。
Gradle
Gradle プロジェクトの
build.gradleファイルを開きます。dependenciesブロック内に次の依存関係情報を追加します。dependencies { // 'the-latest-version' をバージョン 2.16.6 以降に置き換えます。関連するバージョン番号は https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java で確認できます implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version' // 'the-latest-version' を最新バージョンに置き換えます。関連するバージョン番号は https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 で確認できます implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version' }build.gradleファイルを保存します。コマンドラインで、プロジェクトのルートディレクトリに移動し、次の Gradle コマンドを実行してプロジェクトの依存関係を更新します。
./gradlew build --refresh-dependenciesWindows を使用している場合は、次のコマンドを実行します。
gradlew build --refresh-dependencies
接続プールの構成
環境変数を使用して、主要な接続プールパラメーターを構成できます。
環境変数
説明
DASHSCOPE_CONNECTION_POOL_SIZE
接続プールのサイズ。
推奨値:ピーク時の同時実行数の少なくとも 2 倍。
デフォルト値:32
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS
非同期リクエストの最大数。
推奨値:
DASHSCOPE_CONNECTION_POOL_SIZEと同じ。デフォルト値:32
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST
ホストごとの非同期リクエストの最大数。
推奨値:
DASHSCOPE_CONNECTION_POOL_SIZEと同じ。デフォルト値:32
オブジェクトプールの構成
環境変数を使用して、オブジェクトプールのサイズを構成できます。
環境変数
説明
COSYVOICE_OBJECTPOOL_SIZE
オブジェクトプールのサイズ。
推奨値:ピーク時の同時実行数の 1.5 倍から 2 倍。
デフォルト値:500
重要オブジェクトプールのサイズ (
COSYVOICE_OBJECTPOOL_SIZE) は、接続プールのサイズ (DASHSCOPE_CONNECTION_POOL_SIZE) 以下である必要があります。そうでない場合、オブジェクトプールがオブジェクトをリクエストしたときに接続プールがいっぱいだと、呼び出し元のスレッドは接続が利用可能になるまでブロックされます。オブジェクトプールのサイズは、アカウントの QPS 制限を超えてはなりません。
次のコードを使用してオブジェクトプールを作成します。
class CosyvoiceObjectPool { // ... 他のコードは省略されています。完全な例については、完全なコードをご参照ください。 public static GenericObjectPool<SpeechSynthesizer> getInstance() { lock.lock(); if (synthesizerPool == null) { // オブジェクトプールのサイズは、ここで設定するか、COSYVOICE_OBJECTPOOL_SIZE 環境変数で設定できます。 // サーバーの最大同時接続数の 1.5 倍から 2 倍に設定します。 int objectPoolSize = getObjectivePoolSize(); SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory = new SpeechSynthesizerObjectFactory(); GenericObjectPoolConfig<SpeechSynthesizer> config = new GenericObjectPoolConfig<>(); config.setMaxTotal(objectPoolSize); config.setMaxIdle(objectPoolSize); config.setMinIdle(objectPoolSize); synthesizerPool = new GenericObjectPool<>(speechSynthesizerObjectFactory, config); } lock.unlock(); return synthesizerPool; } }オブジェクトプールから
SpeechSynthesizerオブジェクトを取得現在使用中のオブジェクト数がプールの最大容量を超えると、システムは新しい
SpeechSynthesizerオブジェクトを作成します。この新しいオブジェクトは再初期化し、新しい WebSocket 接続を確立する必要があります。プールから既存の接続リソースを使用できないため、リソース再利用の恩恵は受けられません。
synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();音声合成の実行
SpeechSynthesizerオブジェクトの `call` または `streamingCall` メソッドを呼び出して、音声合成を実行します。SpeechSynthesizerオブジェクトを返す音声合成タスクが完了したら、
SpeechSynthesizerオブジェクトを返して、後続のタスクで再利用できるようにします。未完了または失敗したタスクのオブジェクトは返さないでください。
CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
完全なコード
package org.alibaba.bailian.example.examples;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.exception.NoApiKeyException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
/**
* プロジェクトに org.apache.commons.pool2 と DashScope パッケージをインポートする必要があります。
*
* DashScope SDK バージョン 2.16.6 以降は、高同時実行シナリオ向けに最適化されています。
* DashScope SDK 2.16.6 より前のバージョンは、高同時実行シナリオでの使用は推奨されません。
*
*
* 音声合成 (TTS) サービスに対して高同時実行の呼び出しを行う前に、
* 次の環境変数を使用して接続プールパラメーターを構成します。
*
* DASHSCOPE_MAXIMUM_ASYNC_REQUESTS
* DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST
* DASHSCOPE_CONNECTION_POOL_SIZE
*
*/
class SpeechSynthesizerObjectFactory
extends BasePooledObjectFactory<SpeechSynthesizer> {
public SpeechSynthesizerObjectFactory() {
super();
}
@Override
public SpeechSynthesizer create() throws Exception {
return new SpeechSynthesizer();
}
@Override
public PooledObject<SpeechSynthesizer> wrap(SpeechSynthesizer obj) {
return new DefaultPooledObject<>(obj);
}
}
class CosyvoiceObjectPool {
public static GenericObjectPool<SpeechSynthesizer> synthesizerPool;
public static String COSYVOICE_OBJECTPOOL_SIZE_ENV = "COSYVOICE_OBJECTPOOL_SIZE";
public static int DEFAULT_OBJECT_POOL_SIZE = 500;
private static Lock lock = new java.util.concurrent.locks.ReentrantLock();
public static int getObjectivePoolSize() {
try {
Integer n = Integer.parseInt(System.getenv(COSYVOICE_OBJECTPOOL_SIZE_ENV));
System.out.println("Using Object Pool Size In Env: "+ n);
return n;
} catch (NumberFormatException e) {
System.out.println("Using Default Object Pool Size: "+ DEFAULT_OBJECT_POOL_SIZE);
return DEFAULT_OBJECT_POOL_SIZE;
}
}
public static GenericObjectPool<SpeechSynthesizer> getInstance() {
lock.lock();
if (synthesizerPool == null) {
// オブジェクトプールのサイズは、ここで設定するか、COSYVOICE_OBJECTPOOL_SIZE 環境変数で設定できます。
// サーバーの最大同時接続数の 1.5 倍から 2 倍に設定します。
int objectPoolSize = getObjectivePoolSize();
SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory =
new SpeechSynthesizerObjectFactory();
GenericObjectPoolConfig<SpeechSynthesizer> config =
new GenericObjectPoolConfig<>();
config.setMaxTotal(objectPoolSize);
config.setMaxIdle(objectPoolSize);
config.setMinIdle(objectPoolSize);
synthesizerPool =
new GenericObjectPool<>(speechSynthesizerObjectFactory, config);
}
lock.unlock();
return synthesizerPool;
}
}
class SynthesizeTaskWithCallback implements Runnable {
String[] textArray;
String requestId;
long timeCost;
public SynthesizeTaskWithCallback(String[] textArray) {
this.textArray = textArray;
}
@Override
public void run() {
SpeechSynthesizer synthesizer = null;
long startTime = System.currentTimeMillis();
// onError が受信された場合
final boolean[] hasError = {false};
try {
class ReactCallback extends ResultCallback<SpeechSynthesisResult> {
ReactCallback() {}
@Override
public void onEvent(SpeechSynthesisResult message) {
if (message.getAudioFrame() != null) {
try {
byte[] bytesArray = message.getAudioFrame().array();
System.out.println("Received audio. Audio file stream length: " + bytesArray.length);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onComplete() {}
@Override
public void onError(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
hasError[0] = true;
}
}
// your-dashscope-api-key をご自身の API キーに置き換えます。
String dashScopeApiKey = "your-dashscope-api-key";
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
.model("cosyvoice-v3-flash")
.voice("longanyang")
.format(SpeechSynthesisAudioFormat
.MP3_22050HZ_MONO_256KBPS) // ストリーミング合成には PCM または MP3 を使用します。
.apiKey(dashScopeApiKey)
.build();
try {
synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
synthesizer.updateParamAndCallback(param, new ReactCallback());
for (String text : textArray) {
synthesizer.streamingCall(text);
}
Thread.sleep(20);
synthesizer.streamingComplete(60000);
requestId = synthesizer.getLastRequestId();
} catch (Exception e) {
System.out.println("Exception e: " + e.toString());
hasError[0] = true;
}
} catch (Exception e) {
hasError[0] = true;
throw new RuntimeException(e);
}
if (synthesizer != null) {
try {
if (hasError[0] == true) {
// 例外が発生した場合は、接続を閉じてプール内のオブジェクトを無効化します。
synthesizer.getDuplexApi().close(1000, "bye");
CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
} else {
// タスクが正常に終了した場合は、オブジェクトを返します。
CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
timeCost = endTime - startTime;
System.out.println("[Thread " + Thread.currentThread() + "] Speech synthesis task finished. Time cost: " + timeCost + " ms, RequestId: " + requestId);
}
}
}
@Slf4j
public class SynthesizeTextToSpeechWithCallbackConcurrently {
public static void checkoutEnv(String envName, int defaultSize) {
if (System.getenv(envName) != null) {
System.out.println("[ENV CHECK]: " + envName + " "
+ System.getenv(envName));
} else {
System.out.println("[ENV CHECK]: " + envName
+ " Using Default which is " + defaultSize);
}
}
public static void main(String[] args)
throws InterruptedException, NoApiKeyException {
// 接続プールの環境変数を確認します
checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32);
checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32);
checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32);
checkoutEnv(CosyvoiceObjectPool.COSYVOICE_OBJECTPOOL_SIZE_ENV, CosyvoiceObjectPool.DEFAULT_OBJECT_POOL_SIZE);
int runTimes = 3;
// SpeechSynthesizer オブジェクトのプールを作成します
ExecutorService executorService = Executors.newFixedThreadPool(runTimes);
for (int i = 0; i < runTimes; i++) {
// タスクの送信時刻を記録します
LocalDateTime submissionTime = LocalDateTime.now();
executorService.submit(new SynthesizeTaskWithCallback(new String[] {
"Moonlight before my bed,", "Is it frost upon the ground?", "I lift my head to see the moon,", "I drop my head and think of home."}));
}
// ExecutorService をシャットダウンし、すべてのタスクが完了するのを待ちます
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
System.exit(0);
}
}推奨構成
以下の構成は、指定された構成の Alibaba Cloud サーバーで CosyVoice 音声合成サービスを実行したテスト結果に基づいています。高同時実行により、タスクの処理遅延が発生する可能性があります。
サーバーごとの同時実行数は、同時に実行できる CosyVoice 音声合成タスクの数です。この値はワーカースレッドの数に相当します。
サーバー構成 (Alibaba Cloud) | サーバーごとの最大同時実行数 | オブジェクトプールのサイズ | 接続プールのサイズ |
4 コア 8 GiB | 100 | 500 | 2000 |
8 コア 16 GiB | 150 | 500 | 2000 |
16 コア 32 GiB | 200 | 500 | 2000 |
リソース管理と異常処理
タスクの成功:音声合成タスクが正常に完了した場合、`GenericObjectPool` の `returnObject` メソッドを呼び出して
SpeechSynthesizerオブジェクトをプールに返し、再利用できるようにする必要があります。提供されたコード例では、これは
CosyvoiceObjectPool.getInstance().returnObject(synthesizer)を呼び出すことによって行われます。重要未完了または失敗したタスクの
SpeechSynthesizerオブジェクトは返さないでください。タスクの失敗:SDK 内部の例外またはビジネスロジックの例外によってタスクが中断された場合、次の 2 つの操作を実行する必要があります。
基盤となる WebSocket 接続を閉じます。
オブジェクトプール内のオブジェクトを無効化して、再度使用されないようにします。
// これは現在のコードの以下の内容に対応します // 接続を閉じます synthesizer.getDuplexApi().close(1000, "bye"); // オブジェクトプール内の問題のあるシンセサイザーを無効化します CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);`TaskFailed` エラーが発生した場合、追加の処理は必要ありません。
呼び出しのウォームアップとレイテンシー統計
同時呼び出しレイテンシーなど、DashScope Java SDK のパフォーマンスメトリックを評価する場合、正式なテストを開始する前に完全なウォームアップを実行してください。ウォームアップにより、テスト結果が安定した状態でのサービスのパフォーマンスを正確に反映することが保証されます。これにより、初期接続時間によるデータバイアスを防ぎます。
接続再利用メカニズム
DashScope Java SDK は、グローバルなシングルトン接続プールを使用して WebSocket 接続を効率的に管理および再利用します。これにより、頻繁な接続の作成とクローズのオーバーヘッドが削減され、高同時実行シナリオでのパフォーマンスが向上します。
このメカニズムは次のように機能します。
オンデマンド作成:SDK はサービス開始時に WebSocket 接続を作成しません。代わりに、最初の呼び出しで接続を確立します。
期間限定の再利用:リクエストが完了した後、接続は再利用のために最大 60 秒間プールに残ります。
60 秒以内に新しいリクエストが到着した場合、既存の接続が再利用されます。これにより、別のハンドシェイクのオーバーヘッドが回避されます。
接続が 60 秒以上アイドル状態の場合、リソースを解放するために自動的に閉じられます。
ウォームアップの重要性
以下のシナリオでは、接続プールに再利用可能なアクティブな接続がない可能性があります。この場合、新しい接続を確立する必要があります。
アプリケーションが起動したばかりで、まだ呼び出しを行っていない。
サービスが 60 秒以上アイドル状態であったため、プール内の接続がタイムアウトにより閉じられた。
これらの場合、最初のいくつかのリクエストは完全な WebSocket 接続プロセスをトリガーします。このプロセスには、TCP ハンドシェイク、Transport Layer Security (TLS) 暗号化ネゴシエーション、およびプロトコルアップグレードが含まれます。これらのリクエストのエンドツーエンドのレイテンシーは、接続を再利用する後のリクエストよりもはるかに高くなります。この追加時間は、サービスの処理レイテンシーではなく、ネットワーク接続の初期化によるものです。したがって、ウォームアップを行わないと、この初期接続オーバーヘッドが含まれるため、パフォーマンステストの結果に偏りが生じます。
推奨されるプラクティス
信頼性の高いパフォーマンスデータを取得するには、ストレステストやレイテンシー測定を開始する前に、次のウォームアップ手順に従ってください。
正式なテストの同時実行レベルをシミュレートします。事前に多数の呼び出し (たとえば、1〜2 分間) を行い、接続プールを埋めます。
接続プールに十分なアクティブな接続があることを確認します。その後、正式なパフォーマンスデータの収集を開始します。
適切なウォームアップにより、SDK 接続プールは安定した再利用状態になります。これにより、安定したランタイム中のサービスの真のパフォーマンスを反映する、より代表的なレイテンシーメトリックを測定できます。