一日一技:如何從 Redis 的列表中一次性 pop 多條數據?

一日一技:如何從 Redis 的列表中一次性 pop 多條數據?

一日一技:如何從 Redis 的列表中一次性 pop 多條數據?

攝影:產品經理
產品經理說我炒的蛋炒飯比圖中好吃
當咱們想從 Redis 的列表裏面持續彈出數據的時候,咱們通常使用lpop或者rpop:redis

import redis

client = redis.Redis()

while True:
    data = client.lpop('key')
    if not data:
        break
    print(f'彈出一條數據:{data.decode()}')

但這種寫法有一個問題,就是每彈出1條數據都要鏈接一次 Redis 服務器,當你要把1000萬條數據從列表裏面彈出來的時候,實際上超過一半的時間都消耗在了網絡請求上面。服務器

可是lpop與rpop都只接收一個參數,就是key。所以沒有辦法經過傳入參數的方式讓它一次彈出多條數據。網絡

要獲取多條數據,咱們還有另外一種方案,就是lrange:ide

client = client.lrange('key', 0, 5000)

這一行的意思是從列表中,獲取前5001條數據(包含首尾)。但lrange只能獲取數據,卻不能刪除數據。這就會致使在多個進程獲取到重複的數據。線程

咱們還知道Redis 的ltrim來刪除數據:3d

client.ltrim('key', 5000, -1)

這樣就能刪除前5000條數據了。這裏第三個參數之因此要用負數,是由於ltrim(key, start, end)的意思是說,保留列表 Key 的第start項到第end 項,其它項刪除。那麼若是 end爲負數,表示倒數第幾項,例如-1表示倒數第1項,-2表示倒數第2項。假設列表裏面有10000項,那麼 start 爲5000,end 爲-1,表示刪除前5000條數據(0-4999),保留後面的。code

因而有人問,能不能這樣寫代碼呢:blog

import redis

client = redis.Redis()
data = client.lrange('key', 0, 4999)
client.ltrim('key', 5000, -1)

這樣不就看起來像是彈出了5000條數據嗎?進程

想法很好,可是因爲獲取數據與刪除數據是兩條命令,中間有時間差。這就致使在多個線程或者進程同時執行這兩條代碼的時候,出現競爭。也就是進程1剛剛獲取了前5000條數據,而後進程2一樣獲取這5000條數據,而後進程1刪除前5000條數據,而後進程2再刪除5000條數據。事務

這樣一來,兩個進程獲取了相同的5000條數據,可是卻刪了10000條數據。

爲了解決這個問題,必須讓獲取數據與刪除數據這兩個操做變成一個「原子操做」。所謂的原子操做就是隻一個最小的操做單位,它不會被中途打斷。

要解決這個問題,咱們就須要使用 Redis 的pipeline功能。它能夠把多條命令放在一個網絡請求中發送到服務器,並默認在一個事務中執行這些命令。一個事務是不會被打斷的,從事務開始而後執行裏面的多個命令到結束的整個過程,能夠看作一個原子操做。

pipeline的使用方法以下:

import redis

client = redis.Redis()

def batch_lpop(key, n):
    p = client.pipeline()
    p.lrange(key, 0, n - 1)
    p.ltrim(key, n, -1)
    data = p.execute()
    return data

batch_lpop('test_pipeline', 20)

當代碼執行到p.execute()的時候,它纔會真正去鏈接服務器,而後把待執行的命令在一個事務中一次性執行完成。並返回一個列表。返回的列表有兩項,第0項是包含結果的列表,第1項爲ltrim 的返回結果。以下圖所示:

一日一技:如何從 Redis 的列表中一次性 pop 多條數據?
咱們只須要使用第0項的結果便可。
一日一技:如何從 Redis 的列表中一次性 pop 多條數據?

相關文章
相關標籤/搜索