Soul即時(shí)通訊之?dāng)?shù)據(jù)庫優(yōu)化

背景

在Soul的IM上線后,初始時(shí)用戶本地消息量不大的情況下,數(shù)據(jù)庫讀寫良好,不容易發(fā)現(xiàn)問題。

但隨著產(chǎn)品用的時(shí)間越來越近,有些用戶本地聊天數(shù)據(jù)達(dá)到500萬條以上時(shí),數(shù)據(jù)庫性能瓶頸逐漸體現(xiàn)出來。

1.讀寫數(shù)據(jù)較慢。

2.讀寫在同一線程里,當(dāng)大量數(shù)據(jù)寫入時(shí),遲遲讀不出數(shù)據(jù),體驗(yàn)較差

所以數(shù)據(jù)庫這塊急需優(yōu)化

優(yōu)化之前的方案

之前im用的是著名三方數(shù)據(jù)庫FMDB,它只是簡(jiǎn)單的對(duì)sqlite進(jìn)行了封裝,線程安全方案是把所有的數(shù)據(jù)庫操作放到了一個(gè)串行隊(duì)列里去同步執(zhí)行。

因?yàn)閟qlite是不能多個(gè)操作同時(shí)訪問一個(gè)連接,不然會(huì)crash。

這樣做的好處是確保了數(shù)據(jù)庫連接數(shù)據(jù)讀寫安全,缺點(diǎn)是無法進(jìn)行線程并發(fā)操作。

對(duì)于IM這種對(duì)海量數(shù)據(jù)存儲(chǔ)性能要求較高的項(xiàng)目,F(xiàn)MDB不能滿足需求。

優(yōu)化思考

我們所要做的是支持多線程讀寫的數(shù)據(jù)庫。

這里介紹下WAL模式:SQLite引入了 WAL 模式,即 Write-Ahead Log。在這種模式下,所有的修改會(huì)寫入一個(gè)單獨(dú)的 WAL 文件內(nèi)。這種模式下,寫操作甚至可以不去操作數(shù)據(jù)庫,這使得所有的讀操作可以在 "寫的同時(shí)" 直接對(duì)數(shù)據(jù)庫文件進(jìn)行操作,得到更好的并發(fā)性能。
這樣實(shí)現(xiàn)了讀和寫的并發(fā)。

同時(shí)sqlite支持三種線程模式:
單線程模型 這種模型下,所有互斥鎖都被禁用,同一時(shí)間只能由一個(gè)線程訪問。
多線程模型 這種模型下,一個(gè)連接在同一時(shí)間內(nèi)只有一個(gè)線程使用就是安全的。
串行模型 開啟所有鎖,可以隨意訪問。

優(yōu)化嘗試

前提條件:首先串行模式pass掉,只考慮單線程和多線程模式。

1.建表:


create table if not exists ChatModelDB (localId INTEGER primary key AUTOINCREMENT, 
CommonId varchar(256),
  text1 varchar(256), 
  text2 varchar(256),
  text3 varchar(256),
  text4 varchar(256), 
  text5 Long Long, 
  text6 INTEGER), 

CommonId建立索引

2.已在數(shù)據(jù)庫里插入100萬條數(shù)據(jù),每一列都是隨機(jī)字符。

1.單線程模式 +無wal,也是FMDB模式

插入1萬條根據(jù)CommonId讀取20條

- (void)insert {
dispatch_async(queue, ^{
      [db beginTransaction];
for (int j = 0;j<10000;j++) {
       NString *insertSql = @"insert *****;
       BOOL result = [db executeUpdate:insertSql];
            }
        [db commit];
        }];
     });
}
- (void)select{
     dispatch_async(queue, ^{
            NSArray *array = [db getRowsForQuery:selectSql];
     });
}
混合讀寫
for (i = 0;i<100;i++) {
[self insert];
[self select];
}

寫消耗:1237ms,

讀消耗:10-20ms

混合讀寫:72185

2.單線程模式+wal

 if (sqlite3_exec(_wdb, "PRAGMA journal_mode=WAL;", NULL, NULL, &error) != SQLITE_OK) {
        NSLog(@"Failed to set WAL mode: %s", error);
    }

寫消耗:480ms

讀消耗:10-15ms

混合讀寫:76205ms

會(huì)發(fā)現(xiàn),打開wal模式后,寫性能近乎提示了3倍,讀性能差別不大。

3.多線程模式+多線程連接+wal

    dispatch_async(globlequeue, ^{
       sqlite3_open(db);
           [self insert];
       sqlite3_close(db);
     });
    dispatch_async(globlequeue, ^{
       sqlite3_open(db);
           [self select];
       sqlite3_close(db);
     });

混合讀寫:157301ms

會(huì)發(fā)現(xiàn)混合性能很差,真正開啟所謂的多線程操作時(shí),實(shí)際上是每個(gè)線程持有一個(gè)數(shù)據(jù)庫句柄,每做一個(gè)操作前,要重新打開一個(gè)db句柄,執(zhí)行完后再close db句柄,這樣頻繁的打開數(shù)據(jù)是很消耗性能的,一味地追求多線程并不會(huì)達(dá)到很好的性能,并且操作不好還容易crash。

在移動(dòng)端這種微操作系統(tǒng)對(duì)數(shù)據(jù)庫的依賴遠(yuǎn)沒有后端那般沉重,所以接下來考慮用單句柄+隊(duì)列的方式進(jìn)行測(cè)試。

4.多線程模式+wal+異步單隊(duì)列

queue = dispatch_queue_create([[self queueName] UTF8String], DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
           [self insert];
     });

    dispatch_asyncqueue, ^{
           [self select];
     });

讀寫性能和2相差不大
混合讀寫:68300ms
混合讀寫性能優(yōu)于2,在大型app場(chǎng)景下性能尤為明顯,本次測(cè)試是在無其他業(yè)務(wù)線程影響下,等于測(cè)試示例獨(dú)自cpu資源,和線上場(chǎng)景誤差交大。

5.多線程模式+wal+同步單隊(duì)列

queue = dispatch_queue_create([[selfqueueName] UTF8String], DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
           [self insert];
     });
    dispatch_syncqueue, ^{
           [self select];
     });

混合讀寫:66454ms
性能略遜于4,相差微乎其微,考慮切線程影響

考慮,多線程模式+wal模式下,讀和讀并發(fā),讀和寫并發(fā),寫和寫不并發(fā),
而讀速度遠(yuǎn)大于寫速度,因?yàn)榻?jīng)檢驗(yàn),在500萬數(shù)據(jù)量下,讀速度在20ms以內(nèi)速度較快,所以讀并發(fā)暫不考慮,只給數(shù)據(jù)庫開啟兩條連接,讀連接和寫連接, 只做讀和寫之間的并發(fā),為此,設(shè)置兩條隊(duì)列:讀隊(duì)列和寫隊(duì)列,彼此異步執(zhí)行,測(cè)試示例6。

6.多線程模式+wal+讀寫隊(duì)列分離

readqueue = dispatch_queue_create([[self queueName] UTF8String], DISPATCH_QUEUE_SERIAL);
writequeue = dispatch_queue_create([[self queueName] UTF8String], DISPATCH_QUEUE_SERIAL);

    dispatch_async(writequeue, ^{
           [self insert];
     });
    dispatch_async(readqueue, ^{
           [self select];
     });

混合讀寫:73112ms

這里會(huì)發(fā)現(xiàn),雙隊(duì)列的讀寫速度居然略慢于單隊(duì)列讀寫。

分析,因?yàn)閿?shù)據(jù)是在demo上跑,demo上無其他任務(wù)在執(zhí)行,等于cpu滿負(fù)荷只為測(cè)試調(diào)用,而理論上頻繁的線程切換也需要資源開銷,單隊(duì)列模式下的線程切換減少,性能略優(yōu)于雙隊(duì)列。

但在實(shí)際的項(xiàng)目中,app場(chǎng)景復(fù)雜,cpu無時(shí)不在執(zhí)行做一些復(fù)雜的功能,所以在demo上播放一個(gè)長視頻進(jìn)行測(cè)試,并且同時(shí)異步執(zhí)行一個(gè)while循環(huán)空轉(zhuǎn),測(cè)試cpu調(diào)度頻繁被其他業(yè)務(wù)影響時(shí)數(shù)據(jù)庫操作的性能。

經(jīng)測(cè)試混合讀寫:1.多線程模式+wal+異步單隊(duì)列 7800ms左右
2.多線程模式+wal+同步單隊(duì)列 73960ms左右
3. 多線程模式+wal+讀寫隊(duì)列分離 60471ms左右

在cpu調(diào)度頻繁的場(chǎng)景下,多線程的優(yōu)勢(shì)體現(xiàn)了出來,在wal模式下,寫性能提升幾倍,讀和寫并發(fā),讀和讀并發(fā),寫和寫串行,達(dá)到效率最大化。

所以決定選用模式多線程模式+wal+讀寫隊(duì)列分離模式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容