Redis在Web項目中的應用與實踐

原文連接:Redis在Web項目中的應用與實踐git

Redis做爲一個開源的(BSD)基於內存的高性能存儲系統,已經被各大互聯網公司普遍使用,而且有着諸多的應用場景。本篇文章將基於PHP來詳細講解Redis在Web項目中的主要應用與實踐。github

緩存

這裏所介紹的緩存是指能夠丟失或過時的數據。經常使用的命令有 set, hset, get, hget,使用redis做爲緩存時須要注意一下幾個問題:web

  • 因爲redis的可用內存是有限的,不能容忍redis內存的無限增加,建議設置 maxmemory 最大內存。
  • 在開啓maxmemory的狀況下,能夠啓用lru機制,設置key的expire,當到達Redis最大內存時,Redis會根據最近最少用算法對key進行自動淘汰。
  • Redis的持久化策略和Redis故障恢復時間是一個博弈的過程,若是你但願在發生故障時可以儘快恢復,應該啓用dump備份機制,但這樣須要更多的可用內存空間來進行持久化。若是可以容忍Redis漫長的故障恢復時間,可使用AOF持久化機制,同時關閉dump機制,這樣不須要額外的內存空間。

存儲

在web項目中,redis可存儲讀寫很是頻繁的數據來緩解MySQL等數據庫的壓力。redis若是做爲存儲系統的話,爲了防止數據丟失,持久化必須開啓。redis

典型場景算法

  • 計數器

計數器的需求很是廣泛,例如微博點贊數、帖子收藏數、文章分享數、用戶關注數等。數據庫

  • 社交列表

好比使用Sets結構存儲關注列表、收藏列表、點贊列表等。緩存

  • Session

藉助redis高性能的key-value存儲,可將用戶登陸狀態保存到redis中。安全

  • ...

隊列

簡單隊列bash

通常使用redis的list結構做爲隊列,rpush 生產消息,lpop 消費消息,當 lpop 沒有消息的時候,要進行適當的sleep操做。服務器

$queueKey = "queue";

// 生產者
$redis->rpush($queueKey, $data)

// 消費者
while (true) {
    $data = $redis->lpop($queueKey);
    if (null === $data) {
        usleep(100000);
        continue;
    }
    // 業務邏輯
    ...
}
複製代碼

因爲沒有消息時使用的sleep事件很差控制,生產環境儘可能不要使用sleep來休眠,可以使用 blpop 來消費消息,在沒有新消息的時候它會阻塞到消息到來。

延時隊列

延時隊列可以使用redis的 sorted set 數據結構,使用時間戳做爲 score ,消息內容做爲 member,使用 zadd 命令來生產消息,消費者使用 zrangebyscore 命令獲取指定時間以前的消息數據輪詢進行處理。

$queueKey = "queue";

// 生產消息

// 消費時間, 這裏設置爲1小時候
$consumeTimestamp = time() + 3600;
// $data須要添加隨機串前綴(or後綴),防止出現重複member被丟棄
$data = $data . md5(uniqid(rand(), true));
$redis->zadd($queueKey, $consumeTimestamp, $data);

// 消費消息
while (tue) {
    $arrData = $redis->zrangebyscore($queueKey, 0, time());
    if (!$arrData) {
        usleep(100000);
        continue;
    }
    // 業務邏輯
    foreach ($arrData as $data) {
        $data = substr($data, 0, strlen($data) - 32);
        
        // 消費$data

    }
}
複製代碼

多消費者

使用pub/sub主題訂閱者模式,能夠實現1:N的消息隊列。這種模式中在消費者下線的狀況下,生產的消息會丟失,在這裏不推薦使用。

須要強調的是不推薦使用redis做爲消息隊列服務,這不是redis的設計目標。若是必定要用可考慮 disque,是由redis的做者開發。

分佈式鎖

分佈式鎖主要解決的幾個問題:

  • 互斥性: 同一時刻只能有一個服務(或應用)訪問資源
  • 安全性: 鎖只能被持有該鎖的服務(或應用)釋放
  • 容錯: 在持有鎖的服務crash時,鎖仍能獲得釋放
  • 避免死鎖

方案1

咱們可能會考慮使用 setnxexpire 命令來實現加鎖,即當沒有key存在時纔會成功寫入value:

$lockStatus = $redis->setnx($lockKey, 1);
if (1 === $lockStatus) {
    // 加鎖成功,爲鎖設置超時時間
    $redis->expire($lockKey, 300);

    // 進行後續操做

} elseif (0 === $lockStatus) {
    // 加鎖失敗
} else {
    // 其餘異常
}
複製代碼

但這種操做不是原子性的,若是在進行setnx時服務崩潰,沒有來得及對Key進行超時設置,該鎖將一直沒法釋放。

方案2

咱們推薦 set key value [EX seconds] [PX milliseconds] [NX|XX] 命令來進行加鎖

  • EX: key在多少秒以後過時
  • PX:key在多少毫秒以後過時
  • NX: 當key不存在的時候,才建立key,效果等同於setnx
  • XX:當key存在的時候,覆蓋key
$lockStatus = $this->redis->set($lockKey, 1, "EX", 30, "NX");
if ("OK" === $lockStatus) {
    // 加鎖成功,可進行後續操做
    
    //業務邏輯執行完畢,釋放鎖
    $this->redis->del($lockKey);

} elseif (null === $lockStatus) {
    // 加鎖失敗
}
複製代碼

如上代碼所示,若是 set 命令返回OK,那麼客戶端就能夠得到鎖(若是返回null,那麼應用服務能夠在一段時間以後從新嘗試獲取鎖),而且能夠經過 del 命令來釋放鎖。

此方法須要注意的問題:

  • a服務得到的鎖(鍵key)已經因爲已到過時時間被redis服務器刪除,可是這個時候a服務還去執行DEL命令。而b服務經在a設置的過時時間以後從新獲取了這個一樣key的鎖,那麼a執行 del 就會釋放了b服務加好的鎖。
  • 當同一時刻有大量的key過時的時候,刪除key時會增長redis壓力,會影響服務穩定。

能夠經過以下優化使得上面的鎖系統變得更加健壯:

  • 不要設置固定的字符串,而是設置爲隨機的大字符串,能夠稱爲token。
  • 經過腳本刪除指定鎖的key,而不是 del 命令。
  • 在設置key過時時間的時候加上一個隨機值。

優化後的代碼可參考以下:

$lockToken = md5(uniqid(rand(), true));
// 此處超時時間根據具體業務邏輯配置
$expire = rand(280, 320);
$lockStatus = $this->redis->set($lockKey, $lockToken, "EX", $expire, "NX");
if ("OK" === $lockStatus) {
    // 加鎖成功,可進行後續操做
    
    // 業務邏輯執行完畢,釋放鎖
    // 刪除鎖以前須要判斷是不是本身上的鎖
    $currentToken = $this->redis->get($lockKey);
    if ($currentToken === $lockToken) {
        $this->redis->del($lockKey);
    }

} elseif (null === $lockStatus) {
    // 加鎖失敗
}
複製代碼

計算

redis提供的原子自增減方法以及有序集合結構等能夠承擔一些計算任務,例如瀏覽量統計等。

瀏覽計數

文章瀏覽量+1

$redis->incr($postsKey);
複製代碼

批量獲取文章瀏覽量

$arrPostsKey = [
    //...
];
$arrPostsViewNum = $redis->mget($arrPostsKey);
複製代碼

排行榜

可使用redis的有序集合來實現排行榜的功能,score做爲權重排序並取前n條記錄。

// 存儲數據
$sortKey = "sort_key";
$redis->zadd($sortKey, 100, "tom");
$redis->zadd($sortKey, 80, "Jon");
$redis->zadd($sortKey, 59, "Lilei");
$redis->zadd($sortKey, 87, "Hanmeimei");

// 獲取排行

// 由大到小排序
$arrRet = $redis->zrevrange($sortKey, 0, -1, true);

// 由小到大排序
$arrRet = $redis->zrange($sortKey, 0, -1, true);
複製代碼

結尾

redis涉及的應用實踐很是繁多的,因爲篇幅所限沒法所有顧及,本文只針對web應用中最經常使用的幾個場景進行了展開介紹,渴望進一步拓展redis知識的同窗可參考如下連接進一步學習。

原文連接:Redis在Web項目中的應用與實踐

掃碼關注微信公衆號: Learn2Code

相關文章
相關標籤/搜索