自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

指尖上的大數(shù)據(jù) - IM消息處理優(yōu)化方案

大數(shù)據(jù)
消息從服務(wù)端推到客戶端,然后被分發(fā)到消息隊列,在消息隊列中經(jīng)過一番處理后,最終展示到頁面上。這張圖只是簡單地描述了消息的處理流程,下面我們將分步驟說明每個流程的設(shè)計和優(yōu)化。

在即時通訊領(lǐng)域,高并發(fā)消息處理是個很重要的話題。以京東客服系統(tǒng)舉例,每當(dāng)促銷時,促銷店鋪的每個客服短時間內(nèi)可能接收到大量的用戶咨詢,如果不能及時快速地展示出用戶咨詢的信息,那么就無法對用戶的咨詢進行快速的回復(fù),進而可能會造成一定的用戶流失,這是商家和京東所不能接受的。處理這種高并發(fā)的場景時,我們需要在消息查重、數(shù)據(jù)庫IO性能、內(nèi)存緩存、UI顯示等多維度進行優(yōu)化。

[[424648]]

消息從服務(wù)端推到客戶端,然后被分發(fā)到消息隊列,在消息隊列中經(jīng)過一番處理后,最終展示到頁面上。這張圖只是簡單地描述了消息的處理流程,下面我們將分步驟說明每個流程的設(shè)計和優(yōu)化。

一、消息查重設(shè)計

客戶端在消息處理完(存入數(shù)據(jù)庫)后會給服務(wù)端發(fā)送已收回執(zhí),當(dāng)服務(wù)端收到回執(zhí)后便不再重復(fù)給客戶端下發(fā)此消息,然而現(xiàn)實場景中如果我們處理的過慢或者網(wǎng)絡(luò)丟包,那服務(wù)端就會重復(fù)給客戶端發(fā)送該消息。既然我們無法避免重復(fù)消息,那查重流程就是我們首先要考慮的。

1.1**重復(fù)消息處理過濾機制**

通常情況消息處理隊列為一個串行隊列,一條消息進入處理隊列后,會涉及到Message表、User表,Conversation表等多個表的讀寫,而我們知道數(shù)據(jù)庫的IO是非常耗時的。由于消息處理是個串行隊列,消息會按照時間接收順序在隊列中排隊,如果消息處理的不夠快,那么服務(wù)端會因長時間收不到客戶端回執(zhí)而重復(fù)下發(fā)該消息,極端情況下這會導(dǎo)致消息隊列中出現(xiàn)大量的重復(fù)消息,隊列壓力會越來越大,內(nèi)存暴增導(dǎo)致OOM。另外因為重復(fù)的消息導(dǎo)致隊列變長,新消息也不能及時被處理。解決這個問題很簡單,我們可以在消息進入處理隊列前先進行過濾,如果已經(jīng)有同樣的消息進入處理隊列,就直接丟掉。具體設(shè)計如下圖:

1.2**本地緩存過濾機制**

為了避免相同消息重復(fù)處理的情況,消息在進入處理隊列后,首先要判斷該消息是否已經(jīng)處理過(標(biāo)志就是緩存是否已經(jīng)同樣的消息),如果緩存有則不重復(fù)處理。其中緩存分為內(nèi)存緩存和數(shù)據(jù)庫兩部分,當(dāng)消息在持久化時,同時在內(nèi)存和數(shù)據(jù)庫中進行緩存。消息查重分為兩步,首先判斷內(nèi)存緩存中是否有,如果有則直接丟棄該消息,而如果沒有再通過sql來查詢數(shù)據(jù)庫,如果第一步內(nèi)存緩存命中,就可以少一次數(shù)據(jù)庫的查詢。具體設(shè)計如下圖:

二、寫入性能優(yōu)化

2.1**消息批處理寫入**

消息處理完需要入庫持久化,在這里可以分為兩種方式,一種是消息處理完立即入庫,一種是開啟事務(wù)批量入庫。其中第一種比較好理解實現(xiàn)起來也比較簡單,第二種我們在消息積攢到一定量或者一個時間段結(jié)束后批量入庫。SQLite的數(shù)據(jù)操作實質(zhì)上是對數(shù)據(jù)文件的IO操作,頻繁地插入數(shù)據(jù)會導(dǎo)致文件IO經(jīng)常開閉,非常損耗性能。通過開啟事務(wù)將數(shù)據(jù)先緩存在內(nèi)存中,當(dāng)提交事務(wù)時再把所有的更改更新到數(shù)據(jù)文件,此時數(shù)據(jù)文件的IO只需要開閉一次,也避免了長期占用文件IO所導(dǎo)致性能低下的問題。

以下數(shù)據(jù)表記錄了在iPhone 6s設(shè)備上,這兩種方式不同數(shù)據(jù)量寫入數(shù)據(jù)庫消耗的時間:

通過上表,我們可以看到數(shù)據(jù)量越大,開啟事務(wù)后性能提升就越明顯。那是不是在實踐中一定要開啟事務(wù)呢?不一定。對于IM消息來說,大部分服務(wù)端都是一條一條下發(fā)給客戶端,并不存在多條消息同時到達(dá)客戶端的情況,如果我們想用到事務(wù)的特性,需要先將處理完的消息緩存到內(nèi)存中,定時或者定量進行批處理入庫,而這都需要額外的邏輯實現(xiàn),會增加代碼的復(fù)雜度,進而增加維護成本。另外由于消息到達(dá)先后特性,最終的效果會因為網(wǎng)絡(luò)等狀況并沒有上面的數(shù)據(jù)那么好。大家可以根據(jù)自身的情況抉擇。

除了利用事務(wù)來提高寫入性能外,SQLite在3.7.0版本引入了WAL(Write-Ahead Log)模式,在特定情況下可以大幅提升寫入性能。

2.2**開啟WAL模式**

“原子提交(atomic commit)”是SQLite一個重要特性,原子提交意味著單個事務(wù)的所有更改要么全部完成,要么全部不完成,不會出現(xiàn)單個事務(wù)內(nèi)的操作執(zhí)行到一半的情況。為了實現(xiàn)這個特性,SQLite需要臨時文件的輔助,比如rollback模式的journal文件;WAL模式的wal文件和shm文件。

SQLite默認(rèn)為rollback模式,我們可以通過修改配置更改為WAL模式。下面通過對兩種模式的事務(wù)提交流程分析,來看看WAL模式怎么提高寫性能的。

2.2.1ROLLBACK 模式

SQLite數(shù)據(jù)庫連接默認(rèn)為rollback模式(journal_mode = DELETE;)。 rollback模式工作原理大致為:寫操作進行前進行數(shù)據(jù)庫文件拷貝,然后對數(shù)據(jù)庫進行寫操作。如果發(fā)生Crash或者Rollback則將日志中的原始內(nèi)容回滾到數(shù)據(jù)庫文件進行恢復(fù)操作,否則在Commit完成時刪除日志文件。以下為rollback模式下寫入的重要的節(jié)點:

  • 首先,在系統(tǒng)緩存中創(chuàng)建rollback journal文件,把需要修改的原始內(nèi)容保存到這個文件中,然后修改用戶空間的數(shù)據(jù)庫;
  • 然后,將rollback journal文件頭和文件內(nèi)容通過兩次fsync()從系統(tǒng)緩存同步到磁盤中(這個步驟非常耗時);
  • 下一步,先將修改后的數(shù)據(jù)同步到系統(tǒng)緩存,再同步到磁盤中;
  • 最后,刪除rollback journal文件;

以上只列舉了單個事務(wù)提交成功的流程,由于篇幅的原因,如提交失敗(設(shè)備斷電、系統(tǒng)崩潰等)rollback流程等細(xì)節(jié)內(nèi)容可以參考SQLite官方文檔,文檔很完善,強烈建議抽時間學(xué)習(xí)下。

2.2.2WAL**模式**

首先,我們看下官方文檔中對WAL模式的優(yōu)缺點描述:

優(yōu)點有:

  • 在大多數(shù)情況下,使用WAL模式速度更快;
  • WAL模式進一步提升了數(shù)據(jù)庫的并發(fā)性,因為讀不會阻塞寫,而寫也不會阻塞讀,讀和寫可以并發(fā)執(zhí)行;
  • 使用WAL模式,磁盤I/O操作更有秩序;
  • 使用WAL模式減少了fsync()操作次數(shù),因此不易受到系統(tǒng)上的fsync()系統(tǒng)調(diào)用(system call)中斷的影響;

缺點有:

  • WAL模式通常要求VFS支持共享內(nèi)存原語(shared-memoryprimitives);
  • 使用數(shù)據(jù)庫的所有進程必須位于同一臺主機上, WAL無法在網(wǎng)絡(luò)文件系統(tǒng)上運行;
  • 在讀取操作遠(yuǎn)多于寫入操作的應(yīng)用程序中,WAL可能比傳統(tǒng)的日志模式稍慢(可能慢1%或2%);
  • 每個數(shù)據(jù)庫文件都關(guān)聯(lián)了額外的.wal文件和.shm共享內(nèi)存文件;

**寫流程:**

WAL模式相較于rollback則采用了相反的做法。在進行數(shù)據(jù)庫寫操作時,將數(shù)據(jù)append到-wal日志文件中而原有數(shù)據(jù)庫內(nèi)容保存不變。如果事務(wù)失敗,-wal文件中的記錄會被忽略;如果事務(wù)成功,它將在隨后的某個時間被寫回到數(shù)據(jù)庫文件中,該步驟被稱為Checkpoint。WAL模式下寫數(shù)據(jù)庫操作比rollback模式下更為集中,而且該模式下顯著降低了磁盤同步fsync()的頻率,所以相對來說寫性能更優(yōu)秀。我們可以使用以下代碼開啟WAL模式:

  1. \1. PRAGMA journal_mode = WAL; 

**讀流程:**

在WAL模式下讀的時候,SQLite會先在WAL文件中搜索,找到最后一個寫入點,記住它,并忽略在此之后的寫入點(這保證了讀寫和讀讀可以并發(fā)執(zhí)行)。隨后,它確定所要讀的數(shù)據(jù)的所在頁是否在-wal文件中,如果在,則讀-wal文件中的數(shù)據(jù),如果不在,則直接讀數(shù)據(jù)庫文件中的數(shù)據(jù)。為了避免每個讀取操作掃描整個-wal文件來尋找頁面(-wal文件可以增長到幾兆字節(jié),具體取決于Checkpoint運行的頻率,默認(rèn)情況下,當(dāng)-wal文件達(dá)到1000頁的閾值大小時,SQLite會自動執(zhí)行Checkpoint,我們也可以修改SQLITE_DEFAULT_WAL_AUTOCHECKPOINT來指定不同的閾值),SQLite提供了WAL-index文件來輔助頁面的查找。WAL-index文件使用了進程間共享內(nèi)存的技術(shù),共享內(nèi)存是一個以.shm結(jié)尾并且和數(shù)據(jù)庫文件在同一個目錄下的文件,這個文件比較特別,內(nèi)存和文件存在映射關(guān)系,取到這個文件的地址后可以像內(nèi)存一樣對其讀寫,而一般文件需要調(diào)用read、write函數(shù)才能讀寫。WAL-index可以幫助讀取操作快速定位WAL文件中的頁面,極大地提高了讀取的性能。

**讀、寫測試:**

以下數(shù)據(jù)表記錄了在iPhone 6s設(shè)備上,這兩種模式不同數(shù)據(jù)量的寫和讀耗時:

  • 寫入測試
  • 讀測試

從上面兩個表的測試數(shù)據(jù)可以看到WAL模式對讀性能影響有限,而寫入性能相對于rollback模式提升了3**~4倍左右**。iOS系統(tǒng)從5.1.1版本開始SQLite版本便升級到3.7.7,而我們現(xiàn)在大部分應(yīng)用支持的最低版本為iOS8,所以我們可以直接開啟WAL模式來提高寫入性能。

三、查詢性能優(yōu)化

3.1**對常用列查詢添加索引**

為了防止查詢數(shù)據(jù)時每次都遍歷整張表,常見的關(guān)系型數(shù)據(jù)庫均提供了索引,適當(dāng)?shù)靥砑铀饕梢源蟠筇岣邤?shù)據(jù)庫的讀性能。SQLite索引結(jié)構(gòu)為B+樹,也被存在數(shù)據(jù)庫文件里,結(jié)構(gòu)如下圖(該圖來自維基百科) :

提升查找速度的關(guān)鍵在于盡可能減少磁盤I/O,那么可以知道,每個節(jié)點中的key個數(shù)越多,樹的高度就越小,需要I/O的次數(shù)也就越少。因為B+樹的非葉節(jié)點中不存儲data,所以可以存儲更多的key。很多存儲引擎在B+樹的基礎(chǔ)上進行了優(yōu)化,添加了指向相鄰葉節(jié)點的指針,形成了帶有順序訪問指針的B+樹,這樣做可以提高區(qū)間查找的效率,只要找到第一個值那么就可以順序的查找后面的值。

3.1.1**幾種索引方式**

SQLite主要有以下四種索引方式:

  • 普通索引(只基于表的一個列創(chuàng)建的索引)
  • 唯一索引(除了普通索引的特性,索引列重復(fù)的數(shù)據(jù)不允許插入到表中)
  • 隱式索引(數(shù)據(jù)庫隱式為主鍵創(chuàng)建的唯一索引)
  • 組合索引(基于一個表的兩個或多個列創(chuàng)建的索引)

這里重點說下組合索引,例如為table_name表創(chuàng)建了col1,col2,col3組合索引:

  1. \1. ALTER TABLE 'table_name' ADD INDEXindex_name('col1','col2','col3'); 

組合索引遵循”最左前綴”原則,把最常用作為檢索或排序的列放在最左,依次遞減,上面的組合索引相當(dāng)于建立了col1,col1col2,col1col2col3三個索引,而col2或者col3是不能使用索引的,這里一定要注意查詢語句和索引的順序要一致,否則索引無法正常命中。

3.1.2**添加索引性能提升**

以下數(shù)據(jù)表記錄了在iPhone 6s設(shè)備上,不同數(shù)據(jù)量有無索引情況下的性能表現(xiàn):

從上面表來看,添加索引對數(shù)據(jù)庫的讀性能提升很大,尤其是當(dāng)本地數(shù)據(jù)表越來越大,有索引與沒有索引讀性能對比是天壤地別。但是在使用索引時一定要要了解每種索引的適用、命中原則情況,不要一股腦的添加索引。首先,索引是需要額外的磁盤空間存儲;其次,在insert/update數(shù)據(jù)時索引結(jié)構(gòu)可能會發(fā)生變化消耗一部分寫入性能;再次,不合理的查詢語句會命中不了索引。查詢優(yōu)化還是建議大家翻閱官方文檔。

3.2**增加內(nèi)存Cache層提升查詢性能**

雖然我們可以通過添加索引的方式,提升數(shù)據(jù)庫的查詢性能。但畢竟在系統(tǒng)磁盤緩存未命中時還是需要進行磁盤IO,而我們知道磁盤IO是非常耗時,所以減少對庫的操作對讀性能提升也很有幫助。為了實現(xiàn)這點,我們可以在DB層上面增加內(nèi)存Cache層,在讀數(shù)據(jù)時優(yōu)先從內(nèi)存Cache層讀,如果命中便可以少一次讀庫操作。內(nèi)存緩存可以使用簡單的key-value結(jié)構(gòu),key為主鍵(或者其他唯一鍵,這個鍵應(yīng)當(dāng)經(jīng)常被當(dāng)作查詢條件),下圖為增加內(nèi)存Cache層后的查詢和緩存邏輯:

四、消息**UI刷新設(shè)計**

當(dāng)消息處理完后,下一步需要把消息展示在UI上。如果每條消息處理完就立即刷新頁面,在普通低并發(fā)場景下沒有太大問題,但是在高并發(fā)場景下就會造成短時間內(nèi)UI刷新次數(shù)過多,從而導(dǎo)致頁面卡頓,在這里我們可以通過兩種方式進行優(yōu)化。

4.1**延遲刷新**

消息到達(dá)UI隊列時,可以延遲特定時間(比如100ms)再刷新UI,每條消息都將UI刷新的時間延遲100ms刷新。為了防止UI刷新操作因新消息的到來而一直被延遲,可以設(shè)置延遲閾值(比如2s),當(dāng)達(dá)到延時閾值時,直接提交刷新UI操作。

4.2**滑動列表時不刷新UI**

當(dāng)用戶滑動會話列表/會話頁消息列表時,列表不刷新,等到列表停止滑動時再刷新,這樣可以保證列表的滑動流暢度。iOS實現(xiàn)起來很方便,只要把Timer加到NSDefaultRunLoopMode就可以了。下圖為具體的實現(xiàn)邏輯:

五、最終完整的設(shè)計

我們通過上面幾點,將消息處理的每個步驟的優(yōu)化點一一做了說明,下圖詳細(xì)地展示了消息從接收到展示的完整處理流程:

六、最后

我們通過消息查重設(shè)計、寫入性能優(yōu)化、查詢性能優(yōu)化、消息UI刷新設(shè)計四個維度,分別介紹了高并發(fā)消息處理的優(yōu)化邏輯。希望通過此文章,可以給你在設(shè)計客戶端高并發(fā)消息處理方案時提供一種新的思路。

 

責(zé)任編輯:未麗燕 來源: 京東零售云
相關(guān)推薦

2022-12-30 15:29:35

數(shù)據(jù)分析工具Pandas

2024-07-01 08:18:14

2014-04-09 13:18:12

移動互聯(lián)技術(shù)商業(yè)銀行

2012-06-25 15:16:06

搜索

2019-10-12 05:17:11

物聯(lián)網(wǎng)大數(shù)據(jù)IOT

2012-08-01 16:43:40

歐朋瀏覽器

2019-02-21 15:34:08

數(shù)據(jù)分析師大數(shù)據(jù)可視化

2011-12-24 14:16:42

惠普IT績效管理信息優(yōu)化

2024-10-10 10:07:07

2023-08-22 08:01:42

SpringBatch事務(wù)管理

2017-07-21 14:22:17

大數(shù)據(jù)大數(shù)據(jù)平臺數(shù)據(jù)處理

2016-01-15 18:45:24

IM即時通訊云服務(wù)

2016-12-07 15:25:55

2017-06-23 21:32:16

MySQL大數(shù)據(jù)優(yōu)化

2012-06-06 10:38:23

惠普信息優(yōu)化大數(shù)據(jù)

2023-11-29 13:56:00

數(shù)據(jù)技巧

2013-04-18 10:04:02

大數(shù)據(jù)方案Hadoop

2024-06-27 08:00:17

2016-01-31 08:26:48

設(shè)計

2023-03-27 18:33:47

客服IM消息
點贊
收藏

51CTO技術(shù)棧公眾號