All Products
Search
Document Center

Alibaba Cloud Model Studio:Skenario konkurensi tinggi untuk CosyVoice

Last Updated:Feb 11, 2026

Sintesis suara CosyVoice menggunakan protokol WebSocket untuk mendukung komunikasi streaming real-time. Namun, dalam skenario konkurensi tinggi, pembuatan dan penghapusan koneksi WebSocket untuk setiap permintaan menimbulkan overhead signifikan pada jaringan dan sumber daya sistem serta latensi koneksi yang terasa. Untuk mengoptimalkan kinerja dan memastikan stabilitas, SDK DashScope menyediakan mekanisme penggunaan ulang sumber daya yang efisien—seperti connection pool dan object pool. Dokumen ini menjelaskan cara menggunakan fitur-fitur tersebut dalam SDK Python dan Java DashScope untuk memanggil CosyVoice secara efisien dalam kondisi konkurensi tinggi.

Prasyarat

SDK Python: Optimisasi object pool

SDK Python menggunakan SpeechSynthesizerObjectPool untuk menerapkan optimisasi object pool. Pool ini mengelola dan menggunakan kembali objek SpeechSynthesizer.

Saat inisialisasi, object pool membuat sejumlah instance SpeechSynthesizer sesuai yang ditentukan dan menetapkan koneksi WebSocket sebelumnya. Saat Anda mengambil objek dari pool, tidak ada waktu yang dihabiskan untuk membuat koneksi baru. Anda dapat langsung mengirim permintaan, sehingga mengurangi latensi paket pertama. Setelah tugas selesai dan Anda mengembalikan objek ke pool, koneksi WebSocket-nya tetap terbuka dan siap digunakan kembali.

Langkah implementasi

  1. Instal dependensi: Instal paket DashScope (pip install -U dashscope).

  2. Buat dan konfigurasikan object pool.

    Tentukan ukuran pool menggunakan SpeechSynthesizerObjectPool. Nilai yang direkomendasikan adalah 1,5× hingga 2× konkurensi puncak Anda. Jangan atur ukuran pool melebihi batas permintaan per detik (QPS) akun Anda.

    Gunakan kode berikut untuk membuat object pool berukuran tetap global singleton. Pool ini membuat jumlah objek SpeechSynthesizer yang ditentukan dan menetapkan koneksi WebSocket selama inisialisasi. Langkah ini memerlukan waktu.

    from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool
    
    synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)
  3. Ambil objek SpeechSynthesizer dari pool.

    Jika jumlah objek yang sedang dipinjam melebihi ukuran maksimum pool, sistem akan membuat objek SpeechSynthesizer baru.

    Objek yang baru dibuat ini harus melakukan inisialisasi dan menetapkan koneksi WebSocket. Objek tersebut tidak dapat menggunakan kembali koneksi yang ada di pool, sehingga tidak mendapat manfaat dari pooling.

    speech_synthesizer = connectionPool.borrow_synthesizer(
        model='cosyvoice-v3-flash',
        voice='longanyang',
        seed=12382,
        callback=synthesizer_callback
    )
  4. Lakukan sintesis suara.

    Panggil metode call atau streaming_call dari objek SpeechSynthesizer untuk mensintesis suara.

  5. Kembalikan objek SpeechSynthesizer.

    Setelah tugas sintesis suara selesai, kembalikan objek SpeechSynthesizer agar dapat digunakan kembali oleh tugas berikutnya.

    Jangan mengembalikan objek dari tugas yang belum selesai atau gagal.

    connectionPool.return_synthesizer(speech_synthesizer)

Kode lengkap

# !/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 = [
    'First sentence: Welcome to Alibaba Cloud speech synthesis.',
    'Second sentence: Welcome to Alibaba Cloud speech synthesis.',
    'Third sentence: Welcome to Alibaba Cloud speech synthesis.',
]
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():
    '''
    Setel Kunci API DashScope Anda. Informasi lebih lanjut:
    https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
    '''
    # Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API Anda: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
    if 'DASHSCOPE_API_KEY' in os.environ:
        dashscope.api_key = os.environ[
            'DASHSCOPE_API_KEY']  # muat Kunci API dari variabel lingkungan DASHSCOPE_API_KEY
    else:
        dashscope.api_key = '<your-dashscope-api-key>'  # atur Kunci API secara manual


def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id):
    global USE_CONNECTION_POOL, connectionPool
    '''
    Sintesis suara dengan teks yang diberikan dalam mode streaming, panggilan asinkron, dan putar audio hasil sintesis secara real-time.
    Untuk informasi lebih lanjut, lihat https://www.alibabacloud.com/help/document_detail/2712523.html
    '''

    complete_event = threading.Event()

    # Definisikan callback untuk menangani hasil

    class Callback(ResultCallback):
        def on_open(self):
            # saat menggunakan object pool, on_open dipanggil setelah tugas dimulai
            self.file = open(f'result_{task_id}.mp3', 'wb')
            print(f'[task_{task_id}] start')

        def on_complete(self):
            print(f'[task_{task_id}] tugas sintesis suara berhasil diselesaikan.')
            complete_event.set()

        def on_error(self, message: str):
            print(f'[task_{task_id}] tugas sintesis suara gagal, {message}')

        def on_close(self):
            # saat menggunakan object pool, on_open dipanggil setelah tugas selesai
            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:
            # kirim ke pemutar
            # simpan audio ke file
            self.file.write(data)

    # Panggil callback synthesizer suara
    synthesizer_callback = Callback()

    # Inisialisasi synthesizer suara
    # Anda dapat menyesuaikan parameter sintesis, seperti voice, format, sample_rate, atau parameter lainnya
    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}] tugas sintesis suara gagal, {e}')
        if USE_CONNECTION_POOL:
            # tutup koneksi synthesizer secara manual jika tugas gagal saat menggunakan connection pool.
            speech_synthesizer.close()
        return

    print('[task_{}] Teks yang disintesis: {}'.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)


# fungsi utama
if __name__ == '__main__':
    # URL berikut untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti dengan: wss://dashscope.aliyuncs.com/api-ws/v1/inference
    dashscope.base_websocket_api_url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference'
    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()

Manajemen resource dan penanganan error

  • Tugas berhasil: Saat tugas sintesis suara selesai secara normal, panggil connectionPool.return_synthesizer(speech_synthesizer) untuk mengembalikan objek SpeechSynthesizer ke pool agar dapat digunakan kembali.

    Penting

    Jangan mengembalikan objek SpeechSynthesizer dari tugas yang belum selesai atau gagal.

  • Tugas gagal: Saat terjadi exception dari SDK atau logika bisnis Anda yang mengganggu tugas, tutup koneksi WebSocket yang mendasari secara manual: speech_synthesizer.close().

  • Setelah semua tugas sintesis suara selesai, matikan object pool menggunakan connectionPool.shutdown().

  • Tidak diperlukan tindakan tambahan saat layanan mengembalikan error TaskFailed.

SDK Java: Optimisasi connection pool dan object pool

SDK Java mencapai kinerja optimal dengan menggabungkan connection pool internal dan object pool kustom.

  • Connection pool: SDK menggunakan connection pool bawaan OkHttp3 untuk mengelola dan menggunakan kembali koneksi WebSocket yang mendasari. Ini mengurangi overhead handshake dan diaktifkan secara default.

  • Object pool: Dibangun di atas commons-pool2, pool ini mempertahankan sekelompok objek SpeechSynthesizer dengan koneksi yang telah ditetapkan sebelumnya. Mengambil objek dari pool menghilangkan latensi penyiapan koneksi dan secara signifikan mengurangi latensi paket pertama.

Langkah implementasi

  1. Tambahkan dependensi.

    Tambahkan dashscope-sdk-java dan commons-pool2 ke file konfigurasi dependensi proyek Anda, sesuai dengan tool build yang Anda gunakan.

    Contoh untuk Maven dan Gradle:

    Maven

    1. Buka file pom.xml proyek Maven Anda.

    2. Tambahkan dependensi berikut di dalam tag <dependencies>.

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dashscope-sdk-java</artifactId>
        <!-- Ganti 'the-latest-version' dengan versi 2.16.9 atau lebih baru. Periksa versi yang tersedia di: 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>
        <!-- Ganti 'the-latest-version' dengan versi terbaru. Periksa versi yang tersedia di: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <version>the-latest-version</version>
    </dependency>
    1. Simpan file pom.xml.

    2. Jalankan perintah Maven seperti mvn clean install atau mvn compile untuk memperbarui dependensi proyek.

    Gradle

    1. Buka file build.gradle proyek Gradle Anda.

    2. Tambahkan dependensi berikut di dalam blok dependencies.

      dependencies {
          // Ganti 'the-latest-version' dengan versi 2.16.6 atau lebih baru. Periksa versi yang tersedia di: https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java
          implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version'
          
          // Ganti 'the-latest-version' dengan versi terbaru. Periksa versi yang tersedia di: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
          implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version'
      }
    3. Simpan file build.gradle.

    4. Di terminal Anda, navigasi ke direktori root proyek dan jalankan perintah Gradle berikut untuk memperbarui dependensi.

      ./gradlew build --refresh-dependencies

      Atau, jika Anda menggunakan Windows, jalankan:

      gradlew build --refresh-dependencies
  2. Konfigurasikan connection pool.

    Tentukan parameter kunci connection pool menggunakan variabel lingkungan:

    Variabel lingkungan

    Deskripsi

    DASHSCOPE_CONNECTION_POOL_SIZE

    Ukuran connection pool.

    Nilai yang direkomendasikan: lebih dari 2× konkurensi puncak Anda.

    Default: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS

    Jumlah maksimum permintaan asinkron.

    Nilai yang direkomendasikan: sesuaikan dengan DASHSCOPE_CONNECTION_POOL_SIZE.

    Default: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST

    Jumlah maksimum permintaan asinkron per host.

    Nilai yang direkomendasikan: sesuaikan dengan DASHSCOPE_CONNECTION_POOL_SIZE.

    Default: 32.

  3. Konfigurasikan object pool.

    Tentukan ukuran object pool menggunakan variabel lingkungan:

    Variabel lingkungan

    Deskripsi

    COSYVOICE_OBJECTPOOL_SIZE

    Ukuran object pool.

    Nilai yang direkomendasikan: 1,5× hingga 2× konkurensi puncak Anda.

    Default: 500.

    Penting
    • Ukuran object pool (COSYVOICE_OBJECTPOOL_SIZE) harus kurang dari atau sama dengan ukuran connection pool (DASHSCOPE_CONNECTION_POOL_SIZE). Jika tidak, saat connection pool penuh ketika object pool meminta objek, thread pemanggil akan diblokir hingga koneksi tersedia.

    • Ukuran object pool tidak boleh melebihi batas QPS (permintaan per detik) akun Anda.

    Gunakan kode berikut untuk membuat object pool:

    class CosyvoiceObjectPool {
        // 。。。Kode lain dihilangkan. Lihat contoh lengkap di bawah.
        public static GenericObjectPool<SpeechSynthesizer> getInstance() {
            lock.lock();
            if (synthesizerPool == null) {
                // Anda dapat mengatur ukuran object pool di sini. Atau atur melalui variabel lingkungan COSYVOICE_OBJECTPOOL_SIZE.
                // Nilai yang direkomendasikan: 1,5× hingga 2× koneksi bersamaan maksimum server Anda.
                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. Ambil objek SpeechSynthesizer dari pool.

    Jika jumlah objek yang sedang dipinjam melebihi ukuran maksimum pool, sistem akan membuat objek SpeechSynthesizer baru.

    Objek yang baru dibuat ini harus melakukan inisialisasi dan menetapkan koneksi WebSocket. Objek tersebut tidak dapat menggunakan kembali koneksi yang ada di pool, sehingga tidak mendapat manfaat dari pooling.

    synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
  5. Lakukan sintesis suara.

    Anda dapat memanggil metode call atau streamingCall dari objek SpeechSynthesizer untuk melakukan sintesis suara.

  6. Kembalikan objek SpeechSynthesizer.

    Setelah tugas sintesis suara selesai, kembalikan objek SpeechSynthesizer agar dapat digunakan kembali oleh tugas berikutnya.

    Jangan mengembalikan objek dari tugas yang belum selesai atau gagal.

    CosyvoiceObjectPool.getInstance().returnObject(synthesizer);

Kode lengkap

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 com.alibaba.dashscope.utils.Constants;
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;

/**
 * Proyek Anda harus menyertakan paket org.apache.commons.pool2 dan DashScope.
 *
 * SDK DashScope versi 2.16.6 dan lebih baru dioptimalkan untuk skenario konkurensi tinggi.
 * SDK DashScope versi sebelum 2.16.6 tidak direkomendasikan untuk penggunaan konkurensi tinggi.
 *
 *
 * Sebelum melakukan panggilan konkurensi tinggi ke layanan TTS,
 * konfigurasikan parameter connection pool menggunakan variabel lingkungan berikut:
 *
 * 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("Menggunakan Ukuran Object Pool dari Env: "+ n);
            return n;
        } catch (NumberFormatException e) {
            System.out.println("Menggunakan Ukuran Object Pool Default: "+ DEFAULT_OBJECT_POOL_SIZE);
            return DEFAULT_OBJECT_POOL_SIZE;
        }
    }
    public static GenericObjectPool<SpeechSynthesizer> getInstance() {
        lock.lock();
        if (synthesizerPool == null) {
            // Anda dapat mengatur ukuran object pool di sini. Atau atur melalui variabel lingkungan COSYVOICE_OBJECTPOOL_SIZE.
            // Nilai yang direkomendasikan: 1,5× hingga 2× koneksi bersamaan maksimum server Anda.
            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();
        // jika menerima 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("Audio diterima. Panjang aliran audio: " + 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;
                }
            }

            // Ganti your-dashscope-api-key dengan Kunci API Anda sendiri
            String dashScopeApiKey = "your-dashscope-api-key";

            SpeechSynthesisParam param =
                    SpeechSynthesisParam.builder()
                            .model("cosyvoice-v3-flash")
                            .voice("longanyang")
                            // Kunci API berbeda antara wilayah Singapura dan Beijing. Dapatkan Kunci API Anda: https://www.alibabacloud.com/help/zh/model-studio/get-api-key
                            // Jika Anda tidak mengatur variabel lingkungan, ganti baris di bawah dengan: .apiKey("sk-xxx")
                            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                            .format(SpeechSynthesisAudioFormat
                                    .MP3_22050HZ_MONO_256KBPS) // Gunakan PCM atau MP3 untuk sintesis streaming
                            .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) {
                    // Jika terjadi error, tutup koneksi dan batalkan objek di pool.
                    synthesizer.getDuplexApi().close(1000, "bye");
                    CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
                } else {
                    // Jika tugas selesai secara normal, kembalikan objek.
                    CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            long endTime = System.currentTimeMillis();
            timeCost = endTime - startTime;
            System.out.println("[Thread " + Thread.currentThread() + "] Tugas sintesis suara selesai. Waktu yang dibutuhkan: " + 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
                    + " Menggunakan Default yaitu " + defaultSize);
        }
    }

    public static void main(String[] args)
            throws InterruptedException, NoApiKeyException {
        // URL berikut untuk wilayah Singapura. Jika Anda menggunakan model di wilayah Beijing, ganti dengan: wss://dashscope.aliyuncs.com/api-ws/v1/inference
        Constants.baseWebsocketApiUrl = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference";
        // Periksa env connection pool
        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;
        // Buat pool objek SpeechSynthesis
        ExecutorService executorService = Executors.newFixedThreadPool(runTimes);

        for (int i = 0; i < runTimes; i++) {
            // Catat waktu pengiriman tugas
            LocalDateTime submissionTime = LocalDateTime.now();
            executorService.submit(new SynthesizeTaskWithCallback(new String[] {
                    "Before my bed, moonlight gleams,", "It seems like frost upon the ground.", "I lift my gaze to watch the bright moon,", "Then bow my head, thinking of home."}));
        }

        // Matikan ExecutorService dan tunggu semua tugas selesai
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);
        System.exit(0);
    }
}

Konfigurasi yang direkomendasikan

Konfigurasi berikut didasarkan pada pengujian di mana hanya layanan sintesis suara CosyVoice yang berjalan pada Instance ECS Alibaba Cloud dengan spesifikasi yang ditentukan. Terlalu banyak permintaan bersamaan dapat menyebabkan penundaan tugas.

Konkurensi single-machine mengacu pada jumlah tugas sintesis suara CosyVoice yang berjalan secara bersamaan pada satu mesin. Ini setara dengan jumlah thread pekerja.

Konfigurasi mesin (Alibaba Cloud)

Konkurensi Maksimum per Mesin Tunggal

Ukuran object pool

Ukuran connection pool

4 vCPU, memori 8 GiB

100

500

2000

8 vCPU, memori 16 GiB

150

500

2000

16 vCPU, memori 32 GiB

200

500

2000

Manajemen resource dan penanganan error

  • Tugas berhasil: Saat tugas sintesis suara selesai secara normal, Anda harus memanggil metode returnObject dari GenericObjectPool pada objek SpeechSynthesizer untuk mengembalikannya ke pool agar dapat digunakan kembali.

    Dalam kode saat ini, ini sesuai dengan CosyvoiceObjectPool.getInstance().returnObject(synthesizer).

    Penting

    Jangan mengembalikan objek SpeechSynthesizer dari tugas yang belum selesai atau gagal.

  • Tugas gagal: Saat terjadi exception dari SDK atau logika bisnis Anda yang mengganggu tugas, lakukan kedua tindakan berikut:

    1. Tutup koneksi WebSocket yang mendasari secara manual.

    2. Batalkan objek di object pool untuk mencegah penggunaan kembali.

    // Dalam kode saat ini, ini sesuai dengan:
    // Tutup koneksi
    synthesizer.getDuplexApi().close(1000, "bye");
    // Batalkan synthesizer yang rusak di pool
    CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
  • Tidak diperlukan tindakan tambahan saat layanan mengembalikan error TaskFailed.

Pemanasan awal dan metrik pengukuran waktu

Saat mengevaluasi metrik kinerja seperti latensi untuk panggilan bersamaan ke SDK Java DashScope, lakukan pemanasan awal sistem sebelum pengujian formal. Pemanasan awal memastikan hasil pengukuran secara akurat mencerminkan kinerja layanan yang sebenarnya dalam kondisi stabil dan menghindari kesenjangan data yang disebabkan oleh waktu penyiapan koneksi awal.

Mekanisme penggunaan ulang koneksi

SDK Java DashScope menggunakan connection pool singleton global untuk mengelola dan menggunakan kembali koneksi WebSocket secara efisien. Ini mengurangi overhead pembuatan dan pemutusan koneksi yang sering, meningkatkan throughput dalam skenario konkurensi tinggi.

Perilaku utama mekanisme ini:

  • Pembuatan sesuai permintaan: SDK tidak membuat koneksi WebSocket sebelumnya saat startup. Koneksi dibuat hanya saat panggilan pertama terjadi.

  • Penggunaan ulang berbatas waktu: Setelah permintaan selesai, koneksi tetap berada di pool hingga 60 detik untuk penggunaan ulang.

    • Jika permintaan baru tiba dalam waktu 60 detik, koneksi yang ada digunakan kembali. Ini menghindari overhead handshake.

    • Jika koneksi menganggur lebih dari 60 detik, koneksi tersebut ditutup secara otomatis untuk membebaskan sumber daya.

Mengapa pemanasan awal penting

Dalam kasus berikut, connection pool mungkin tidak berisi koneksi aktif yang dapat digunakan kembali. Permintaan kemudian perlu membuat koneksi baru:

  • Aplikasi Anda baru saja dimulai dan belum melakukan panggilan apa pun.

  • Layanan Anda menganggur lebih dari 60 detik, sehingga semua koneksi di pool telah habis waktu dan ditutup.

Dalam kasus ini, permintaan pertama atau awal memicu proses penyiapan koneksi WebSocket lengkap—termasuk handshake TCP, negosiasi TLS, dan upgrade protokol. Latensi end-to-end mereka jauh lebih tinggi daripada permintaan berikutnya yang menggunakan kembali koneksi. Waktu ekstra ini berasal dari inisialisasi koneksi jaringan—bukan dari pemrosesan layanan. Tanpa pemanasan awal, hasil pengujian kinerja akan condong karena mencakup waktu koneksi awal.

Praktik terbaik

Untuk mengumpulkan data kinerja yang andal, ikuti langkah pemanasan awal berikut sebelum pengujian stres formal atau pengukuran latensi:

  1. Simulasikan tingkat konkurensi pengujian formal Anda. Kirim sejumlah panggilan pemanasan yang cukup—misalnya, selama 1–2 menit—untuk mengisi penuh connection pool.

  2. Pastikan connection pool telah menetapkan dan mempertahankan cukup banyak koneksi aktif sebelum memulai pengumpulan data kinerja formal.

Pemanasan awal yang tepat membawa connection pool SDK ke kondisi penggunaan ulang yang stabil. Ini menghasilkan metrik latensi yang lebih representatif—dan mencerminkan kinerja online dunia nyata.

Error umum SDK Java

Exception 1: Lalu lintas layanan stabil, tetapi jumlah koneksi TCP di server terus meningkat

Penyebab:

Jenis 1:

Setiap objek SDK meminta koneksi saat dibuat. Jika Anda tidak menggunakan object pool, objek dihapus setelah setiap tugas selesai. Tindakan ini meninggalkan koneksi dalam keadaan tidak direferensikan, dan koneksi tersebut diputus hanya setelah timeout koneksi sisi server 61 detik. Akibatnya, koneksi tidak dapat digunakan kembali selama periode 61 detik ini.

Dalam skenario konkurensi tinggi, tugas baru membuat koneksi baru jika tidak ada koneksi yang dapat digunakan kembali. Ini menyebabkan masalah berikut:

  1. Jumlah koneksi terus meningkat.

  2. Kinerja server menurun karena jumlah koneksi yang berlebihan mengonsumsi sumber daya server yang tersedia.

  3. Connection pool menjadi penuh, dan tugas baru diblokir sambil menunggu koneksi yang tersedia.

Jenis 2:

Parameter `MaxIdle` object pool diatur ke nilai yang lebih kecil dari parameter `MaxTotal`. Akibatnya, saat pool memiliki objek menganggur, objek apa pun yang melebihi batas `MaxIdle` dihancurkan. Proses ini dapat menyebabkan kebocoran koneksi. Koneksi yang bocor ini diputus hanya setelah timeout 61 detik. Mirip dengan penyebab Jenis 1, ini menyebabkan peningkatan jumlah koneksi secara terus-menerus.

Solusi:

Untuk penyebab Jenis 1, gunakan object pool.

Untuk penyebab Jenis 2, periksa parameter konfigurasi object pool. Atur `MaxIdle` dan `MaxTotal` ke nilai yang sama, dan nonaktifkan kebijakan penghancuran otomatis object pool.

Exception 2: Tugas membutuhkan waktu 60 detik lebih lama daripada panggilan normal

Penyebabnya sama dengan Exception 1. Connection pool telah mencapai jumlah maksimum koneksi. Tugas baru harus menunggu 61 detik hingga koneksi yang tidak direferensikan habis waktu sebelum tugas tersebut dapat memperoleh koneksi baru.

Exception 3: Tugas lambat saat layanan dimulai dan kemudian secara bertahap kembali normal

Penyebab:

Saat panggilan konkurensi tinggi, satu objek menggunakan kembali koneksi WebSocket-nya untuk beberapa tugas. Oleh karena itu, koneksi WebSocket biasanya dibuat hanya saat layanan dimulai. Perhatikan bahwa jika panggilan konkurensi tinggi dimulai segera selama tahap startup tugas, membuat terlalu banyak koneksi WebSocket secara bersamaan dapat menyebabkan pemblokiran.

Solusi:

Tingkatkan konkurensi secara bertahap, atau tambahkan tugas pra-ambil setelah layanan dimulai.

Exception 4: Server melaporkan error "Invalid action('run-task')! Please follow the protocol!"

Penyebab:

Saat terjadi error di sisi klien, server tidak diberi tahu, dan koneksi tetap dalam keadaan tugas sedang berlangsung. Jika koneksi ini dan objek terkaitnya kemudian digunakan kembali untuk tugas baru, terjadi error protokol, yang menyebabkan tugas baru gagal.

Solusi:

Setelah exception sisi klien dilemparkan, Anda harus secara eksplisit menutup koneksi WebSocket dan kemudian mengembalikan objek ke object pool.

Exception 5: Lalu lintas layanan stabil, tetapi volume panggilan mengalami lonjakan abnormal

Penyebab:

Membuat terlalu banyak koneksi WebSocket secara bersamaan menyebabkan pemblokiran. Karena lalu lintas layanan yang masuk terus berlanjut, backlog tugas jangka pendek tercipta. Setelah pemblokiran terselesaikan, semua tugas yang tertunda dipanggil sekaligus. Ini menyebabkan lonjakan volume panggilan yang dapat sesaat melebihi batas konkurensi untuk Akun Alibaba Cloud Anda, yang dapat mengakibatkan kegagalan tugas, degradasi kinerja server, dan masalah lainnya.

Membuat terlalu banyak koneksi WebSocket sekaligus biasanya terjadi dalam skenario berikut:

  • Selama tahap startup layanan

  • Terjadi exception jaringan yang menyebabkan banyak koneksi WebSocket terputus dan terhubung kembali secara bersamaan.

  • Banyak error sisi server terjadi secara bersamaan, yang menyebabkan banyak koneksi WebSocket terhubung kembali. Error umum terjadi saat konkurensi melebihi batas akun ("Requests rate limit exceeded, please try again later.").

Solusi:

  1. Periksa kondisi jaringan Anda.

  2. Periksa apakah banyak error sisi server lain terjadi sebelum lonjakan.

  3. Tingkatkan batas konkurensi untuk Akun Alibaba Cloud Anda.

  4. Kurangi ukuran object pool dan connection pool. Anda juga dapat membatasi konkurensi maksimum menggunakan batas atas object pool.

  5. Tingkatkan konfigurasi server Anda atau tambah jumlah server.

Exception 6: Semua tugas melambat seiring peningkatan konkurensi

Solusi:

  1. Periksa apakah Anda telah mencapai batas lebar pita jaringan.

  2. Periksa apakah konkurensi aktual terlalu tinggi untuk spesifikasi server Anda.