Gunakan kembali objek sintesis dan koneksi WebSocket melalui pool objek, serta atur konkurensi, pemanasan, dan penanganan error untuk mengurangi overhead koneksi.
Cakupan
Pool menyimpan objek sintesis dan koneksi WebSocket untuk mengurangi pembuatan objek, handshake TCP/TLS, dan upgrade protokol. Seri model dan SDK berikut mendukung pool objek:
- Qwen-Audio-TTS, CosyVoice: pool objek Python dan Java.
Prasyarat
- Dapatkan kunci API dan atur
DASHSCOPE_API_KEY. Gunakan kunci untuk wilayah yang dipanggil. - Ganti
{WorkspaceId}dengan ID ruang kerja wilayah tersebut dan atur endpoint WebSocket-nya.
Python SDK
Pool objek Qwen-Audio-TTS dan CosyVoice memerlukan DashScope Python SDK 1.25.2 atau yang lebih baru:
pip install "dashscope>=1.25.2"
Java SDK
- Qwen-Audio-TTS, CosyVoice: DashScope Java SDK 2.16.6 atau yang lebih baru.
Contoh Java juga memerlukan Apache Commons Pool 2. Konfigurasikan dependensi dengan Maven atau Gradle:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.23.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
dependencies {
implementation 'com.alibaba:dashscope-sdk-java:2.23.2'
implementation 'org.apache.commons:commons-pool2:2.12.0'
}
Mengatur konkurensi dan pool sumber daya
Atur pool sumber daya sesuai jumlah tugas simultan yang diperkirakan dan spesifikasi server. Batas model dihitung per akun Alibaba Cloud dan digunakan bersama oleh pengguna RAM, workspace, dan kunci API dalam akun tersebut. Untuk batas frekuensi dan konkurensi model, lihat Batas laju.
- Mulai dengan kapasitas pool sebesar 1,5–2 kali konkurensi puncak, lalu sesuaikan dengan beban aktual.
- Pool Python dapat membuat objek tambahan saat semua objek dipinjam. Objek tambahan ini memerlukan koneksi baru ketika digunakan.
Pengaturan Java
Atur variabel lingkungan SDK sebelum proses Java dimulai. Tiga variabel pertama merupakan pengaturan SDK; variabel kapasitas berikutnya ditentukan oleh kode contoh:
| Variabel lingkungan | Default | Deskripsi |
|---|---|---|
| 32 | Pengaturan pool koneksi idle OkHttp dasar. |
| 32 | Jumlah permintaan maksimum pada dispatcher asinkron dasar. |
| 32 | Jumlah permintaan maksimum per host pada dispatcher asinkron dasar. |
| 500 | Kapasitas pool contoh Java Qwen-Audio-TTS/CosyVoice untuk |
Contoh Java menyetel maxIdle sama dengan maxTotal untuk mengurangi pembuatan ulang setelah objek idle dihancurkan. Tutup koneksi saat menghancurkan objek. Pada maxTotal, permintaan peminjaman baru menunggu objek yang tersedia.
Referensi konfigurasi server
Konfigurasi server Alibaba Cloud berikut merupakan referensi ketika hanya layanan sintesis yang ditunjukkan yang dijalankan. Konkurensi per server berarti tugas sintesis simultan. Sesuaikan konfigurasi dengan beban aktual.
Pengaturan pool koneksi pada tabel menjadi nilai referensi untuk ketiga variabel: DASHSCOPE_CONNECTION_POOL_SIZE, DASHSCOPE_MAXIMUM_ASYNC_REQUESTS, dan DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST. Untuk konfigurasi referensi ini, atur ketiganya ke 2000. Atur kapasitas pool objek melalui variabel lingkungan pada contoh yang sesuai.
| Seri | Server Alibaba Cloud | Tugas simultan | Kapasitas pool objek | Pengaturan pool koneksi |
|---|---|---|---|---|
CosyVoice | 4 vCPU / 8 GiB | 100 | 500 | 2000 |
CosyVoice | 8 vCPU / 16 GiB | 150 | 500 | 2000 |
CosyVoice | 16 vCPU / 32 GiB | 200 | 500 | 2000 |
Contoh kode
Qwen-Audio-TTS/CosyVoice
Contoh menggunakan qwen-audio-3.1-tts-flash dan longanhuan_v3.1 untuk tiga tugas simultan. Untuk CosyVoice, ganti model dan suara menjadi cosyvoice-v3-flash dan longanyang. Untuk model lain dan persyaratan suara, lihat Sintesis ucapan real-time.
Python SDK
import os
import threading
from concurrent.futures import ThreadPoolExecutor
import dashscope
from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool, ResultCallback, AudioFormat
dashscope.api_key = os.environ["DASHSCOPE_API_KEY"]
dashscope.base_websocket_api_url = "wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference"
pool = SpeechSynthesizerObjectPool(max_size=3)
def synthesize(index):
done = threading.Event()
errors = []
with open(f"output_{index}.mp3", "wb") as audio:
class Callback(ResultCallback):
def on_data(self, data: bytes):
audio.write(data)
def on_complete(self):
done.set()
def on_error(self, message):
errors.append(message)
done.set()
synthesizer = pool.borrow_synthesizer(
model="qwen-audio-3.1-tts-flash",
voice="longanhuan_v3.1",
format=AudioFormat.MP3_24000HZ_MONO_256KBPS,
callback=Callback(),
)
successful = False
try:
synthesizer.streaming_call("你好,欢迎使用阿里云百炼。")
synthesizer.streaming_complete(60000)
if not done.wait(60):
raise TimeoutError("Synthesis timed out")
if errors:
raise RuntimeError(errors[0])
successful = True
finally:
if successful:
pool.return_synthesizer(synthesizer)
else:
synthesizer.close()
try:
with ThreadPoolExecutor(max_workers=3) as executor:
list(executor.map(synthesize, range(3)))
finally:
pool.shutdown()
Java SDK
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.utils.Constants;
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.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class PooledTts {
static class Factory extends BasePooledObjectFactory<SpeechSynthesizer> {
public SpeechSynthesizer create() { return new SpeechSynthesizer(); }
public PooledObject<SpeechSynthesizer> wrap(SpeechSynthesizer value) {
return new DefaultPooledObject<>(value);
}
public void destroyObject(PooledObject<SpeechSynthesizer> value) {
value.getObject().getDuplexApi().close(1000, "done");
}
}
static void synthesize(GenericObjectPool<SpeechSynthesizer> pool, int index) throws Exception {
SpeechSynthesizer synthesizer = pool.borrowObject();
boolean successful = false;
AtomicReference<Exception> error = new AtomicReference<>();
try (FileOutputStream audio = new FileOutputStream("output_" + index + ".mp3")) {
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model("qwen-audio-3.1-tts-flash")
.voice("longanhuan_v3.1")
.format(SpeechSynthesisAudioFormat.MP3_24000HZ_MONO_256KBPS)
.build();
synthesizer.updateParamAndCallback(param, new ResultCallback<SpeechSynthesisResult>() {
public void onEvent(SpeechSynthesisResult result) {
ByteBuffer buffer = result.getAudioFrame();
if (buffer != null) {
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
try { audio.write(bytes); }
catch (Exception e) { error.set(e); }
}
}
public void onComplete() {}
public void onError(Exception e) { error.set(e); }
});
synthesizer.streamingCall("你好,欢迎使用阿里云百炼。");
synthesizer.streamingComplete(60000);
if (error.get() != null) { throw error.get(); }
successful = true;
} finally {
if (successful) { pool.returnObject(synthesizer); }
else { pool.invalidateObject(synthesizer); }
}
}
public static void main(String[] args) throws Exception {
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference";
GenericObjectPoolConfig<SpeechSynthesizer> config = new GenericObjectPoolConfig<>();
int poolSize = Integer.parseInt(System.getenv().getOrDefault("COSYVOICE_OBJECTPOOL_SIZE", "500"));
config.setMaxTotal(poolSize);
config.setMaxIdle(poolSize);
config.setMaxWait(Duration.ofSeconds(60));
ExecutorService executor = Executors.newFixedThreadPool(3);
try (GenericObjectPool<SpeechSynthesizer> pool = new GenericObjectPool<>(new Factory(), config)) {
java.util.List<Future<?>> futures = new java.util.ArrayList<>();
for (int i = 0; i < 3; i++) {
final int index = i;
futures.add(executor.submit(() -> {
try { synthesize(pool, index); }
catch (Exception e) { throw new RuntimeException(e); }
}));
}
for (Future<?> future : futures) { future.get(); }
} finally {
executor.shutdownNow();
}
}
}
Penggunaan di lingkungan produksi
Pengelolaan sumber daya
- Atur kunci API dan endpoint, lalu buat pool objek yang digunakan bersama oleh tugas aplikasi.
- Pinjam dan konfigurasi objek. Di Python, teruskan model, suara, format, dan callback ke
borrow_synthesizer. Pada contoh Java Qwen-Audio-TTS/CosyVoice, panggilborrowObject, lalu selalu panggilupdateParamAndCallback(param, callback)setiap kali meminjam untuk mengatur parameter dan callback tugas. - Jalankan sintesis. Objek Qwen-Audio-TTS/CosyVoice mendukung
callataustreaming_calldi Python, sertacallataustreamingCalldi Java. - Setelah selesai normal, kembalikan objek melalui
return_synthesizerdi Python ataureturnObjectdi Java. Jangan langsung mengembalikan objek yang belum selesai atau gagal: panggilclose()pada objek di Python, atau buang melaluiinvalidateObjectdi Java agar factory menutup koneksinya. - Setelah semua tugas selesai, lepaskan sumber daya pool dengan
shutdown()di Python atauclose()pada pool di Java.
Autentikasi dan beberapa kunci API
- Kunci API dikirim melalui header
Authorizationhanya saat handshake WebSocket. Pesanrun-taskberikutnya tidak menyertakannya. Mengubah kunci pada parameter atau konfigurasi global tidak mengubah identitas koneksi yang sudah ada, sehingga identitas, penggunaan kuota, atau biaya dapat dibebankan secara berbeda dari yang diharapkan. - Di Python, atur
dashscope.api_keysebelum membuat pool. Koneksi yang dibangun saat inisialisasi tetap menggunakan kunci saat handshake, danborrow_synthesizertidak menyediakan parameter kunci API.SpeechSynthesizerObjectPooladalah singleton per proses, sehingga kunci berbeda harus dipisahkan ke proses berbeda. - Di Java, buat pool independen untuk setiap kunci API dan gunakan kunci yang sama dalam satu pool. Untuk Qwen-Audio-TTS/CosyVoice, panggil
updateParamAndCallbacksetiap kali objek dipinjam. Metode ini tidak mengautentikasi ulang koneksi yang sudah ada.
Pemanasan dan pengukuran latensi
- Setelah startup atau pemulihan jaringan, jalankan pemanasan dan tingkatkan konkurensi secara bertahap. Sebelum pengujian kinerja, lakukan pemanasan pada konkurensi target, misalnya selama 1–2 menit, lalu kumpulkan pengukuran setelah koneksi digunakan kembali secara stabil.
- Pool Python membangun koneksi saat inisialisasi. Java
GenericObjectPoolmembuat objek sesuai kebutuhan dan terhubung pada sintesis pertama. Karena itu, pemanasan koneksi Java memerlukan permintaan sintesis yang benar-benar dijalankan. - Panggilan pertama mencakup overhead pembuatan koneksi WebSocket. Ukur latensi audio pertama dan waktu totalnya secara terpisah dari panggilan yang menggunakan kembali koneksi. Faktor waktu nyata (RTF, total waktu sintesis / durasi audio) juga dapat digunakan untuk menilai kecepatan sintesis.
- Untuk Qwen-Audio-TTS, CosyVoice, koneksi ditutup setelah 60 detik idle seusai tugas. Nilai panggilan setelah idle lama sebagai koneksi baru.
Latensi audio pertama di sisi SDK mencakup pembuatan koneksi dan transmisi jaringan, sehingga bukan hanya waktu inferensi model. Untuk Qwen-Audio-TTS/CosyVoice, objek Python menyediakan get_first_package_delay() untuk latensi audio pertama dan get_last_request_id() untuk ID yang digunakan dalam korelasi log permintaan. Objek Java menyediakan getLastRequestId() untuk ID permintaan.
Pemecahan masalah
Koneksi TCP bertambah meskipun trafik stabil
Sering membuat objek sintesis tanpa menutup koneksi lama dapat terus menambah jumlah koneksi dan penggunaan sumber daya server. Periksa apakah objek dikembalikan setelah tugas selesai dan koneksi yang tidak dipakai sudah ditutup.
Di Java, jika maxIdle lebih kecil dari maxTotal, objek yang dikembalikan melebihi kapasitas idle akan dihancurkan. Jika destroyObject pada factory tidak menutup WebSocket, koneksi dapat tetap terbuka setelah objek dihancurkan. Atur maxIdle sama dengan maxTotal untuk mengurangi penghancuran dan pembuatan berulang, serta tutup koneksi setiap kali menghancurkan objek.
Tugas menunggu lebih lama atau timeout
Pada maxTotal, peminjaman dari pool Java menunggu objek yang tersedia. Periksa kapasitas pool, pengembalian objek, dan penutupan koneksi untuk menghindari tambahan waktu tunggu objek.
Tugas melambat setelah startup atau pemulihan jaringan
Setelah startup atau pemulihan jaringan, jalankan pemanasan dan tingkatkan konkurensi secara bertahap agar banyak handshake WebSocket simultan tidak menyebabkan pemblokiran.
Server mengembalikan Invalid action('run-task')! Please follow the protocol!
Setelah error klien menginterupsi tugas, server mungkin masih menganggap tugas sebelumnya aktif. Menggunakan ulang koneksi untuk run-task baru akan menyebabkan error urutan protokol. Tutup koneksi dan buang objek, lalu bangun koneksi baru untuk tugas berikutnya.
Panggilan melonjak meskipun trafik aplikasi stabil
Koneksi ulang massal saat startup, setelah gangguan jaringan, atau setelah banyak error server dapat menumpuk tugas. Setelah koneksi pulih, pengiriman tugas tersebut secara bersamaan dapat menyebabkan lonjakan dan memicu batas akun.
- Periksa masalah jaringan atau banyak error sebelum lonjakan, misalnya
Requests rate limit exceeded, please try again later.. - Kendalikan konkurensi saat startup dan pemulihan. Di Java,
maxTotalmembatasi jumlah objek yang dipinjam secara bersamaan dari pool yang sama. - Periksa kuota akun. Untuk meminta peningkatan, ikuti prosedur pada Batas laju.
- Sesuaikan spesifikasi server atau tambah server sesuai beban aktual.
Semua tugas melambat saat konkurensi bertambah
Periksa apakah bandwidth jaringan sudah mencapai batas atau konkurensi terlalu tinggi. Sesuaikan konkurensi atau spesifikasi server dengan beban kerja.