談談 redis 在項目中的常見使用場景

最近在寫一個腳手架,發現其中 redis 的使用場景還挺多,因而總結下它的常見使用場景javascript

<!--more-->java

緩存

> set User:1:name shanyue EX 100 NX
OK
> get User:1:name
"shanyue"

緩存是 redis 出鏡率最高的一種使用場景,僅僅使用 set/get 就能夠實現,不過也有一些須要考慮的點node

  • 如何更好地設置緩存
  • 如何保持緩存與上游數據的一致性
  • 如何解決緩存血崩,緩存擊穿問題

session: 用戶登陸及驗證碼

> set 5d27e60e6fb9a07f03576687 '{"id": 10086, role: "ADMIN"}' EX 7200
OK
> get 5d27e60e6fb9a07f03576687
"{\"id\": 10086, role: \"ADMIN\"}"

這也是很經常使用的一種場景,不過相對於有狀態的 session,也能夠考慮使用 JWT,各有利弊python

消息隊列

> lpush UserEmailQueue 1 2 3 4
lpop UserEmailQueue
> rpop UserEmailQueue
1
> rpop UserEmailQueue
2

能夠把 redis 的隊列視爲分佈式隊列,做爲消息隊列時,生產者在一頭塞數據,消費者在另外一頭出數據: (lpush/rpop, rpush/lpop)。不過也有一些不足,而這些不足有多是致命的,不過對於一些丟幾條消息也不要緊的場景仍是能夠考慮的git

  1. 沒有 ack,有可能丟消息
  2. 須要作 redis 的持久化配置

過濾器 (dupefilter)

> sadd UrlSet http://1
(integer) 1
> sadd UrlSet http://2
(integer) 1
> sadd UrlSet http://2
(integer) 0
> smembers UrlSet
1) "http://1"
2) "http://2"

scrapy-redis 做爲分佈式的爬蟲框架,即是使用了 redisSet 這個數據結構來對將要爬取的 url 進行去重處理。github

# https://github.com/rmax/scrapy-redis/blob/master/src/scrapy_redis/dupefilter.py
def request_seen(self, request):
    """Returns True if request was already seen.
    Parameters
    ----------
    request : scrapy.http.Request
    Returns
    -------
    bool
    """
    fp = self.request_fingerprint(request)
    added = self.server.sadd(self.key, fp)
    return added == 0

不過當 url 過多時,會有內存佔用過大的問題web

分佈式鎖

set Lock:User:10086 06be97fc-f258-4202-b60b-8d5412dd5605 EX 60 NX

# 釋放鎖,一段 LUA 腳本
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

這是一個最簡單的單機版的分佈式鎖,有如下要點redis

  • EX 表示鎖會過時釋放
  • NX 保證原子性
  • 解鎖時對比資源對應產生的 UUID,避免誤解鎖

當你使用分佈式鎖是爲了解決一些性能問題,如分佈式定時任務防止執行屢次 (作好冪等性),並且鑑於單點 redis 掛掉的可能性很小,可使用這種單機版的分佈式鎖。shell

Rate Limit

限流即在單位時間內只容許經過特定數量的請求,有兩個關鍵參數編程

  • window,單位時間
  • max,最大請求數量

最多見的場景: 短信驗證碼一分鐘只能發送兩次

FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
    ERROR "too many requests per second"
ELSE
    value = INCR(ip)
    IF value == 1 THEN
        EXPIRE(ip,1)
    END
    PERFORM_API_CALL()
END

可使用計數器對 API 的請求進行限流處理,可是要注意幾個問題

  1. 在平滑的滑動窗口時間內在極限狀況下會有兩倍數量的請求數
  2. 條件競爭 (Race Condition)

這時候能夠經過編程,根據 TTL key 進行進一步限制,或者使用一個 LIST 來維護每次請求打來的時間戳進行實時過濾。如下是 node 實現的一個 Rate Limter。參考源碼 node-rate-limiter-flexible

this.client
  .multi()
  .set(rlKey, 0, 'EX', secDuration, 'NX')
  .incrby(rlKey, points)
  .pttl(rlKey)
  .exec((err, res) => {
    if (err) {
      return reject(err);
    }

    return resolve(res);
  })

if (res.consumedPoints > this.points) {
  // ...
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
  // ...
  setTimeout(resolve, delay, res);
} else {
  resolve(res);
}

分佈式 websocket

能夠經過 redis 的 PUB/SUB 來在 websocket server 間進行交流。能夠參考如下項目

相關文章
相關標籤/搜索