上一篇文章:Python–Redis實戰:第三章:Redis命令:第六節:釋出與訂閱
下一篇文章:Python–Redis實戰:第四章:資料安全與效能保障:第1節:持久化選項
到目前為止,本章介紹了Redis提供的5種結構以及Redis的釋出與訂閱模式。本節將要介紹的命令則可以用於處理多種型別的資料:首先要介紹的是可以同時處理字串、集合、列表和雜湊的sort命令;之後要介紹的是用於實現基本事務特性的multi命令和exec命令,這兩個命令可以讓使用者將多個命令當做一個命令來執行;最後要介紹的是幾個不同的自動過期命令,亞麻可以自動刪除無用的資料。
閱讀本節有助於讀者更好的理解如何同時組合和操作多種資料型別。
排序
Redis的排序操作和其他程式語言的排序操作一樣,都可以根據某種比較規則對一系列元素進行有序排列。負責執行排序操作的sort命令可以根據字串、列表、集合、有序集合、雜湊這5種鍵Kim儲存的資料,對列表、集合以及有序集合進行排序。如果讀者之前曾經使用過關聯式資料庫的話,那麼可以將soft命令看做是sql語言裡的order by。
下表展示了sort命令的定義:
命令 | 用例 | 用例描述 |
---|---|---|
soft | soft source-key [by pattern] [limit offset count] [get pattern get pattern …]] [asc/desc] [alph] [store dest-key] | 根據給定的選項,對輸入列表、集合或者有序集合進行排序,然後返回或者儲存排序的結果。 |
使用sort命令提供的選項可以實現以下功能:
- 根據降序而不是預設的升序來排列元素;
- 將元素看作是數字來進行排序,或者將元素看作是二進位制字串來進行排序(比如排序字串`110`和`12`的排序結果就跟排序數字110和12的結果不一樣);
- 使用被排序元素之外的其他值作為權重進行排序,甚至還可以從輸入的列表、集合、有序集合以外的其他地方進行取值。
例項
import redis # 匯入redis包包
# 與本地redis進行連結,地址為:localhost,埠號為6379
r = redis.StrictRedis(host=`localhost`, port=6379)
r.delete(`sort-input`)
#首先將一些元素新增到列表裡面
print(r.rpush(`sort-input`,23,15,110,7))
#根據數字大小對元素進行排序
print(r.sort(`sort-input`))
#根據字母順序對元素進行排序
print(r.sort(`sort-input`,alpha=True))
#新增一些用於執行排序操作和獲取操作的附加資料
print(r.hset(`d-7`,`field`,5))
print(r.hset(`d-15`,`field`,1))
print(r.hset(`d-23`,`field`,9))
print(r.hset(`d-110`,`field`,3))
#將雜湊的域(field)用作權重,對sort-input列表進行排序
print(r.sort(`sort-input`,by=`d-*->field`))
#獲取外部資料,並將它們用作命令的返回值,而不是返回被排序的資料
print(r.sort(`sort-input`,by=`d-*->field`,get=`d-*->field`))
執行結果:
4
[b`7`, b`15`, b`23`, b`110`]
[b`110`, b`15`, b`23`, b`7`]
0
0
0
0
[b`15`, b`110`, b`7`, b`23`]
[b`1`, b`3`, b`5`, b`9`]
最開頭的幾行程式碼設定了一些初始資料,然後對這些資料進行了數值排序和字串排序,最後的程式碼演示瞭如果通過sort命令的特殊語法來將雜湊儲存的資料作為權重進行排序,以及怎樣獲取並返回雜湊儲存的資料。
sort命令不僅可以對列表進行排序,還可以對集合進行排序,然後返回一個列表形式的排序結果。上述例項除了展示瞭如果使用alpha關鍵字引數對元素進行字串排序之外,還展示瞭如果基於外部資料對元素進行排序,以及如何獲取並返回外部資料。
後面講介紹如何組合使用集合操作和sort命令:當集合結構計算交集、並集和差集的能力,與sort命令獲取雜湊儲存的外部資料的能力相結合時,sort命令將變得非常強大。
儘管sort是Redis中唯一一個可以同時處理3種不同型別的資料的命令,但基本的Redis事務同樣可以讓我們在一連串不斷執行的命令裡面操作多種不同型別的資料。
基本的Redis事務
有時候為了同時處理多個結構,我們需要向Redis傳送多個命令。儘管Redis有幾個可以在兩個鍵之間複製或者移動元素,但卻沒有那種可以在兩個不同型別之間移動元素的命令(雖然可以使用zunionstore命令將元素從一個集合複製到一個有序集合)。為了對相同或者不同型別的多個鍵執行操作,Redis有5個命令可以讓使用者在不被打斷(interruption)的情況下對多個鍵執行操作,它們分別是watch、multi、exec、unwatch、discard。
這一節中介紹最基本的Redis事務用法,其中只會用到multi命令和exec命令。
什麼是Redis的基本事務
Redis的基本事務需要用到multi命令和exec命令,這種事務可以讓一個客戶端在不被其他客戶端打斷的情況下執行多個命令。和關聯式資料庫那種可以在執行的過程中進行回滾(rollback)的事務不同,在Redis裡面,被multi命令和exec命令包圍的所有命令會一個接一個的執行,直到所有命令都執行完畢。當一個事務執行完畢之後,Redis才會處理其他客戶端的命令。
要在Redis裡面執行事務,我們首先需要執行multi命令,然後輸入那些我們想要在事務裡面執行的命令,最後再執行exec命令。當Redis從一個客戶端那裡接受到multi命令時,Redis會將這個客戶端之後傳送的所有命令都放入到一個佇列裡面,直到這個客戶端傳送exec命令為止,然後Redis就會在不被打斷的情況下,一個接一個地執行儲存在佇列裡面的命令。從語義上來說,Redis事務在Python客戶端上面是由流水線(pipelien)實現:對連線物件用pipeline()方法將建立一個事務,在一切正常的情況下,客戶端會自動地使用multi和exec包裹起使用者輸入的多個命令。此處,為了減少Redis與客戶端之間的通訊往返次數,提升執行多個命令時的效能,Python的Redis客戶端會儲存起事務包含的多個命令,然後在事務執行時一次性地將所有命令都傳送給Redis。
跟介紹publish命令和subscribe命令時的情況一樣,要展示事務執行結果,最簡單的方法就是將事務放到執行緒裡執行。
下面程式碼展示了在沒有使用事務的情況下,執行並行(parallel)自增操作的結果:
import redis # 匯入redis包包
import time,threading
# 與本地redis進行連結,地址為:localhost,埠號為6379
r = redis.StrictRedis(host=`localhost`, port=6379)
r.delete(`notrans:`)
def notrans():
#對`notrans:`計數器執行自增操作並列印操作的執行結果
print(r.incr(`notrans:`))
#等待100毫秒
time.sleep(.1)
#對`notrans:`計數器執行自減操作。
r.incr(`notrans:`,-1)
if __name__ == `__main__`:
# 啟動3個執行緒來執行沒有被事務包裹的自增、休眠和自減操作
for i in range(3):
threading.Thread(target=notrans).start()
# 等待500毫秒,讓操作有足夠的時間完成
time.sleep(.5)
結果:
1
2
3
因為沒有使用事務,所以三個執行緒都可以在執行自減操作之前,對notrans:計數器執行自增操作。雖然程式碼裡面通過休眠100毫秒放大了潛在問題,但如果我們確實需要在不受其它命令干擾的情況下,對計數器執行自增操作和自減操作,那麼我們就不得不解決這個潛在問題。
下面程式碼使用事務來執行相同的操作:
import redis # 匯入redis包包
import time,threading
# 與本地redis進行連結,地址為:localhost,埠號為6379
r = redis.StrictRedis(host=`localhost`, port=6379)
r.delete(`trans:`)
def notrans():
#建立一個事務型(transactional)流水線物件
pipeline=r.pipeline()
#把針對`trans:`計數器的自增操作放入佇列
pipeline.incr(`trans:`)
#等待100毫秒
time.sleep(.1)
#把針對`trans:`計數器的自減操作放入佇列
pipeline.incr(`trans:`,-1)
#執行被事務包裹的命令,並列印自增操作的執行結果
print(pipeline.execute()[0])
if __name__ == `__main__`:
# 啟動3個執行緒來執行沒有被事務包裹的自增、休眠和自減操作
for i in range(3):
threading.Thread(target=notrans).start()
# 等待500毫秒,讓操作有足夠的時間完成
time.sleep(.5)
結果:
1
1
1
可以看出,儘管自增操作和自減操作直接有一段延遲時間,但通過使用事務,各個執行緒都可以在不被其它執行緒打斷的情況下,執行各自佇列裡面的命令。記住,Redis要在接收到Exec命令之後,才會執行那些位於multi和exec之間的入隊命令。
在使用Redis儲存資料的時候,有些資料僅在一段很短的時間內有用,雖然我們可以在資料的有效期過了之後刪除無用的資料,但更好的辦法是使用Redis提供的鍵過期操作來自動刪除無用資料。
鍵的過期時間
在使用Redis儲存資料的時候,有些資料可能在某個時間點之後就不再有用了,使用者可以使用DEL命令顯示刪除這些無用資料,也可以通過Redis的過期時間(expiration)特徵來讓一個鍵再給定的時限(timeout)之後自動被刪除。當我們說一個鍵【帶有生存時間】(time to live)或者一個鍵【會在特定時間之後過期】時,我們指的是Redis會在這個鍵的過期時間到達時自動刪除該鍵。
雖然過期時間特性對於清理快取資料非常有用,不過通常只有少數幾個命令可以原子地為鍵設定過期時間,並且對於列表、集合、雜湊和有序集合這樣的容器來說,鍵過期命令只能為整個鍵設定過期時間,而沒辦法為鍵裡面的單個元素設定過期時間(為了解決這個問題,可以使用儲存時間戳的有序集合來實現針對的那個元素的過期操作)。
本節將對那些可以在給定時限或者給定時間之後,自動刪除過期鍵的Redis命令進行介紹。通過閱讀本節,讀者可以學會如何使用過期操作來自動刪除過期資料並降低Redis的記憶體佔用。
下表列出了Redis提供的用於為鍵設定過期時間的命令,已經檢視鍵的過期時間的命令:
命令 | 示例 | 描述 |
---|---|---|
persist | persist key-name | 移除鍵的過期時間 |
ttl | ttl key-name | 檢視給定鍵距離過期還有多少秒 |
expire | expire key-name seconds | 讓給定鍵再指定的秒數之後過期 |
expireat | expireat key-name timestamp | 將給定鍵的過期時間設定為給定的UNIX時間戳。 |
pttl | pttl key-name | 檢視給定鍵距離過期時間還有多少毫秒,這個命令在Redis2.6或以上版本可用, |
pexpire | pexpire key-name milliseconds | 讓給定鍵再指定的毫秒之後過期。這個命令在Redis2.6或以上版本可用。 |
pexpireat | pexpireat key-name timestamp-milliseconds | 將一個毫秒級精確的UNIX時間戳設定為給定鍵的過期時間,這個命令在Redis2.6或以上版本可用。 |
下面程式碼展示了幾個對鍵執行過期時間操作的例子:
import redis # 匯入redis包包
import time
# 與本地redis進行連結,地址為:localhost,埠號為6379
r = redis.StrictRedis(host=`localhost`, port=6379)
r.delete(`trans:`)
#設定一個簡單的字串值作為過期時間的設定物件
print(r.set(`key`,`value`))
print(r.get(`key`))
print(r.expire(`key`,2))
time.sleep(1)
#檢視鍵距離過期還有多長時間
print(r.ttl(`key`))
time.sleep(1)
#此時鍵已經過期,並被刪除
print(r.get(`key`))
執行結果:
True
b`value`
True
1
None
上一篇文章:Python–Redis實戰:第三章:Redis命令:第六節:釋出與訂閱
下一篇文章:Python–Redis實戰:第四章:資料安全與效能保障:第1節:持久化選項