Redis深度歷險(xiǎn)-讀書筆記(4)
簡(jiǎn)單限流
限流
- 由于系統(tǒng)的處理能力是有限的,當(dāng)外部的請(qǐng)求流量不斷增長(zhǎng)時(shí),為了保證服務(wù)的可用性,需要阻止計(jì)劃外的請(qǐng)求繼續(xù)對(duì)系統(tǒng)施壓攻谁。
- 除了控制流量,限流還有一個(gè)用處:控制用戶行為弯予,避免垃圾請(qǐng)求戚宦。例如社區(qū)中的用戶,回帖锈嫩、回復(fù)受楼、點(diǎn)贊等行為都要受到控制,如果在規(guī)定時(shí)間內(nèi)的某種操作超過了設(shè)定的閾值祠挫,那這種操作就是非法行為那槽,需要對(duì)其進(jìn)行相應(yīng)的處理
如何使用Redis來實(shí)現(xiàn)簡(jiǎn)單限流
- 策略:系統(tǒng)要限定用戶的某個(gè)行為在指定的時(shí)間里只能允許發(fā)生 N 次
- 接口定義:
# 指定用戶 user_id 的某個(gè)行為 action_key 在特定的時(shí)間內(nèi) period 只允許發(fā)生一定的次數(shù) max_count
def is_action_allowed(user_id, action_key, period, max_count):
return True
# 調(diào)用這個(gè)接口 , 一分鐘內(nèi)只允許最多回復(fù) 5 個(gè)帖子
can_reply = is_action_allowed("user", "reply", 60, 5)
if can_reply:
do_reply()
else:
raise ActionThresholdOverflow()
方案
限流需求需要得到在60秒內(nèi)用戶的行為次數(shù)信息,我們通過構(gòu)造一個(gè)滑動(dòng)時(shí)間窗口等舔,獲取這段時(shí)間內(nèi)用戶的行為次數(shù)來判斷是否觸發(fā)限流骚灸,窗口的長(zhǎng)度60秒,窗口之外的數(shù)據(jù)都可以忽略慌植。在Redis中甚牲,可以使用zset
的score
值來構(gòu)造,用一個(gè)zset
結(jié)構(gòu)記錄用戶的行為歷史蝶柿,每一個(gè)行為都會(huì)作為zset
中的一個(gè)key
保存下來丈钙。同一個(gè)用戶同一種行為用一個(gè)zset
記錄,通過統(tǒng)計(jì)滑動(dòng)窗口內(nèi)的行為數(shù)量與閾值 max_count 進(jìn)行比較就可以得出當(dāng)前的行為是否符合限流條件交汤。
為了節(jié)省空間雏赦,我們只需要保留時(shí)間窗口內(nèi)的行為記錄劫笙,如果記錄為空,那么這個(gè) zset 就可以從內(nèi)存中移除,不再占用空間星岗。
# coding: utf8
import time
import redis
client = redis.StrictRedis()
def is_action_allowed(user_id, action_key, period, max_count):
key = 'hist:%s:%s' % (user_id, action_key)
now_ts = int(time.time() * 1000) # 毫秒時(shí)間戳
with client.pipeline() as pipe: # client 是 StrictRedis 實(shí)例
# 記錄行為
pipe.zadd(key, now_ts, now_ts) # value 和 score 都使用毫秒時(shí)間戳
# 移除時(shí)間窗口之前的行為記錄填大,剩下的都是時(shí)間窗口內(nèi)的
pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
# 獲取窗口內(nèi)的行為數(shù)量
pipe.zcard(key)
# 設(shè)置 zset 過期時(shí)間,避免冷用戶持續(xù)占用內(nèi)存
# 過期時(shí)間應(yīng)該等于時(shí)間窗口的長(zhǎng)度俏橘,再多寬限 1s
pipe.expire(key, period + 1)
# 批量執(zhí)行
_, _, current_count, _ = pipe.execute()
# 比較數(shù)量是否超標(biāo)
return current_count <= max_count
for i in range(20):
print is_action_allowed("user", "reply", 60, 5)
每一個(gè)行為到來時(shí)允华,都維護(hù)一次時(shí)間窗口。將時(shí)間窗口外的記錄全部清理掉寥掐,只保留窗口內(nèi)的記錄靴寂。zset 集合中只有 score
值非常重要,value
值沒有特別的意義召耘,只需要保證它是唯一的就可以了百炬。
不足
因?yàn)楹?jiǎn)單限流要記錄時(shí)間窗口內(nèi)所有的行為記錄,如果這個(gè)量很大污它,比如限定 60s 內(nèi)操作不得超過 100w 次這樣的參數(shù)收壕,它這樣做會(huì)消耗大量的存儲(chǔ)空間,是不合適的轨蛤。
Redis中的Info指令
在使用 Redis 時(shí),通過
Info
指令虫埂,可以清晰地知道 Redis 內(nèi)部一系列運(yùn)行參數(shù)祥山。-
Info 指令顯示的信息非常繁多,分為 9 大塊掉伏,每個(gè)塊都有非常多的參數(shù)缝呕,這 9 個(gè)塊分別是:
- Server 服務(wù)器運(yùn)行的環(huán)境參數(shù)
- Clients 客戶端相關(guān)信息
- Memory 服務(wù)器運(yùn)行內(nèi)存統(tǒng)計(jì)數(shù)據(jù)
- Persistence 持久化信息
- Stats 通用統(tǒng)計(jì)數(shù)據(jù)
- Replication 主從復(fù)制相關(guān)信息
- CPU CPU 使用情況
- Cluster 集群信息
- KeySpace 鍵值對(duì)統(tǒng)計(jì)數(shù)量信息
Info 可以一次性獲取所有的信息,也可以按塊取信息斧散。
# 獲取所有信息
> info
# 獲取內(nèi)存相關(guān)信息
> info memory
# 獲取復(fù)制相關(guān)信息
> info replication
一些常用指令
- 查看每秒操作數(shù)
# ops_per_sec: operations per second供常,也就是每秒操作數(shù)
> redis-cli info stats |grep ops
instantaneous_ops_per_sec:789
ops 是 789,也就是所有客戶端每秒會(huì)發(fā)送 789 條指令到服務(wù)器執(zhí)行鸡捐。極限情況下栈暇,Redis 可以每秒執(zhí)行 10w 次指令,CPU 幾乎完全榨干箍镜。如果 qps 過高源祈,可以考慮通過 monitor 指令快速觀察一下究竟是哪些 key 訪問比較頻繁,從而在相應(yīng)的業(yè)務(wù)上進(jìn)行優(yōu)化色迂,以減少 IO 次數(shù)香缺。
- 查看連接了多少客戶端
> redis-cli info clients
# Clients
connected_clients:124 # 這個(gè)就是正在連接的客戶端數(shù)量
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
- 查看內(nèi)存占用
> redis-cli info memory | grep used | grep human
used_memory_human:827.46K # 內(nèi)存分配器 (jemalloc) 從操作系統(tǒng)分配的內(nèi)存總量
used_memory_rss_human:3.61M # 操作系統(tǒng)看到的內(nèi)存占用 ,top 命令看到的內(nèi)存
used_memory_peak_human:829.41K # Redis 內(nèi)存消耗的峰值
used_memory_lua_human:37.00K # lua 腳本引擎占用的內(nèi)存大小