《redis入門指南》進階場景之用戶注冊和登錄與在線保持

前面兩節(jié)主要是對redis的基礎知識的梳理商乎,這一篇更多是作者對常見的應用場景的羅列和分析破喻,一共囊括了如下五個應用場景:

  • 進行用戶注冊和登錄的全流程
  • 保持用戶在線的功能
  • 獲取用戶在線的好友
  • 通過IP地址查詢訪客所在地址
  • 搜索中常見的auto-complete功能

進行用戶注冊和登錄

用戶注冊和登錄的功能,常見的模塊一般有三個:

  • 用戶注冊
  • 用戶登錄
  • 忘記密碼
  • 安全防護

用戶注冊

這三個模塊都有各自涉及到的技術點褥赊。首先是用戶注冊模塊袱箱,要滿足用戶注冊的需求,首先明確用戶信息存儲所需要的數(shù)據(jù)結構。一般而言咆槽,一個基本的用戶注冊陈轿,需要如下的字段:

  • 用戶名
  • 郵箱
  • 密碼
  • 注冊時間
  • 手機等其他需要額外標注的信息
  • 可擴展的字段(你不知道之后業(yè)務的形態(tài)發(fā)展,如果你不想連表查詢的話)

那么在這種需求之下,redis中的哈希類型來存儲用戶的基本信息是非常適合的麦射,從存儲優(yōu)勢上面來講蛾娶,使用哈希類型的存儲能夠讓每個用戶的非必填字段不占用多余的內(nèi)存。那么每次創(chuàng)建用戶的時候潜秋,只需要在業(yè)務層保證用戶必填的字段都存在蛔琅,然后插入新用戶的信息即可:
HSET user:$userid userName $userName email $email password $password registerTime $registerTime ...

與此同時,為了保證用戶注冊的唯一性峻呛,我們將用戶的郵箱作為獨一無二的存在罗售,每次新用戶注冊的時候,只需要使用HEXISTS user:$userid email $email就可以判斷出用戶是否注冊過钩述。這樣做的好處是顯而易見的寨躁,很多熱門的產(chǎn)品,都會進行賬號的搶注牙勘。那么在搶注的過程中,一個高效的,用戶是否已經(jīng)注冊的服務是非常有必要的竞漾。否則一旦過載岩榆,會使得所有的注冊用戶都被拒之門外,這對于產(chǎn)品的傷害性無疑是巨大的恭金。

再者操禀,就像每一個web安全的教程里都會說的,密碼不能被明文存儲蔚叨。這點無論是對mysql還是redis來存儲密碼都是毫無疑問的床蜘。常見的方式是對密碼使用不可逆的單向加密算法,比如Md5算法(應該說是一種摘要的算法)蔑水,同時在生成最終存儲的密碼時還需要一個由服務端控制的對應每個用戶的鹽來避免可能的Rainbow table的攻擊邢锯。這個鹽一般會寫在服務端的代碼配置或是在用戶表中增加一個字段進行單獨的存儲。但是對這個鹽的維護成本較高搀别, 而且沒有必要丹擎。

所以更好的方式其實是使用bcrypt算法,他主要有幾個不錯的優(yōu)勢:

  • 它的算法是漸進式的歇父,會隨著你嘗試的次數(shù)增加而增加計算的時間蒂培,也就是說如果有人用brute-force攻擊的話,那么他就必須具備大量的資源榜苫,也就是大量的肉雞來嘗試护戳;
  • 它使用了blowfish算法來對密碼進行哈希,這樣做的優(yōu)勢在于加密的每一步都完全取決于salt和用戶的密碼垂睬,攻擊者很難在沒有兩者的情況下進行模擬媳荒;
  • 它是單向的加密算法抗悍;
  • 可配置的round參數(shù),round為5的情況下钳枕,一個6位的密碼破解需要數(shù)年的時間缴渊;
  • 無需存儲salt,算法本身會把salt加入到最終生成的值中鱼炒,這就省去了對salt的單獨處理

關于bcrypt衔沼,有幾篇不錯的post推薦:

由此,我們就解決了用戶注冊中最大頭的用戶信息存儲昔瞧、用戶注冊校驗指蚁,以及用戶密碼加密的三個部分。接下來繼續(xù)進入到用戶登錄與忘記密碼模塊硬爆。

用戶登錄與忘記密碼

對于每個用戶欣舵,我們需要用用戶id進行關聯(lián),這個用戶id同時也可能關聯(lián)了其他的業(yè)務相關的存儲缀磕。這些存儲并不一定是redis或是mysql缘圈,但是必須要有這么一個表示保證業(yè)務的連通性。所以我們單獨保存了郵箱與用戶id的映射關系袜蚕。而每當用戶登陸的時候糟把,我們就可以:

  • 通過郵箱,根據(jù)映射獲取用戶的id
  • 與redis中存儲的哈希數(shù)據(jù)對比郵箱和密碼

如果驗證通過牲剃,那么用戶登錄成功遣疯,通過相應的方式保持用戶的在線狀態(tài)。這里簡略的說兩種方式凿傅,后文會詳述:

  • 瀏覽器cookie存儲缠犀,通過cookie保存用戶的登錄標識
  • 后端保持登錄,服務器session保持用戶的登錄狀態(tài)

另外一個常見的場景就是與郵件相關的聪舒,比如忘記密碼辨液,比如校驗郵箱的有效性。這些功能都需要異步的發(fā)送郵件箱残,同時在用戶進行相應的鏈接操作之后滔迈,修改特定的狀態(tài)。我們以修改密碼為例被辑,一旦用戶發(fā)出了修改密碼的請求燎悍,那么需要做如下幾件事情:

  • 郵箱有效,發(fā)送修改密碼的郵件盼理;
  • 在發(fā)送的郵件中的修改密碼的鏈接中谈山,生成隨機的驗證碼,并且設置固定的有效期為一個小時(expire命令)宏怔;
  • 提供校驗驗證碼的服務奏路,如果用戶在一個小時之內(nèi)點擊跳轉抗蠢,并且驗證碼校驗通過,提供修改密碼的頁面思劳;

與此同時需要注意的是,必須對用戶此類涉及到郵箱的服務妨猩,進行訪問頻率限制潜叛,從而防止大量無效的郵箱發(fā)送請求。

用戶在線保持

用戶在線保持的方案有很多壶硅,上文中也提到了威兜,主要是分成兩個流派。
第一個流派就是使用前端的存儲來記錄用戶的狀態(tài)庐椒,包括但不限于cookie椒舵、端的本地存儲等等。每次用戶請求的時候约谈,都會帶上這個客戶端的標識笔宿,而服務端提供相應的校驗機制。這么做的好處是服務端實現(xiàn)比較簡單棱诱,只需要做兩件事:

  • 在用戶登錄的時候泼橘,種上有一定時間期限的標識
  • 用戶請求的時候,對于此類標識進行校驗

但是不可避免的迈勋,使用客戶端存儲用戶標識會有一定的安全風險炬灭,其中的風險在于:

  • cookie可能會被xss攻擊獲得,從而破解了用戶的登錄身份
  • 一些不應該暴露給用戶的信息靡菇,通過本地存儲的方式暴露給用戶了重归,存在潛在的安全風險。

因此厦凤,第二個流派就是服務端的登錄態(tài)保持鼻吮。這個登錄態(tài)保持由來已久,之前最經(jīng)典就是服務端的session泳唠。其依賴于一個sessionid(一般也是存儲在cookie中的)作為key狈网,然后將用戶的登錄信息存儲在服務端。存儲在服務端的方式有很多笨腥,redis作為服務端內(nèi)存存儲的佼佼者拓哺,當然是一個不錯的方式。

首先我們使用一個散列來存儲sessionid與已登錄用戶之間的映射脖母。要檢查一個用戶是否登錄士鸥,需要根據(jù)給定的sessionid來查找與之對應的用戶,并在他已經(jīng)登錄的情況下谆级,返回用戶的ID烤礁。
hget login: $sessionid

而比較復雜的情況出現(xiàn)在用戶的登錄態(tài)刷新上讼积,每當用戶瀏覽頁面的時候,我們都對用戶存儲在登錄散列里面的信息進行更新脚仔,并將用戶的sessionid和當前時間戳添加到記錄最近登錄用戶的有序集合中勤众。

hset login: $sessionid $user
zadd recent: $sessionid time()

一般而言,單臺redis的存儲容量有限鲤脏,并且為了不占用不必要的內(nèi)存们颜,我們肯定需要對用戶的登錄狀態(tài)進行清理。假定產(chǎn)品設計只需要同時保持100萬個用戶在線猎醇,那么我們可以通過定時腳本的方式窥突,在存儲超限的情況下,找到最舊的sessionid硫嘶,將其清理阻问。

while
    size = redis.zcard('recent:')
    if size <= LIMIT
        time.sleep(1)
        continue
    end_index = min(size - LIMIT ,100 ) //每次不要刪除太多
    sessionids = redis.zrange('recent:',0,end_index-1)
    for sessionid in sessionids:
        session_key = 'delete:'+sessionid
        multi
        delete session_key
        hdel login: session_key
        zrem recent: session_key
        exec

在實際運行時,每秒最多可以清理60000多個令牌沦疾,完全夠用了称近。

本文主要對redis在登錄注冊、登錄態(tài)保持方面的應用進行了探討曹鸠,同時也結合了一些我的實際使用與開發(fā)的經(jīng)驗煌茬。有人說redis的使用非黑即白,需要摒棄關系型數(shù)據(jù)庫彻桃。但是在實際的工程中坛善,頗為不可行,很多系統(tǒng)仍然依賴于關系型數(shù)據(jù)庫存儲的方方面面邻眷,這點在后文中也會有所體現(xiàn)眠屎。接下來的一文,會對用戶的好友列表拉取肆饶、auto-complete實現(xiàn)改衩、以及ip地址查詢訪客等功能進行更進一步的討論。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驯镊,一起剝皮案震驚了整個濱河市葫督,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌板惑,老刑警劉巖橄镜,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冯乘,居然都是意外死亡洽胶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門裆馒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姊氓,“玉大人丐怯,你說我怎么就攤上這事∠韬幔” “怎么了读跷?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長禾唁。 經(jīng)常有香客問我舔亭,道長,這世上最難降的妖魔是什么蟀俊? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮订雾,結果婚禮上肢预,老公的妹妹穿的比我還像新娘。我一直安慰自己洼哎,他們只是感情好烫映,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著噩峦,像睡著了一般锭沟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上识补,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天族淮,我揣著相機與錄音,去河邊找鬼凭涂。 笑死祝辣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的切油。 我是一名探鬼主播蝙斜,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼澎胡!你這毒婦竟也來了孕荠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤攻谁,失蹤者是張志新(化名)和其女友劉穎稚伍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巢株,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡槐瑞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阁苞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片困檩。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡祠挫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悼沿,到底是詐尸還是另有隱情等舔,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布糟趾,位于F島的核電站慌植,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏义郑。R本人自食惡果不足惜蝶柿,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望非驮。 院中可真熱鬧交汤,春花似錦、人聲如沸劫笙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽填大。三九已至戒洼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間允华,已是汗流浹背圈浇。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留靴寂,地道東北人汉额。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像榨汤,于是被迫代替她去往敵國和親蠕搜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容