本文主要介紹Redis的常用場景,這都是由Redis的特性決定的。并盡量從源碼角度說明Redis為什么適合于這種場景。
I、積分排行榜
1、積分排行榜是Redis的經(jīng)典應(yīng)用,這是得益于Redis提供了zset的有序集合數(shù)據(jù)結(jié)構(gòu)。
2、針對zset,Redis提供了一系列的命令:
ZADD: 添加新元素;
ZRANGE: 按分數(shù)從低到高返回給定排名區(qū)間的元素;
ZREVRANGE: 按分數(shù)從高到底返回給定區(qū)間的元素;
ZRANK: 返回某個元素的排名。
3、實現(xiàn):下面的例子為用python完成的一個leaderboard
# -*- coding: utf-8 -*-
#!/usr/bin/python
import redis
class Leaderboard:
def __init__(self,host,port,key,db):
self.host = host
self.port = port
self.key = key
self.db = db
self.r = redis.StrictRedis(host=self.host,port=self.port,db=self.db)
def isRedisValid(self):
return self.r is None
def addMember(self,member,score):
if self.isRedisValid():
return None
return self.r.zadd(self.key,score,member)
def delMember(self,member):
if self.isRedisValid():
return None
return self.r.zrem(self.key,member)
def incrScore(self,member,increment):
"""increase score on specified member"""
if self.isRedisValid():
return None
return self.r.zincrby(self.key,member,increment)
def getRankByMember(self,member):
"""Get ranking by specified member."""
if self.isRedisValid():
return None
return self.r.zrank(self.key,member)
def getLeaderboard(self,start,stop,reverse,with_score):
"""Return the whole leaderboard."""
if self.isRedisValid():
return None
return self.r.zrange(self.key,start,stop,reverse,with_score)
def getLeaderboardByPage(self,item_per_page,page_num,reverse=False,with_score=False):
"""Return part of leaderboard configurably."""
# fix parameters
if item_per_page <= 0:
item_per_page = 5
if page_num <= 0:
page_num = 1
# note: it is possible that return value is empty list.
return self.getLeaderboard((page_num-1)*item_per_page,
page_num*item_per_page-1,
reverse,with_score)
def getWholeLeaderboard(self,reverse=False,with_score=False):
"""Return the whole leaderboard."""
return self.getLeaderboard(0,-1,reverse,with_score)
II、分布式鎖
1、我們通常所提到的鎖,一般都是在本地環(huán)境下對多線程完成互斥操作,而如果共享資源存在于網(wǎng)絡(luò)上,本地的鎖就不起作用了。
互斥訪問某個網(wǎng)絡(luò)資源的時候,需要有一個在網(wǎng)絡(luò)上的鎖服務(wù)器,負責(zé)鎖的申請與回收,Redis就可以作為這種分布式鎖的服務(wù)器。
2、因為Redis是單進程單線程的工作模式,所以前來申請鎖資源的請求都被排隊處理,能保證鎖資源的同步訪問。
3、實現(xiàn):可以在Redis服務(wù)器設(shè)置一個鍵值對,用以表示一把分布式互斥鎖,當(dāng)申請鎖的時候,申請方SET這個鍵值對,當(dāng)釋放鎖的時候,釋放方DEL這個鍵值對:
lock = redis.get("mutex_lock");
if(!lock)
error("apply the lock error.");
else
-- 確定可以申請鎖
redis.set("mutex_lock","locking");
do_something();
4、上述的申請方法,涉及到客戶端與Redis服務(wù)器的多次交互,當(dāng)客戶端確定可以加鎖的時候,可能這時候鎖已經(jīng)被其他客戶端申請了,最終導(dǎo)致兩個客戶端同時持有鎖。解決這個問題的方法就是將申請/釋放鎖的邏輯都放在服務(wù)器上。Redis Lua腳本可以完成這個問題:
-- apply for lock
local key = KEYS[1]
local res = redis.call('get', key)
-- 鎖被占用,申請失敗
if res == '0' then
return -1
-- 鎖可以被申請
else
local setres = redis.call('set', key, 0)
if setres['ok'] == 'OK' then
return 0
end
end
return -1
get 命令不成功返回(nil).
實驗命令:保存lua 腳本redis-cli script load ”$(cat mutex_lock.lua)”
-- releae lock
local key = KEYS[1]
local setres = redis.call('set', key, 1)
if setres['ok'] == 'OK' then
return 0
return -1
5、死鎖問題
分布式鎖由于客戶端的崩潰很容易造成鎖無法釋放,從而出現(xiàn)死鎖。所以需要使用Redis的TTL功能,設(shè)置超時釋放:
-- apply for lock
local key = KEYS[1]
local timeout = KEYS[2]
local res = redis.call('get', key)
-- 鎖被占用,申請失敗
if res == '0' then
return -1
-- 鎖可以被申請
else
local setres = redis.call('set', key, 0)
local exp_res = redis.call('pexpire', key, timeout)
if exp_res == 1 then
return 0
end
end
return -1
6、此外如出現(xiàn)服務(wù)器宕機,也會出現(xiàn)死鎖問題,這就需要slave服務(wù)器。
III、消息中間件
1、消息中間件可以理解為分布式的消息隊列。
消息中間件負責(zé)接收生產(chǎn)者的消息,并存儲轉(zhuǎn)發(fā)給對應(yīng)的消費者,生產(chǎn)者按照topic發(fā)布消息,消費者按topic訂閱各種消息。生產(chǎn)者只需要向中間件中推送消息,不用等待消費者回應(yīng)。
2、也就是說,這種分布式的消息中間件就是網(wǎng)絡(luò)上的一個服務(wù)器,起到一個數(shù)據(jù)中轉(zhuǎn)的功能。
IV、Web服務(wù)器存儲session
1、以Redis作為session的存儲,可以加速后臺的處理速度,如購物車數(shù)據(jù),就不必直接存儲到磁盤數(shù)據(jù)庫中,在這里Redis的作用就是一個緩存。
【參考】
[1] 《Redis源碼日志》