微信 SQLite 数据库修复实践
1、前言
眾所周知,微信在后臺(tái)服務(wù)器不保存聊天記錄,微信在移動(dòng)客戶端所有的聊天記錄都存儲(chǔ)在一個(gè) SQLite 數(shù)據(jù)庫(kù)中,一旦這個(gè)數(shù)據(jù)庫(kù)損壞,將會(huì)丟失用戶多年的聊天記錄。而我們監(jiān)控到現(xiàn)網(wǎng)的損壞率是0.02%,也就是每 1w 個(gè)用戶就有 2 個(gè)會(huì)遇到數(shù)據(jù)庫(kù)損壞。考慮到微信這么龐大的用戶基數(shù),這個(gè)損壞率就很嚴(yán)重了。更嚴(yán)重的是我們用的官方修復(fù)算法,修復(fù)成功率只有 30%。損壞率高,修復(fù)率低,這兩個(gè)問(wèn)題都需要我們著手解決。
2、SQLite 損壞原因及其優(yōu)化
我們首先來(lái)看 SQLite 損壞的原因,SQLite官網(wǎng)(http://www.sqlite.org/howtocorrupt.html)上列出以下幾點(diǎn):
- 文件錯(cuò)寫
- 文件鎖 bug
- 文件 sync 失敗
- 設(shè)備損壞
- 內(nèi)存覆蓋
- 操作系統(tǒng) bug
- SQLite bug
但是我們通過(guò)收集到的大量案例和日志,分析出實(shí)際上移動(dòng)端數(shù)據(jù)庫(kù)損壞的真正原因其實(shí)就3個(gè):
- 空間不足
- 設(shè)備斷電
- 文件 sync 失敗
我們需要針對(duì)這些原因一一進(jìn)行優(yōu)化。
2.1、優(yōu)化空間占用
首先我們來(lái)優(yōu)化微信的空間占用問(wèn)題。在這之前微信的部分業(yè)務(wù)也做了空間清理,例如朋友圈會(huì)自動(dòng)刪除7天前緩存的圖片。但是總的來(lái)說(shuō)對(duì)文件空間的使用缺乏一個(gè)全局把控,全靠各個(gè)業(yè)務(wù)自覺(jué)。我們需要做得更積極主動(dòng),要讓開(kāi)發(fā)人員意識(shí)到用戶的存儲(chǔ)空間是寶貴的。我們采取以下措施:
- 業(yè)務(wù)文件先申請(qǐng)后使用,如果某個(gè)文件沒(méi)有申請(qǐng)就使用了,會(huì)被自動(dòng)掃描出來(lái)并刪除;
- 每個(gè)業(yè)務(wù)文件都要申明有效期,是一天、一個(gè)星期、一個(gè)月還是永久存儲(chǔ);
- 過(guò)期文件會(huì)被自動(dòng)清理。
對(duì)于微信之外的空間占用,例如相冊(cè)、視頻、其他App的空間占用,微信本身是做不了什么事情的,我們可以提示用戶進(jìn)行空間清理:
2.2、優(yōu)化文件 sync
2.2.1、synchronous = FULL
設(shè)置SQLite的文件同步機(jī)制為全同步,亦即要求每個(gè)事物的寫操作是真的flush到文件里去。
2.2.1、fullfsync = 1
通過(guò)與蘋果工程師的交流,我們發(fā)現(xiàn)在 iOS 平臺(tái)下還有 fullfsync (https://www.sqlite.org/pragma.html#pragma_fullfsync) 這個(gè)選項(xiàng),可以嚴(yán)格保證寫入順序跟提交順序一致。設(shè)備開(kāi)發(fā)商為了測(cè)評(píng)數(shù)據(jù)好看,往往會(huì)對(duì)提交的數(shù)據(jù)進(jìn)行重排,再統(tǒng)一寫入,亦即寫入順序跟App提交的順序不一致。在某些情況下,例如斷電,就可能導(dǎo)致寫入文件不一致的情況,導(dǎo)致文件損壞。
2.3、優(yōu)化效果
多管齊下之后,我們成功將損壞率降低了一半多;DB損壞還是無(wú)法完全避免,我們還是得提高修復(fù)成功率。
3、SQLite 修復(fù)邏輯優(yōu)化
3.1、master 表
首先我們來(lái)看 SQLite 的架構(gòu)。SQLite 使用 B+樹(shù) 存儲(chǔ)一個(gè)表,整個(gè) SQLite 數(shù)據(jù)庫(kù)就是這些 B+樹(shù) 組成的森林。對(duì)于每個(gè)表的元數(shù)據(jù)(表名、根節(jié)點(diǎn)地址、表 scheme 等),都記錄在一個(gè)叫 sql_master 的表中。這個(gè) sql_master 表(下簡(jiǎn)稱 master 表) 本身也是一個(gè) B+樹(shù) 存儲(chǔ)的普通表。
3.2、官方修復(fù)算法率低下原因
官方修復(fù)算法是這樣一個(gè)流程:從 master 表中讀出一個(gè)個(gè)表的信息,根據(jù)根節(jié)點(diǎn)地址和創(chuàng)表語(yǔ)句來(lái) select 出表里的數(shù)據(jù),能 select 多少是多少,然后插入到一個(gè)新 DB 中。要注意的是 master 表他本身也是一個(gè) B+樹(shù) 形式的普通表,DB 第0頁(yè)就是他的根節(jié)點(diǎn)。那么只要 master 表某個(gè)節(jié)點(diǎn)損壞,這個(gè)節(jié)點(diǎn)下面記錄的表就都恢復(fù)不了。更壞的情況是 DB 第0頁(yè)損壞,那么整個(gè) master 表都讀不出來(lái),就導(dǎo)致整個(gè)DB都恢復(fù)失敗。這就是官方修復(fù)算法成功率這么低的原因,太依賴 master 表了。
3.3、備份 master 表
那么最自然的想法,自然是另外備份一份 master 表了,也不需要用B+樹(shù),直接用數(shù)組序列化存儲(chǔ)就好。我們只需要每隔一段時(shí)間輪詢 master 表,看看最近有沒(méi)有增刪 table,有的話就全量備份。
3.3.1、備份時(shí)機(jī)
這里有個(gè)擔(dān)憂,就是普通數(shù)據(jù)表的插入會(huì)不會(huì)導(dǎo)致表的根節(jié)點(diǎn)發(fā)生變化,也就是說(shuō) master 表會(huì)不會(huì)頻繁變化,如果變化很頻繁的話,我們就不能簡(jiǎn)單地進(jìn)行輪詢方案了。通過(guò)分析源碼,我們發(fā)現(xiàn) SQLite 里面 B+樹(shù) 算法的實(shí)現(xiàn)是 向下分裂 的,也就是說(shuō)當(dāng)一個(gè)葉子頁(yè)滿了需要分裂時(shí),原來(lái)的葉子頁(yè)會(huì)成為內(nèi)部節(jié)點(diǎn),然后新申請(qǐng)兩個(gè)頁(yè)作為他的葉子頁(yè)。這就保證了根節(jié)點(diǎn)一旦定下來(lái),是再也不會(huì)變動(dòng)的。實(shí)際的代碼調(diào)試也證實(shí)了我們這個(gè)推論。所以說(shuō) master 表只會(huì)在新創(chuàng)建表或者刪除一個(gè)表時(shí)才會(huì)發(fā)生變化,我們完全可以采用定時(shí)輪詢方案。
3.3.2、備份文件有效性
接下來(lái)的難題是既然 DB 可以損壞,那么這個(gè)備份文件也會(huì)損壞,怎么辦呢?我們采用了 雙備份 的機(jī)制。具體來(lái)說(shuō)就是會(huì)有新舊兩個(gè)備份文件,每個(gè)文件頭都加上 CRC 校驗(yàn);每次備份時(shí),從兩個(gè)備份文件中選出一個(gè)進(jìn)行覆蓋。具體怎么選呢?優(yōu)先選損壞那個(gè)備份文件,如果兩個(gè)都有效,那么就選相對(duì)較舊的。這就保證了即使本次寫入導(dǎo)致文件損壞,還有另外一份備份可以用。這個(gè)做法跟 Realm 標(biāo)榜的 MVCC(多版本并發(fā)控制)的做法有異曲同工之妙,相當(dāng)于確認(rèn)新寫入的文件有效之后,才使用新寫入的文件,否則還是繼續(xù)用舊的有效的文件。
前面提到 DB 損壞的一個(gè)常見(jiàn)場(chǎng)景是空間不足,這種情況下還要分配文件空間給備份文件也是會(huì)失敗的。為了解決這個(gè)問(wèn)題,我們采取 預(yù)先分配空間 的做法,初始值是 32K,大約可存 750 個(gè)表的元信息,后續(xù)則按照32K的倍數(shù)進(jìn)行增長(zhǎng)。
3.4、優(yōu)化效果
通過(guò)備份 master 表,我們成功將修復(fù)成功率提高了一倍多。
4、其他
通過(guò)這些優(yōu)化,我們提高了微信聊天記錄存儲(chǔ)的可靠性。這些優(yōu)化實(shí)踐,會(huì)同之前在并發(fā)性能方面的優(yōu)化實(shí)踐(微信iOS SQLite源碼優(yōu)化實(shí)踐),將會(huì)合并到微信即將開(kāi)源的 WCDB(WeChat Database)組件中。我們正在進(jìn)行緊張的代碼整理工作,爭(zhēng)取在 2017 年年中開(kāi)源 WCDB。
更多精彩內(nèi)容歡迎關(guān)注騰訊 Bugly的微信公眾賬號(hào):
騰訊 Bugly是一款專為移動(dòng)開(kāi)發(fā)者打造的質(zhì)量監(jiān)控工具,幫助開(kāi)發(fā)者快速,便捷的定位線上應(yīng)用崩潰的情況以及解決方案。智能合并功能幫助開(kāi)發(fā)同學(xué)把每天上報(bào)的數(shù)千條 Crash 根據(jù)根因合并分類,每日日?qǐng)?bào)會(huì)列出影響用戶數(shù)最多的崩潰,精準(zhǔn)定位功能幫助開(kāi)發(fā)同學(xué)定位到出問(wèn)題的代碼行,實(shí)時(shí)上報(bào)可以在發(fā)布后快速的了解應(yīng)用的質(zhì)量情況,適配最新的 iOS, Android 官方操作系統(tǒng),鵝廠的工程師都在使用,快來(lái)加入我們吧!
轉(zhuǎn)載于:https://www.cnblogs.com/bugly/p/6824261.html
總結(jié)
以上是生活随笔為你收集整理的微信 SQLite 数据库修复实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: FineUI 页面跳转
- 下一篇: 神经网络之dropout层