Redis 刪除1.2億指定字首的key
背景
因為更換IDC的原因,我們需要遷移快取到新的機房,開發同學提出老的快取有1.2億無效(未設定過期時間)的key和正常在用的業務key,在遷移之前可以先指定字首將key刪除。那麼問題來了,如何快速刪除1.2億的key呢?
如何獲取指定的 key
大家都知道由於Redis的單執行緒服務模式,命令 keys * 會阻塞正常的業務請求,所以肯定不行。
在這裡我們利用Redis 提供的 SCAN 功能。SCAN 命令是一個基於遊標的迭代器(cursor based iterator): SCAN 命令每次被呼叫之後, 都會向使用者返回一個新的遊標, 使用者在下次迭代時需要使用這個新遊標作為 SCAN 命令的遊標引數, 以此來延續之前的迭代過程。
當 SCAN 命令的遊標引數被設定為 0 時, 伺服器將開始一次新的迭代, 而當伺服器向使用者返回值為 0 的遊標時, 表示迭代已結束。 SCAN的語法如下
SCAN cursor [MATCH pattern] [COUNT count]
其中 cousor 是遊標,MATCH 則支援正則匹配,我們正好可以利用此功能,比如匹配 字首為"dba_"的key, COUNT 是每次獲取多少個key。
redis 127.0.0.1:6379> scan 0 1) "17" 2) 1) "key:12" 2) "key:8" 3) "key:4" 4) "key:14" 5) "key:16" 6) "key:17" 7) "key:15" 8) "key:10" 9) "key:3" 10) "key:7" 11) "key:1" redis 127.0.0.1:6379> scan 17 1) "0" 2) 1) "key:5" 2) "key:18" 3) "key:0" 4) "key:2" 5) "key:19" 6) "key:13" 7) "key:6" 8) "key:9" 9) "key:11"
在上面這個例子中, 第一次迭代使用 0 作為遊標, 表示開始一次新的迭代。第二次迭代使用的是第一次迭代時返回的遊標, 也即是命令回覆第一個元素的值 —— 17 。 在第二次呼叫 SCAN 命令時, 命令返回了遊標 0 , 這表示迭代已經結束, 整個資料集(collection)已經被完整遍歷過了。
從上面的示例可以看到, SCAN 命令的回覆是一個包含兩個元素的陣列, 第一個陣列元素是用於進行下一次迭代的新遊標, 而第二個陣列元素則是一個陣列, 這個陣列中包含了所有被迭代的元素。
注意:以 0 作為遊標開始一次新的迭代, 一直呼叫 SCAN 命令, 直到命令返回遊標 0 , 我們稱這個過程為一次完整遍歷(full iteration)。 我們會在後面的程式碼實現中利用此特點。
Python的redis 模組提供 scan_iter 迭代器來遍歷key,其返回的結果迭代器物件。
In [53]: ret=r.scan_iter('dba_*',20) In [54]: print ret
至此,我們解決了如何獲取資料的問題,下面思考第二個問題。
如何執行刪除
這個相對比較簡單,Redis 提供DEL 命令
127.0.0.1:6379[2]> get "dba_7" "r06cVX9" 127.0.0.1:6379[2]> get "dba_1" "ETX57PA" 127.0.0.1:6379[2]> del "dba_7" "dba_1" (integer) 2 127.0.0.1:6379[2]>
在redis-py 中,提供了delete(key),delete(*key)的函式, 其中引數 *key 是多個值的列表。 到這裡,我們大致可以想到獲取key,然後批次刪除
(mytest)? test git:(master) ? python delete_key.py initial keys successfully,use time: 90.2497739792 normal ways end at: 68.685477972 normal ways delete numbers: 1000000
常規方式的刪除10W個key耗時68.7秒,如果是1.2億個key 要多少時間呢?68*1000/3600=18.8小時。能不能更快呢?
如何提高執行速度
Redis本身是基於Request/Response協議的,客戶端傳送一個命令,等待Redis應答,Redis在接收到命令,處理後應答。其中傳送命令加上返回結果的時間稱為(Round Time Trip)RRT-往返時間。如果客戶端傳送大量的命令給Redis,那就是等待上一條命令應答後再執行再執行下一條命令,這中間不僅僅多了RTT,而且還頻繁的呼叫系統IO,傳送網路請求。
Pipeline(流水線)功能極大的改善了上面的缺點。Pipeline能將一組Redis命令進行組裝,然後一次性傳輸給Redis,再將Redis執行這組命令的結果按照順序返回給客戶端。
需要注意的是Pipeline 雖然好用,但是Pipline組裝的命令個數不能沒有限制,否則一次組裝資料量過大,一方面增加客戶端的等待時間,另一方面會造成網路阻塞,需要批次組裝。使用Pepline 和常規方式的效能對比如下:
程式碼
-
# encoding: utf-8
-
"""
-
author: yangyi@youzan.com
-
time: 2018/3/9 下午8:35
-
func:
-
"""
-
import redis
-
import random
-
import string
-
import time
-
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=2)
-
r = redis.Redis(connection_pool=pool)
-
-
-
def random_str():
-
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(7))
-
-
-
def init_keys():
-
start_time = time.time()
-
for i in xrange(0, 20):
-
key_name = 'dba_'+str(i)
-
value_name = random_str()
-
r.set(key_name, value_name)
-
print 'initial keys successfully,use time:', time.time() - start_time
-
-
-
def del_keys_without_pipe():
-
start_time = time.time()
-
result_length = 0
-
for key in r.scan_iter(match='dba_*', count=2000):
-
r.delete(key)
-
result_length += 1
-
print "normal ways end at:", time.time() - start_time
-
print "normal ways delete numbers:", result_length
-
-
-
def del_keys_with_pipe():
-
start_time = time.time()
-
result_length = 0
-
pipe = r.pipeline()
-
for key in r.scan_iter(match='dba_*', count=5000):
-
pipe.delete(key)
-
result_length += 1
-
if result_length % 5000 == 0:
-
pipe.execute()
-
pip_time = time.time()
-
print "use pipeline scan time ", time.time() - start_time
-
pipe.execute()
-
-
print "use pipeline end at:", time.time() - pip_time
-
print "use pipeline ways delete numbers:", result_length
-
-
-
def main():
-
init_keys()
-
del_keys_without_pipe()
-
init_keys()
-
del_keys_with_pipe()
-
-
-
if __name__ == '__main__':
-
main()
-
# encoding: utf-8
-
"""
-
author: yangyi@youzan.com
-
time: 2018/3/9 下午8:35
-
func:
-
"""
-
import redis
-
import random
-
import string
-
import time
-
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=2)
-
r = redis.Redis(connection_pool=pool)
-
-
-
def random_str():
-
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(7))
-
-
-
def init_keys():
-
start_time = time.time()
-
for i in xrange(0, 20):
-
key_name = 'dba_'+str(i)
-
value_name = random_str()
-
r.set(key_name, value_name)
-
print 'initial keys successfully,use time:', time.time() - start_time
-
-
-
def del_keys_without_pipe():
-
start_time = time.time()
-
result_length = 0
-
for key in r.scan_iter(match='dba_*', count=2000):
-
r.delete(key)
-
result_length += 1
-
print "normal ways end at:", time.time() - start_time
-
print "normal ways delete numbers:", result_length
-
-
-
def del_keys_with_pipe():
-
start_time = time.time()
-
result_length = 0
-
pipe = r.pipeline()
-
for key in r.scan_iter(match='dba_*', count=5000):
-
pipe.delete(key)
-
result_length += 1
-
if result_length % 5000 == 0:
-
pipe.execute()
-
pip_time = time.time()
-
print "use pipeline scan time ", time.time() - start_time
-
pipe.execute()
-
-
print "use pipeline end at:", time.time() - pip_time
-
print "use pipeline ways delete numbers:", result_length
-
-
-
def main():
-
init_keys()
-
del_keys_without_pipe()
-
init_keys()
-
del_keys_with_pipe()
-
-
-
if __name__ == '__main__':
- main()
如果讀者朋友有其他更好的方式的,歡迎交流。
資料
本文同步釋出於個人公眾號 ,歡迎關注 ,交流技術。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29096438/viewspace-2152247/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【Redis】 redis-cluster刪除指定的keyRedis
- Redis刪除特定字首key的優雅實現Redis
- Redis 實用小技巧——批次刪除指定的 keyRedis
- Redis刪除大KeyRedis
- 面試官:Redis中大Key怎麼刪除?面試Redis
- laravel: 指定redis快取項的字首LaravelRedis快取
- [轉帖]Redis中刪除過期Key的三種策略Redis
- 前端刪除多條資料,如何將多個被刪除項指定key傳給後臺前端
- 字串-刪除指定字元字串字元
- Git刪除指定分支Git
- Git刪除指定commitGitMIT
- 刪除指定名稱的程式
- 刪除大key時要小心
- jupyter notebook 刪除指定 kernel
- 陣列刪除指定項陣列
- Git刪除指定檔案Git
- 如何批量刪除指定的GitHub ReposGithub
- JavaScript刪除字串中的指定字元JavaScript字串字元
- Redis 可以根據訊息儲存時長 將key 刪除嗎Redis
- 【臨實戰】使用 Python 從 Redis 中刪除 4000W 個 KEYPythonRedis
- 刪除指定目錄下指定字尾的檔案
- Array · 刪除陣列中指定的元素陣列
- jQuery刪除具有指定文字的li元素jQuery
- JavaScript 刪除陣列指定元素JavaScript陣列
- 從Bash中的字串中刪除固定的字首/字尾字串
- 批量刪除 redis keysRedis
- Redis刪除大量key後,佔用的系統記憶體卻沒有釋放?Redis記憶體
- 指定刪除幾天前的索引資料索引
- Linux批量刪除指定型別的檔案Linux型別
- Linux刪除指定時間之前的檔案Linux
- Git刪除暫存區的指定檔案Git
- liunx批量刪除指定字尾的檔案
- git 刪除歷史指定檔案Git
- JavaScript 刪除字串中所有指定字元JavaScript字串字元
- JavaScript刪除字串中所有指定字元JavaScript字串字元
- redis lRem 刪除失敗?RedisREM
- redis-20.刪除策略Redis
- R語言中根據列名刪除指定的列R語言