李鵬鵬,鄭揚飛,劉玉龍
(華北計算技術(shù)研究所,北京 100083)
Redis在即時通訊系統(tǒng)中的應(yīng)用
李鵬鵬,鄭揚飛,劉玉龍
(華北計算技術(shù)研究所,北京 100083)
傳統(tǒng)的即時通訊系統(tǒng)的弊端之一是數(shù)據(jù)庫I/O次數(shù)頻繁,因為在IM中大量短而多的消息持續(xù)在磁盤的數(shù)據(jù)庫上進行讀寫。為了解決數(shù)據(jù)庫造成的系統(tǒng)瓶頸,選取Mysql和內(nèi)存Key-Value引擎的NoSQL數(shù)據(jù)庫Redis兩級存儲,從而給出基于XMPP的即時通訊系統(tǒng)高可用的優(yōu)化方案。
Redis;XMPP;即時通訊系統(tǒng);數(shù)據(jù)分片
即時通信[1]是以Internet網(wǎng)絡(luò)及有線、無線網(wǎng)絡(luò)為基礎(chǔ)物理設(shè)施,在交互雙方之間實時地傳送文本、語音和圖像等信息的通信方式。新時代的即時通訊系統(tǒng),用戶需要其提供更快、更穩(wěn)定、更可靠的即時通信服務(wù)。伴隨著Internet技術(shù)的不斷發(fā)展,影響網(wǎng)絡(luò)速度的瓶頸主要集中在訪問距離和服務(wù)器承載負(fù)荷能力方面[2]。內(nèi)存數(shù)據(jù)庫以其明顯的優(yōu)勢補足了傳統(tǒng)通信技術(shù)架構(gòu)[3]的不足。本文主要介紹NoSQL在即時通訊系統(tǒng)中的設(shè)計與實踐。
1.1 Redis技術(shù)簡介
Redis[4]是一個開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。
Redis是一個Key-Value存儲系統(tǒng)。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)和zset(有序集合)。這些數(shù)據(jù)類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎(chǔ)上,Redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。區(qū)別的是Redis會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎(chǔ)上實現(xiàn)了master-slave(主從)同步。
Redis是一個遠(yuǎn)程內(nèi)存數(shù)據(jù)庫[5],它不僅性能強勁,而且還具有復(fù)制特性以及為解決問題而生的獨一無二的數(shù)據(jù)模型。Redis提供了5種不同類型的數(shù)據(jù)結(jié)構(gòu),各式各樣的問題都可以很自然地映射到這些數(shù)據(jù)結(jié)構(gòu)上:Redis的數(shù)據(jù)結(jié)構(gòu)致力于幫助用戶解決問題,而不會像其他數(shù)據(jù)庫那樣,要求用戶扭曲問題來適應(yīng)數(shù)據(jù)庫。除此之外,通過復(fù)制、持久化(persistence)和客戶端分片(client-side sharding)等特性,用戶可以很方便地將Redis擴展成一個能夠包含數(shù)百GB數(shù)據(jù)、每秒處理上百萬次請求的系統(tǒng)。
1.2 Redis特性
NoSQL概念在2009年被提了出來。NoSQL最常見的解釋是“non-relational”,“Not Only SQL”也被很多人接受。(“NoSQL”一詞最早于1998年被用于一個輕量級的關(guān)系數(shù)據(jù)庫的名字。)
傳統(tǒng)的關(guān)系數(shù)據(jù)庫具有不錯的性能,高穩(wěn)定型,久經(jīng)歷史考驗,而且使用簡單,功能強大,同時也積累了大量的成功案例。在互聯(lián)網(wǎng)領(lǐng)域,MySQL成為了絕對靠前的王者,毫不夸張的說,MySQL為互聯(lián)網(wǎng)的發(fā)展做出了卓越的貢獻(xiàn)。隨著時間的推移,線上數(shù)據(jù)越來越多,悄然進入大數(shù)據(jù)時代。網(wǎng)站開始快速發(fā)展,火爆的論壇、博客、sns、微博逐漸引領(lǐng)web領(lǐng)域的潮流。巨大的流量給公司帶來了可觀的收入,也帶來了數(shù)據(jù)庫存儲與查詢并優(yōu)的瓶頸。
Redis是基于C/C++開發(fā)的一款K-V數(shù)據(jù)庫,特點是運行異???,使用協(xié)議是類Telnet。除此之外還有以下幾個特性:
● Redis持久化
通常,Redis將數(shù)據(jù)存儲于內(nèi)存中,或被配置為使用虛擬內(nèi)存。通過兩種方式可以實現(xiàn)數(shù)據(jù)持久化:使用截圖的方式,將內(nèi)存中的數(shù)據(jù)不斷寫入磁盤;或使用類似MySQL的日志方式,記錄每次更新的日志。前者性能較高,但是可能會引起一定程度的數(shù)據(jù)丟失;后者相反。
● Redis主從同步
Redis支持將數(shù)據(jù)同步到多臺從庫,這種特性對提高讀取性能非常有益。
● Redis數(shù)據(jù)類型
作為Key-value型數(shù)據(jù)庫,Redis也提供了鍵(Key)和鍵值(Value)的映射關(guān)系。但是,除了常規(guī)的數(shù)值或字符串,Redis的鍵值還可以是以下形式之一:
? Lists(列表)
? Sets(集合)
? Sorted sets(有序集合)
? Hashes(哈希表)
鍵值的數(shù)據(jù)類型決定了該鍵值支持的操作。Redis支持諸如列表、集合或有序集合的交集、并集、查集等高級原子操作;同時,如果鍵值的類型是普通數(shù)字,Redis則提供自增等原子操作。
● 支持事務(wù)
Redis事務(wù)可以一次執(zhí)行多個命令,并且?guī)в幸韵聝蓚€重要的保證。第一,事務(wù)是一個單獨的隔離操作,事務(wù)中的所有命令都會序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷;第二,事務(wù)是一個原子操作,事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。
一個事務(wù)從開始到執(zhí)行會經(jīng)歷以下三個階段:開始事務(wù),命令入隊,執(zhí)行事務(wù)。
● Redis管道技術(shù)
Redis是一種基于客戶端-服務(wù)端模型以及請求/響應(yīng)協(xié)議的TCP服務(wù)。這意味著通常情況下一個請求會遵循以下步驟:首先,客戶端向服務(wù)端發(fā)送一個查詢請求,并監(jiān)聽Socket返回,通常是以阻塞模式,等待服務(wù)端響應(yīng);然后,服務(wù)端處理命令;最后,將結(jié)果返回給客戶端。Redis管道技術(shù)可以在服務(wù)端未響應(yīng)時,客戶端可以繼續(xù)向服務(wù)端發(fā)送請求,并最終一次性讀取所有服務(wù)端的響應(yīng)。
Redis其實開創(chuàng)了一種新的數(shù)據(jù)存儲思路,使用Redis,我們不用再面對功能單調(diào)的數(shù)據(jù)庫時,把精力放在如何把大象放進冰箱的問題,而是利用Redis提供的靈活多變的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)操作,為不同的大象構(gòu)建不同的冰箱。根據(jù)Redis的特性,總結(jié)出其最佳應(yīng)用場景有:適用于數(shù)據(jù)變化快且數(shù)據(jù)庫大小可遇見(適合內(nèi)存容量)的應(yīng)用程序,例如:股票價格、數(shù)據(jù)分析、實時數(shù)據(jù)搜集、實時通訊。
1.3 Redis優(yōu)勢分析
Redis有著更為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)并且提供對他們的原子性操作,這是一個不同于其他數(shù)據(jù)庫的進化路徑。Redis的數(shù)據(jù)類型都是基于基本數(shù)據(jù)結(jié)構(gòu)的同時對程序員透明,無需進行額外的抽象。Redis運行在內(nèi)存中但是可以持久化到磁盤,所以在對不同數(shù)據(jù)集進行高速讀寫時需要權(quán)衡內(nèi)存,因為數(shù)據(jù)量不能大于硬件內(nèi)存。在內(nèi)存數(shù)據(jù)庫方面的另一個優(yōu)點是,相比在磁盤上相同的復(fù)雜的數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中操作起來非常簡單,這樣Redis可以做很多內(nèi)部復(fù)雜性很強的事情。同時,在磁盤格式方面他們是緊湊的以追加的方式產(chǎn)生的,因為他們并不需要進行隨機訪問。
Redis特點突出,正是因為這些特點所以才凸顯其以下幾個優(yōu)點:
? 性能極高-Redis能讀的速度是110000次/s,寫的速度是81000次/s。
? 豐富的數(shù)據(jù)類型-Redis支持二進制案例的Strings,Lists,Hashes,Sets及Ordered Sets數(shù)據(jù)類型操作。
? 原子-Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執(zhí)行。
? 豐富的特性-Redis還支持publish/subscribe,通知,key過期等等特性。
即時通訊系統(tǒng)一般分為客戶端與服務(wù)端,客戶端較輕,關(guān)鍵邏輯在服務(wù)端,一般分為三到四層,采用B/S和C/S混合架構(gòu)。即時通訊系統(tǒng),主要包含的業(yè)務(wù)場景有,注冊登錄、即時會話[6]、組織機構(gòu)、文件傳輸。對于所有的用戶,有在線和離線的狀態(tài)。并且,對于在線狀態(tài)用戶的長連接需要有專用服務(wù)器來維護,表現(xiàn)在架構(gòu)中的連接服務(wù)。系統(tǒng)邏輯架構(gòu)示意圖如圖1。
2.1 數(shù)據(jù)庫詳細(xì)設(shè)計
數(shù)據(jù)庫設(shè)計圖,見圖2:
如圖2所示,根據(jù)業(yè)務(wù)邏輯層,將數(shù)據(jù)庫進行分庫設(shè)計[7],并且每一個分片是主從設(shè)計。
初始設(shè)計使用4臺MySQL分庫。分庫的策略是:
圖1 系統(tǒng)邏輯架構(gòu)示意圖
圖2 數(shù)據(jù)庫設(shè)計圖
如公式(1),proxyNo代表選擇Redis數(shù)據(jù)庫編號0-3。具體計算公式解釋為,使用SID(發(fā)送方用戶ID)+RID(接收方用戶ID) mod 64,0-15落在第0個庫rdb0上,16-31落在第1個庫rdb1上,32-47落在第2個庫rdb2上,48-63落在第3個庫rdb3上。這樣的優(yōu)勢在于,A用戶發(fā)送B用戶和B用戶發(fā)送A用戶的消息,都會落在同一個底層數(shù)據(jù)庫?;诖嗽O(shè)計,對于查詢用戶A和B的聊天記錄,只需要使用一個SQL查詢即可,而不需要寫腳本跨庫查詢,減少IO的開銷,以達(dá)到性能提升的效果。
分庫設(shè)計和主從備份實現(xiàn)了高并發(fā)和高可用的設(shè)計。但是,當(dāng)Mysql單表的數(shù)據(jù)量達(dá)到1000W的時候,使用索引等對表進行優(yōu)化已經(jīng)到達(dá)瓶頸,所以需要從DDL、DML、DQL對數(shù)據(jù)庫進行設(shè)計。根據(jù)業(yè)務(wù)需求,在Redis上設(shè)計了消息表、用戶狀態(tài)表及其索引,并且實現(xiàn)兩個用戶之間消息記錄的查詢。
(a)針對聊天消息表
按照時間進行分區(qū)?;诖嗽O(shè)計的原因在于:用戶使用場景一般為按照時間逆序查詢聊天記錄,越新的數(shù)據(jù)越熱。創(chuàng)建聊天表的SQL如圖3。
圖3 消息表創(chuàng)建sql
(b)用戶狀態(tài)表
該表主要用于維護用戶連接信息,當(dāng)長鏈接被意外打斷,用此表信息恢復(fù)用戶之前狀態(tài),起到“保留現(xiàn)場”的作用。創(chuàng)建表sql如圖4。
圖4 用戶狀態(tài)表創(chuàng)建sql
其中,sessionId是聊天雙方,(較小的ID在前,較大的ID在后)的會話狀態(tài)信息,pcMid是用戶pc端已讀的最大消息ID;mobileMid是用戶手機端已讀的最大消息ID;hashvalue是(發(fā)送方用戶ID+接收方用戶ID)mod 64計算后的值,為了記錄該條數(shù)據(jù)在具體的某一個數(shù)據(jù)庫中。
(c)未讀消息索引
對于未讀消息的查詢,為了實現(xiàn)大數(shù)據(jù)量更加高效的查詢,在數(shù)據(jù)庫中為消息表和用戶狀態(tài)表添加索引。創(chuàng)建索引SQL如圖5。
圖5 獲取未讀消息索引
(d)DQL未讀消息查詢
根據(jù)以上設(shè)計,查詢用戶id為1,2之間的聊天內(nèi)容,優(yōu)化[8]后的SQL語句如圖6。
2.2 業(yè)務(wù)場景應(yīng)用
在3.1章節(jié)中介紹了數(shù)據(jù)庫的設(shè)計,并且給出了優(yōu)化設(shè)計方案。針對設(shè)計的方案,我們利用具體的業(yè)務(wù)場景來完成對設(shè)計方案的試驗。首先,圖7給出的是id為1的用戶與id為2的用戶之間的文本通訊過程以及步驟。
圖6 未讀消息SQL
圖7 業(yè)務(wù)結(jié)構(gòu)圖
如圖7所示,首先,定義:發(fā)送方用戶ID為srcid,接收方用戶ID為destid,每個會話的當(dāng)前消息ID為msgid(針對每個發(fā)送方和接收方,自增)。
針對離線消息的場景流程如下:
1. 發(fā)送方用戶通過電腦將消息發(fā)送至Web服務(wù)器.消息主要內(nèi)容(srcid,destid,msgid)
2. Web服務(wù)器根據(jù)srcid,destid獲取會話的mid(會話狀態(tài)的自增ID)
每個會話都按照較小ID在前,較大ID在后作為Redis的key,并且,每個會話的狀態(tài)信息都設(shè)置過期時間。如果Redis里沒有會話的狀態(tài),則用時間戳填充。數(shù)據(jù)格式如表1。
3. 將消息放入持久化隊列,并且更新Redis未讀消息列表
首先將(srcid,destid,mid,msg)放入持久化隊列,然后更新Redis的用戶未讀消息列表,未讀消息列表在Redis的存儲結(jié)構(gòu)如表2。
表1 公共數(shù)據(jù)區(qū)數(shù)據(jù)結(jié)構(gòu)
表2 PC端未讀消息存儲結(jié)構(gòu)
如表2所示,Mobile端未讀消息存儲結(jié)構(gòu)和PC端設(shè)計原理一致。其中,在接收方ID的前面增加一個前綴,表明是手機未讀消息還是PC未讀消息。這個作為表結(jié)構(gòu)的key。而value是一個HashMap,這個HashMap的key是發(fā)送方ID,value是未讀消息數(shù)量。
一旦用戶登錄,直接拉取該用戶的HashMap內(nèi)容展示,這部分內(nèi)容是有過期時間的,假如用戶長時間未使用,這個PC-destid和Mobile-destid的條目將被刪除。如果程序發(fā)現(xiàn)這個條目不存在.則去數(shù)據(jù)庫中查詢未讀消息列表。
假如A的ID是1000,B的ID是1001,B給A發(fā)送了三條消息,但是A一直沒有在線。程序?qū)⒆鋈缦虏僮鳎?/p>
如果此時A登錄手機客戶端,點擊未讀消息.則對Redis操作如下,刪除對應(yīng)條目。
在此情況下,并且回寫數(shù)據(jù)庫一個狀態(tài)標(biāo)識(已讀的最大mid)。這個回寫數(shù)據(jù)庫的過程,也是異步的,每隔30 s回寫一次數(shù)據(jù)庫狀態(tài)。這樣的設(shè)計,雖然在數(shù)據(jù)庫上對不同類型設(shè)備進行了狀態(tài)冗余,但是保證了未讀消息在不同端多次提醒。
4. 如果發(fā)送方的其他設(shè)備在線,或者接收方的設(shè)備在線,則轉(zhuǎn)發(fā)消息。
5. JAVA從隊列中異步獲取消息,然后批量Insert到數(shù)據(jù)庫。
2.3 實驗驗證
對于系統(tǒng)設(shè)計的方案,編寫腳本進行性能測試[9]。測試結(jié)果,如。
圖8 測試結(jié)果圖
Redis插入效率,在不使用持久化的情況下,可以達(dá)到7.3 W;在持久化情況下,保證數(shù)據(jù)最終一致性的前提下,也可以達(dá)到2.3 W。針對本文的設(shè)計,此套系統(tǒng)可以勝任日均超1 KW量的即時通訊任務(wù)。
在即時通訊系統(tǒng)中使用Redis進行優(yōu)化,為傳統(tǒng)IM的升級革新提供了新的思路。由于使用了無事務(wù)的NoSQL數(shù)據(jù)庫Redis,所以在本文中對所有消息有編號進行確認(rèn),類似于網(wǎng)絡(luò)中的ACK,解決了在故障情況下批量消息未成功發(fā)送到指定用戶端所帶來的數(shù)據(jù)丟失的問題。最后,基于Redis的即時通訊系統(tǒng),相比于傳統(tǒng)的即時通訊系統(tǒng)無論在性能還是用戶體驗度都有很大的提升,達(dá)到了預(yù)期的效果。
[1] 郭鑫杰. 即時通訊系統(tǒng)的設(shè)計與實現(xiàn)[D]. 南京大學(xué)2012.
[2] 林源晟. 基于XMPP協(xié)議的即時通信服務(wù)器的設(shè)計與實現(xiàn)[D]. 電子科技大學(xué) 2013.
[3] 董恒競. 一種企業(yè)移動應(yīng)用平臺架構(gòu)設(shè)計[J]. 軟件, 2016, 37(01)∶ 136-138.
[4] 馬豫星. Redis數(shù)據(jù)庫特性分析[J]. 物聯(lián)網(wǎng)技術(shù). 2015(03).
[5] 邱祝文. 基于redis的分布式緩存系統(tǒng)架構(gòu)研究[J]. 網(wǎng)絡(luò)安全技術(shù)與應(yīng)用. 2014(10).
[6] 張勇, 裴東良, 張會兵. 消息傳輸系統(tǒng)研究[J]. 軟件, 2016, 37(3)∶ 51-54.
[7] 朱思征, 王山山, 敖麗娜, 等. 大數(shù)據(jù)環(huán)境下刀具倉儲與采購智能協(xié)同研究[J]. 軟件, 2016, 37(02)∶ 29-32.
[8] 楊淙鈞, 艾中良, 劉忠麟, 等. 基于多級列式索引的海量數(shù)據(jù)高效查詢設(shè)計[J]. 軟件, 2016, 37(3)∶ 79-83.
[9] 凌高源, 朱琳. 上網(wǎng)流量監(jiān)測管理軟件設(shè)計與實現(xiàn)[J]. 軟件, 2016, 37(01)∶ 48-52文獻(xiàn)內(nèi)容.
[10] Wang S, Liu Z, Sun Q, Zou H, Yang F. Towards an accurate evaluation of quality of cloud service in service-oriented cloud computing. Journal of Intelligent Manufacturing, 2014, 25(2)∶ 283-291.
The Application of Instant Messaging System Based on Redis
LI Peng-peng, ZHENG Yang-fei, LIU Yu-long
(North China Institute of Computing Technology, Beijing 100083)
One of the drawbacks of the traditional instant messaging system is the frequent number of database I / O, because a large number of short messages in IM continue to read and write on the disk database. In order to solve the system bottleneck caused by the database, the NoSQL database Redis two-level storage of Mysql and Key-Value engine of memory is selected, and the optimization scheme of XMPP-based instant communication system is presented.
Redis; XMPP protocol; Instant Messaging system; Database sharding
TP311
A
10.3969/j.issn.1003-6970.2017.01.024
李鵬鵬(1991-),男,研究生,計算機應(yīng)用技術(shù);鄭揚飛(1976-),男,高級工程師,主要研究方向為企業(yè)信息化,分布式系統(tǒng);劉玉龍,男,高級工程師,信息系統(tǒng)頂層設(shè)計,信息集成
本文著錄格式:李鵬鵬,鄭揚飛,劉玉龍. Redis在即時通訊系統(tǒng)中的應(yīng)用[J]. 軟件,2017,38(1):115-119