Skip to main content

語音合成高並發最佳實務

透過物件集區重用語音合成物件與 WebSocket 連線,設定並行、預熱和錯誤處理,減少重複建立連線的開銷。

適用範圍

物件集區保留合成物件及其 WebSocket 連線,減少重複建立物件、TCP/TLS 交握和通訊協定升級的開銷。支援的模型系列與 SDK 如下:
  • Qwen-Audio-TTS、CosyVoice:Python、Java 物件集區。

準備工作

  • 取得 API Key並設定 DASHSCOPE_API_KEY,使用與呼叫地區對應的 API Key。
  • 將範例中的 {WorkspaceId} 取代為對應地區的業務空間 ID,並按地區設定 WebSocket 位址。
  • Python SDK
  • Java SDK
Qwen-Audio-TTS、CosyVoice 的物件集區需要 DashScope Python SDK 1.25.2 及以上版本:
pip install "dashscope>=1.25.2"

設定並行與資源集區

根據預期同時執行的任務數和伺服器規格設定資源集區。模型限流按主帳號統計,RAM 使用者、工作空間和 API Key 共用主帳號的額度;各模型的呼叫頻率和並行限制請參見限流。
  • 物件集區容量可從峰值並行數的 1.5~2 倍開始設定,並根據實際負載調整。
  • Python 集區內的物件全部借出後,仍可能建立額外物件;這些物件在使用時需要建立新連線。
  • Java 參數
  • 伺服器設定參考
SDK 環境變數應在 Java 程序啟動前設定。下列前 3 項是 SDK 設定,後面的物件集區容量變數由範例程式碼自訂:
環境變數預設值說明
DASHSCOPE_CONNECTION_POOL_SIZE32底層 OkHttp 閒置連線集區的設定。
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS32底層非同步請求排程的最大請求數。
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST32底層非同步請求排程的單主機最大請求數。
COSYVOICE_OBJECTPOOL_SIZE500本文 Qwen-Audio-TTS/CosyVoice Java 範例的物件集區容量,對應 maxTotal 和 maxIdle,不是 SDK 內建參數。
本文 Java 範例將 maxIdle 和 maxTotal 設為相同值,減少閒置物件銷毀後再次建立的開銷。銷毀物件時應關閉其連線。達到 maxTotal 後,新的借用請求將等待可用物件。

範例程式碼

Qwen-Audio-TTS/CosyVoice

以下範例使用 qwen-audio-3.1-tts-flash 和 longanhuan_v3.1,並行執行 3 次合成。使用 CosyVoice 時,可將模型和音色分別改為 cosyvoice-v3-flash 和 longanyang;其他模型選擇與音色要求參見即時語音合成。
  • Python SDK
  • Java 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()

應用於生產環境

資源管理

  1. 設定 API Key 和接入位址後,建立共用物件集區,供業務任務借用。
  2. 借用並設定物件:Python 呼叫 borrow_synthesizer 時傳入模型、音色、格式和回呼;Qwen-Audio-TTS/CosyVoice 的 Java 範例在呼叫 borrowObject 後,每次都需透過 updateParamAndCallback(param, callback) 設定本次任務的參數和回呼。
  3. 執行合成:Qwen-Audio-TTS/CosyVoice 的 Python 物件支援 call 或 streaming_call,Java 物件支援 call 或 streamingCall。
  4. 任務正常完成後,Python 呼叫 return_synthesizer,Java 呼叫 returnObject 歸還物件。未完成或失敗的物件不得直接歸還:Python 呼叫物件的 close();Java 使用 invalidateObject 廢棄物件,並由工廠關閉連線。
  5. 所有任務結束後,Python 呼叫 shutdown(),Java 呼叫物件集區的 close(),釋放物件集區資源。

驗證與多個 API Key

  • API Key 僅在 WebSocket 建立連線的交握階段透過 Authorization 請求標頭驗證,後續 run-task 訊息不攜帶 API Key。重用連線期間,修改參數或全域設定中的 API Key 不會改變該連線的驗證身分,可能導致身分、額度或計費歸屬與預期不一致。
  • Python 應在建立集區前設定 dashscope.api_key。集區初始化時建立的連線沿用交握時的 API Key,borrow_synthesizer 不提供 API Key 參數。SpeechSynthesizerObjectPool 是程序內單例,多 API Key 應按程序隔離。
  • Java 為不同 API Key 建立獨立物件集區,同一集區始終使用相同的 API Key。Qwen-Audio-TTS/CosyVoice 每次借出物件後都應呼叫 updateParamAndCallback,但該方法不會為已有連線重新驗證。

預熱與耗時統計

  1. 服務啟動或網路恢復後先執行預熱呼叫,逐步提高並行數。效能測試前,可按目標並行數持續預熱(例如 1~2 分鐘),待連線穩定重用後再收集資料。
  2. Python 物件集區初始化時會建立連線;Java GenericObjectPool 按需建立物件,首次合成時才建立連線,因此 Java 連線預熱需要實際執行合成請求。
  3. 首次呼叫包含 WebSocket 建立連線的開銷,應與重用連線後的首包延遲和任務總耗時分別統計。也可透過即時率(RTF,合成總耗時 / 音訊時長)評估合成速度。
  4. 對於 Qwen-Audio-TTS、CosyVoice,任務結束後閒置 60 秒會自動中斷連線。長時間閒置後的呼叫應按重新建立連線的情況評估。
SDK 端首包延遲包含建立連線和網路傳輸等耗時,不等同於純模型推論耗時。Qwen-Audio-TTS/CosyVoice 的 Python 物件可透過 get_first_package_delay() 取得首包延遲,並透過 get_last_request_id() 取得請求 ID,用於關聯請求記錄。Java 物件透過 getLastRequestId() 取得請求 ID。

常見問題

業務流量平穩,但 TCP 連線數持續上升

頻繁建立合成物件而未關閉舊連線,會使連線數持續增長,增加伺服器資源開銷。檢查任務結束後是否正確歸還物件,以及不再使用的連線是否關閉。 Java 中,若 maxIdle 小於 maxTotal,物件歸還時超出閒置容量的物件會被銷毀。如果工廠的 destroyObject 沒有關閉 WebSocket,連線可能在物件銷毀後仍保持開啟。可將 maxIdle 與 maxTotal 設為相同值,減少反覆銷毀和建立,同時確保銷毀物件時關閉連線。

任務出現額外等待或逾時

Java 物件集區達到 maxTotal 後,借用操作會等待可用物件。檢查集區容量、物件歸還及連線關閉情況,避免任務因等待物件而增加耗時。

服務啟動或網路恢復後任務變慢

啟動服務或網路恢復後,先執行預熱呼叫並逐步提高並行數,避免同時建立大量 WebSocket 連線造成阻塞。

傳回 Invalid action('run-task')! Please follow the protocol!

用戶端異常中斷任務後,服務端可能仍認為上一輪任務正在執行。此時重用連線傳送新的 run-task 會造成協定順序錯誤。應關閉連線並廢棄該物件,再建立新連線執行後續任務。

業務流量平穩,但呼叫量出現尖峰

服務啟動、網路中斷或大量服務端錯誤後,集中重連可能使任務積壓;連線恢復後,積壓任務同時提交又會形成呼叫尖峰,並可能觸發帳號限流。
  1. 檢查尖峰前是否出現網路異常或大量服務端錯誤,例如 Requests rate limit exceeded, please try again later.。
  2. 控制啟動和恢復階段的並行數。Java 可透過物件集區的 maxTotal 限制同一集區同時借出的物件數。
  3. 核對帳號限流額度;需要提高額度時,按限流中的流程申請。
  4. 根據實際負載調整伺服器設定或增加機器數。

所有任務都隨並行數增加而變慢

檢查網路頻寬是否達到上限,以及實際並行數是否過高,再根據業務負載調整並行數或伺服器設定。