說一下場景
線上總有反饋說從直播間掉線,然后測試開始壓測,發(fā)現(xiàn)對于低端設備在我們業(yè)務中推流場景下只能到60听怕,即會發(fā)生異常;
先說一下優(yōu)化前的問題
- 業(yè)務邏輯層捧挺,在一個協(xié)程里順序處理每一個 ws 接收者,eg:IM,Inner Notification,直播間..., 處理完后尿瞭,才會開始開始下一條 ws 消息的處理;
- 對于 IM 的ws 消息闽烙,也會走進 直播間 ws 消息的處理,只是直播間 ws 解析 command 后声搁,發(fā)現(xiàn)自己無需處理黑竞,才會return;
- 直播間有個特殊命令「簡稱 special cmd」確保用戶始終和直播間保持鏈接,若 n 秒內未接收到 special cmd疏旨,則會執(zhí)行退房處理. 問題是在第1條的描述中很魂,ws 會有堆積,若之前的消息處理邏輯過多檐涝,則會導致這個 special cmd 始終無法處理;
- 在直播間中遏匆,用戶會刷評論/送禮等 level 較低的消息,若設備低端谁榜,無法處理大量低 level 消息時幅聘,可做丟棄或用其他 coroutine 處理,不要影響直播間中處理主流程 ws 的coroutine;
- ws 中 jsonstring 重復解析問題窃植,Gson 解析耗時問題眾所周知帝蒿,重復解析且解析方式問題,導致 CPU 占用增大 and 發(fā)熱 and 耗時增多巷怜,進而降低設備 CPU 處理能力葛超,惡性循環(huán);
優(yōu)化思路
- 不同場景只處理屬于自己的 ws;
- 解決 ws 積壓導致后面消息無法及時處理的問題;
- 對于直播間等重 ws 的業(yè)務場景,low_level 且對 UI 線程影響較大的 msg 單獨 coroutine 處理丛版,eg:評論,送禮「有動畫」;
- Gson 解析中高頻 key 解析優(yōu)化 and 優(yōu)化 Gson 解析使用方式;
優(yōu)化實戰(zhàn)
二次分發(fā)
- 目前所有的 ws 都在 totalFlow 中巩掺,需要做二次分發(fā). 根據(jù)目前公司提供的后端數(shù)據(jù)偏序,只能根據(jù) ws 中攜帶的 command 做 filter 過濾页畦;
- 對于少量且低頻的 msg,eg:IM, InnerNotification 等封裝為一個 otherFlow;
- 對于大量且重要的 msg,eg:直播間流程中的各 msg 封裝為一個 liveFlow;
- 對于大量且可根據(jù)設備處理能力丟棄 or 延遲處理的msg,eg:評論,送禮 等封裝為一個 highFreqFlow;
otherFlow, liveFlow, highFreqFlow 均為 MutableSharedFlow;
其中 extraBufferCapacity 分別為 5, 800, 300「5:基本不會有積壓; 800:重要流程消息,為防止被丟棄設為 較高閥值研儒,服務端目前最高并發(fā)貌似也不會超過 800; 300:評論,送禮都是可丟棄的豫缨,且評論區(qū)最高存儲消息 count 為 150」;
onBufferOverflow 均為 BufferOverflow.DROP_OLDEST;
處理之后也解決了 special cmd 無法及時處理,導致退房的bug.
Gson 解析
對于剛需字段 command端朵,初次解析后好芭,在 ws 的 data 里增加一個 command 即可, so easy.
data class Broadcast(val message: JSONObject, val command: Int)
對于 Gson 解析,改之前寫法如下
val a = jsonString.optJSONObject("A")?.toString() ?: ""
val b = jsonString.optJSONObject("B")?.toString() ?: ""
val aa = try {
GsonManager.gson().fromJson<User>(a, object : TypeToken<User>() {}.type)
} catch (e: Exception) {
User()
}
val bb = GsonManager.gson().fromJson<User>(b, object : TypeToken<User>() {}.type)
val c = jsonString.optLong("c")
val d = jsonString.optInt("d")
var e = jsonString.optString("e", "")
val f = jsonString.optBoolean("f", false)
val g = jsonString.optJSONObject("g")?.toString() ?: ""
val extra = try {
GsonManager.gson().fromJson<CCC>(g, object : TypeToken<CCC>() {}.type)
} catch (e: Throwable) {
null
}
改完后
val data = try {
GsonManager.gson().fromJson<AB>(jsonString, object : TypeToken<AB>() {}.type)
} catch (e: Throwable) {
return
}
data class AB(
@SerializedName("a")
var a: User,
@SerializedName("b")
var b: User,
@SerializedName("c")
var c: Long,
@SerializedName("d")
var d: Int,
@SerializedName("e")
var e: String,
@SerializedName("f")
var f: Boolean? = null,
@SerializedName("g")
var g: CCC? = null,
)
至于 Gson 解析耗時原理的話冲呢,自行查閱吧舍败,我也不太熟...
其他優(yōu)化
在對上面的 totalFlow 做初步解析的時候,用到了很多 filter 方法,每一次 filter 都是新建的一個 flow邻薯,盡量一次 filter 完成功能裙戏,如下
public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
if (predicate(value)) return@transform emit(value)
}
//優(yōu)化前如下:
flow.filter {
//**
}.map {
//***
}.filter {
//**
}.catch {
//**
}
//優(yōu)化后如下:
flow.mapNotNull{
//**
}.catch {
//**
}
另外對于瘋狂刷評論等操作,肯定會導致 評論區(qū)的UI 瘋狂刷新厕诡,可以新建一個隊列緩存 comment msg累榜,每秒取 3-4 次,每次取的 msg count 根據(jù)設備 level 來定灵嫌,減少 UI 繪制壓力壹罚;
成果
單叢直播壓測的角度來講,對于低端設備寿羞,結論如下:
優(yōu)化前 推流-評論-60 掉線;
注:以上數(shù)字為每秒發(fā)送聊天消息*條猖凛;
卡頓檢測工具
說一下發(fā)現(xiàn) gson 耗時的檢測工具:
這版優(yōu)化前,還有一版優(yōu)化绪穆,當時發(fā)現(xiàn)的問題是大量 gson 解析發(fā)生在 UI thread, 看下圖:
原因是: ws 接收的 coroutine dispatcher 是 Main.
不過也能發(fā)現(xiàn) gson 耗時問題.
拓展
目前進一步的優(yōu)化所用工具為 tencent matrix.