Redis 大key
Redis使用過程中經常會有各種大key的情況, 比如:
- 單個簡單的key存儲的value很大
- hash, set,zset,list 中存儲過多的元素(以萬為單位)
由於redis是單線程運行的,如果一次操作的value很大會對整個redis的響應時間造成負面影響,所以,業務上能拆則拆,下面舉幾個典型的分拆方案。
業務場景:
即通過hash的方式來存儲每一天用戶訂單次數。那么key = order_20181010, field = order_id, value = 10。那么如果一天有百萬千萬甚至上億訂單的時候,key后面的值是很多,存儲空間也很大,造成所謂的大key。
大key的風險:
- 讀寫大key會導致超時嚴重,甚至阻塞服務。
- 如果刪除大key,DEL命令可能阻塞Redis進程數十秒,使得其他請求阻塞,對應用程序和Redis集群可用性造成嚴重的影響。
- 建議每個key不要超過M級別。
單個簡單的key存儲的value很大
改對象需要每次都整存整取
可以嘗試將對象分拆成幾個key-value, 使用multiGet獲取值,這樣分拆的意義在於分拆單次操作的壓力,將操作壓力平攤到多個redis實例中,降低對單個redis的IO影響;
該對象每次只需要存取部分數據
可以像第一種做法一樣,分拆成幾個key-value, 也可以將這個存儲在一個hash中,每個field代表一個具體的屬性,使用hget,hmget來獲取部分的value,使用hset,hmset來更新部分屬性
hash, set,zset,list 中存儲過多的元素
類似於場景一種的第一個做法,可以將這些元素分拆。以hash為例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value),現在,固定一個桶的數量,比如 10000, 每次存取的時候,先在本地計算field的hash值,模除 10000, 確定了該field落在哪個key上。
newHashKey = hashKey + (*hash*(field) % 10000);
hset (newHashKey, field, value) ;
hget(newHashKey, field)
set, zset, list 也可以類似上述做法。但有些不適合的場景,比如,要保證 lpop 的數據的確是最早push到list中去的,這個就需要一些附加的屬性,或者是在 key的拼接上做一些工作(比如list按照時間來分拆)。
如何優雅地刪除Redis大鍵
關於Redis大鍵(Key),我們從[空間復雜性]和訪問它的[時間復雜度]兩個方面來定義大鍵。
前者主要表示Redis鍵的占用內存大小;后者表示Redis集合數據類型(set/hash/list/sorted set)鍵,所含有的元素個數。以下兩個示例:
1個大小200MB的String鍵(String Object最大512MB);內存空間角度占用較大
1個包含100000000(1kw)個字段的Hash鍵,對應訪問模式(如hgetall)時間復雜度高
因為內存空間復雜性處理耗時都非常小,測試 del 200MB String鍵耗時約1毫秒,而刪除一個含有1kw個字段的Hash鍵,卻會阻塞Redis進程數十秒。所以本文只從時間復雜度分析大的集合類鍵。刪除這種大鍵的風險,以及怎么優雅地刪除。在Redis集群中,應用程序盡量避免使用大鍵;直接影響容易導致集群的容量和請求出現”傾斜問題“,具體分析見文章:redis-cluster-imbalance。但在實際生產過程中,總會有業務使用不合理,出現這類大鍵;當DBA發現后推進業務優化改造,然后刪除這個大鍵;如果直接刪除它,DEL命令可能阻塞Redis進程數十秒,對應用程序和Redis集群可用性造成嚴重的影響。
直接刪除大Key的風險
DEL命令在刪除單個集合類型的Key時,命令的時間復雜度是O(M),其中M是集合類型Key包含的元素個數。生產環境中遇到過多次因業務刪除大Key,導致Redis阻塞,出現故障切換和應用程序雪崩的故障。測試刪除集合類型大Key耗時,一般每秒可清理100w~數百w個元素; 如果數千w個元素的大Key時,會導致Redis阻塞上10秒。可能導致集群判斷Redis已經故障,出現故障切換;或應用程序出現雪崩的情況。
說明:Redis是單線程處理。
單個耗時過大命令,導致阻塞其他命令,容易引起應用程序雪崩或Redis集群發生故障切換。
所以避免在生產環境中使用耗時過大命令。
Redis刪除大的集合鍵的耗時, 測試估算,可參考;和硬件環境、Redis版本和負載等因素有關:
Key類型 | Item數量 | 耗時 |
---|---|---|
Hash | ~100萬 | ~1000ms |
List | ~100萬 | ~1000ms |
Set | ~100萬 | ~1000ms |
Sorted Set | ~100萬 | ~1000ms |
當我們發現集群中有大key時,要刪除時,如何優雅地刪除大Key?
如何優雅地刪除各類大Key
從Redis2.8版本開始支持SCAN命令,通過m次時間復雜度為O(1)的方式,遍歷包含n個元素的大key。這樣避免單個O(n)的大命令,導致Redis阻塞。 這里刪除大key操作的思想也是如此。
Delete Large Hash Key
通過hscan命令,每次獲取500個字段,再用hdel命令,每次刪除1個字段。
Python代碼:
def del_large_hash(): r = redis.StrictRedis(host='redis-host1', port=6379) large_hash_key ="xxx" #要刪除的大hash鍵名 cursor = '0' while cursor != 0: cursor, data = r.hscan(large_hash_key, cursor=cursor, count=500) for item in data.items(): r.hdel(large_hash_key, item[0])
Delete Large Set Key
刪除大set鍵,使用sscan命令,每次掃描集合中500個元素,再用srem命令每次刪除一個鍵
Python代碼:
def del_large_set(): r = redis.StrictRedis(host='redis-host1', port=6379) large_set_key = 'xxx' # 要刪除的大set的鍵名 cursor = '0' while cursor != 0: cursor, data = r.sscan(large_set_key, cursor=cursor, count=500) for item in data: r.srem(large_size_key, item)
Delete Large List Key
刪除大的List鍵,未使用scan命令; 通過ltrim命令每次刪除少量元素。
Python代碼:
def del_large_list(): r = redis.StrictRedis(host='redis-host1', port=6379) large_list_key = 'xxx' #要刪除的大list的鍵名 while r.llen(large_list_key)>0: r.ltrim(large_list_key, 0, -101) #每次只刪除最右100個元素
Delete Large Sorted set key
刪除大的有序集合鍵,和List類似,使用sortedset自帶的zremrangebyrank命令,每次刪除top 100個元素。
Python代碼:
def del_large_sortedset(): r = redis.StrictRedis(host='large_sortedset_key', port=6379) large_sortedset_key='xxx' while r.zcard(large_sortedset_key)>0: r.zremrangebyrank(large_sortedset_key,0,99)#時間復雜度更低 , 每次刪除O(log(N)+100)
Redis Lazy Free
應該從3.4版本開始,Redis會支持lazy delete free的方式,刪除大鍵的過程不會阻塞正常請求。查看《Redis4.0新特性 -Lazy Free》
參考博客:
https://blog.csdn.net/u013474436/article/details/88808914