1、基礎知識java
1.全局命令python
bigjun@myubuntu:/$ redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> set java jedis OK 127.0.0.1:6379> set python redis-py OK 127.0.0.1:6379> keys * 1) "hello" 2) "python" 3) "java" 4) "myname" 127.0.0.1:6379> dbsize (integer) 4 127.0.0.1:6379> exists java (integer) 1 127.0.0.1:6379> exists not_exist_key (integer) 0 127.0.0.1:6379> del java (integer) 1 127.0.0.1:6379> exists java (integer) 0 127.0.0.1:6379> keys * 1) "hello" 2) "python" 3) "myname" 127.0.0.1:6379> del python myname (integer) 2 127.0.0.1:6379> keys * 1) "hello" 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> expire hello 10 (integer) 1 127.0.0.1:6379> ttl hello (integer) 5 127.0.0.1:6379> ttl hello (integer) 3 127.0.0.1:6379> ttl hello (integer) 2 127.0.0.1:6379> ttl hello (integer) 1 127.0.0.1:6379> ttl hello (integer) 0 127.0.0.1:6379> ttl hello (integer) -2 127.0.0.1:6379> ttl hello (integer) -2 127.0.0.1:6379> get hello (nil) 127.0.0.1:6379> del hello (integer) 0 127.0.0.1:6379> keys * (empty list or set) 127.0.0.1:6379> set a b OK 127.0.0.1:6379> type a string 127.0.0.1:6379> rpush mylist a b c d e f g (integer) 7 127.0.0.1:6379> type mylist list 127.0.0.1:6379> type not_exsit_key none 127.0.0.1:6379> shutdown nosave not connected>
2.數據結構和內部編碼mysql
使用type key命令能夠返回當前鍵的數據結構類型,分別包括:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。redis
實際上Redis每種數據結構都有本身底層的內部編碼實現,並且是多種實現,這樣Redis會在合適的場景選擇合適的內部編碼。算法
能夠經過object encoding命令查詢內部編碼:sql
127.0.0.1:6379> keys * 1) "a" 127.0.0.1:6379> rpush mylist a b c (integer) 3 127.0.0.1:6379> object encoding a "embstr" 127.0.0.1:6379> object encoding mylist "quicklist"
Redis這樣設計有兩個好處:數據庫
3.單線程架構編程
Redis使用單線程架構和I/O多路複用模型來實現高性能的內存數據庫服務。ubuntu
Redis客戶端調用都經歷了發送命令、執行命令、返回結果三個過程。後端
由於Redis是單線程來處理命令的,因此一條命令從客戶端到服務器端不會馬上被執行,全部命令都會進入一個隊列中,而後逐個被執行,能夠肯定不會有兩條命令被同時執行,不會產生併發問題,這就是Redis單線程的基本模型。
爲何Redis使用單線程模型會達到每秒萬級別的處理能力呢:
單線程能帶來幾個好處:
單線程會有一個問題:
對於每一個命令的執行時間是有要求的,若是某個命令執行過長,會形成其餘命令的阻塞,對於Redis這種高性能的服務來講是致命的,因此Redis是面向快速執行場景的數據庫。
2、字符串
字符串類型是Redis最基礎的數據結構。首先鍵都是字符串類型,值能夠是字符串(簡單的字符串、複雜的字符串(JSON、XML))、數字(整數、浮點數)、甚至是二進制(圖片、音頻、視頻),可是值最大不能超多512MB。
1.命令
(1)經常使用命令
set key [ex seconds] [px milliseconds] [nx|xx]
ex seconds:爲鍵設置秒級過時時間
px milliseconds:爲鍵設置毫秒級過時時間
nx:鍵必須不存在,才能夠設置成功,用於添加
xx:與nx相反,鍵必須存在,才能夠設置成功,用於更新
除了set選項,Redis還提供setex和setnx兩個命令:
setex key seconds value:做用和ex選項同樣
setnx key value:做用和nx選項同樣
先來驗證nx和xx兩個選項:
127.0.0.1:6379> exists hello (integer) 0 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> setnx hello redis (integer) 0 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> set hello jedis xx OK 127.0.0.1:6379> get hello "jedis"
因爲鍵hello已存在,因此setnx失敗,返回結果爲0,而set xx成功,返回結果爲OK。
因爲Redis的單線程命令處理機制,若是有多個客戶端同時執行setnx key value,根據setnx特性只有一個客戶端能設置成功,setnx能夠做爲分佈式鎖的一種實現方案。
127.0.0.1:6379> get hello "jedis" 127.0.0.1:6379> get not_exist_key (nil)
mset key value [key value...]
經過mset命令一次性設置4個鍵值對:
127.0.0.1:6379> mset a 1 b 2 c 3 d 4 OK
127.0.0.1:6379> mget a b c d 1) "1" 2) "2" 3) "3" 4) "4"
若是某些鍵不存在,則它的值爲空:
127.0.0.1:6379> mget a b f d 1) "1" 2) "2" 3) (nil) 4) "4"
執行n次get命令的時間:n次get時間=n次網絡時間+n次命令時間
執行1次mget命令的時間:1次mget時間=1次網絡時間+n次命令時間
批量操做有助於提升業務處理效率,可是每次批量操做所發送的命令數不是無節制的,若是數量過多可能形成Redis阻塞或者網絡阻塞。
incr key 用於對值作自增操做,返回結果分爲三種狀況: 1.值不是整數,返回錯誤。 2.值是整數,返回自增後的結果。 3.鍵不存在,按照值爲0自增,返回結果爲1。
除了incr命令,Redis還提供了decr(自減)、incrby(自增指定數字)、decrby(自減指定數字)、incrbyfloat(自增浮點數)
(integer) 0 127.0.0.1:6379> incr key (integer) 1 127.0.0.1:6379> incr key (integer) 2 127.0.0.1:6379> incr hello (error) ERR value is not an integer or out of range
(2)不經常使用命令
127.0.0.1:6379> get hello "jedis" 127.0.0.1:6379> append hello Java (integer) 9 127.0.0.1:6379> get hello "jedisJava"
127.0.0.1:6379> get hello "jedisJava" 127.0.0.1:6379> strlen hello (integer) 9
127.0.0.1:6379> exists hello (integer) 0 127.0.0.1:6379> getset hello world (nil) 127.0.0.1:6379> getset hello redis "world"
127.0.0.1:6379> set redis pest OK 127.0.0.1:6379> setrange redis 0 b (integer) 4 127.0.0.1:6379> get redis "best"
127.0.0.1:6379> getrange redis 0 1 "be"
2.內部編碼
字符串類型的內部編碼有3種:
Redis會根據當前值的類型和長度決定使用哪一種內部編碼實現。
(1)整數類型:
127.0.0.1:6379> set int 8023 OK 127.0.0.1:6379> object encoding int "int"
(2)短字符串類型:
127.0.0.1:6379> set shortstring "hello world" OK 127.0.0.1:6379> object encoding shortstring "embstr"
(3)場字符串類型:
127.0.0.1:6379> set longstring "I am a string which has greater than 39 byte, you know?" OK 127.0.0.1:6379> object encoding longstring "raw" 127.0.0.1:6379> strlen longstring (integer) 55
3.使用場景
(1)緩存功能
比較典型的緩存使用場景是,Redis做爲緩存層,MySQL做爲存儲層,絕大部分請求的數據都是從Redis中獲取。
因爲Redis具備支撐高併發的特性,因此緩存一般能起到加速讀寫和下降後端壓力的做用。
例如這麼一個場景:用戶想要獲取用戶信息的話,首先須要根據用戶提供的id,先去Redis中尋找用戶信息,若是沒有從Redis中獲取到用戶信息,那就須要從MySQL中進行獲取,並將結果回寫到Redis,添加1小時過時時間,若是這一個小時之內用戶再次想獲取信息的話,就直接從Redis中去獲取到信息而不用再返回到MySQL中了。
UserInfo getUserInfo(long id){
// 根據用戶提供的id,定義Redis中鍵key的值 userRedisKey = "user:info:" + id
// 根據鍵key的值,從Redis中獲取到對應的value值 value = redis.get(userRedisKey);
// 聲明UserInfo類的對象變量 UserInfo userInfo;
// 若是從Redis中獲取到了value值 if (value != null) {
// 將Redis中存的value值反序列化爲Java對象userInfo userInfo = deserialize(value); } else {
// 若是沒有從Redis中獲取到值,那麼就從MySQL中去尋找 userInfo = mysql.get(id);
// 若是從MySQL中獲取到了值 if (userInfo != null)
// 將從MySQL中獲取到的值序列化並回寫到Redis中並設置過時時間1小時 redis.setex(userRedisKey, 3600, serialize(userInfo)); } return userInfo; }
(2)計數
許多應用都會使用Redis做爲計數的基礎工具,它能夠實現快速計數、查詢緩存的功能,同時數據能夠異步落地到其餘數據源。視頻播放數系統就是使用Redis做爲視頻播放數計數的基礎組件,用戶每播放一次視頻,相應的視頻播放數就會自增1:
long incrVideoCounter(long id) { key = "video:playCount:" + id; return redis.incr(key); }
(3)共享Session
一個分佈式Web服務將用戶的Session信息(例如用戶登陸信息)保存在各自服務器中,這樣會形成一個問題,出於負載均衡的考慮,分佈式服務會將用戶的訪問均衡到不一樣服務器上,用戶刷新一次訪問可能會發現須要從新登陸,這個問題是用戶沒法容忍的。
爲了解決這個問題,可使用Redis將用戶的Session進行集中管理,在這種模式下只要保證Redis是高可用和擴展性的,每次用戶更新或者查詢登陸信息都直接從Redis中集中獲取。
(4)限速
不少應用出於安全的考慮,會在每次進行登陸時,讓用戶輸入手機驗證碼,從而肯定是不是用戶本人。可是爲了短信接口不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過5次:
phoneNum = "138xxxxxxxx"; key = "shortMsg:limit:" + phoneNum; // SET key value EX 60 NX isExists = redis.set(key,1,"EX 60","NX"); if(isExists != null || redis.incr(key) <=5){ // 經過 }else{ // 限速 }
上述就是利用Redis實現了限速功能,例如一些網站限制一個IP地址不能在一秒鐘以內訪問超過n次也能夠採用相似的思路。
3、哈希
Redis中,哈希類型是指鍵值自己又是一個鍵值對結構,形如value={{field1,value1},...{fieldN,valueN}}。
1.命令
(1)設置值:hset key field value (設置成功返回1,反之返回0。和setnx命令同樣有hsetnx命令)
127.0.0.1:6379> hset user:1 name tom (integer) 1
(2)獲取值:hget key field
127.0.0.1:6379> hget user:1 name "tom" 127.0.0.1:6379> hget user:2 name (nil) 127.0.0.1:6379> hget user:1 age (nil)
(3)刪除field:hdel key field [field...](返回成功刪除field的個數)
127.0.0.1:6379> hdel user:1 name (integer) 1 127.0.0.1:6379> hget user:1 name (nil) 127.0.0.1:6379> hdel user:1 age (integer) 0
(4)計算field個數:hlen key
127.0.0.1:6379> hset user:1 name LianJiang (integer) 1 127.0.0.1:6379> hset user:1 age 23 (integer) 1 127.0.0.1:6379> hset user:1 sex boy (integer) 1 127.0.0.1:6379> hlen user:1 (integer) 3
(5)批量設置或獲取field-value:hmget key field [field...] hmset key field value [field value ...]
127.0.0.1:6379> hmset user:1 name QiaoJiang age 22 sex boy OK 127.0.0.1:6379> hmget user:1 name age 1) "QiaoJiang" 2) "22"
(6)判斷field是否存在:hexists key field
127.0.0.1:6379> hexists user:1 name (integer) 1 127.0.0.1:6379> hexists user:1 city (integer) 0
(7)獲取全部field: hkey key
127.0.0.1:6379> hkeys user:1 1) "name" 2) "age" 3) "sex"
(8)獲取全部value:hvals key
127.0.0.1:6379> hvals user:1 1) "QiaoJiang" 2) "22" 3) "boy"
(9)獲取全部的field-value:hgetall key
127.0.0.1:6379> hgetall user:1 1) "name" 2) "QiaoJiang" 3) "age" 4) "22" 5) "sex" 6) "boy"
(10)自增指定數字,自增指定浮點數:hincrby key field hincrbyfloat key field
(11)計算value的字符串長度:hstrlen key field
127.0.0.1:6379> hstrlen user:1 name (integer) 9
2.內部編碼
(1)當field個數比較少且沒有大的value時,內部編碼爲ziplist
(2)當有value大於64字節,內部編碼會由ziplist變爲hashtable
(3)當field個數超過512,內部編碼也會有ziplist變爲hashtable
3.使用場景
將關係型數據庫和Redis哈希類型數據庫作對比:
相比於使用字符串序列化緩存用戶信息,哈希類型變得更加直觀,而且在更新操做上會更加便捷。能夠將每一個用戶的id定義爲鍵後綴,多對field-value對應每一個用戶的屬性:
UserInfo getUserInfo(long id){ // 用戶 id 做爲 key 後綴 userRedisKey = "user:info:" + id; // 使用 hgetall 獲取全部用戶信息映射關係 userInfoMap = redis.hgetAll(userRedisKey); UserInfo userInfo; if (userInfoMap != null) { // 將映射關係轉換爲 UserInfo userInfo = transferMapToUserInfo(userInfoMap); } else { // 從 MySQL 中獲取用戶信息 userInfo = mysql.get(id); // 將 userInfo 變爲映射關係使用 hmset 保存到 Redis 中 redis.hmset(userRedisKey, transferUserInfoToMap(userInfo)); // 添加過時時間 redis.expire(userRedisKey, 3600); } return userInfo; }
可是須要注意的是哈希類型和關係型數據庫有兩點不一樣之處:
總結一下三種緩存用戶信息的方法並給出三種方案的實現方法和優缺點分析。
set user:1:name tom set user:1:age 23 set user:1:city beijing
優勢:簡單直觀,每一個屬性都支持更新操做。
缺點:佔用過多的鍵,內存佔用量較大,同時用戶信息內聚性比較差,因此此種方案通常不會在生產環境使用。
set user:1 serialize(userInfo)
優勢:簡化編程,若是合理的使用序列化能夠提升內存的使用效率。
缺點:序列化和反序列化有必定的開銷,同時每次更新屬性都須要把所有數據取出進行反序列化,更新後再序列化到Redis中。
hmset user:1 name tomage 23 city beijing
優勢:簡單直觀,若是使用合理能夠減小內存空間的使用。
缺點:要控制哈希在ziplist和hashtable兩種內部編碼的轉換,hashtable會消耗更多內存。
4、列表
列表(list)類型是用來存儲多個有序的字符串,如圖2-18所示,a、b、c、d、e五個元素從左到右組成了一個有序的列表,列表中的每一個字符串稱爲元素(element),一個列表最多能夠存儲2 32 -1個元素。在Redis中,能夠對列表兩端插入(push)和彈出(pop),還能夠獲取指定範圍的元素列表、獲取指定索引下標的元素等(如圖2-18和圖2-19所示)。列表是一種比較靈活的數據結構,它能夠充當棧和隊列的角色,在實際開發上有不少應用場景。
列表類型有兩個特色:
1.命令
(1)添加 rpush lpush linsert
127.0.0.1:6379> rpush listkey c b a (integer) 3 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a"
127.0.0.1:6379> lpush listkey d e f (integer) 6 127.0.0.1:6379> lrange listkey 0 -1 1) "f" 2) "e" 3) "d" 4) "c" 5) "b" 6) "a"
127.0.0.1:6379> linsert listkey before b java (integer) 7 127.0.0.1:6379> lrange listkey 0 -1 1) "f" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 7) "a"
(2)查找 lrange lindex lien
索引下標有兩個特色:
①索引下標從左到右分別是0到N-1,可是從右到左分別是-1和-N。
②lrange中的end選項包含了自身,若是想落得第2到第4個元素,end的值就要爲3
127.0.0.1:6379> lrange listkey 1 3 1) "e" 2) "d" 3) "c" 127.0.0.1:6379> lrange listkey 0 6 1) "f" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 7) "a"
127.0.0.1:6379> lindex listkey -1 "a"
127.0.0.1:6379> llen listkey (integer) 7
(3)刪除 lpop rpop lrem ltrim
127.0.0.1:6379> lrange listkey 0 -1 1) "f" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 7) "a" 127.0.0.1:6379> lpop listkey "f" 127.0.0.1:6379> lrange listkey 0 -1 1) "e" 2) "d" 3) "c" 4) "java" 5) "b" 6) "a"
127.0.0.1:6379> rpop listkey "a" 127.0.0.1:6379> lrange listkey 0 -1 1) "e" 2) "d" 3) "c" 4) "java" 5) "b"
lrem key count value 從列表中找到等於value的元素進行刪除,根據count的不一樣分爲三種狀況: count>0,從左到右,刪除最多count個元素 count<0, 從右到左,刪除最多count絕對值個元素 count=0,刪除全部 127.0.0.1:6379> lpush listkey a a a a a (integer) 10 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "a" 3) "a" 4) "a" 5) "a" 6) "e" 7) "d" 8) "c" 9) "java" 10) "b" 127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b"
127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 127.0.0.1:6379> ltrim listkey 3 -1 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "b"
(4)修改 lset key index newValue
127.0.0.1:6379> lset listkey 2 python OK 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "python"
(5)阻塞操做(經過timeout定義阻塞時間) blpop key [key ...] timeout brpop key [key ...] timeout
在客戶端1執行timeout=0時,會一直阻塞下去
127.0.0.1:6379> brpop list:test 3 (nil) (3.06s)
127.0.0.1:6379> brpop list:test 0
...
這個時候在客戶端2添加了數據element1
127.0.0.1:6379> rpush list:test element1 (integer) 1
客戶端2添加完以後客戶端1當即有返回:
127.0.0.1:6379> brpop list:test 3 (nil) (3.06s) 127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1" (71.97s)
127.0.0.1:6379> rpush list:test element1 (integer) 1 127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1"
2.內部編碼
(1)當元素個數較少且沒有大元素時,內部編碼爲ziplist
(2)當元素個數超過512個,內部編碼變爲linkedlist
(3)當某個元素超過64字節,內部編碼也會變爲linkedlist
3.使用場景
列表的使用場景不少,能夠記住這些公式:
(1)消息隊列
Redis的lpush+brpop命令組合便可實現阻塞隊列,生產者客戶端使用lrpush從列表左側插入元素,多個消費者客戶端使用brpop命令阻塞式的「搶」列表尾部的元素,多個消費者客戶端保證了消費的負載均衡和高可用性。
(2)文章列表
hmset acticle:1 title xx timestamp 1476536196 content xxxx ... hmset acticle:k title yy timestamp 1476512536 content yyyy
lpush user:1:acticles article:1 article3 ... lpush user:k:acticles article:5 ...
articles = lrange user:1:articles 0 9 for article in {articles} hgetall {article}
使用列表類型保存和獲取文章列表會存在兩個問題。
5、集合
集合(set)類型也是用來保存多個的字符串元素,但和列表類型不同的是,集合中不容許有重複元素,而且集合中的元素是無序的,不能經過索引下標獲取元素。如圖2-22所示,集合user:1:follow包含
着"it"、"music"、"his"、"sports"四個元素,一個集合最多能夠存儲2 ^32 -1個元素。Redis除了支持集合內的增刪改查,同時還支持多個集合取交集、並集、差集,合理地使用好集合類型,能在實際開發中解決不少實際問題。
1.命令
(1)集合內操做
127.0.0.1:6379> exists myset (integer) 0 127.0.0.1:6379> sadd myset a b c (integer) 3 127.0.0.1:6379> smembers myset 1) "a" 2) "b" 3) "c" 127.0.0.1:6379> sadd myset a b (integer) 0
127.0.0.1:6379> sadd myset a b (integer) 0 127.0.0.1:6379> srem myset a b (integer) 2 127.0.0.1:6379> srem myset a (integer) 0 127.0.0.1:6379> smembers myset 1) "c"
127.0.0.1:6379> smembers myset 1) "c" 127.0.0.1:6379> scard myset (integer) 1
127.0.0.1:6379> sismember myset c (integer) 1 127.0.0.1:6379> sismember myset a (integer) 0
127.0.0.1:6379> sadd myset a b d e f g (integer) 6 127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "b" 4) "e" 5) "a" 6) "g" 7) "f" 127.0.0.1:6379> srandmember myset 3 1) "c" 2) "f" 3) "g" 127.0.0.1:6379> srandmember myset 3 1) "a" 2) "c" 3) "g"
127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "b" 4) "e" 5) "a" 6) "g" 7) "f" 127.0.0.1:6379> spop myset 1 1) "d" 127.0.0.1:6379> spop myset 2 1) "f" 2) "e" 127.0.0.1:6379> smembers myset 1) "b" 2) "a" 3) "c" 4) "g"
127.0.0.1:6379> smembers myset 1) "b" 2) "a" 3) "c" 4) "g"
(2)集合間操做
首先定義兩個集合,user:1:follow & user:2:follow
127.0.0.1:6379> sadd user:1:follow it music his sports (integer) 4 127.0.0.1:6379> sadd user:2:follow it news ent sports (integer) 4
127.0.0.1:6379> sinter user:1:follow user:2:follow 1) "it" 2) "sports"
127.0.0.1:6379> sunion user:1:follow user:2:follow 1) "ent" 2) "his" 3) "it" 4) "news" 5) "music" 6) "sports"
127.0.0.1:6379> sdiff user:1:follow user:2:follow 1) "his" 2) "music" 127.0.0.1:6379> sdiff user:2:follow user:1:follow 1) "ent" 2) "news"
127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow (integer) 2 127.0.0.1:6379> type user:1_2:inter set 127.0.0.1:6379> smembers user:1_2:inter 1) "it" 2) "sports"
2.內部編碼
(1)當元素個數較少且都爲整數時,內部編碼爲intset
(2)當元素個數超過512個,內部編碼變爲hashtable
(3)當某個元素不爲整數時,內部編碼也會變爲hashtable
3.使用場景
集合類型的應用場景一般分爲幾下幾種:
其中使用最多的場景是標籤(tag),主要分爲如下功能:
(1)給用戶添加標籤
sadd user:1:tags tag1 tag2 tag5 sadd user:2:tags tag2 tag3 tag5 ... sadd user:k:tags tag1 tag2 tag4 ...
(2)給標籤添加用戶
sadd tag1:users user:1 user:3 sadd tag2:users user:1 user:2 user:3 ... sadd tagk:users user:1 user:2 ...
(3)刪除用戶下的標籤
srem user:1:tags tag1 tag5 ...
(4)刪除標籤下的用戶
srem tag1:users user:1 srem tag5:users user:1 ...
(5)計算用戶共同感興趣的標籤
sinter user:1:tags user:2:tags
6、有序集合
有序集合保留了集合不能有重複成員的特性,但不一樣的是,有序集合中的元素能夠排序。可是它和列表使用索引下標做爲排序依據不一樣的是,它給每一個元素設置一個分數(score)做爲排序的依據。如圖2-24所示,該有序集合包含kris、mike、frank、tim、martin、tom,它們的分數分別是一、9一、200、220、250、251,有序集合提供了獲取指定分數和元素範圍查詢、計算成員排名等功能,合理的利用有序集合,能幫助咱們在實際開發中解決不少問題。(有序集合中的元素不能重複,可是score能夠重複,就和一個班裏的同窗學號不能重複,可是考試成績能夠相同)
列表、集合和有序集合三者的異同點:
1.命令
(1)集合內操做
zadd key [NX|XX] [CH] [INCR] score member [score member ...] nx:member必須不存在,才能夠設置成功,用於添加 xx:member必須存在,才能夠設置成功,用於更新 ch:返回這次操做後,有序集合元素和分數發生變化的個數 incr:對score作增長,至關於zincrby 127.0.0.1:6379> zadd user:ranking 251 tom (integer) 1 127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin (integer) 5
127.0.0.1:6379> zcard user:ranking (integer) 6
127.0.0.1:6379> zscore user:ranking tom "251" 127.0.0.1:6379> zscore user:ranking not_exist_member (nil)
127.0.0.1:6379> zrank user:ranking tom (integer) 5 127.0.0.1:6379> zrevrank user:ranking tom (integer) 0
127.0.0.1:6379> zrem user:ranking mike (integer) 1
127.0.0.1:6379> zscore user:ranking tom "251" 127.0.0.1:6379> zincrby user:ranking 9 tom "260"
127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "kris" 2) "1" 3) "frank" 4) "200" 5) "tim" 6) "220" 7) "martin" 8) "250" 9) "tom" 10) "260" 127.0.0.1:6379> zrevrange user:ranking 0 3 withscores 1) "tom" 2) "260" 3) "martin" 4) "250" 5) "tim" 6) "220" 7) "frank" 8) "200" 127.0.0.1:6379> zrevrange user:ranking 0 3 1) "tom" 2) "martin" 3) "tim" 4) "frank"
127.0.0.1:6379> zrangebyscore user:ranking 200 221 withscores 1) "frank" 2) "200" 3) "tim" 4) "220" 127.0.0.1:6379> zrangebyscore user:ranking 200 220 withscores 1) "frank" 2) "200" 3) "tim" 4) "220" 127.0.0.1:6379> zrangebyscore user:ranking 200 219 withscores 1) "frank" 2) "200" 127.0.0.1:6379> zrevrangebyscore user:ranking 260 200 withscores 1) "tom" 2) "260" 3) "martin" 4) "250" 5) "tim" 6) "220" 7) "frank" 8) "200" 127.0.0.1:6379> zrevrangebyscore user:ranking 260 201 withscores 1) "tom" 2) "260" 3) "martin" 4) "250" 5) "tim" 6) "220"
127.0.0.1:6379> zrevrangebyscore user:ranking 260 -inf withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
7) "frank"
8) "200"
9) "kris"
10) "1"
127.0.0.1:6379> zcount user:ranking 200 220 (integer) 2
127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "kris" 2) "1" 3) "frank" 4) "200" 5) "tim" 6) "220" 7) "martin" 8) "250" 9) "tom" 10) "260" 127.0.0.1:6379> zremrangebyrank user:ranking 0 2 (integer) 3 127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "martin" 2) "250" 3) "tom" 4) "260"
127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "martin" 2) "250" 3) "tom" 4) "260" 127.0.0.1:6379> zremrangebyscore user:ranking 250 +inf (integer) 2 127.0.0.1:6379> zrange user:ranking 0 -1 withscores (empty list or set)
(2)集合間操做
首先定義兩個有序集合:
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom (integer) 6 127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom (integer) 4 127.0.0.1:6379> zrange user:ranking:1 0 -1 withscores 1) "kris" 2) "1" 3) "mike" 4) "91" 5) "frank" 6) "200" 7) "tim" 8) "220" 9) "martin" 10) "250" 11) "tom" 12) "251" 127.0.0.1:6379> zrange user:ranking:2 0 -1 withscores 1) "james" 2) "8" 3) "mike" 4) "77" 5) "martin" 6) "625" 7) "tom" 8) "888"
zinterstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max]
destination :交集計算結果保存到這個鍵
numkeys:須要作交集計算鍵的個數
key [key ...]:須要作交集計算的鍵
weights weight [weight ...]:每一個鍵的權重,在作交集計算時,每一個鍵中的每一個member會將本身分數乘以這個權重,每一個鍵的權重默認是1
aggregate sum|min|max:計算成員交集後,分值能夠按照sum(和)、min(最小值)、max(最大值)作彙總,默認值是sum
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2 (integer) 7 127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores 1) "kris" 2) "1" 3) "james" 4) "8" 5) "mike" 6) "168" 7) "frank" 8) "200" 9) "tim" 10) "220" 11) "martin" 12) "875" 13) "tom" 14) "1139"
2.內部編碼
(1)當元素較少且每一個元素較小時,內部編碼爲ziplist
(2)當元素個數超過128個,內部編碼變爲skiplist
(3)當某個元素大於64字節時,內部編碼變爲hashtable
3.使用場景
有序集合比較典型的使用場景就是排行榜系統。例如視頻網站須要對用戶上傳的視頻作排行榜,榜單的維度多是多個方面的:按照上傳時間、按照播放數量、按照點贊次數、按照收藏次數等。
以按照點贊次數記錄天天上傳視頻的排行榜,主要須要實現如下4個功能:
(1)添加用戶贊數
例如用戶mike上傳了一個視頻,並得到了3個贊,可使用有序集合的zadd和zincrby功能:
zadd user:ranking:2016_03_15 mike 3
若是以後再得到一個贊,可使用zincrby:
zincrby user:ranking:2016_03_15 mike 1
(2)取消用戶贊數
因爲各類緣由(例如用戶註銷、用戶做弊)須要將用戶刪除,此時須要將用戶從榜單中刪除掉,可使用zrem。例如刪除成員tom:
zrem user:ranking:2016_03_15 mike
(3)展現獲贊數最多的十個用戶
zrevrangebyrank user:ranking:2016_03_15 0 9
(4)展現用戶信息及用戶分數
將用戶名做爲鍵後綴,將用戶信息保存在哈希類型中,至於用戶的分數和排名可使用zscore和zrank兩個功能:
hgetall user:info:tom zscore user:ranking:2016_03_15 mike zrank user:ranking:2016_03_15 mike
7、鍵管理
1.單個鍵管理
127.0.0.1:6379> get hello "world" 127.0.0.1:6379> rename hello redis OK 127.0.0.1:6379> get redis "world" 127.0.0.1:6379> get hello (nil) 若是在rename以前,鍵已經存在,那麼它的值將被覆蓋: 127.0.0.1:6379> get redis "world" 127.0.0.1:6379> get java "jedis" 127.0.0.1:6379> rename redis java OK 127.0.0.1:6379> get java "world" 127.0.0.1:6379> gert redis (error) ERR unknown command 'gert' 127.0.0.1:6379> get redis (nil) 爲了防止被強行rename,可使用renamenx來確保只有newKey不存在時才被覆蓋: 127.0.0.1:6379> get java "world" 127.0.0.1:6379> get python "redis-py" 127.0.0.1:6379> renamenx java python (integer) 0 127.0.0.1:6379> get java "world"
127.0.0.1:6379> keys * 1) "python" 2) "java" 127.0.0.1:6379> randomkey "python" 127.0.0.1:6379> randomkey "java" 127.0.0.1:6379> randomkey "python" 127.0.0.1:6379> randomkey "python"
①expire key seconds:鍵在seconds秒後過時
②expireat key timastamp:鍵在秒級時間戳timestamp後過時
③pexpire key milliseconds:鍵在milliseconds毫秒後過時
④pexpireat key milliseconds-timestamp:鍵在毫秒時間戳timestamp後過時
①move key db
②dump key + restore key ttl value
③migrate host port key| destination-db timeout [COPY] [REPLACE] [KEYS key]
2.遍歷鍵
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> mset hello world redis test jedis best hill high OK 127.0.0.1:6379> keys * 1) "redis" 2) "hill" 3) "jedis" 4) "hello" 127.0.0.1:6379> keys [j,r]edis 1) "redis" 2) "jedis" 127.0.0.1:6379> keys h?ll* 1) "hill" 2) "hello"
考慮到Redis的單線程架構,若是Redis包含了大量的鍵,執行keys命令極可能會形成Redis阻塞,因此通常建議不要在生產環境中使用keys命令。在一下三種狀況下,可使用:
①在一個不對外提供服務的Redis從節點上執行,這樣不會阻塞到客戶端的請求,可是會影響到主從複製。
②若是確認鍵值總數確實比較少,能夠執行該命令。
③可使用scan命令漸進式地遍歷全部鍵,能夠有效防止阻塞。
Redis存儲鍵值對實際使用的是hashtable的數據結構:
每次執行scan,只掃描一個字典中的一部分鍵,直到將字典中的全部鍵遍歷完畢:
scan cursor [match pattern] [count number] cursor:必需參數,實際上cursor是一個遊標,第一次遍歷從0開始,每次scan遍歷完都會返回當前遊標的值,直到遊標值爲0,表示遍歷結束。 match pattern:可選參數,它的做用的是作模式的匹配,這點和keys的模式匹配很像。 count number:可選參數,它的做用是代表每次要遍歷的鍵個數,默認值是10,此參數能夠適當增大。 127.0.0.1:6379> keys * 1) "s" 2) "u" 3) "d" 4) "h" 5) "y" 6) "b" 7) "q" 8) "p" 9) "r" 10) "i" 11) "m" 12) "z" 13) "f" 14) "w" 15) "c" 16) "n" 17) "e" 18) "a" 19) "k" 20) "v" 21) "j" 22) "x" 23) "l" 24) "o" 25) "t" 26) "g" 127.0.0.1:6379> scan 0 1) "17" 2) 1) "s" 2) "e" 3) "b" 4) "q" 5) "f" 6) "h" 7) "m" 8) "t" 9) "u" 10) "d" 127.0.0.1:6379> scan 17 1) "7" 2) 1) "a" 2) "v" 3) "j" 4) "p" 5) "r" 6) "l" 7) "o" 8) "y" 9) "z" 10) "x" 127.0.0.1:6379> scan 7 1) "0" 2) 1) "i" 2) "k" 3) "w" 4) "c" 5) "n" 6) "g"
3.數據庫管理
MySql這種關係型數據庫支持在一個實例下有多個數據庫存在的,可是與關係型數據庫用字符來區分不一樣數據庫名不一樣,Redis只是用數字做爲多個數據庫的實現。
Redis默認配置中有16個數據庫,在redis.conf中有databases 16定義了默認有16個數據庫。
當使用redis-cli -h {ip} -p {port}時默認使用的就是0號數據庫,最後一個數據庫是15號數據庫,不一樣數據庫之間的數據沒有任何關聯,甚至能夠存在相同的鍵。
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> select 15 OK 127.0.0.1:6379[15]> get hello (nil)
爲何Redis最好是隻使用0號數據庫,不提倡使用多個數據庫:
①Redis是單線程的,。若是使用多個數據庫,那麼這些數據庫仍熱是使用一個CPU,彼此之間仍是會受到影響的。
②多數據庫的使用方式,會讓調試和運維不一樣業務的數據庫變得困難,假若有一個慢查詢存在,仍然會影響其餘數據庫,這樣會使得別的業務方定位問題很是的困難。
③部分Redis的客戶端根本就不支持這種方式。即便支持,在開發的時候來回切換數字形式的數據庫,很容易弄亂。
若是要使用多個數據庫功能,徹底能夠在一臺機器上部署多個Redis實例,彼此用端口來作區分,由於現代計算機或者服務器一般是有多個CPU的,這樣既保證了業務之間不會受到影響,又合理地使用了CPU資源。
127.0.0.1:6379> flushdb OK 127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> set redis jedis OK 127.0.0.1:6379[1]> flushall OK 127.0.0.1:6379[1]> get redis (nil) 127.0.0.1:6379[1]> select 0 OK 127.0.0.1:6379> get hello (nil)