全部产品
Search
文档中心

Alibaba Cloud Model Studio:Skenario konkurensi tinggi untuk CosyVoice

更新时间:Jan 06, 2026

Layanan sintesis suara CosyVoice menggunakan protokol WebSocket untuk komunikasi streaming real-time. Dalam skenario konkurensi tinggi, pembuatan dan penghapusan koneksi WebSocket untuk setiap permintaan menimbulkan overhead signifikan pada jaringan dan sumber daya sistem serta memperkenalkan latensi koneksi. Untuk mengoptimalkan performa dan memastikan stabilitas, kit pengembangan perangkat lunak (SDK) DashScope menyediakan mekanisme reuse resource, seperti connection pool dan object pool. Dokumen ini menjelaskan cara menggunakan fitur-fitur tersebut dalam SDK Python dan Java DashScope untuk memanggil layanan CosyVoice secara efisien dalam skenario konkurensi tinggi.

Penting

Untuk menggunakan model di wilayah China (Beijing), buka halaman Kunci API untuk wilayah China (Beijing)

Prasyarat

Python SDK: Optimisasi object pool

Python SDK menggunakan kelas SpeechSynthesizerObjectPool untuk mengelola dan menggunakan kembali objek SpeechSynthesizer melalui optimisasi object pool.

Saat object pool diinisialisasi, SDK membuat sejumlah instance SpeechSynthesizer tertentu dan menetapkan koneksi WebSocket sebelumnya. Saat Anda mengambil objek dari pool, Anda dapat langsung mengirim permintaan tanpa menunggu koneksi dibuat. Praktik ini secara efektif mengurangi latensi paket pertama. Setelah tugas selesai dan objek dikembalikan ke pool, koneksi WebSocket-nya tetap aktif dan siap digunakan untuk tugas berikutnya.

Langkah implementasi

  1. Instal dependensi: Instal dependensi DashScope dengan menjalankan pip install -U dashscope.

  2. Buat dan konfigurasikan object pool.

    Tentukan ukuran object pool menggunakan SpeechSynthesizerObjectPool. Kami merekomendasikan nilai ini disetel antara 1,5 hingga 2 kali puncak konkurensi Anda. Ukuran object pool tidak boleh melebihi batas permintaan per detik (QPS) akun Anda.

    Gunakan kode berikut untuk membuat object pool singleton global dengan ukuran tetap. Saat object pool diinisialisasi, SDK langsung membuat jumlah objek SpeechSynthesizer yang ditentukan dan menetapkan koneksi WebSocket. Proses ini memerlukan waktu tertentu.

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

    Jika jumlah objek yang sedang digunakan melebihi kapasitas maksimum pool, sistem akan membuat objek SpeechSynthesizer baru.

    Objek baru ini harus diinisialisasi ulang dan menetapkan koneksi WebSocket baru. Objek tersebut tidak dapat menggunakan sumber daya koneksi yang ada di pool sehingga tidak mendapat manfaat dari reuse resource.

    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 melakukan sintesis 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 = [
    'Kalimat 1: Selamat datang di layanan sintesis suara Alibaba Cloud.',
    'Kalimat 2: Selamat datang di layanan sintesis suara Alibaba Cloud.',
    'Kalimat 3: Selamat datang di layanan sintesis suara Alibaba Cloud.',
]
connectionPool = None
if USE_CONNECTION_POOL:
    print('membuat connection pool')
    start_time = time.time() * 1000
    connectionPool = SpeechSynthesizerObjectPool(max_size=3)
    end_time = time.time() * 1000
    print('connection pool dibuat, biaya: {} ms'.format(end_time - start_time))

def init_dashscope_api_key():
    '''
    Tetapkan Kunci API DashScope Anda. Untuk informasi selengkapnya, lihat
    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']  # muat Kunci API dari variabel lingkungan DASHSCOPE_API_KEY
    else:
        dashscope.api_key = '<your-dashscope-api-key>'  # tetapkan 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 selengkapnya, 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}] mulai')

        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_close dipanggil setelah tugas selesai.
            print(f'[task_{task_id}] selesai')

        def on_event(self, message):
            # print(f'menerima pesan sintesis suara {message}')
            pass

        def on_data(self, data: bytes) -> None:
            # kirim ke pemutar
            # simpan audio ke file
            self.file.write(data)

    # Panggil callback synthesizer
    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:
            # Jika tugas gagal saat menggunakan connection pool, tutup koneksi synthesizer secara manual.
            speech_synthesizer.close()
        return

    print('[task_{}] Teks yang disintesis: {}'.format(task_id, text))
    complete_event.wait()
    print('[task_{}][Metric] requestId: {}, penundaan paket pertama dalam 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__':
    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 Pengecualian

  • Tugas berhasil: Saat tugas sintesis suara berhasil diselesaikan, Anda harus memanggil 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: Jika tugas terganggu oleh pengecualian internal SDK atau pengecualian logika bisnis, Anda harus menutup koneksi WebSocket yang mendasari secara manual dengan memanggil speech_synthesizer.close().

  • Setelah semua tugas sintesis suara selesai, tutup object pool dengan memanggil connectionPool.shutdown().

  • Jika terjadi error `TaskFailed`, tidak diperlukan penanganan tambahan.

Java SDK: Optimisasi connection pool dan object pool

Java SDK menggunakan connection pool bawaan dan object pool kustom yang bekerja sama untuk memberikan performa optimal.

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

  • Object pool: Diimplementasikan berdasarkan commons-pool2, pool ini mempertahankan serangkaian objek SpeechSynthesizer dengan koneksi yang telah ditetapkan sebelumnya. Mengambil objek dari pool menghilangkan latensi setup koneksi dan secara signifikan mengurangi latensi paket pertama.

Langkah implementasi

  1. Tambahkan dependensi.

    Berdasarkan alat build proyek Anda, tambahkan `dashscope-sdk-java` dan `commons-pool2` ke file konfigurasi dependensi Anda.

    Bagian berikut menunjukkan konfigurasi untuk Maven dan Gradle.

    Maven

    1. Buka file pom.xml proyek Maven Anda.

    2. Tambahkan informasi 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. Anda dapat menemukan nomor versi yang relevan 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. Anda dapat menemukan nomor versi yang relevan di: https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <version>the-latest-version</version>
    </dependency>
    1. Simpan file pom.xml.

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

    Gradle

    1. Buka file build.gradle proyek Gradle Anda.

    2. Tambahkan informasi dependensi berikut di dalam blok dependencies.

      dependencies {
          // Ganti 'the-latest-version' dengan versi 2.16.6 atau lebih baru. Anda dapat menemukan nomor versi yang relevan 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. Anda dapat menemukan nomor versi yang relevan 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 command line, navigasi ke direktori root proyek Anda dan jalankan perintah Gradle berikut untuk memperbarui dependensi proyek.

      ./gradlew build --refresh-dependencies

      Jika Anda menggunakan Windows, jalankan perintah berikut:

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

    Anda dapat mengonfigurasi parameter kunci connection pool menggunakan variabel lingkungan:

    Variabel lingkungan

    Deskripsi

    DASHSCOPE_CONNECTION_POOL_SIZE

    Ukuran connection pool.

    Nilai yang direkomendasikan: Minimal dua kali puncak konkurensi Anda.

    Nilai default: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS

    Jumlah maksimum permintaan asinkron.

    Nilai yang direkomendasikan: Sama dengan DASHSCOPE_CONNECTION_POOL_SIZE.

    Nilai default: 32.

    DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST

    Jumlah maksimum permintaan asinkron per host.

    Nilai yang direkomendasikan: Sama dengan DASHSCOPE_CONNECTION_POOL_SIZE.

    Nilai default: 32.

  3. Konfigurasikan object pool.

    Anda dapat mengonfigurasi ukuran object pool menggunakan variabel lingkungan:

    Variabel lingkungan

    Deskripsi

    COSYVOICE_OBJECTPOOL_SIZE

    Ukuran object pool.

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

    Nilai default: 500.

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

    • Ukuran object pool tidak boleh melebihi batas QPS akun Anda.

    Gunakan kode berikut untuk membuat object pool:

    class CosyvoiceObjectPool {
        // ... Kode lain dihilangkan di sini. Untuk contoh lengkap, lihat kode lengkap.
        public static GenericObjectPool<SpeechSynthesizer> getInstance() {
            lock.lock();
            if (synthesizerPool == null) {
                // Anda dapat menyetel ukuran object pool di sini atau di variabel lingkungan COSYVOICE_OBJECTPOOL_SIZE.
                // Setel ke 1,5 hingga 2 kali koneksi bersamaan maksimum server.
                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 object pool.

    Jika jumlah objek yang sedang digunakan melebihi kapasitas maksimum pool, sistem akan membuat objek SpeechSynthesizer baru.

    Objek baru ini harus diinisialisasi ulang dan menetapkan koneksi WebSocket baru. Objek tersebut tidak dapat menggunakan sumber daya koneksi yang ada di pool sehingga tidak mendapat manfaat dari reuse resource.

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

    Panggil 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

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;

/**
 * Anda perlu mengimpor paket org.apache.commons.pool2 dan DashScope ke dalam proyek Anda.
 *
 * SDK DashScope versi 2.16.6 dan lebih baru dioptimalkan untuk skenario konkurensi tinggi.
 * Versi SDK DashScope sebelum 2.16.6 tidak direkomendasikan untuk digunakan dalam skenario konkurensi tinggi.
 *
 *
 * Sebelum melakukan panggilan konkurensi tinggi ke layanan teks-ke-ucapan (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 menyetel ukuran object pool di sini atau di variabel lingkungan COSYVOICE_OBJECTPOOL_SIZE.
            // Setel ke 1,5 hingga 2 kali koneksi bersamaan maksimum server.
            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 onError diterima
        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 stream file 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.
            String dashScopeApiKey = "your-dashscope-api-key";

            SpeechSynthesisParam param =
                    SpeechSynthesisParam.builder()
                            .model("cosyvoice-v3-flash")
                            .voice("longanyang")
                            .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 pengecualian, tutup koneksi dan batalkan objek di pool.
                    synthesizer.getDuplexApi().close(1000, "bye");
                    CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
                } else {
                    // Jika tugas selesai 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. Biaya waktu: " + 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 {
        // Periksa variabel lingkungan 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 SpeechSynthesizer
        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[] {
                    "Cahaya bulan di depan tempat tidurku,", "Apakah itu embun beku di tanah?", "Aku angkat kepalaku melihat bulan,", "Aku turunkan kepalaku dan rindu rumah."}));
        }

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

Konfigurasi yang direkomendasikan

Konfigurasi berikut didasarkan pada hasil pengujian saat menjalankan layanan sintesis suara CosyVoice di server Alibaba Cloud dengan konfigurasi yang ditentukan. Konkurensi tinggi dapat menyebabkan penundaan pemrosesan tugas.

Konkurensi per server adalah jumlah tugas sintesis suara CosyVoice yang dapat berjalan secara bersamaan. Nilai ini setara dengan jumlah thread pekerja.

Konfigurasi server (Alibaba Cloud)

Konkurensi maksimum per server

Ukuran object pool

Ukuran connection pool

4 core 8 GiB

100

500

2000

8 core 16 GiB

150

500

2000

16 core 32 GiB

200

500

2000

Manajemen Resource dan Penanganan Anomali

  • Tugas berhasil: Saat tugas sintesis suara berhasil diselesaikan, Anda harus memanggil metode `returnObject` dari `GenericObjectPool` untuk mengembalikan objek SpeechSynthesizer ke pool agar dapat digunakan kembali.

    Dalam contoh kode yang diberikan, hal ini dilakukan dengan memanggil CosyvoiceObjectPool.getInstance().returnObject(synthesizer).

    Penting

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

  • Tugas gagal: Jika tugas terganggu oleh pengecualian internal SDK atau pengecualian logika bisnis, Anda harus melakukan dua operasi berikut:

    1. Tutup koneksi WebSocket yang mendasari.

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

    // Ini sesuai dengan konten berikut dalam kode saat ini
    // Tutup koneksi
    synthesizer.getDuplexApi().close(1000, "bye");
    // Batalkan synthesizer bermasalah di object pool
    CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
  • Jika terjadi error `TaskFailed`, tidak diperlukan penanganan tambahan.

Pemanasan panggilan dan statistik latensi

Saat menilai metrik performa untuk SDK Java DashScope, seperti latensi panggilan konkuren, lakukan pemanasan penuh sebelum memulai pengujian formal. Pemanasan memastikan bahwa hasil pengujian secara akurat mencerminkan performa layanan dalam kondisi stabil. Hal ini mencegah bias data yang disebabkan oleh waktu koneksi awal.

Mekanisme reuse koneksi

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

Mekanisme ini bekerja sebagai berikut:

  • Pembuatan sesuai permintaan: SDK tidak membuat koneksi WebSocket saat layanan dimulai. Sebaliknya, koneksi ditetapkan pada panggilan pertama.

  • Reuse berbatas waktu: Setelah permintaan selesai, koneksi tetap berada di pool hingga 60 detik untuk reuse.

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

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

Pentingnya pemanasan

Dalam skenario berikut, connection pool mungkin tidak memiliki koneksi aktif untuk digunakan kembali. Hal ini memerlukan pembuatan koneksi baru:

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

  • Layanan menganggur lebih dari 60 detik, menyebabkan koneksi di pool ditutup karena timeout.

Dalam kasus ini, beberapa permintaan pertama memicu proses koneksi WebSocket lengkap. Proses ini mencakup handshake TCP, negosiasi enkripsi Transport Layer Security (TLS), dan upgrade protokol. Latensi end-to-end untuk permintaan ini jauh lebih tinggi daripada permintaan berikutnya yang menggunakan kembali koneksi. Waktu tambahan ini disebabkan oleh inisialisasi koneksi jaringan, bukan latensi pemrosesan layanan. Oleh karena itu, tanpa pemanasan, hasil pengujian performa Anda akan bias karena mencakup overhead koneksi awal ini.

Praktik yang direkomendasikan

Untuk mendapatkan data performa yang andal, ikuti langkah pemanasan berikut sebelum memulai uji stres atau mengukur latensi:

  1. Simulasikan tingkat konkurensi pengujian formal Anda. Lakukan sejumlah panggilan terlebih dahulu, misalnya selama 1 hingga 2 menit, untuk mengisi connection pool.

  2. Pastikan connection pool memiliki cukup koneksi aktif. Kemudian, mulai mengumpulkan data performa formal.

Pemanasan yang tepat membawa connection pool SDK ke kondisi stabil reuse. Hal ini memungkinkan Anda mengukur metrik latensi yang lebih representatif yang mencerminkan performa sebenarnya dari layanan selama runtime stabil.

Pengecualian umum SDK Java

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

Penyebab:

Tipe 1:

Setiap objek SDK meminta koneksi saat dibuat. Jika Anda tidak menggunakan object pool, objek dihancurkan 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. Hal ini menyebabkan masalah berikut:

  1. Jumlah koneksi terus meningkat.

  2. Performa 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 tersedia.

Tipe 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 Tipe 1, hal ini menyebabkan peningkatan jumlah koneksi secara terus-menerus.

Solusi:

Untuk penyebab Tipe 1, gunakan object pool.

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

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

Penyebabnya sama dengan Pengecualian 1. Connection pool telah mencapai jumlah koneksi maksimumnya. Tugas baru harus menunggu 61 detik agar koneksi yang tidak direferensikan mengalami timeout sebelum tugas tersebut dapat memperoleh koneksi baru.

Pengecualian 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 hanya dibuat saat layanan dimulai. Perhatikan bahwa jika panggilan konkurensi tinggi dimulai segera selama tahap startup tugas, pembuatan terlalu banyak koneksi WebSocket secara bersamaan dapat menyebabkan pemblokiran.

Solusi:

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

Pengecualian 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 pengecualian sisi klien dilemparkan, Anda harus secara eksplisit menutup koneksi WebSocket dan kemudian mengembalikan objek ke object pool.

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

Penyebab:

Pembuatan terlalu banyak koneksi WebSocket secara bersamaan menyebabkan pemblokiran. Karena lalu lintas layanan yang masuk terus berlanjut, backlog tugas jangka pendek terbentuk. Setelah pemblokiran terselesaikan, semua tugas yang tertunda dipanggil sekaligus. Hal ini menyebabkan lonjakan volume panggilan yang dapat sesaat melebihi batas konkurensi akun Alibaba Cloud Anda, yang dapat mengakibatkan kegagalan tugas, degradasi performa server, dan masalah lainnya.

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

  • Selama tahap startup layanan

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

  • Banyak error sisi server terjadi secara bersamaan, yang menyebabkan banyak rekoneksi WebSocket. Error umum terjadi ketika 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 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 tambahkan jumlah server.

Pengecualian 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.