すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud Model Studio:CosyVoice の高同時実行シナリオ

最終更新日:Jan 06, 2026

CosyVoice 音声合成サービスは、WebSocket プロトコルを使用してリアルタイムのストリーミング通信を行います。高同時実行シナリオでは、リクエストごとに WebSocket 接続を作成および破棄すると、ネットワークとシステムリソースに大きなオーバーヘッドが生じ、接続レイテンシーが発生します。パフォーマンスを最適化し、安定性を確保するために、DashScope ソフトウェア開発キット (SDK) は、接続プールやオブジェクトプールなどのリソース再利用メカニズムを提供します。このドキュメントでは、DashScope Python および Java SDK でこれらの機能を使用して、高同時実行シナリオで CosyVoice サービスを効率的に呼び出す方法について説明します。

重要

中国 (北京) リージョンのモデルを使用するには、中国 (北京) リージョンの API キー ページに移動します

前提条件

  • API キーの取得

  • 互換性のあるバージョンの DashScope SDK がインストールされていること。最新バージョンをインストールすることを推奨します。

    • Python SDK:バージョン 1.25.2 以降

    • Java SDK:バージョン 2.16.6 以降

Python SDK:オブジェクトプールの最適化

Python SDK は、SpeechSynthesizerObjectPool クラスを使用して、オブジェクトプールの最適化を通じて SpeechSynthesizer オブジェクトを管理および再利用します。

オブジェクトプールが初期化されると、指定された数の SpeechSynthesizer インスタンスが作成され、WebSocket 接続が事前に確立されます。プールからオブジェクトを取得すると、接続が確立されるのを待たずに直接リクエストを送信できます。この方法により、ファーストパケットレイテンシーを効果的に削減できます。タスクが完了し、オブジェクトがプールに返されると、その WebSocket 接続はアクティブなままで、次のタスクに備えます。

実装手順

  1. 依存関係のインストール:pip install -U dashscope を実行して DashScope の依存関係をインストールします。

  2. オブジェクトプールの作成と構成

    SpeechSynthesizerObjectPool を使用してオブジェクトプールのサイズを設定します。この値は、ピーク時の同時実行数の 1.5 倍から 2 倍に設定することを推奨します。オブジェクトプールのサイズは、アカウントのクエリ/秒 (QPS) 制限を超えてはなりません。

    次のコードを使用して、固定サイズのグローバルなシングルトンオブジェクトプールを作成します。オブジェクトプールが初期化されると、指定された数の SpeechSynthesizer オブジェクトがすぐに作成され、WebSocket 接続が確立されます。このプロセスには時間がかかります。

    from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool
    
    synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)
  3. オブジェクトプールから SpeechSynthesizer オブジェクトを取得

    現在使用中のオブジェクト数がプールの最大容量を超えると、システムは新しい SpeechSynthesizer オブジェクトを作成します。

    この新しいオブジェクトは再初期化し、新しい WebSocket 接続を確立する必要があります。プールから既存の接続リソースを使用できないため、リソース再利用の恩恵は受けられません。

    speech_synthesizer = connectionPool.borrow_synthesizer(
        model='cosyvoice-v3-flash',
        voice='longanyang',
        seed=12382,
        callback=synthesizer_callback
    )
  4. 音声合成の実行

    SpeechSynthesizer オブジェクトの `call` または `streaming_call` メソッドを呼び出して、音声合成を実行します。

  5. 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 オブジェクトのセットを維持します。プールからオブジェクトを取得することで、接続設定のレイテンシーがなくなり、ファーストパケットレイテンシーが大幅に削減されます。

実装手順

  1. 依存関係の追加

    プロジェクトのビルドツールに基づいて、`dashscope-sdk-java` と `commons-pool2` を依存関係構成ファイルに追加します。

    以下のセクションでは、Maven と Gradle の構成を示します。

    Maven

    1. Maven プロジェクトの pom.xml ファイルを開きます。

    2. <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>
    1. pom.xml ファイルを保存します。

    2. mvn clean installmvn compile などの Maven コマンドを使用して、プロジェクトの依存関係を更新します。

    Gradle

    1. Gradle プロジェクトの build.gradle ファイルを開きます。

    2. 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'
      }
    3. build.gradle ファイルを保存します。

    4. コマンドラインで、プロジェクトのルートディレクトリに移動し、次の Gradle コマンドを実行してプロジェクトの依存関係を更新します。

      ./gradlew build --refresh-dependencies

      Windows を使用している場合は、次のコマンドを実行します。

      gradlew build --refresh-dependencies
  2. 接続プールの構成

    環境変数を使用して、主要な接続プールパラメーターを構成できます。

    環境変数

    説明

    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

  3. オブジェクトプールの構成

    環境変数を使用して、オブジェクトプールのサイズを構成できます。

    環境変数

    説明

    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;
        }
    }
  4. オブジェクトプールから SpeechSynthesizer オブジェクトを取得

    現在使用中のオブジェクト数がプールの最大容量を超えると、システムは新しい SpeechSynthesizer オブジェクトを作成します。

    この新しいオブジェクトは再初期化し、新しい WebSocket 接続を確立する必要があります。プールから既存の接続リソースを使用できないため、リソース再利用の恩恵は受けられません。

    synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
  5. 音声合成の実行

    SpeechSynthesizer オブジェクトの `call` または `streamingCall` メソッドを呼び出して、音声合成を実行します。

  6. 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 つの操作を実行する必要があります。

    1. 基盤となる WebSocket 接続を閉じます。

    2. オブジェクトプール内のオブジェクトを無効化して、再度使用されないようにします。

    // これは現在のコードの以下の内容に対応します
    // 接続を閉じます
    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. 正式なテストの同時実行レベルをシミュレートします。事前に多数の呼び出し (たとえば、1〜2 分間) を行い、接続プールを埋めます。

  2. 接続プールに十分なアクティブな接続があることを確認します。その後、正式なパフォーマンスデータの収集を開始します。

適切なウォームアップにより、SDK 接続プールは安定した再利用状態になります。これにより、安定したランタイム中のサービスの真のパフォーマンスを反映する、より代表的なレイテンシーメトリックを測定できます。

Java SDK の一般的な例外

例外 1:サービストラフィックは安定しているが、サーバー上の TCP 接続数が増え続ける

原因:

タイプ 1:

各 SDK オブジェクトは作成時に接続を要求します。オブジェクトプールを使用しない場合、オブジェクトは各タスクの完了後に破棄されます。このアクションにより、接続は参照されていない状態のままになり、サーバー側の接続タイムアウトである 61 秒後にのみ切断されます。その結果、この 61 秒間は接続を再利用できません。

高同時実行シナリオでは、再利用可能な接続がない場合、新しいタスクは新しい接続を作成します。これにより、次の問題が発生します。

  1. 接続数が継続的に増加する。

  2. 過剰な接続が利用可能なサーバーリソースを消費するため、サーバーのパフォーマンスが低下する。

  3. 接続プールがいっぱいになり、新しいタスクは利用可能な接続を待つ間ブロックされる。

タイプ 2:

オブジェクトプールの `MaxIdle` パラメーターが `MaxTotal` パラメーターよりも小さい値に設定されています。その結果、プールにアイドル状態のオブジェクトがある場合、`MaxIdle` 制限を超えるオブジェクトは破棄されます。このプロセスにより、接続リークが発生する可能性があります。これらのリークした接続は、61 秒のタイムアウト後にのみ切断されます。タイプ 1 の原因と同様に、これにより接続数が継続的に増加します。

解決策:

タイプ 1 の原因については、オブジェクトプールを使用してください。

タイプ 2 の原因については、オブジェクトプールの構成パラメーターを確認してください。`MaxIdle` と `MaxTotal` を同じ値に設定し、自動オブジェクトプール破棄ポリシーを無効にします。

例外 2:タスクが通常の呼び出しより 60 秒長くかかる

原因は例外 1 と同じです。接続プールが最大接続数に達しました。新しいタスクは、新しい接続を取得する前に、参照されていない接続がタイムアウトするまで 61 秒待つ必要があります。

例外 3:サービス開始時にタスクが遅く、その後徐々に正常に戻る

原因:

高同時実行の呼び出し中、単一のオブジェクトは複数のタスクに対して WebSocket 接続を再利用します。したがって、WebSocket 接続は通常、サービス開始時にのみ作成されます。タスクの起動段階で高同時実行の呼び出しがすぐに開始されると、同時にあまりにも多くの WebSocket 接続を作成するとブロッキングが発生する可能性があることに注意してください。

解決策:

同時実行数を徐々に増やすか、サービス開始後にプリフェッチタスクを追加します。

例外 4:サーバーが「Invalid action('run-task')! Please follow the protocol!」エラーを報告する

原因:

クライアント側でエラーが発生した場合、サーバーには通知されず、接続はタスク進行中の状態のままになります。この接続と関連するオブジェクトが新しいタスクに再利用されると、プロトコルエラーが発生し、新しいタスクが失敗します。

解決策:

クライアント側の例外がスローされた後、WebSocket 接続を明示的に閉じてから、オブジェクトをオブジェクトプールに返す必要があります。

例外 5:サービストラフィックは安定しているが、呼び出し量に異常なスパイクがある

原因:

同時にあまりにも多くの WebSocket 接続を作成すると、ブロッキングが発生します。受信サービストラフィックは継続するため、タスクの短期的なバックログが作成されます。ブロッキングが解決されると、すべてのバックログされたタスクが一度に呼び出されます。これにより、呼び出し量が急増し、Alibaba Cloud アカウントの同時実行数制限を一時的に超える可能性があり、タスクの失敗、サーバーのパフォーマンス低下、その他の問題が発生する可能性があります。

一度にあまりにも多くの WebSocket 接続を作成することは、通常、次のシナリオで発生します。

  • サービス起動段階

  • 多くの WebSocket 接続が同時に中断され、再接続されるネットワーク例外が発生した場合。

  • 多くのサーバー側エラーが同時に発生し、多くの WebSocket 再接続につながる場合。一般的なエラーは、同時実行数がアカウント制限を超えた場合に発生します (「Requests rate limit exceeded, please try again later.」)。

解決策:

  1. ネットワークの状態を確認します。

  2. スパイクの前に他の多くのサーバー側エラーが発生したかどうかを確認します。

  3. Alibaba Cloud アカウントの同時実行数制限を増やします。

  4. オブジェクトプールと接続プールのサイズを小さくします。オブジェクトプールの上限を使用して最大同時実行数を制限することもできます。

  5. サーバー構成をアップグレードするか、サーバーの数を増やします。

例外 6:同時実行数が増加するにつれて、すべてのタスクが遅くなる

解決策:

  1. ネットワーク帯域幅制限に達したかどうかを確認します。

  2. 実際の同時実行数がサーバーの仕様に対して高すぎるかどうかを確認します。