一、Memcached
1、簡介
Memcached 是一個高性能的分布式內存對象緩存系統,一般的使用目的是,通過緩存數據庫查詢結果,減少數據庫訪問次數,以提高動態Web應用的速度、提高可擴展性。用來存儲小塊的任意數據(字符串、對象)。比如:數據庫調用、API調用或者是頁面渲染的結果。
2、本質
它是一個簡潔的key-value存儲系統。其守護進程(daemon )是用C寫的,但是客戶端可以用任何語言來編寫,並通過memcached協議與守護進程通信。
3、特征
①協議簡單
②基於libevent的事件處理
③內置內存存儲方式
④memcached不互相通信的分布式
4、主要操作
① add:添加一條鍵值對,如果已經存在的 key,重復執行add操作異常
1 import memcache 2 3 mc = memcache.Client(["192.168.20.219:12000"], debug=True) 4 mc.add('k1', 'v1')
②replace:修改某個key的值,如果key不存在,則異常
1 import memcache 2 3 mc = memcache.Client(["192.168.20.219:12000"], debug=True) 4 # 如果memcache中存在kkkk,則替換成功,否則異常 5 mc.replace('kkkk', '999') 6 7 #MemCached: while expecting 'STORED', got unexpected response 'NOT_STORED'
③set 和 set_multi
set :設置一個鍵值對,如果key不存在,則創建,如果key存在,則修改
set_multi :設置多個鍵值對,如果key不存在,則創建,如果key存在,則修改
import memcache mc = memcache.Client(["192.168.20.219:12000"], debug=True) mc.set('key0', 'lianzhilei') mc.set_multi({'key1': 'val1', 'key2': 'val2'})
④、delete 和 delete_multi
delete :在Memcached中刪除指定的一個鍵值對
delete_multi :在Memcached中刪除指定的多個鍵值對
import memcache mc = memcache.Client(['192.168.20.219:12000'], debug=True) mc.delete('key0') mc.delete_multi(['key1', 'key2'])
⑤、get 和 get_multi
get :獲取一個鍵值對
get_multi :獲取多一個鍵值對
import memcache mc = memcache.Client(['192.168.20.219:12000'], debug=True) val = mc.get('key0') item_dict = mc.get_multi(["key1", "key2", "key3"])
⑦、decr 和 incr
incr :自增,將Memcached中的某一個值增加 N ( N默認為1 )
decr : 自減,將Memcached中的某一個值減少 N ( N默認為1 )
import memcache mc = memcache.Client(["192.168.20.219:12000"], debug=True) mc.set('k1', '777') mc.incr('k1') # k1 = 778 mc.incr('k1', 10) # k1 = 788 mc.decr('k1') # k1 = 787 mc.decr('k1', 10) # k1 = 777
⑧、gets 和 cas
如商城商品剩余個數,假設改值保存在memcache中,product_count = 900
A用戶刷新頁面從memcache中讀取到product_count = 900
B用戶刷新頁面從memcache中讀取到product_count = 900
如果A、B用戶均購買商品
A用戶修改商品剩余個數 product_count=899
B用戶修改商品剩余個數 product_count=899
如此一來緩存內的數據便不在正確,兩個用戶購買商品后,商品剩余還是 899
如果使用python的set和get來操作以上過程,那么程序就會如上述所示情況!
如果想要避免此情況的發生,只要使用 gets 和 cas 即可,如:
import memcache mc = memcache.Client(['192.168.20.219:12000'], debug=True, cache_cas=True) v = mc.gets('product_count') # ... # 如果有人在gets之后和cas之前修改了product_count,那么,下面的設置將會執行失敗,剖出異常,從而避免非正常數據的產生 mc.cas('product_count', "899")
Ps:本質上每次執行gets時,會從memcache中獲取一個自增的數字,通過cas去修改gets的值時,會攜帶之前獲取的自增值和memcache中的自增值進行比較,如果相等,則可以提交,如果不想等,那表示在gets和cas執行之間,又有其他人執行了gets(獲取了緩沖的指定值), 如此一來有可能出現非正常數據,則不允許修改
二、Redis
1、簡介
redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。
2、特點
①、Redis支持數據的持久化,可以將內存中的數據保存在磁盤中,重啟的時候可以再次加載進行使用。
②、Redis不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。
③、Redis支持數據的備份,即master-slave模式的數據備份。
3、redis-py 的API的使用
1)連接方式
2)連接池
3)操作管道
<1>String 操作
<2>Hash 操作
<3>List 操作
<4>Set 操作
<5>Sort Set 操作
4)管道
5)發布訂閱
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實例都會維護一個自己的連接池,避免每次建立、釋放連接的開銷。可以直接建立一個連接池,然后作為參數Redis,這樣就可以實現多個Redis實例共享一個連接池。連接池的最大數據庫連接數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。
1 import redis 2 3 pool = redis.ConnectionPool(host='10.211.55.4', port=6379) 4 5 r = redis.Redis(connection_pool=pool) 6 r.set('foo', 'Bar') 7 print r.get('foo')
3)操作管道
<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操作才執行 127.0.0.1:6379> set name lzl ex 2 OK 127.0.0.1:6379> get name "lzl" 127.0.0.1:6379> get name (nil) ② setnx(name, value) 設置值,只有name不存在時,執行設置操作(添加) 127.0.0.1:6379> setnx name lianzhilei (integer) 0 127.0.0.1:6379> get name "lzl" 127.0.0.1:6379> setnx name2 lianzhilei (integer) 1 127.0.0.1:6379> get name2 "lianzhilei" ③ setex(name, value, time) # 設置值 # 參數: # time,過期時間(數字秒 或 timedelta對象) 127.0.0.1:6379> setex age 2 18 OK 127.0.0.1:6379> get age "18" 127.0.0.1:6379> get age (nil) ④ psetex(name, time_ms, value) # 設置值 # 參數: # time_ms,過期時間(數字毫秒 或 timedelta對象) ⑤ mset(*args, **kwargs) 批量設置值 如: mset(k1='v1', k2='v2') 或 mget({'k1': 'v1', 'k2': 'v2'}) 127.0.0.1:6379[2]> mset name lzl age 18 OK 127.0.0.1:6379[2]> keys * 1) "age" 2) "name" ⑥ get(name) #獲取值 r.set("name","lzl") print(r.get('name')) # b'lzl' ⑦ mget(keys, *args) 批量獲取 如: mget('name', 'age') 或 r.mget(['name', 'age']) 127.0.0.1:6379[2]> mget name age 1) "lzl" 2) "18" ⑧ getset(name, value) 設置新值並獲取原來的值 127.0.0.1:6379[2]> getset name eric "lzl" 127.0.0.1:6379[2]> get name "eric" ⑨ getrange(key, start, end) # 獲取子序列(根據字節獲取,非字符) # 參數: # name,Redis 的 name # start,起始位置(字節) # end,結束位置(字節) # 如: "連志雷" ,0-3表示 "連" 127.0.0.1:6379[2]> getrange name 0 5 "lianzh" ⑩ setrange(name, offset, value) # 修改字符串內容,從指定字符串索引開始向后替換(新值太長時,則向后添加) # 參數: # offset,字符串的索引,字節(一個漢字三個字節) # value,要設置的值 ⑪ 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" for i in source: num = ord(i) print(bin(num).replace('b','')) 特別的,如果source是漢字 "連志雷"怎么辦? 答:對於utf-8,每一個漢字占 3 個字節,那么 "武沛齊" 則有 9個字節 對於漢字,for循環時候會按照 字節 迭代,那么在迭代時,將每一個字節轉換 十進制數,然后再將十進制數轉換成二進制 11100110 10101101 10100110 11100110 10110010 10011011 11101001 10111101 10010000 127.0.0.1:6379[2]> set n1 foo OK 127.0.0.1:6379[2]> setbit n1 7 1 (integer) 0 127.0.0.1:6379[2]> get n1 "goo" setbit巨流弊的應用場景,想想什么情況下會用到這個功能呢?超大型的應用平台,比如新浪微博,我想查看當前正在登陸的用戶,如何實現?當然你會想到,用戶登陸后在數據庫上的用戶信息上做個標記,然后count去統計做標記的用戶一共有多少,so,當前用戶查看迎刃而解;OK,好好,首先每個用戶登錄都要設置標記,如果當前用戶幾個億,那么得存幾個億的標記位,超級占用庫的開銷;現在就有一個無敵高效的辦法,即統計當前在線用戶,又顯示在線用戶ID,那就是利用二進制位,什么意思呢?看下面的代碼就能明白了 統計在線用戶數 127.0.0.1:6379[2]> setbit lineuser 1000 1 #1000表示用戶id (integer) 0 127.0.0.1:6379[2]> setbit lineuser 55 1 (integer) 0 127.0.0.1:6379[2]> setbit lineuser 6000 1 (integer) 0 127.0.0.1:6379[2]> bitcount lineuser #統計當前二進制位1的個數,即當前在線用戶數 (integer) 3 bitcount統計二級制位中1的個數,setbit和bitcount配合使用,輕松解決當前在線用戶數的問題,這還不是最厲害的,我們還能利用這個統計當前在線的都有誰 查看用戶ID有沒有在線 127.0.0.1:6379[2]> getbit lineuser 1000 #查看id1000 1表示在線 0表示不在線 (integer) 1 127.0.0.1:6379[2]> getbit lineuser 100 (integer) 0 當然我們還可以通過for循環或者yield生成器打印出所有的在線用戶ID,1字節=8位,那么10m=8000萬位,即一個億的在線用戶也就10m多的內存就可搞定,優化就在點滴之間 ⑫ getbit(name, offset) # 獲取name對應的值的二進制表示中的某位的值 (0或1) ⑬ bitcount(key, start=None, end=None) # 獲取name對應的值的二進制表示中 1 的個數 # 參數: # key,Redis的name # start,位起始位置 # end,位結束位置 ⑭ bitop(operation, dest, *keys) # 獲取多個值,並將值做位運算,將最后的結果保存至新的name對應的值 # 參數: # operation,AND(並) 、 OR(或) 、 NOT(非) 、 XOR(異或) # dest, 新的Redis的name # *keys,要查找的Redis的name # 如: bitop("AND", 'new_name', 'n1', 'n2', 'n3') # 獲取Redis中n1,n2,n3對應的值,然后講所有的值做位運算(求並集),然后將結果保存 new_name 對應的值中 ⑮ strlen(name) # 返回name對應值的字節長度(一個漢字3個字節) 127.0.0.1:6379[2]> get name "lianzhilei" 127.0.0.1:6379[2]> strlen name (integer) 10 ⑯ incr(self, name, amount=1) # 自增 name對應的值,當name不存在時,則創建name=amount,否則,則自增。 # 參數: # name,Redis的name # amount,自增數(必須是整數) # 注:同incrby 127.0.0.1:6379[2]> incr login_users #自增 (integer) 1 127.0.0.1:6379[2]> incr login_users (integer) 2 127.0.0.1:6379[2]> incr login_users (integer) 3 ⑰ 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,自減數(整數) 127.0.0.1:6379[2]> decr login_users #自減 (integer) 2 127.0.0.1:6379[2]> decr login_users (integer) 1 127.0.0.1:6379[2]> decr login_users (integer) 0 ⑲ append(key, value) # 在redis name對應的值后面追加內容 # 參數: key, redis的name value, 要追加的字符串 127.0.0.1:6379[2]> append name jjjj (integer) 14 127.0.0.1:6379[2]> get name "lianzhileijjjj"
<2>Hash 操作
在內存中按照一個name對應多個value來存儲。
① hset(name, key, value) # name對應的hash中設置一個鍵值對(不存在,則創建;否則,修改) # 參數: # name,redis的name # key,name對應的hash中的key # value,name對應的hash中的value # 注: # hsetnx(name, key, value),當name對應的hash中不存在當前key時則創建(相當於添加) 127.0.0.1:6379[3]> hset class14 name lzl (integer) 1 127.0.0.1:6379[3]> hset class14 age 18 (integer) 1 127.0.0.1:6379[3]> hset class14 id 10001 (integer) 1 127.0.0.1:6379[3]> hgetall class14 1) "name" 2) "lzl" 3) "age" 4) "18" 5) "id" 6) "10001" 127.0.0.1:6379[3]> hget class14 name "lzl" 127.0.0.1:6379[3]> hkeys class14 1) "name" 2) "age" 3) "id" 127.0.0.1:6379[3]> hvals class14 1) "lzl" 2) "18" 3) "10001" ② hmset(name, mapping) # 在name對應的hash中批量設置鍵值對 # 參數: # name,redis的name # mapping,字典,如:{'k1':'v1', 'k2': 'v2'} # 如: # r.hmset('xx', {'k1':'v1', 'k2': 'v2'}) 127.0.0.1:6379[3]> hmset info k1 1 k2 2 OK 127.0.0.1:6379[3]> hmget info k1 k2 1) "1" 2) "2" ③ 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') 127.0.0.1:6379[3]> hmset info k1 1 k2 2 OK 127.0.0.1:6379[3]> hmget info k1 k2 1) "1" 2) "2" ⑤ hgetall(name) 獲取name對應hash的所有鍵值 ⑥ hlen(name) # 獲取name對應的hash中鍵值對的個數 127.0.0.1:6379[3]> hlen info (integer) 2 ⑦ hkeys(name) # 獲取name對應的hash中所有的key的值 ⑧ hvals(name) # 獲取name對應的hash中所有的value的值 ⑨ hexists(name, key) # 檢查name對應的hash是否存在當前傳入的key 127.0.0.1:6379[3]> hexists info k1 (integer) 1 127.0.0.1:6379[3]> hexists info k3 (integer) 0 ⑩ hdel(name,*keys) # 將name對應的hash中指定key的鍵值對刪除 ⑪ hincrby(name, key, amount=1) # 自增name對應的hash中的指定key的值,不存在則創建key=amount # 參數: # name,redis中的name # key, hash對應的key # amount,自增數(整數) 127.0.0.1:6379[3]> hincrby info k3 1 (integer) 1 127.0.0.1:6379[3]> hincrby info k3 1 (integer) 2 127.0.0.1:6379[3]> hincrby info k3 1 (integer) 3 ⑫ hincrbyfloat(name, key, amount=1.0) # 自增name對應的hash中的指定key的值,不存在則創建key=amount # 參數: # name,redis中的name # key, hash對應的key # amount,自增數(浮點數) # 自增name對應的hash中的指定key的值,不存在則創建key=amount ⑬ 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時,表示數據已經通過分片獲取完畢 127.0.0.1:6379[3]> hscan info 0 match k* 1) "0" 2) 1) "k1" 2) "1" 3) "k2" 4) "2" 5) "k3" 6) "3" ⑭ 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
<3>List 操作
redis中的List在在內存中按照一個name對應一個List來存儲。
① lpush(name,values) # 在name對應的list中添加元素,每個新的元素都添加到列表的最左邊 # 如: # r.lpush('oo', 11,22,33) # 保存順序為: 33,22,11 # 擴展: # rpush(name, values) 表示從右向左操作 127.0.0.1:6379[3]> lpush names lzl alex wupeiqi (integer) 3 127.0.0.1:6379[3]> lrange names 0 -1 1) "wupeiqi" 2) "alex" 3) "lzl" ② lpushx(name,value) # 在name對應的list中添加元素,只有name已經存在時,值添加到列表的最左邊 # 更多: # rpushx(name, value) 表示從右向左操作 ③ llen(name) name對應的list元素的個數 127.0.0.1:6379[3]> llen names (integer) 3 ④ linsert(name, where, refvalue, value)) # 在name對應的列表的某一個值前或后插入一個新值 # 參數: # name,redis的name # where,BEFORE或AFTER # refvalue,標桿值,即:在它前后插入數據 # value,要插入的數據 127.0.0.1:6379[3]> linsert names BEFORE alex befor (integer) 4 127.0.0.1:6379[3]> lrange names 0 -1 1) "wupeiqi" 2) "befor" 3) "alex" 4) "lzl" ⑤ lset(name, index, value) # 對name對應的list中的某一個索引位置重新賦值 # 參數: # name,redis的name # index,list的索引位置 # value,要設置的值 127.0.0.1:6379[3]> lset names 3 LianZhiLei OK 127.0.0.1:6379[3]> lrange names 0 -1 1) "wupeiqi" 2) "befor" 3) "alex" 4) "LianZhiLei" ⑥ lrem(name, value, num) # 在name對應的list中刪除指定的值 # 參數: # name,redis的name # value,要刪除的值 # num, num=0,刪除列表中所有的指定值; # num=2,從前到后,刪除2個; # num=-2,從后向前,刪除2個 127.0.0.1:6379[3]> lrem names 1 befor (integer) 1 127.0.0.1:6379[3]> lrange names 0 -1 1) "wupeiqi" 2) "alex" 3) "LianZhiLei" ⑦ lpop(name) # 在name對應的列表的左側獲取第一個元素並在列表中移除,返回值則是第一個元素 # 更多: # rpop(name) 表示從右向左操作 127.0.0.1:6379[3]> lpop names "wupeiqi" ⑧ 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,索引結束位置 127.0.0.1:6379[3]> LRANGE names 0 -1 1) "eric" 2) "wupeiqi" 3) "alex" 4) "LianZhiLei" 127.0.0.1:6379[3]> ltrim names 1 2 OK 127.0.0.1:6379[3]> LRANGE names 0 -1 1) "wupeiqi" 2) "alex" ⑪ rpoplpush(src, dst) # 從一個列表取出最右邊的元素,同時將其添加至另一個列表的最左邊 # 參數: # src,要取數據的列表的name # dst,要添加數據的列表的name 127.0.0.1:6379[3]> rpush names2 LZL (integer) 1 127.0.0.1:6379[3]> RPOPLPUSH names names2 "alex" 127.0.0.1:6379[3]> lrange names 0 -1 1) "wupeiqi" 127.0.0.1:6379[3]> lrange names2 0 -1 1) "alex" 2) "LZL" ⑫ blpop(keys, timeout) # 將多個列表排列,按照從左到右去pop對應列表的元素 # 參數: # keys,redis的name的集合 # timeout,超時時間,當元素所有列表的元素獲取完之后,阻塞等待列表內有數據的時間(秒), 0 表示永遠阻塞 # 更多: # r.brpop(keys, timeout),從右向左獲取數據 ⑬ brpoplpush(src, dst, timeout=0) # 從一個列表的右側移除一個元素並將其添加到另一個列表的左側 # 參數: # src,取出並要移除元素的列表對應的name # dst,要插入元素的列表對應的name # timeout,當src對應的列表中沒有數據時,阻塞等待其有數據的超時時間(秒),0 表示永遠阻塞 ⑭ 自定義增量迭代 # 由於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 xrange(list_count): yield r.lindex(name, index) # 使用 for item in list_iter('pp'): print item
<4>Set 操作
Set集合就是不允許重復的列表
① sadd(name,values) # name對應的集合中添加元素 127.0.0.1:6379[3]> sadd names alex alex lzl lzl jack (integer) 3 127.0.0.1:6379[3]> smembers names 1) "jack" 2) "alex" 3) "lzl" ② scard(name) # 獲取name對應的集合中元素個數 127.0.0.1:6379[3]> scard names (integer) 3 ③ sdiff(keys, *args) # 在第一個name對應的集合中且不在其他name對應的集合的元素集合(差級) 127.0.0.1:6379[3]> sadd names2 eric lzl wupeiqi (integer) 3 127.0.0.1:6379[3]> sdiff names names2 1) "alex" 2) "jack" ④ sdiffstore(dest, keys, *args) # 獲取name1集合里有但是name2集合里沒有的元素,然后把元素添加到dest對應的集合中 127.0.0.1:6379[3]> SMEMBERS names 1) "jack" 2) "alex" 3) "lzl" 127.0.0.1:6379[3]> SMEMBERS names2 1) "wupeiqi" 2) "lzl" 3) "eric" 127.0.0.1:6379[3]> SDIFFSTORE dest names names2 (integer) 2 127.0.0.1:6379[3]> SMEMBERS dest 1) "alex" 2) "jack" ⑤ sinter(keys, *args) # 獲取多個name集合中的交集 127.0.0.1:6379[3]> SINTER names names2 1) "lzl" ⑥ sinterstore(dest, keys, *args) # 獲取多個name對應集合的交集,再講其加入到dest對應的集合中 ⑦ sismember(name, value) # 檢查value是否是name對應的集合的成員 127.0.0.1:6379[3]> SISMEMBER names lzl (integer) 1 ⑧ 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) # 同字符串的操作,用於增量迭代分批獲取元素,避免內存消耗太大 127.0.0.1:6379[3]> sscan names 0 match l* 1) "0" 2) 1) "lzl"
<5>Sort Set 操作
在集合的基礎上,為每元素排序;元素的排序需要根據另外一個值來進行比較,所以,對於有序集合,每一個元素有兩個值,即:值和分數,分數專門用來做排序
① zadd(name, *args, **kwargs) # 在name對應的有序集合中添加元素 # 如: # zadd('zz', 'n1', 1, 'n2', 2) # 或 # zadd('zz', n1=11, n2=22) 127.0.0.1:6379[4]> zadd z1 10 lzl 5 alex 8 jack 11 lzl (integer) 3 127.0.0.1:6379[4]> ZRANGE z1 0 -1 1) "alex" 2) "jack" 3) "lzl" #按分值排序並去重 ② zcard(name) # 獲取name對應的有序集合元素的數量 ③ zcount(name, min, max) # 獲取name對應的有序集合中分數 在 [min,max] 之間的個數 127.0.0.1:6379[4]> ZCOUNT z1 6 9 (integer) 1 ④ zincrby(name, value, amount) # 自增name對應的有序集合的 name 對應的分數 ⑤ 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) 127.0.0.1:6379[4]> ZRANGE z1 0 -1 withscores 1) "alex" 2) "5" 3) "jack" 4) "8" 5) "lzl" 6) "11" ⑥ zrank(name, value) # 獲取某個值在 name對應的有序集合中的排行(從 0 開始) # 更多: # zrevrank(name, value),從大到小排序 127.0.0.1:6379[4]> ZRANK z1 lzl (integer) 2 #排名第二 ⑦ 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,用來對分數進行操作
<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重命名為 ⑥ move(name, db)) # 將redis的某個值移動到指定的db下 ⑦ randomkey() # 隨機獲取一個redis的name(不刪除) ⑧ type(name) # 獲取name對應值的類型 ⑨ scan(cursor=0, match=None, count=None) scan_iter(match=None, count=None) # 同字符串操作,用於增量迭代獲取key
4)管道
redis-py默認在執行每次請求都會創建(連接池申請連接)和斷開(歸還連接池)一次連接操作,如果想要在一次請求中指定多個命令,則可以使用pipline實現一次請求指定多個命令,並且默認情況下一次pipline 是原子性操作

1 import redis 2 3 pool = redis.ConnectionPool(host='10.211.55.4', port=6379) 4 5 r = redis.Redis(connection_pool=pool) 6 7 # pipe = r.pipeline(transaction=False) 8 pipe = r.pipeline(transaction=True) 9 10 pipe.set('name', 'alex') 11 pipe.set('role', 'sb') 12 13 pipe.execute()
5)發布/訂閱
1 ##redishelper## 2 3 #!/usr/bin/env python 4 # -*- coding:utf-8 -*- 5 #-Author-soloLi 6 7 import redis 8 9 class RedisHelper: 10 11 def __init__(self): 12 self.__conn = redis.Redis(host='192.168.20.219') 13 self.chan_sub = 'fm104.5' 14 self.chan_pub = 'fm104.5' 15 16 def public(self, msg): 17 self.__conn.publish(self.chan_pub, msg) 18 return True 19 20 def subscribe(self): 21 pub = self.__conn.pubsub() #打開收音機 22 pub.subscribe(self.chan_sub) #調頻道 23 pub.parse_response() #准備接受 24 return pub
1 ##訂閱者## 2 3 #!/usr/bin/env python 4 # -*- coding:utf-8 -*- 5 #-Author-soloLi 6 7 from redishelper import RedisHelper 8 9 obj = RedisHelper() 10 redis_sub = obj.subscribe() 11 12 while True: 13 msg = redis_sub.parse_response() 14 print(msg)
1 ##發布者## 2 3 #!/usr/bin/env python 4 # -*- coding:utf-8 -*- 5 #-Author-soloLi 6 7 from redishelper import RedisHelper 8 9 obj = RedisHelper() 10 obj.public('hello')
更多操作:http://doc.redisfans.com/
三、Redis和Memcached的區別
1、數據類型支持不同
Memcached僅支持簡單的key-value結構的數據記錄,Redis擁有更多的數據結構和並支持更豐富的數據操作,
2、內存管理機制不同
在Redis中,並不是所有的數據都一直存儲在內存中的。這是和Memcached相比一個最大的區別。使用簡單的key-value存儲的話,Memcached的內存利用率更高,而如果Redis采用hash結構來做key-value存儲,由於其組合式的壓縮,其內存利用率會高於Memcached。當物理內存用完時,Redis可以將一些很久沒用到的value交換到磁盤。
3、數據持久化支持
Redis支持內存數據的持久化,而memcached是不支持。
4、集群管理的不同
Memcached只能采用客戶端實現分布式存儲,Redis更偏向於在服務器端構建分布式存儲。
5、性能對比
由於Redis只使用單核,而Memcached可以使用多核,所以平均每一個核上Redis在存儲小數據時比Memcached性能更高。而在100k以上的數據中,Memcached性能要高於Redis,雖然Redis最近也在存儲大數據的性能上進行優化,但是比起Memcached,還是稍有遜色。
四、Redis主從架構
Redis主從復制的功能非常強大,它有以下好處:
①避免Redis單點故障
②構建讀寫分離架構,滿足讀多寫少的應用場景
1、主從架構
1.Redis主從架構拓撲圖結構
2.主從結構搭建
Redis集群不用安裝多個Redis,只需復制多個配置文件,修改即可。所以如果要進行主從結構搭建,需先安裝單機版Redis。

進入redis所在目錄 # cd /opt/redis/redis-3.2.8 創建6379、6380、6381目錄,分別將安裝目錄下的redis.conf拷貝到這三個目錄下。 # mkdir -p /opt/redis/6379 && cp redis.conf /opt/redis/6379/6379.conf # mkdir -p /opt/redis/6380 && cp redis.conf /opt/redis/6380/6380.conf # mkdir -p /opt/redis/6381 && cp redis.conf /opt/redis/6381/6381.conf

# vim /opt/redis/6379/6379.conf # Redis使用后台模式 daemonize yes # 關閉保護模式 protected-mode no # 注釋以下內容開啟遠程訪問 # bind 127.0.0.1 # 修改啟動端口為6379 port 6379 # 修改pidfile指向路徑 pidfile /opt/redis/6379/redis_6379.pid 以此類推,修改端口6380及6381配置。

/opt/redis/redis-3.2.8/bin/redis-server /opt/redis/6379/6379.conf /opt/redis/redis-3.2.8/bin/redis-server /opt/redis/6380/6380.conf /opt/redis/redis-3.2.8/bin/redis-server /opt/redis/6381/6381.conf

在Redis中設置主從有2種方式: 1.在redis.conf中設置slaveof a) slaveof <masterip> <masterport> 2、 使用redis-cli客戶端連接到redis服務,執行slaveof命令 a) slaveof <masterip> <masterport> 第二種方式在重啟后將失去主從復制關系。 我們這里使用第二種方式設置主從: 使用Redis客戶端連接上6380端口 # redis-cli -h 192.168.29.128 -p 6380 設置6380端口Redis為6379的從 192.168.29.128:6380> slaveof 192.168.29.128 6379 OK 使用Redis客戶端連接上6381端口 # redis-cli -h 192.168.29.128 -p 6381 設置6381端口Redis為6379的從 192.168.29.128:6381> slaveof 192.168.29.128 6379 OK

使用Redis客戶端連接上6379端口 # redis-cli -h 192.168.29.128 -p 6379 查看Redis主從關系 如下圖所示 192.168.29.128:6379> info replication
role:角色信息
slaveX:從庫信息
connected_slaves:從庫數量
⑥測試
在主庫寫入數據
在從庫讀取數據