1. 程式人生 > 實用技巧 >Redis快取資料庫

Redis快取資料庫

Redis快取資料庫

介紹

redis是業界主流的key-value nosql 資料庫之一。和Memcached類似,它支援儲存的value型別相對更多,包括string(字串)、list(連結串列)、set(集合)、zset(sorted set --有序集合)和hash(雜湊型別)。這些資料型別都支援push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支援各種不同方式的排序。與memcached一樣,為了保證效率,資料都是快取在記憶體中。區別的是redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。

Redis優點

  • 異常快速:Redis是非常快的,每秒可以執行大約110000設定操作,81000個/每秒的讀取操作。

  • 支援豐富的資料型別:Redis支援最大多數開發人員已經知道如列表,集合,可排序集合,雜湊等資料型別。

    這使得在應用中很容易解決的各種問題,因為我們知道哪些問題處理使用哪種資料型別更好解決。
  • 操作都是原子的:所有Redis的操作都是原子,從而確保當兩個客戶同時訪問Redis伺服器得到的是更新後的值(最新值)。

  • MultiUtility工具:Redis是一個多功能實用工具,可以在很多如:快取,訊息傳遞佇列中使用(Redis原生支援釋出/訂閱),在應用程式中,如:Web應用程式會話,網站頁面點選數等任何短暫的資料;

Redis安裝

Linux下安裝:

安裝完後

1 2 3 4 $ wget http://download.redis.io/releases/redis-2.8.17.tar.gz $ tar xzf redis-2.8.17.tar.gz $ cd redis-2.8.17 $ make

安裝完後將配置檔案redis.conf拷貝到我們自己的資料夾下,myconf/redis.conf,目的是為了後臺執行:

1 2 mkdir myconf cp redis.conf myconf/redis.conf

然後修改配置檔案中的daemonize為yes,這樣當我們開啟redis時指定我們剛剛拷貝的配置檔案,就能在後臺運行了:

1 daemonize yes

然後我們就可以啟動redis了,指定配置檔案:

1 2 cd src $ ./redis-server ../myconf/redis.conf

如果需要外部連線redis的話就需要設定配置檔案,或者直接啟動的時候帶引數 --protected-mode no來取消保護模式:

1 2 3 bind 0.0.0.0 # 允許任何IP訪問 protected-mode no # 取消保護模式,或者設定密碼訪問 requirepass 123456 # 設定密碼登入

啟動後,就可以使用測試客戶端程式redis-cli和redis服務互動了。如:

1 2 3 4 5 6 $ cd src $ ./redis-cli redis>setfoo bar OK redis>getfoo "bar"

Ubuntu下安裝

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 在 Ubuntu 系統安裝 Redis 可以使用以下命令: $sudo apt-getupdate $sudo apt-getinstall redis-server 啟動 Redis $ redis-server 檢視 redis 是否啟動? $ redis-cli 以上命令將開啟以下終端: redis 127.0.0.1:6379> 127.0.0.1 是本機 IP ,6379 是 redis 服務埠。現在我們輸入 PING 命令。 redis 127.0.0.1:6379> ping PONG

Redis API使用

redis基本命令

1 2 3 4 5 6 7 8 9 10 11 12 13 14 String setsetex psetex mset mget getset getrange setrange setbit getbit bitcount bittop strlen incr incrfloat decr append Hash hset hmset hmget hgetall hlen hkeys hvals hexists hdel hincrby hincrbyfloat hscan hscan_iter List lpush rpush lpushx rpushx linsert lset lrem lpop ltrim lindex rpoplpush brpoplpush blpop 自定義增量迭代 有序集合sortset操作 zadd zcard zrange zcount zincraby zrank zrem zremrangebyrank zscore 其他常用操作 delete exists keys expire rname randomkey type scan scan_iter 管道(pipeline)

String型別

set

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 set(name, value, ex=None, px=None, nx=False, xx=False) 在Redis中設定值,預設,不存在則建立,存在則修改 引數說明: ex,過期時間(秒) px,過期時間(毫秒) nx,如果設定為True,則只有name不存在時,當前set操作才執行 xx,如果設定為True,則只有name存在時,當前set操作才執行 1.ex,過期時間(秒) 這裡過期時間是3秒,3秒後p,鍵food的值就變成None import redis pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True) r = redis.Redis(connection_pool=pool) r.set('food','mutton', ex=3) # key是"food"value是"mutton"將鍵值對存入redis快取 print(r.get('food')) # mutton 取出鍵food對應的值 2.px,過期時間(豪秒) 這裡過期時間是3豪秒,3毫秒後,鍵foo的值就變成None import redis pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True) r = redis.Redis(connection_pool=pool) r.set('food','beef', px=3) print(r.get('food')) 3.nx,如果設定為True,則只有name不存在時,當前set操作才執行 (新建) import redis pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True) r = redis.Redis(connection_pool=pool) print(r.set('fruit','watermelon', nx=True)) # True--不存在 # 如果鍵fruit不存在,那麼輸出是True;如果鍵fruit已經存在,輸出是None 4.xx,如果設定為True,則只有name存在時,當前set操作才執行 (修改) print((r.set('fruit','watermelon', xx=True))) # True--已經存在 # 如果鍵fruit已經存在,那麼輸出是True;如果鍵fruit不存在,輸出是None 5.setnx(name, value) 設定值,只有name不存在時,執行設定操作(新增) print(r.setnx('fruit1','banana')) # fruit1不存在,輸出為True

setex

1 2 3 4 5 6 7 8 9 10 11 12 setex(name, value, time) 設定值 引數: time,過期時間(數字秒 或 timedelta物件) import redis import time pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True) r = redis.Redis(connection_pool=pool) r.setex("fruit2","orange", 5) time.sleep(5) print(r.get('fruit2')) # 5秒後,取值就從orange變成None

psetex

1 2 3 4 5 6 7 psetex(name, time_ms, value) 設定值 引數: time_ms,過期時間(數字毫秒 或 timedelta物件) r.psetex("fruit3", 5000,"apple") time.sleep(5) print(r.get('fruit3')) # 5000毫秒後,取值就從apple變成None

mset(*args, **kwargs)

1 2 3 4 5 6 批量設定值 如: r.mget({'k1':'v1','k2':'v2'}) r.mset(k1="v1", k2="v2") # 這裡k1 和k2 不能帶引號 一次設定對個鍵值對 print(r.mget("k1","k2")) # 一次取出多個鍵對應的值 print(r.mget("k1"))

get(name)

1 2 獲取值,如: getage

mget(keys, *args)

1 2 3 4 5 6 mget(keys, *args) 批量獲取 如: print(r.mget('k1','k2')) print(r.mget(['k1','k2'])) print(r.mget("fruit","fruit1","fruit2","k1","k2")) # 將目前redis快取中的鍵對應的值批量取出來

getset(name, value)

1 2 3 getset(name, value) 設定新值並獲取原來的值 print(r.getset("food","barbecue")) # 設定的新值是barbecue 設定前的值是beef1

getrange(key, start, end)

1 2 3 4 5 6 7 8 9 10 11 12 13 getrange(key, start, end) 獲取子序列(根據位元組獲取,非字元) 引數: name,Redis 的 name start,起始位置(位元組) end,結束位置(位元組) 如: “君惜大大” ,0-3表示 “君” r.set("cn_name","君惜大大") # 漢字 print(r.getrange("cn_name", 0, 2)) # 取索引號是0-2 前3位的位元組 君 切片操作 (一個漢字3個位元組 1個字母一個位元組 每個位元組8bit) print(r.getrange("cn_name", 0, -1)) # 取所有的位元組 君惜大大 切片操作 r.set("en_name","junxi") # 字母 print(r.getrange("en_name", 0, 2)) # 取索引號是0-2 前3位的位元組 jun 切片操作 (一個漢字3個位元組 1個字母一個位元組 每個位元組8bit) print(r.getrange("en_name", 0, -1)) # 取所有的位元組 junxi 切片操作

setbit(name, offset, value)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 setbit(name, offset, value) 對name對應值的二進位制表示的位進行操作 引數: name,redis的name offset,位的索引(將值變換成二進位制後再進行索引) value,值只能是 1 或 0 注:如果在Redis中有一個對應: n1 ="foo" 那麼字串foo的二進位制表示為:01100110 01101111 01101111 所以,如果執行 setbit('n1', 7, 1),則就會將第7位設定為1, 那麼最終二進位制則變成 01100111 01101111 01101111,即:"goo" 擴充套件,轉換二進位制表示: source ="張三" source ="foo" foriinsource: num = ord(i) print bin(num).replace('b','') 特別的,如果source是漢字"張三"怎麼辦? 答:對於utf-8,每一個漢字佔 3 個位元組,那麼"張三"則有 6個位元組 對於漢字,for迴圈時候會按照 位元組 迭代,那麼在迭代時,將每一個位元組轉換 十進位制數,然後再將十進位制數轉換成二進位制

getbit(name, offset)

1 2 3 getbit(name, offset) 獲取name對應的值的二進位制表示中的某位的值 (0或1) print(r.getbit("foo1", 0)) # 0 foo1 對應的二進位制 4個位元組 32位 第0位是0還是1

bitcount(key, start=None, end=None)

1 2 3 4 5 6 7 8 bitcount(key, start=None, end=None) 獲取name對應的值的二進位制表示中 1 的個數 引數: key,Redis的name start 位元組起始位置 end,位元組結束位置 print(r.get("foo")) # goo1 01100111 print(r.bitcount("foo",0,1)) # 11 表示前2個位元組中,1出現的個數

strlen(name)

1 2 3 strlen(name) 返回name對應值的位元組長度(一個漢字3個位元組) print(r.strlen("foo")) # 4'goo1'的長度是4

incr(self, name, amount=1)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 incr(self, name, amount=1) 自增 name對應的值,當name不存在時,則建立name=amount,否則,則自增。 引數: name,Redis的name amount,自增數(必須是整數) 注:同incrby r.set("foo", 123) print(r.mget("foo","foo1","foo2","k1","k2")) r.incr("foo", amount=1) print(r.mget("foo","foo1","foo2","k1","k2")) 應用場景 – 頁面點選數 假定我們對一系列頁面需要記錄點選次數。例如論壇的每個帖子都要記錄點選次數,而點選次數比回帖的次數的多得多。如果使用關係資料庫來儲存點選,可能存在大量的行級鎖爭用。所以, 點選數的增加使用redis的INCR命令最好不過了。 當redis伺服器啟動時,可以從關係資料庫讀入點選數的初始值(12306這個頁面被訪問了34634次) r.set("visit:12306:totals", 34634) print(r.get("visit:12306:totals")) 每當有一個頁面點選,則使用INCR增加點選數即可。 r.incr("visit:12306:totals") r.incr("visit:12306:totals") 頁面載入的時候則可直接獲取這個值 print(r.get("visit:12306:totals"))

incrbyfloat(self, name, amount=1.0)

1 2 3 4 5 6 7 8 9 10 11 incrbyfloat(self, name, amount=1.0) 自增 name對應的值,當name不存在時,則建立name=amount,否則,則自增。 引數: name,Redis的name amount,自增數(浮點型) r.set("foo1","123.0") r.set("foo2","221.0") print(r.mget("foo1","foo2")) r.incrbyfloat("foo1", amount=2.0) r.incrbyfloat("foo2", amount=3.0) print(r.mget("foo1","foo2"))

decr(self, name, amount=1)

1 2 3 4 5 6 7 8 decr(self, name, amount=1) 自減 name對應的值,當name不存在時,則建立name=amount,否則,則自減。 引數: name,Redis的name amount,自減數(整數) r.decr("foo4", amount=3) # 遞減3 r.decr("foo1", amount=1) # 遞減1 print(r.mget("foo1","foo4"))

append(key, value)

1 2 3 4 5 6 7 append(key, value) 在redis name對應的值後面追加內容 引數: key, redis的name value, 要追加的字串 r.append("name","haha") # 在name對應的值junxi後面追加字串haha print(r.mget("name"))

  

集合set操作

sadd

1 2 3 4 5 6 新增 sadd(name,values) name對應的集合中新增元素 r.sadd("set1", 33, 44, 55, 66) # 往集合中新增元素 print(r.scard("set1")) # 集合的長度是4 print(r.smembers("set1")) # 獲取集合中所有的成員

scard

1 2 3 4 獲取元素個數 類似於len scard(name) 獲取name對應的集合中元素個數 print(r.scard("set1")) # 集合的長度是4

smembers

1 2 3 4 5 6 7 8 9 10 11 12 13 獲取集合中所有的成員 smembers(name) 獲取name對應的集合的所有成員 print(r.smembers("set1")) # 獲取集合中所有的成員 獲取集合中所有的成員–元組形式 sscan(name, cursor=0, match=None, count=None) print(r.sscan("set1")) 1 獲取集合中所有的成員–迭代器的方式 sscan_iter(name, match=None, count=None) 同字串的操作,用於增量迭代分批獲取元素,避免記憶體消耗太大 foriinr.sscan_iter("set1"): print(i)2

sdiff

1 2 3 4 5 6 7 8 差集 sdiff(keys, *args) 在第一個name對應的集合中且不在其他name對應的集合的元素集合 r.sadd("set2", 11, 22, 33) print(r.smembers("set1")) # 獲取集合中所有的成員 print(r.smembers("set2")) print(r.sdiff("set1","set2")) # 在集合set1但是不在集合set2中 print(r.sdiff("set2","set1")) # 在集合set2但是不在集合set1中

sdiffstore

1 2 3 4 5 差集–差集存在一個新的集合中 sdiffstore(dest, keys, *args) 獲取第一個name對應的集合中且不在其他name對應的集合,再將其新加入到dest對應的集合中 r.sdiffstore("set3","set1","set2") # 在集合set1但是不在集合set2中 print(r.smembers("set3")) # 獲取集合3中所有的成員

sinter

1 2 3 4 交集 sinter(keys, *args) 獲取多一個name對應集合的交集 print(r.sinter("set1","set2")) # 取2個集合的交集

sinterstore

1 2 3 4 5 交集–交集存在一個新的集合中 sinterstore(dest, keys, *args) 獲取多一個name對應集合的並集,再將其加入到dest對應的集合中 print(r.sinterstore("set3","set1","set2")) # 取2個集合的交集 print(r.smembers("set3"))

sunion

1 2 3 4 5 6 7 8 9 並集 sunion(keys, *args) 獲取多個name對應的集合的並集 print(r.sunion("set1","set2")) # 取2個集合的並集1 並集–並集存在一個新的集合 sunionstore(dest,keys, *args) 獲取多一個name對應的集合的並集,並將結果儲存到dest對應的集合中 print(r.sunionstore("set3","set1","set2")) # 取2個集合的並集 print(r.smembers("set3"))

sismember

1 2 3 4 5 判斷是否是集合的成員 類似in sismember(name, value) 檢查value是否是name對應的集合的成員,結果為True和False print(r.sismember("set1", 33)) # 33是集合的成員 print(r.sismember("set1", 23)) # 23不是集合的成員

smove

1 2 3 4 5 6 移動 smove(src, dst, value) 將某個成員從一個集合中移動到另外一個集合 r.smove("set1","set2", 44) print(r.smembers("set1")) print(r.smembers("set2"))

spop

1 2 3 4 5 6 7 8 9 10 刪除–隨機刪除並且返回被刪除值 spop(name) 從集合移除一個成員,並將其返回,說明一下,集合是無序的,所有是隨機刪除的 print(r.spop("set2")) # 這個刪除的值是隨機刪除的,集合是無序的 print(r.smembers("set2")) 11.刪除–指定值刪除 srem(name, values) 在name對應的集合中刪除某些值 print(r.srem("set2", 11)) # 從集合中刪除指定值 11 print(r.smembers("set2"))

  

有序集合sort set操作

有序集合,在集合的基礎上,為每元素排序;

元素的排序需要根據另外一個值來進行比較,所以,對於有序集合,每一個元素有兩個值,即:值和分數,分數專門用來做排序。

zadd

新增
zadd(name,args, *kwargs)
在name對應的有序集合中新增元素
如:
import redis
import time

pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)

r.zadd("zset1", n1=11, n2=22)
r.zadd("zset2", 'm1', 22, 'm2', 44)
print(r.zcard("zset1")) # 集合長度
print(r.zcard("zset2")) # 集合長度
print(r.zrange("zset1", 0, -1))   # 獲取有序集合中所有元素
print(r.zrange("zset2", 0, -1, withscores=True))   # 獲取有序集合中所有元素和分數2

zcard

獲取有序集合元素個數 類似於len
zcard(name)
獲取name對應的有序集合元素的數量
print(r.zcard("zset1")) # 集合長度1

zrange

獲取有序集合的所有元素
r.zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
按照索引範圍獲取name對應的有序集合的元素
引數:
name,redis的name
start,有序集合索引起始位置(非分數)
end,有序集合索引結束位置(非分數)
desc,排序規則,預設按照分數從小到大排序
withscores,是否獲取元素的分數,預設只獲取元素的值
score_cast_func,對分數進行資料轉換的函式
3-1 從大到小排序(同zrange,集合是從大到小排序的)
zrevrange(name, start, end, withscores=False, score_cast_func=float)
print(r.zrevrange("zset1", 0, -1))    # 只獲取元素,不顯示分數
print(r.zrevrange("zset1", 0, -1, withscores=True)) # 獲取有序集合中所有元素和分數,分數倒序
3-2 按照分數範圍獲取name對應的有序集合的元素
zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
for i in range(1, 30):
   element = 'n' + str(i)
   r.zadd("zset3", element, i)
print(r.zrangebyscore("zset3", 15, 25)) # # 在分數是15-25之間,取出符合條件的元素
print(r.zrangebyscore("zset3", 12, 22, withscores=True))    # 在分數是12-22之間,取出符合條件的元素(帶分數)
3-3 按照分數範圍獲取有序集合的元素並排序(預設從大到小排序)
zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)
print(r.zrevrangebyscore("zset3", 22, 11, withscores=True)) # 在分數是22-11之間,取出符合條件的元素 按照分數倒序1
3-4 獲取所有元素–預設按照分數順序排序
zscan(name, cursor=0, match=None, count=None, score_cast_func=float)
print(r.zscan("zset3"))1
3-5 獲取所有元素–迭代器
zscan_iter(name, match=None, count=None,score_cast_func=float)
for i in r.zscan_iter("zset3"): # 遍歷迭代器
    print(i)

zcount

zcount(name, min, max)
獲取name對應的有序集合中分數 在 [min,max] 之間的個數
print(r.zrange("zset3", 0, -1, withscores=True))
print(r.zcount("zset3", 11, 22))

zincrby

自增
zincrby(name, value, amount)
自增name對應的有序集合的 name 對應的分數
r.zincrby("zset3", "n2", amount=2)    # 每次將n2的分數自增2
print(r.zrange("zset3", 0, -1, withscores=True))

zrank

獲取值的索引號
zrank(name, value)
獲取某個值在 name對應的有序集合中的索引(從 0 開始)
更多:
zrevrank(name, value),從大到小排序
print(r.zrank("zset3", "n1"))   # n1的索引號是0 這裡按照分數順序(從小到大)
print(r.zrank("zset3", "n6"))   # n6的索引號是1

print(r.zrevrank("zset3", "n1"))    # n1的索引號是29 這裡安照分數倒序(從大到小)

zrem

刪除–指定值刪除
zrem(name, values)
刪除name對應的有序集合中值是values的成員
r.zrem("zset3", "n3")   # 刪除有序集合中的元素n3 刪除單個
print(r.zrange("zset3", 0, -1))

zremrangebyrank

刪除–根據排行範圍刪除,按照索引號來刪除
zremrangebyrank(name, min, max)
根據排行範圍刪除
r.zremrangebyrank("zset3", 0, 1)  # 刪除有序集合中的索引號是0, 1的元素
print(r.zrange("zset3", 0, -1))
zremrangebyscore(name, min, max)
根據分數範圍刪除
r.zremrangebyscore("zset3", 11, 22)   # 刪除有序集合中的分數是11-22的元素
print(r.zrange("zset3", 0, -1))

zscore

獲取值對應的分數 zscore(name, value) 獲取name對應有序集合中 value 對應的分數 print(r.zscore("zset3", "n27")) # 獲取元素n27對應的分數271

  

其他常用操作

del

delete (*names)
根據刪除redis中的任意資料型別(string、hash、list、set、有序set)
r.delete("gender")  # 刪除key為gender的鍵值對1

exists

檢查名字是否存在
exists(name)
檢測redis的name是否存在,存在就是True,False 不存在
print(r.exists("zset1"))1

keys

模糊匹配
keys(pattern=’‘)
根據模型獲取redis的name
更多:
KEYS匹配資料庫中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS h*llo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo
print(r.keys("foo*"))
1

expire

設定超時時間
expire(name ,time)
為某個redis的某個name設定超時時間
r.lpush("list5", 11, 22)
r.expire("list5", time=3)
print(r.lrange("list5", 0, -1))
time.sleep(3)
print(r.lrange("list5", 0, -1))

rname

重新命名
rename(src, dst)
對redis的name重新命名
r.lpush("list5", 11, 22)
r.rename("list5", "list5-1")

randomkey

隨機獲取name
randomkey()
隨機獲取一個redis的name(不刪除)
print(r.randomkey())
1

type

獲取型別
type(name)
獲取name對應值的型別
print(r.type("set1"))
print(r.type("hash2"))

scan

檢視所有元素
scan(cursor=0, match=None, count=None)
print(r.hscan("hash2"))
print(r.sscan("set3"))
print(r.zscan("zset2"))
print(r.getrange("foo1", 0, -1))
print(r.lrange("list2", 0, -1))
print(r.smembers("set3"))
print(r.zrange("zset3", 0, -1))
print(r.hgetall("hash1"))

scan_iter

檢視所有元素–迭代器
scan_iter(match=None, count=None)
for i in r.hscan_iter("hash1"):
    print(i)

for i in r.sscan_iter("set3"):
    print(i)

for i in r.zscan_iter("zset3"):
    print(i)8
other 方法
print(r.get('name'))    # 查詢key為name的值
r.delete("gender")  # 刪除key為gender的鍵值對
print(r.keys()) # 查詢所有的Key
print(r.dbsize())   # 當前redis包含多少條資料
r.save()    # 執行"檢查點"操作,將資料寫回磁碟。儲存時阻塞
# r.flushdb()        # 清空r中的所有資料

管道(pipeline)

redis-py預設在執行每次請求都會建立(連線池申請連線)和斷開(歸還連線池)一次連線操作,如果想要在一次請求中指定多個命令,則可以使用pipline實現一次請求指定多個命令,並且預設情況下一次pipline 是原子性操作。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/usr/bin/env python # -*- coding:utf-8 -*- import redis pool = redis.ConnectionPool(host='10.211.55.4', port=6379) r = redis.Redis(connection_pool=pool) # pipe = r.pipeline(transaction=False) pipe = r.pipeline(transaction=True) pipe.set('name','alex') pipe.set('role','sb') pipe.execute()<br><br>================================
管道的命令可以寫在一起,如:
pipe.set('hello', 'redis').sadd('faz', 'baz').incr('num').execute()
print(r.get("name"))
print(r.get("role"))
print(r.get("num"))