一、Redis介紹
Redis 是一個高性能的key-value數據格式的內存緩存,NoSQL數據庫。NOSQL:not only sql,泛指非關系型數據庫。關系型數據庫: (mysql, oracle, sql server, sqlite)
1. 數據存放在表中,表之間有關系。 2. 通用的SQL操作語言。 3. 大部分支持事務。
非關系型數據庫[ redis,hadoop,mangoDB]:
1. 沒有數據表的概念,不同的nosql數據庫存放數據位置不同。 2. nosql數據庫沒有通用的操作語言。 3. 基本不支持事務。 redis支持簡單事務
redis是業界主流的key-value nosql 數據庫之一。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。redis是一款基於CS架構的數據庫,所以redis有客戶端,也有服務端。
Redis優點
- 異常快速 : Redis是非常快的,每秒可以執行大約110000設置操作,81000個/每秒的讀取操作。
- 支持豐富的數據類型 : Redis支持最大多數開發人員已經知道如列表,集合,可排序集合,哈希等數據類型。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功執行要么失敗完全不執行。單個操作是原子性的。多個操作也支持事務,即原子性,通過MULTI和EXEC指令包起來。
- Redis是一個多功能實用工具,可以在很多如,消息傳遞隊列中使用(Redis原生支持發布/訂閱)
- 單線程特性,秒殺系統,基於redis是單線程特征,防止出現數據庫“爆破”
redis的典型應用
(一)性能
Redis 中緩存熱點數據,能夠保護數據庫,提高查詢效率。如下圖所示,我們在碰到需要執行耗時特別久,且結果不頻繁變動的SQL,就特別適合將運行結果放入緩存。這樣,后面的請求就去緩存中讀取,使得請求能夠迅速響應。
(二)並發
還是如上圖所示,在大並發的情況下,所有的請求直接訪問數據庫,數據庫會出現連接異常。這個時候,就需要使用redis做一個緩沖操作,讓請求先訪問到redis,而不是直接訪問數據庫。
安裝Redis環境
$sudo apt-get update $sudo apt-get install redis-server
啟動 Redis
$redis-server
查看 redis 是否還在運行
$redis-cli
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> ping PONG
二、Python操作Redis
redis-py 的API的使用可以分類為:
- 連接方式
- 操作
- String 操作
- Hash 操作
- List 操作
- Set 操作
- Sort Set 操作
- 管道
- 發布訂閱
2.1、連接方式
1、操作模式
redis-py提供兩個類Redis和StrictRedis用於實現Redis的命令,StrictRedis用於實現大部分官方的命令,並使用官方的語法和命令,Redis是StrictRedis的子類,用於向后兼容舊版本的redis-py。
import redis r = redis.Redis(host='10.211.55.4', port=6379) r.set('foo', 'Bar') print r.get('foo')
2、連接池
redis-py使用connection pool來管理對一個redis server的所有連接,避免每次建立、釋放連接的開銷。默認,每個Redis實例都會維護一個自己的連接池。可以直接建立一個連接池,然后作為參數Redis,這樣就可以實現多個Redis實例共享一個連接池。
import redis pool = redis.ConnectionPool(host='10.211.55.4', port=6379) r = redis.Redis(connection_pool=pool) r.set('foo', 'Bar') print r.get('foo')
2.2、操作
1. string類型: 字符串類型是 Redis 中最為基礎的數據存儲類型,它在 Redis 中是二進制安全的,也就是byte類型 最大容量是512M。 2. hash類型: hash用於存儲對象,對象的結構為屬性、值,值的類型為string。 key:{ 域:值[這里的值只能是字符串], 域:值, 域:值, 域:值, ... } 3. list類型: 列表的元素類型為string。 key:[ 值1,值2,值3..... ] 4. set類型: 無序集合,元素為string類型,元素唯一不重復,沒有修改操作。 {值1,值4,值3,值5} 5. zset類型: 有序集合,元素為string類型,元素唯一不重復,沒有修改操作。
2.2.1、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操作才執行
setnx(name, value)
設置值,只有name不存在時,執行設置操作(添加)
setex(name, value, time)
# 設置值 # 參數: # time,過期時間(數字秒 或 timedelta對象)
psetex(name, time_ms, value)
# 設置值 # 參數: # time_ms,過期時間(數字毫秒 或 timedelta對象)
mset(*args, **kwargs)
批量設置值 如: mset(k1='v1', k2='v2') 或 mget({'k1': 'v1', 'k2': 'v2'})
get(name)
獲取值
mget(keys, *args)
批量獲取 如: mget('ylr', 'wupeiqi') 或 r.mget(['ylr', 'wupeiqi'])
getset(name, value)
設置新值並獲取原來的值
getrange(key, start, end)
# 獲取子序列(根據字節獲取,非字符) # 參數: # name,Redis 的 name # start,起始位置(字節) # end,結束位置(字節)
setrange(name, offset, value)
# 修改字符串內容,從指定字符串索引開始向后替換(新值太長時,則向后添加) # 參數: # offset,字符串的索引,字節(一個漢字三個字節) # value,要設置的值
strlen(name)
# 返回name對應值的字節長度(一個漢字3個字節)
incr(self, name, amount=1)
# 自增 name對應的值,當name不存在時,則創建name=amount,否則,則自增。 # 參數: # name,Redis的name # amount,自增數(必須是整數) # 注:同incrby
incrbyfloat(self, name, amount=1.0)
# 自增 name對應的值,當name不存在時,則創建name=amount,否則,則自增。 # 參數: # name,Redis的name # amount,自增數(浮點型)
decr(self, name, amount=1)
# 自減 name對應的值,當name不存在時,則創建name=amount,否則,則自減。 # 參數: # name,Redis的name # amount,自減數(整數)
append(key, value)
# 在redis name對應的值后面追加內容 # 參數: key, redis的name value, 要追加的字符串
2.2.2、Hash 操作
hash表現形式上有些像pyhton中的dict,可以存儲一組關聯性較強的數據 , 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時則創建(相當於添加)
hmset(name, mapping)
# 在name對應的hash中批量設置鍵值對 # 參數: # name,redis的name # mapping,字典,如:{'k1':'v1', 'k2': 'v2'} # 如: # r.hmset('xx', {'k1':'v1', 'k2': 'v2'})
hget(name,key)
# 在name對應的hash中獲取根據key獲取value
hmget(name, keys, *args)
# 在name對應的hash中獲取多個key的值 # 參數: # name,reids對應的name # keys,要獲取key集合,如:['k1', 'k2', 'k3'] # *args,要獲取的key,如:k1,k2,k3 # 如: # r.mget('xx', ['k1', 'k2']) # 或 # print r.hmget('xx', 'k1', 'k2')
hgetall(name)
# 獲取name對應hash的所有鍵值
hlen(name)
# 獲取name對應的hash中鍵值對的個數
hkeys(name)
# 獲取name對應的hash中所有的key的值
hvals(name)
# 獲取name對應的hash中所有的value的值
hexists(name, key)
# 檢查name對應的hash是否存在當前傳入的key
hdel(name,*keys)
# 將name對應的hash中指定key的鍵值對刪除
hincrby(name, key, amount=1)
# 自增name對應的hash中的指定key的值,不存在則創建key=amount # 參數: # name,redis中的name # key, hash對應的key # amount,自增數(整數)
hincrbyfloat(name, key, amount=1.0)
# 自增name對應的hash中的指定key的值,不存在則創建key=amount # 參數: # name,redis中的name # key, hash對應的key # amount,自增數(浮點數) # 自增name對應的hash中的指定key的值,不存在則創建key=amount
hscan_iter(name, match=None, count=None)
# 利用yield封裝hscan創建生成器,實現分批去redis中獲取數據 # 參數: # match,匹配指定key,默認None 表示所有的key # count,每次分片最少獲取個數,默認None表示采用Redis的默認分片個數 # 如: # for item in r.hscan_iter('xx'): # print item
2.2.3、List 操作
List操作,redis中的List在在內存中按照一個name對應一個List來存儲。如圖:
lpush(name,values)
# 在name對應的list中添加元素,每個新的元素都添加到列表的最左邊 # 如: # r.lpush('oo', 11,22,33) # 保存順序為: 33,22,11 # 擴展: # rpush(name, values) 表示從右向左操作
lpushx(name,value)
# 在name對應的list中添加元素,只有name已經存在時,值添加到列表的最左邊 # 更多: # rpushx(name, value) 表示從右向左操作
llen(name)
# name對應的list元素的個數
linsert(name, where, refvalue, value))
# 在name對應的列表的某一個值前或后插入一個新值 # 參數: # name,redis的name # where,BEFORE或AFTER # refvalue,標桿值,即:在它前后插入數據 # value,要插入的數據
r.lset(name, index, value)
# 對name對應的list中的某一個索引位置重新賦值 # 參數: # name,redis的name # index,list的索引位置 # value,要設置的值
r.lrem(name, value, num)
# 在name對應的list中刪除指定的值 # 參數: # name,redis的name # value,要刪除的值 # num, num=0,刪除列表中所有的指定值; # num=2,從前到后,刪除2個; # num=-2,從后向前,刪除2個
lpop(name)
# 在name對應的列表的左側獲取第一個元素並在列表中移除,返回值則是第一個元素 # 更多: # rpop(name) 表示從右向左操作
lindex(name, index)
# 在name對應的列表中根據索引獲取列表元素
lrange(name, start, end)
# 在name對應的列表分片獲取數據 # 參數: # name,redis的name # start,索引的起始位置 # end,索引結束位置
ltrim(name, start, end)
# 在name對應的列表中移除沒有在start-end索引之間的值 # 參數: # name,redis的name # start,索引的起始位置 # end,索引結束位置
2.2.4、Set 操作
Set操作,Set集合就是不允許重復的列表
sadd(name,values)
# name對應的集合中添加元素
scard(name)
# 獲取name對應的集合中元素個數
sdiff(keys, *args)
# 在第一個name對應的集合中且不在其他name對應的集合的元素集合
sinter(keys, *args)
# 獲取多一個name對應集合的交集
sismember(name, value)
# 檢查value是否是name對應的集合的成員
smembers(name)
# 獲取name對應的集合的所有成員
spop(name)
# 從集合中隨機移除一個成員,並將其返回
srandmember(name, numbers)
# 從name對應的集合中隨機獲取 numbers 個元素
srem(name, values)
# 在name對應的集合中刪除某些值
sunion(keys, *args)
# 獲取多一個name對應的集合的並集
sscan_iter(name, match=None, count=None)
# 同字符串的操作,用於增量迭代分批獲取元素,避免內存消耗太大
2.2.5、Sort Set 操作
有序集合,在集合的基礎上,為每元素排序;元素的排序需要根據另外一個值來進行比較,所以,對於有序集合,每一個元素有兩個值,即:值和分數,分數專門用來做排序。
zadd(name, *args, **kwargs)
# 在name對應的有序集合中添加元素 # 如:print(r.zadd("zz",{"n1":1,"n2":2,"n3":3,"n4":4}))
# 查看:print(r.zscan("zz"))
zcard(name)
# 獲取name對應的有序集合元素的數量
zcount(name, min, max)
# 獲取name對應的有序集合中分數 在 [min,max] 之間的個數
zincrby(name, value, amount)
# 自增name對應的有序集合的 name 對應的分數
zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
# 按照索引范圍獲取name對應的有序集合的元素 aa=r.zrange("zset_name",0,1,desc=False,withscores=True,score_cast_func=int) print(aa) '''參數: name redis的name start 有序集合索引起始位置 end 有序集合索引結束位置 desc 排序規則,默認按照分數從小到大排序 withscores 是否獲取元素的分數,默認只獲取元素的值 score_cast_func 對分數進行數據轉換的函數'''
zscore(name, value)
#獲取name對應有序集合中 value 對應的分數
zrank(name, value)
# 獲取某個值在 name對應的有序集合中的排行(從 0 開始) # 更多: # zrevrank(name, value),從大到小排序
zrem(name, values)
# 刪除name對應的有序集合中值是values的成員 # 如:zrem('zz', ['s1', 's2'])
zremrangebyrank(name, min, max)
# 根據排行范圍刪除
zremrangebyscore(name, min, max)
# 根據分數范圍刪除
zinterstore(dest, keys, aggregate=None)
# 獲取兩個有序集合的交集,如果遇到相同值不同分數,則按照aggregate進行操作 # aggregate的值為: SUM MIN MAX
zunionstore(dest, keys, aggregate=None)
# 獲取兩個有序集合的並集,如果遇到相同值不同分數,則按照aggregate進行操作 # aggregate的值為: SUM MIN MAX
2.2.6、其他常用操作
delete(*names)
# 根據刪除redis中的任意數據類型
exists(name)
# 檢測redis的name是否存在
keys(pattern='*')
# 根據模型獲取redis的name # 更多: # KEYS * 匹配數據庫中所有 key 。 # KEYS h?llo 匹配 hello , hallo 和 hxllo 等。 # KEYS h*llo 匹配 hllo 和 heeeeello 等。 # KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo
expire(name ,time)
# 為某個redis的某個name設置超時時間
rename(src, dst)
# 對redis的name重命名為
randomkey()
# 隨機獲取一個redis的name(不刪除)
type(name)
# 獲取name對應值的類型
scan_iter(match=None, count=None)
# 同字符串操作,用於增量迭代獲取key
2.2.7、使用場景
針對各種數據類型使用場景如下:
(一)String
這個其實沒啥好說的,最常規的set/get操作,value可以是String也可以是數字。一般做一些復雜的計數功能的緩存,比如減少庫存。
(二)hash
這里value存放的是結構化的對象,比較方便的就是操作其中的某個字段。博主在做單點登錄的時候,就是用這種數據結構存儲用戶信息,以cookieId作為key,設置30分鍾為緩存過期時間,能很好的模擬出類似session的效果。
(三)list
使用List的數據結構,可以做簡單的消息隊列的功能。另外還有一個就是,可以利用lrange命令,做基於redis的分頁功能,性能極佳,用戶體驗好。本人還用一個場景,很合適---取行情信息。就也是個生產者和消費者的場景。LIST可以很好的完成排隊,先進先出的原則。
(四)set
因為set堆放的是一堆不重復值的集合。所以可以做全局去重的功能。
另外,就是利用交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。
(五)sorted set
sorted set多了一個權重參數score,集合中的元素能夠按score進行排列。可以做排行榜應用,取TOP N操作。
2.3、管道
redis-py默認在執行每次請求都會創建(連接池申請連接)和斷開(歸還連接池)一次連接操作,如果想要在一次請求中指定多個命令,則可以使用pipline實現一次請求指定多個命令,並且默認情況下一次pipline 是原子性操作。
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()
2.4、發布訂閱
訂閱者:
import redis r=redis.Redis(host='127.0.0.1') pub=r.pubsub() pub.subscribe("fm104.5") pub.parse_response() while 1: msg = pub.parse_response() print(msg)
發布者:
import redis r=redis.Redis(host='127.0.0.1') r.publish("fm104.5", "Hi,yuan!")
發布訂閱的特性用來做一個簡單的實時聊天系統再適合不過了,當然這樣的東西開發中很少涉及到。再比如在分布式架構中,常常會遇到讀寫分離的場景,在寫入的過程中,就可以使用redis發布訂閱,使得寫入值及時發布到各個讀的程序中,就保證數據的完整一致性。再比如,在一個博客網站中,有100個粉絲訂閱了你,當你發布新文章,就可以推送消息給粉絲們拉。