Redis
redis是一個key-value存儲系統。和Memcached類似,它支援存儲的value類型相對更多,包括string(字元串)、list(連結清單)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些資料類型都 支援push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支援各種不同方式的排 序。與memcached一樣,為了保證效率,資料都是緩存在記憶體中。差別的是redis會周期性的把更新的資料寫入磁盤或者把修改操作寫入追加的記錄文 件,并且在此基礎上實作了master-slave(主從)同步。
一、Redis安裝和基本使用
wget http://download.redis.io/releases/redis-3.0.6.tar.gz
tar xzf redis-3.0.6.tar.gz
cd redis-3.0.6
make
啟動服務端
src/redis-server
啟動用戶端
src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"
二、Python操作Redis
sudo pip install redis
or
sudo easy_install redis
or
源碼安裝
詳見:https://github.com/WoLpH/redis-py
API使用
redis-py 的API的使用可以分類為:
- 連接配接方式
- 連接配接池
- 操作
- String 操作
- Hash 操作
- List 操作
- Set 操作
- Sort Set 操作
- 管道
- 釋出訂閱
1、操作模式
redis-py提供兩個類Redis和StrictRedis用于實作Redis的指令,StrictRedis用于實作大部分官方的指令,并使用官方的文法和指令,Redis是StrictRedis的子類,用于向後相容舊版本的redis-py。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
r = redis.Redis(host='192.168.1.113', port=6379)
r.set('foo', 'Bar')
print r.get('foo')
2、連接配接池
redis-py使用connection pool來管理對一個redis server的所有連接配接,避免每次建立、釋放連接配接的開銷。預設,每個Redis執行個體都會維護一個自己的連接配接池。可以直接建立一個連接配接池,然後作為參數 Redis,這樣就可以實作多個Redis執行個體共享一個連接配接池。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.1.113', port=6379)
r = redis.Redis(connection_pool=pool)
r.set('foo', 'Bar')
print r.get('foo')
3、操作
String操作,redis中的String在在記憶體中按照一個name對應一個value來存儲。如圖:

set(name, value, ex=None, px=None, nx=False, xx=False)
在Redis中設定值,預設,不存在則建立,存在則修改
參數:
ex,過期時間(秒)
px,過期時間(毫秒)
nx,如果設定為True,則隻有name不存在時,目前set操作才執行
xx,如果設定為True,則隻有name存在時,崗前set操作才執行
r.get("name") # 擷取字段
b'haha'
r.set("name", "hehe", ex=5) # 保留時長 5秒
r.get("name")
b'hehe'
r.get("name")
r.set({'k1': 'v1', 'k2': 'v2'}) # 支援字典格式
r.get("k1")
b'v1'
mset(*args, **kwargs)
# 批量設定
r.mset(name="xixi", age=18)
r.get("name")
b'xixi'
r.get("age")
b'18'
mget(keys, *args)
# 批量擷取
r.mset(name="xixi", age=18)
r.mget("name", "age")
[b'xixi', b'18']
r.set("name", "koka")
getset(name, value)
# 設定新值并擷取原來的值
r.getset("name", "akok")
b'koka'
r.get("name")
b'akok'
getrange(key, start, end)
# 擷取子序列(根據位元組擷取,非字元)
# 參數:
# name,Redis 的 name
# start,起始位置(位元組)
# end,結束位置(位元組)
# 如: "武沛齊" ,0-2表示 "武"
r.set("parter", "柴少")
r.getrange("parter", 0, 2).decode()
'柴'
setrange(name, offset, value)
# 修改字元串内容,從指定字元串索引開始向後替換(新值太長時,則向後添加)
# 參數:
# offset,字元串的索引,位元組(一個漢字三個位元組)
# value,要設定的值
r.set("name", 'ok')
# 修改字元串内容,從指定字元串索引開始向後替換
r.setrange("name", 0, "ko")
r.get("name")
b'ko'
setbit(name, offset, value)
# 對name對應值的二進制表示的位進行操作
# 參數:
# name,redis的name
# offset,位的索引(将值變換成二進制後再進行索引)
# value,值隻能是 1 或 0
ord('k') # 107
ord('o') # 111
# 107 => 0 1 1 0 1 0 1 1
# 111 => 0 1 1 0 1 1 1 1
r.setbit("name", 7, 0)
# 0 1 1 0 1 0 1 1 => 0 1 1 0 1 0 1 0 106 => j
r.get("name")
b'jo'
r.setbit("name", 15, 0)
# 0 1 1 0 1 1 1 1 => 0 1 1 0 1 1 1 0 110 => n
r.get("name")
b'jn'
getbit(name, offset)
# 擷取name對應的值的二進制表示中的某位的值 (0或1)
bin(ord('j')).replace('b', '')
'01101010'
bin(ord('n')).replace('b', '')
'01101110'
r.set("name", "jn")
r.getbit("name", "2")
1
bitcount(key, start=None, end=None)
# 擷取name對應的值的二進制表示中 1 的個數
# 參數:
# key,Redis的name
# start,位起始位置
# end,位結束位置
r.bitcount("name", 0, 1)
9
str(bin(ord('j')) + bin(ord('n'))).replace('b', '').count("1")
9
incr(self, name, amount=1)
# 自增 name對應的值,當name不存在時,則建立name=amount,否則,則自增。
# 參數:
# name,Redis的name
# amount,自增數(必須是整數)
# 注:同incrby
r.set('n', 1)
r.incr("n", '3')
4
incrbyfloat(self, name, amount=1.0)
# 自增 name對應的值,當name不存在時,則建立name=amount,否則,則自增。
# 參數:
# name,Redis的name
# amount,自增數(浮點型)
r.incrbyfloat('n', 1)
3.0
decr(self, name, amount=1)
# 自減 name對應的值,當name不存在時,則建立name=amount,否則,則自減。
# 參數:
# name,Redis的name
# amount,自減數(整數)
r.set('n', '2')
r.decr('n', 1)
1
append(key, value)
# 在redis name對應的值後面追加内容
# 參數:
key, redis的name
value, 要追加的字元串
r.append('n', 'a')
2
r.get("n")
b'1a'
Hash操作,redis中Hash在記憶體中的存儲格式如下圖:
hset(name, key, value)
# name對應的hash中設定一個鍵值對(不存在,則建立;否則,修改)
# 參數:
# name,redis的name
# key,name對應的hash中的key
# value,name對應的hash中的value
# 注:
# hsetnx(name, key, value),當name對應的hash中不存在目前key時則建立(相當于添加)
r.hset("h1", "name", "koka")
1
r.hget("h1", "name")
b'koka'
hmset(name, mapping)
# 在name對應的hash中批量設定鍵值對
# 參數:
# name,redis的name
# mapping,字典,如:{'k1':'v1', 'k2': 'v2'}
# 如:
# r.hmset('xx', {'k1':'v1', 'k2': 'v2'})
r.hmset('n1', {'k1': 'v1', 'k2': 'v2'})
r.hmget("n1", "k1", "k2")
[b'v1', b'v2']
hgetall(name)
# 擷取name對應hash的所有鍵值
r.hgetall("n1")
{b'k2': b'v2', b'k1': b'v1'}
hlen(name)
# 擷取name對應的hash中鍵值對的個數
r.hlen("n1")
2
hkeys(name)
# 擷取name對應的hash中所有的key的值
r.hkeys("n1")
[b'k2', b'k1']
hvals(name)
# 擷取name對應的hash中所有的value的值
r.hvals("n1")
[b'v2', b'v1']
hexists(name, key)
# 檢查name對應的hash是否存在目前傳入的key
r.hexists("n1", "k1")
hdel(name,*keys)
# 将name對應的hash中指定key的鍵值對删除
r.hdel("n1", "k1")
1
hincrby(name, key, amount=1)
# 自增name對應的hash中的指定key的值,不存在則建立key=amount
# 參數:
# name,redis中的name
# key, hash對應的key
# amount,自增數(整數)
r.hincrby("n1", "k1", 1)
1
hincrbyfloat(name, key, amount=1.0)
# 自增name對應的hash中的指定key的值,不存在則建立key=amount
# 參數:
# name,redis中的name
# key, hash對應的key
# amount,自增數(浮點數)
# 自增name對應的hash中的指定key的值,不存在則建立key=amount
r.hincrbyfloat("n1", "k1", 1)
2.0
hscan(name, cursor=0, match=None, count=None)
# 增量式疊代擷取,對于資料大的資料非常有用,hscan可以實作分片的擷取資料,并非一次性将資料全部擷取完,進而放置記憶體被撐爆
# 參數:
# name,redis的name
# cursor,遊标(基于遊标分批取擷取資料)
# match,比對指定key,預設None 表示所有的key
# count,每次分片最少擷取個數,預設None表示采用Redis的預設分片個數
# 如:
# 第一次:cursor1, data1 = r.hscan('xx', cursor=0, match=None, count=None)
# 第二次:cursor2, data1 = r.hscan('xx', cursor=cursor1, match=None, count=None)
# ...
# 直到傳回值cursor的值為0時,表示資料已經通過分片擷取完畢
r.hscan("n1")
(0, {b'k2': b'v2', b'k1': b'3'})
hscan_iter(name, match=None, count=None)
# 利用yield封裝hscan建立生成器,實作分批去redis中擷取資料
# 參數:
# match,比對指定key,預設None 表示所有的key
# count,每次分片最少擷取個數,預設None表示采用Redis的預設分片個數
for i in r.hscan_iter("n1", match=None, count=None):print(i)
(b'k2', b'v2')
(b'k1', b'3')
List操作,redis中的List在在記憶體中按照一個name對應一個List來存儲。如圖:
lpush(name,values)
# 在name對應的list中添加元素,每個新的元素都添加到清單的最左邊
# 如:
# r.lpush('oo', 11,22,33)
# 儲存順序為: 33,22,11
# 擴充:
# rpush(name, values) 表示從右向左操作
r.lpush("l1", [11, 22, 33, 44])
1
r.lpush("l1", [11, 22, 33, 44])
2
r.lrange("l1", '0', '3')
[b'[11, 22, 33, 44]', b'[11, 22, 33, 44]']
r.lpush("l3", "AA", "BB", "CC", "DD")
1
r.lrange("l3",0,2)
[b'CC', b'BB', b'AA']
lpushx(name,value)
# 在name對應的list中添加元素,隻有name已經存在時,值添加到清單的最左邊
# 更多:
# rpushx(name, value) 表示從右向左操作
r.lpushx("l2", "33")
0
r.lrange("l2",0,1)
[]
llen(name)
# name對應的list元素的個數
r.llen("l1")
2
linsert(name, where, refvalue, value))
# 在name對應的清單的某一個值前或後插入一個新值
# 參數:
# name,redis的name
# where,BEFORE或AFTER
# refvalue,标杆值,即:在它前後插入資料
# value,要插入的資料
r.lpush("l3", "AA", "BB", "CC", "DD")
r.lrange("l3", 0, 2)
[b'CC', b'BB', b'AA']
r.linsert("l3", "AFTER", "CC", "cc")
r.lrange("l3", 0, 3)
[b'CC', b'cc', b'BB', b'AA']
r.lset(name, index, value)
# 對name對應的list中的某一個索引位置重新指派
# 參數:
# name,redis的name
# index,list的索引位置
# value,要設定的值
r.lset("l3", '2', 'Cc')
r.lrange("l3", '0', '5')
[b'DD', b'CC', b'Cc', b'BB', b'AA']
r.lrem(name, value, num)
# 在name對應的list中删除指定的值
# 參數:
# name,redis的name
# value,要删除的值
# num, num=0,删除清單中所有的指定值;
# num=2,從前到後,删除2個;
# num=-2,從後向前,删除2個
r.lrem("l3", 'Cc')
1
r.lrange("l3", '0', '4')
[b'DD', b'CC', b'BB', b'AA']
lpop(name)
# 在name對應的清單的左側擷取第一個元素并在清單中移除,傳回值則是第一個元素
# 更多:
# rpop(name) 表示從右向左操作
r.lpop("l3")
b'DD'
r.lrange("l3", '0', '2')
[b'CC', b'BB', b'AA']
ltrim(name, start, end)
# 在name對應的清單中移除沒有在start-end索引之間的值
# 參數:
# name,redis的name
# start,索引的起始位置
# end,索引結束位置
r.ltrim("l3", "0", "1")
True
r.lrange("l3", '0', '3')
[b'CC', b'BB']
rpoplpush(src, dst)
# 從一個清單取出最右邊的元素,同時将其添加至另一個清單的最左邊
# 參數:
# src,要取資料的清單的name
# dst,要添加資料的清單的name
r.lpush("l4", '11', '22', '33')
r.lpush("l5", '44', '55', '66')
r.rpoplpush("l4", "l5")
b'11'
r.lrange("l4", '0', '3')
[b'33', b'22']
r.lrange("l5", '0', '3')
[b'11', b'66', b'55', b'44']
blpop(keys, timeout)
# 将多個清單排列,按照從左到右去pop對應清單的元素
# 參數:
# keys,redis的name的集合
# timeout,逾時時間,當元素所有清單的元素擷取完之後,阻塞等待清單内有資料的時間(秒), 0 表示永遠阻塞
# 更多:
# r.brpop(keys, timeout),從右向左擷取資料
r.blpop("l5", timeout=1)
(b'l5', b'11')
r.lrange("l5",0,3)
[b'66', b'55', b'44']
lindex(name, index)
#在name對應的清單中根據索引擷取清單元素
>>> r.lrange("l3",0,3)
[b'CC', b'cc', b'BB', b'AA']
>>> r.lindex("l3","0")
b'CC'
自定義增量疊代
# 由于redis類庫中沒有提供對清單元素的增量疊代,如果想要循環name對應的清單的所有元素,那麼就需要:
# 1、擷取name對應的所有清單
# 2、循環清單
# 但是,如果清單非常大,那麼就有可能在第一步時就将程式的内容撐爆,所有有必要自定義一個增量疊代的功能:
def list_iter(name):
"""
自定義redis清單增量疊代
:param name: redis中的name,即:疊代name對應的清單
:return: yield 傳回 清單元素
"""
list_count = r.llen(name)
for index in range(list_count):
yield r.lindex(name, index)
# 使用
for item in list_iter('l3'):
print(item)
Set操作,Set集合就是不允許重複的清單
sadd(name,values)
# name對應的集合中添加元素
scard(name)
# 擷取name對應的集合中元素個數
sdiff(keys, *args)
# 在第一個name對應的集合中且不在其他name對應的集合的元素集合
sdiffstore(dest, keys, *args)
# 擷取第一個name對應的集合中且不在其他name對應的集合,再将其新加入到dest對應的集合中
sinter(keys, *args)
# 擷取多一個name對應集合的并集
sinterstore(dest, keys, *args)
# 擷取多一個name對應集合的并集,再講其加入到dest對應的集合中
sismember(name, value)
# 檢查value是否是name對應的集合的成員
smembers(name)
# 擷取name對應的集合的所有成員
smove(src, dst, value)
# 将某個成員從一個集合中移動到另外一個集合
spop(name)
# 從集合的右側(尾部)移除一個成員,并将其傳回
srandmember(name, numbers)
# 從name對應的集合中随機擷取 numbers 個元素
srem(name, values)
# 在name對應的集合中删除某些值
sunion(keys, *args)
# 擷取多一個name對應的集合的并集
sunionstore(dest,keys, *args)
# 擷取多一個name對應的集合的并集,并将結果儲存到dest對應的集合中
sscan(name, cursor=0, match=None, count=None)
sscan_iter(name, match=None, count=None)
# 同字元串的操作,用于增量疊代分批擷取元素,避免記憶體消耗太大
有序集合,在集合的基礎上,為每元素排序;元素的排序需要根據另外一個值來進行比較,是以,對于有序集合,每一個元素有兩個值,即:值和分數,分數專門用來做排序。
zadd(name, *args, **kwargs)
# 在name對應的有序集合中添加元素
# 如:
# zadd('zz', 'n1', 1, 'n2', 2)
# 或
# zadd('zz', n1=11, n2=22)
zcard(name)
# 擷取name對應的有序集合元素的數量
zcount(name, min, max)
# 擷取name對應的有序集合中分數 在 [min,max] 之間的個數
zincrby(name, value, amount)
# 自增name對應的有序集合的 name 對應的分數
r.zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
# 按照索引範圍擷取name對應的有序集合的元素
# 參數:
# name,redis的name
# start,有序集合索引起始位置(非分數)
# end,有序集合索引結束位置(非分數)
# desc,排序規則,預設按照分數從小到大排序
# withscores,是否擷取元素的分數,預設隻擷取元素的值
# score_cast_func,對分數進行資料轉換的函數
# 更多:
# 從大到小排序
# zrevrange(name, start, end, withscores=False, score_cast_func=float)
# 按照分數範圍擷取name對應的有序集合的元素
# zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
# 從大到小排序
# zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)
zrank(name, value)
# 擷取某個值在 name對應的有序集合中的排行(從 0 開始)
# 更多:
# zrevrank(name, value),從大到小排序
zrangebylex(name, min, max, start=None, num=None)
# 當有序集合的所有成員都具有相同的分值時,有序集合的元素會根據成員的 值 (lexicographical ordering)來進行排序,而這個指令則可以傳回給定的有序集合鍵 key 中, 元素的值介于 min 和 max 之間的成員
# 對集合中的每個成員進行逐個位元組的對比(byte-by-byte compare), 并按照從低到高的順序, 傳回排序後的集合成員。 如果兩個字元串有一部分内容是相同的話, 那麼指令會認為較長的字元串比較短的字元串要大
# 參數:
# name,redis的name
# min,左區間(值)。 + 表示正無限; - 表示負無限; ( 表示開區間; [ 則表示閉區間
# min,右區間(值)
# start,對結果進行分片處理,索引位置
# num,對結果進行分片處理,索引後面的num個元素
# 如:
# ZADD myzset 0 aa 0 ba 0 ca 0 da 0 ea 0 fa 0 ga
# r.zrangebylex('myzset', "-", "[ca") 結果為:['aa', 'ba', 'ca']
# 更多:
# 從大到小排序
# zrevrangebylex(name, max, min, start=None, num=None)
zrem(name, values)
# 删除name對應的有序集合中值是values的成員
# 如:zrem('zz', ['s1', 's2'])
zremrangebyrank(name, min, max)
# 根據排行範圍删除
zremrangebyscore(name, min, max)
# 根據分數範圍删除
zremrangebylex(name, min, max)
# 根據值傳回删除
zscore(name, value)
# 擷取name對應有序集合中 value 對應的分數
zinterstore(dest, keys, aggregate=None)
# 擷取兩個有序集合的交集,如果遇到相同值不同分數,則按照aggregate進行操作
# aggregate的值為: SUM MIN MAX
zunionstore(dest, keys, aggregate=None)
# 擷取兩個有序集合的并集,如果遇到相同值不同分數,則按照aggregate進行操作
# aggregate的值為: SUM MIN MAX
zscan(name, cursor=0, match=None, count=None, score_cast_func=float)
zscan_iter(name, match=None, count=None,score_cast_func=float)
# 同字元串相似,相較于字元串新增score_cast_func,用來對分數進行操作
其他常用操作
delete(*names)
# 根據删除redis中的任意資料類型
r.set("test","123")
r.get('test')
b'123'
r.delete("test")
1
r.get("test")
None
exists(name)
# 根據删除redis中的任意資料類型
r.set("test","123")
r.get('test')
b'123'
r.delete("test")
1
r.exists("test")
False
keys(pattern='*')
# 根據模型擷取redis的name
# 更多:
# KEYS * 比對資料庫中所有 key 。
# KEYS h?llo 比對 hello , hallo 和 hxllo 等。
# KEYS h*llo 比對 hllo 和 heeeeello 等。
# KEYS h[ae]llo 比對 hello 和 hallo ,但不比對 hillo
r.keys(pattern="*")
[b'l5', b'parter', b'name', b'l3', b'l4', b'foo']
expire(name ,time)
# 為某個redis的某個name設定逾時時間
r.set("t","ok")
r.expire("t",3)
r.get("t") # 隔3秒後取值
None
rename(src, dst)
# 對redis的name重命名為
r.set('rn','123')
r.rename('rn','rename')
r.get('rename')
b'123'
move(name, db))
# 将redis的某個值移動到指定的db下
randomkey()
# 随機擷取一個redis的name(不删除)
type(name)
# 擷取name對應值的類型
r.get("name")
b'jn'
r.type("name")
b'string'
scan(cursor=0, match=None, count=None)
scan_iter(match=None, count=None)
# 同字元串操作,用于增量疊代擷取key
4、管道
redis-py預設在執行每次請求都會建立(連接配接池申請連接配接)和斷開(歸還連接配接池)一次連接配接操作,如果想要在一次請求中指定多個指令,則可以使用pipline實作一次請求指定多個指令,并且預設情況下一次pipline 是原子性操作。
#!/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)
r.set('username', 'koka')
r.set('passwd', '123')
pipe.execute()
5、釋出訂閱
釋出者:伺服器
訂閱者:Dashboad和資料處理
Demo如下:
RedisHelper
訂閱者:
# !/usr/bin/env python
# -*- coding:utf-8 -*-
from monitor.RedisHelper import RedisHelper
obj = RedisHelper()
redis_sub = obj.subscribe()
while True:
msg= redis_sub.parse_response()
print(msg)
釋出者:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from monitor.RedisHelper import RedisHelper
obj = RedisHelper()
obj.public('hello')
sentinel
redis中的sentinel主要用于在redis主從複制中,如果master故障,則自動将slave替換成master
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from redis.sentinel import Sentinel
# 連接配接哨兵伺服器(主機名也可以用域名)
sentinel = Sentinel([('10.211.55.20', 26379),
('10.211.55.20', 26380),
],
socket_timeout=0.5)
# # 擷取主伺服器位址
# master = sentinel.discover_master('mymaster')
# print(master)
#
# # # 擷取從伺服器位址
# slave = sentinel.discover_slaves('mymaster')
# print(slave)
#
#
# # # 擷取主伺服器進行寫入
# master = sentinel.master_for('mymaster')
# master.set('foo', 'bar')
# # # # 擷取從伺服器進行讀取(預設是round-roubin)
# slave = sentinel.slave_for('mymaster', password='redis_auth_pass')
# r_ret = slave.get('foo')
# print(r_ret)
更多參見:
https://github.com/andymccurdy/redis-py/
http://doc.redisfans.com/