吳霖 劉振宇 李佳
摘要:訂閱推送系統(tǒng)是為解決在移動(dòng)互聯(lián)網(wǎng)下,由服務(wù)端向客戶端進(jìn)行主動(dòng)推送圖文消息的需求而提出的?;赗edis的高速緩存機(jī)制在訂閱推送系統(tǒng)中的應(yīng)用進(jìn)行了研究,詳細(xì)說明了如何在程序開發(fā)中有效地利用Redis中的各種數(shù)據(jù)結(jié)構(gòu)。
關(guān)鍵詞:Redis;內(nèi)存數(shù)據(jù)庫;訂閱;推送
中圖分類號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2015)07-0292-03
Abstract: Subscription and push system is proposed for solving the problem of how to push diagram text combined messages from the server to the client actively in Mobile Internet. This article studied the applications in subscription and push system, based on the caching mechanism of Redis, and explain how to effectively make use of the data structures of Redis in program development detailedly.
Key words: Redis; memory database; subscription; push
近年來,隨著智能手機(jī)的普及和移動(dòng)互聯(lián)網(wǎng)的快速發(fā)展,越來越多的移動(dòng)APP應(yīng)用如春風(fēng)般涌現(xiàn)出來。而在移動(dòng)APP應(yīng)用中,經(jīng)常會(huì)有由服務(wù)端向客戶端進(jìn)行主動(dòng)推送圖文消息的需求,如向用戶推送優(yōu)質(zhì)資源、問卷調(diào)查、APP使用教程等。而目前移動(dòng)APP應(yīng)用的用戶規(guī)模往往都是千萬級(jí)別,大規(guī)模地批量推送對程序性能有很高的要求,因此,如何合理地設(shè)計(jì)一個(gè)訂閱推送系統(tǒng)非常重要。
本文首先簡單地介紹了Redis及其特性;其次,概要描述了作者最近開發(fā)的一個(gè)訂閱推送系統(tǒng)及其系統(tǒng)架構(gòu);最后,詳細(xì)地介紹了Redis的各種數(shù)據(jù)結(jié)構(gòu)在本系統(tǒng)中的應(yīng)用。
1 Redis
1.1 Redis簡介
Redis是一個(gè)基于內(nèi)存的高性能key-value數(shù)據(jù)庫,其全名為遠(yuǎn)程字典服務(wù)(Remote Dictionary Server)[1]。與Memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中,即以空間換取時(shí)間,從而達(dá)到提高性能的目的。而與Memcached的最大區(qū)別在于,Redis會(huì)周期性地把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,并在此基礎(chǔ)上實(shí)現(xiàn)了主從同步。
1.2 Redis的特性
1.2.1 支持持久化
Redis和Memcached一樣都是把數(shù)據(jù)存在內(nèi)存中。不同的是,Memcached把數(shù)據(jù)全部存放在內(nèi)存之中,服務(wù)器重啟后數(shù)據(jù)會(huì)消失,而且緩存的數(shù)據(jù)不能超過內(nèi)存大?。欢赗edis中不是所有的數(shù)據(jù)都一直存儲(chǔ)在內(nèi)存中,Redis會(huì)根據(jù)數(shù)據(jù)的更新量和更新間隔時(shí)間定期進(jìn)行異步更新,將數(shù)據(jù)保存在磁盤上,實(shí)現(xiàn)數(shù)據(jù)的持久化操作??梢酝ㄟ^配置文件配置持久化操作的時(shí)間間隔和臨界數(shù)據(jù)量,這樣Redis就會(huì)自動(dòng)在一定的時(shí)間間隔或者當(dāng)數(shù)據(jù)超過臨界值時(shí)進(jìn)行持久化操作[2]。此外,服務(wù)器重啟后,數(shù)據(jù)也可以很快恢復(fù)使用。
1.2.2 支持豐富的數(shù)據(jù)結(jié)構(gòu)
Redis的數(shù)據(jù)結(jié)構(gòu)非常豐富。Redis支持簡單的key-value類型數(shù)據(jù),其中key是string類型,而value除了常規(guī)的string(字符串)之外,還包括list(鏈表)、set(集合)、zset(sorted set有序集合)和hash(哈希類型)。這些數(shù)據(jù)類型都支持push/pop、add/remove、取交集、并集和差集等操作,而且這些操作都具有原子性,它還支持各種不同的排序能力[2]。
1.2.3 支持主從復(fù)制
Redis的主從復(fù)制實(shí)現(xiàn)簡單卻功能強(qiáng)大,支持多級(jí)Master/Slave,一個(gè)master支持多個(gè)slave連接,slave可以接受其他slave的連接。主從同步時(shí)master和slave都是非阻塞的。
利用Redis的主從復(fù)制特性,可以實(shí)現(xiàn)以下功能:1)實(shí)現(xiàn)讀寫分離,如用主服務(wù)實(shí)現(xiàn)讀操作,從服務(wù)實(shí)現(xiàn)寫操作;2)備份數(shù)據(jù),利用主從服務(wù)器的方便性來備份,專門做臺(tái)從服務(wù)器用于備份功能。
2 系統(tǒng)模型
2.1 訂閱推送系統(tǒng)簡介
訂閱推送系統(tǒng)主要面向兩類不同的用戶:訂閱號(hào)用戶和移動(dòng)端用戶。
2.1.1 訂閱號(hào)用戶
訂閱號(hào)用戶主要是在該系統(tǒng)中使用富文本編輯器編輯圖文消息,再將圖文消息推送給其訂閱用戶。
目前有三種類別的訂閱號(hào)用戶,其分類依據(jù)是:用戶默認(rèn)是否訂閱、用戶能否退訂。這三種類別分別是:1)官方號(hào)。用戶默認(rèn)訂閱,且不能退訂;2)合作號(hào)。用戶默認(rèn)訂閱,但可以退訂;3)第三方申請?zhí)?。用戶默認(rèn)不訂閱,必須主動(dòng)訂閱。
而對于不同類別的訂閱號(hào),其推送的“目標(biāo)用戶”也不同:1)官方號(hào)會(huì)推送給所有用戶;2)合作號(hào)則推送給除主動(dòng)退訂的用戶外的所有用戶;3)第三方申請?zhí)杽t僅推送給主動(dòng)訂閱的用戶。
2.1.2 移動(dòng)端用戶
移動(dòng)端用戶可以訂閱一個(gè)或多個(gè)訂閱號(hào)。移動(dòng)端用戶主要是閱讀訂閱號(hào)所推送過來的圖文消息。
2.2 系統(tǒng)架構(gòu)
本系統(tǒng)是基于著名的Python Web框架Tornado進(jìn)行開發(fā)的。正式部署環(huán)境下,啟動(dòng)了8個(gè)Tornado進(jìn)程共同提供web服務(wù),并使用Nginx作為其反向代理(提供靜態(tài)資源服務(wù)和請求轉(zhuǎn)發(fā)服務(wù))。使用MySQL作為關(guān)系數(shù)據(jù)庫,存儲(chǔ)訂閱/退訂關(guān)系、圖文消息的數(shù)據(jù)內(nèi)容等。使用Redis作為key-value數(shù)據(jù)庫,主要用于數(shù)據(jù)統(tǒng)計(jì)、session緩存、進(jìn)程間協(xié)作通信等。
由圖1所示,該系統(tǒng)主要分為四大模塊,各模塊的主要功能如下:
1)內(nèi)容編輯模塊:供訂閱號(hào)用戶在該平臺(tái)登錄后,使用富文本編輯器編輯圖文消息,作為推送素材。
2)消息審核模塊:為了防止訂閱號(hào)用戶將一些不合規(guī)的內(nèi)容推送給訂閱用戶,故圖文消息在推送之前,必須申請審核,經(jīng)審核通過的圖文消息才可以推送給用戶。
3)訂閱退訂模塊:每個(gè)訂閱號(hào)用戶所推送的圖文消息,僅會(huì)推送給訂閱了該訂閱號(hào)的用戶,用戶可以訂閱或退訂某訂閱號(hào)。該模塊負(fù)責(zé)維護(hù)訂閱號(hào)和用戶之間的訂閱/退訂關(guān)系。
4)推送模塊:考慮到推送操作耗時(shí)較長,為了避免Tornado長期阻塞而無法處理其他請求,故將推送操作的任務(wù)交由另一進(jìn)程PushProcess完成。Tornado進(jìn)程與推送進(jìn)程PushProcess是一個(gè)生產(chǎn)者-消費(fèi)者模型,兩者間通過Redis消息隊(duì)列進(jìn)行協(xié)作通信。
3 Redis的應(yīng)用
3.1 使用Redis緩存作為多進(jìn)程間的數(shù)據(jù)共享:服務(wù)端Session
眾所周知,HTTP協(xié)議是無狀態(tài)的。在Web開發(fā)中,主要通過服務(wù)端session和客戶端cookie這兩大技術(shù)來保存用戶登陸信息。在單進(jìn)程下,可以將session保存在服務(wù)器進(jìn)程的進(jìn)程空間中。但是,實(shí)際生產(chǎn)環(huán)境中,單個(gè)進(jìn)程所能提供的最大服務(wù)容量往往有限,故需要在單臺(tái)服務(wù)器中開啟多個(gè)進(jìn)程,或者在多臺(tái)服務(wù)器中開啟多個(gè)進(jìn)程,通過多進(jìn)程同時(shí)提供服務(wù)以提升系統(tǒng)的最大服務(wù)容量。此時(shí),就不能再將服務(wù)端的session存儲(chǔ)于某個(gè)進(jìn)程內(nèi),而需要將session存儲(chǔ)在所有進(jìn)程都能夠共享訪問到的位置,而Redis恰好就能提供這種服務(wù)。
本系統(tǒng)的部署環(huán)境中,共啟動(dòng)了8個(gè)Tornado進(jìn)程提供Web服務(wù),并使用Nginx作為其反向代理。每個(gè)Http請求到達(dá)后,服務(wù)器進(jìn)程會(huì)從根據(jù)請求中的cookie信息來判斷該用戶是否已經(jīng)登陸。若用戶還未登陸,則跳轉(zhuǎn)登陸頁面,當(dāng)用戶成功登陸后,會(huì)在Redis中保存該用戶的登陸信息,并設(shè)置相應(yīng)信息至用戶瀏覽器的cookie中,以便在后續(xù)的Http請求中帶上相應(yīng)的cookie信息;若用戶已經(jīng)成功登陸,則服務(wù)器進(jìn)程能夠根據(jù)cookie中所帶來的相關(guān)信息,從Redis中找到該用戶的相關(guān)信息。
3.2 使用Redis消息隊(duì)列作為協(xié)作進(jìn)程間的通信
根據(jù)高內(nèi)聚低耦合的程序設(shè)計(jì)原則,應(yīng)盡量讓不同的進(jìn)程提供不同的服務(wù)。本系統(tǒng)中,Nginx進(jìn)程提供靜態(tài)資源服務(wù)和請求轉(zhuǎn)發(fā)服務(wù),Tornado進(jìn)程提供動(dòng)態(tài)資源服務(wù)和業(yè)務(wù)邏輯處理服務(wù)??紤]到實(shí)際的推送操作可能會(huì)耗時(shí)較長,特別是第1、2種類型的訂閱號(hào),其推送的目標(biāo)用戶多達(dá)數(shù)百萬,因此,我們將推送業(yè)務(wù)的處理操作從Tornado進(jìn)程中提取出來,交由另一單獨(dú)的進(jìn)程PushProcess負(fù)責(zé)。
如此一來,就涉及Tornado進(jìn)程與推送進(jìn)程PushProcess間的協(xié)作通信,本系統(tǒng)中通過Redis消息隊(duì)列實(shí)現(xiàn)。當(dāng)訂閱號(hào)用戶提交推送請求后,由Tornado進(jìn)程將本次推送的關(guān)鍵信息寫入Redis消息隊(duì)列中。而推送進(jìn)程PushProcess則長期阻塞監(jiān)聽該消息隊(duì)列,一旦有推送請求入隊(duì),推送進(jìn)程就能成功獲取該請求,并進(jìn)行實(shí)際推送操作,可能會(huì)導(dǎo)致該進(jìn)程一段時(shí)間內(nèi)繁忙,而Tornado進(jìn)程則不會(huì)受到影響。另外,也可同時(shí)啟動(dòng)多個(gè)推送進(jìn)程,以共同提供推送服務(wù)。
3.3 使用Redis Bitmap作為基于user id的數(shù)據(jù)統(tǒng)計(jì)
對于移動(dòng)APP應(yīng)用來說,進(jìn)行一些常見的數(shù)據(jù)統(tǒng)計(jì)和分析非常必要,能夠幫助提升產(chǎn)品。在訂閱推送系統(tǒng)中,一個(gè)常見的需求就是統(tǒng)計(jì)每次推送的客戶端打開率。那么,就需要對每條消息的推送用戶總數(shù),以及用戶收到消息后的打開情況進(jìn)行統(tǒng)計(jì),即統(tǒng)計(jì)每條消息推送給哪些用戶,這些用戶在接收到消息后是否打開閱讀了。
本系統(tǒng)中,使用兩個(gè)Redis Bitmap來記錄每條消息的推送情況。Bitmap是一種基于偏移位進(jìn)行置0、置1的數(shù)據(jù)結(jié)構(gòu),本系統(tǒng)中使用用戶的user id作為偏移位。
第一個(gè)Bitmap用于在推送過程中,每推送一個(gè)用戶,就在該用戶的相應(yīng)user id位置1。最后,只要統(tǒng)計(jì)Bitmap總共有多少位置為1,即可知道該條消息共推送了多少個(gè)用戶。另外,即使在推送過程中發(fā)生意外導(dǎo)致推送終止,在修復(fù)故障繼續(xù)推送時(shí),也可根據(jù)該Bitmap得知該條消息是否已經(jīng)推送給某一用戶,這樣也就不會(huì)導(dǎo)致對同一個(gè)用戶多次推送同一條消息。
第二個(gè)Bitmap用于在用戶打開閱讀推送消息時(shí),觸發(fā)Http請求,服務(wù)器在收到該請求后,在該Bitmap的相應(yīng)用戶user id位置1,這樣一來,也就可以知道哪些用戶打開閱讀過該消息,以及總共有多少個(gè)用戶閱讀過該消息。
4 結(jié)束語
總之,Redis數(shù)據(jù)庫憑其自身的特點(diǎn)以及高效的讀寫效率在頻繁的大數(shù)據(jù)處理上有一定的優(yōu)勢。Redis所提供的豐富的數(shù)據(jù)結(jié)構(gòu),能夠靈活地應(yīng)用在不同進(jìn)程間的協(xié)作通信,以及不同編程語言間的程序設(shè)計(jì)。
參考文獻(xiàn):
[1] 維基百科. Redis [EB/OL]. (2014-06-23). http://zh.wikipedia.org/wiki/Redis.
[2] 王心妍. Memcached和Redis在高速緩存方面的應(yīng)用[J]. 無線互聯(lián)科技, 2012(9):8-9.