本文主要介紹Redis的常用場景缀去,這都是由Redis的特性決定的履恩。并盡量從源碼角度說明Redis為什么適合于這種場景。
I倔既、積分排行榜
1锦茁、積分排行榜是Redis的經典應用,這是得益于Redis提供了zset的有序集合數(shù)據結構叉存。
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)境下對多線程完成互斥操作,而如果共享資源存在于網絡上腺晾,本地的鎖就不起作用了燕锥。
互斥訪問某個網絡資源的時候,需要有一個在網絡上的鎖服務器悯蝉,負責鎖的申請與回收归形,Redis就可以作為這種分布式鎖的服務器。
2鼻由、因為Redis是單進程單線程的工作模式暇榴,所以前來申請鎖資源的請求都被排隊處理,能保證鎖資源的同步訪問蕉世。
3蔼紧、實現(xiàn):可以在Redis服務器設置一個鍵值對,用以表示一把分布式互斥鎖狠轻,當申請鎖的時候奸例,申請方SET
這個鍵值對,當釋放鎖的時候哈误,釋放方DEL
這個鍵值對:
lock = redis.get("mutex_lock");
if(!lock)
error("apply the lock error.");
else
-- 確定可以申請鎖
redis.set("mutex_lock","locking");
do_something();
4哩至、上述的申請方法躏嚎,涉及到客戶端與Redis服務器的多次交互,當客戶端確定可以加鎖的時候菩貌,可能這時候鎖已經被其他客戶端申請了卢佣,最終導致兩個客戶端同時持有鎖。解決這個問題的方法就是將申請/釋放鎖的邏輯都放在服務器上箭阶。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功能,設置超時釋放:
-- 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)服務器宕機,也會出現(xiàn)死鎖問題怕磨,這就需要slave服務器喂饥。
III、消息中間件
1肠鲫、消息中間件可以理解為分布式的消息隊列员帮。
消息中間件負責接收生產者的消息,并存儲轉發(fā)給對應的消費者导饲,生產者按照topic發(fā)布消息捞高,消費者按topic訂閱各種消息。生產者只需要向中間件中推送消息渣锦,不用等待消費者回應硝岗。
2、也就是說泡挺,這種分布式的消息中間件就是網絡上的一個服務器辈讶,起到一個數(shù)據中轉的功能命浴。
IV娄猫、Web服務器存儲session
1、以Redis作為session的存儲生闲,可以加速后臺的處理速度媳溺,如購物車數(shù)據,就不必直接存儲到磁盤數(shù)據庫中碍讯,在這里Redis的作用就是一個緩存悬蔽。
【參考】
[1] 《Redis源碼日志》