Redis 安裝
體驗(yàn) Redis 需要使用 Linux 或者 Mac 環(huán)境巩掺,如果是 Windows 可以考慮使用虛擬機(jī)醇锚。
主要方式有四種:
1、使用 Docker 安裝题山。
2、通過 Github 源碼編譯故痊。
3顶瞳、直接安裝 apt-get install(Ubuntu)、yum install(RedHat) 或者 brew install(Mac)愕秫。
4慨菱、如果讀者懶于安裝操作,也可以使用網(wǎng)頁(yè)版的 Web Redis 直接體驗(yàn)戴甩。
具體操作如下:
Docker 方式
拉取 redis 鏡像
docker pull redis
運(yùn)行 redis 容器
docker run --name myredis -d -p6379:6379 redis
執(zhí)行容器中的 redis-cli符喝,可以直接使用命令行操作 redis
docker exec -it myredis redis-cli
Github 源碼編譯方式
下載源碼
git clone --branch 2.8 --depth 1 git@github.com:antirez/redis.git
cd redis
編譯
make
cd src
運(yùn)行服務(wù)器,daemonize 表示在后臺(tái)運(yùn)行
./redis-server --daemonize yes
運(yùn)行命令行
./redis-cli
直接安裝方式
mac
brew install redis
ubuntu
apt-get install redis
redhat
yum install redis
運(yùn)行客戶端
redis-cli
Redis 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
Redis 有 5 種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)甜孤,分別為:string (字符串)协饲、list (列表)、set (集合)缴川、hash (哈
希) 和 zset (有序集合)茉稠。
string ( 字符串)
字符串 string 是 Redis 最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)。Redis 所有的數(shù)據(jù)結(jié)構(gòu)都是以唯一的 key
字符串作為名稱把夸,然后通過這個(gè)唯一 key 值來獲取相應(yīng)的 value 數(shù)據(jù)而线。不同類型的數(shù)據(jù)結(jié)
構(gòu)的差異就在于 value 的結(jié)構(gòu)不一樣。
字符串結(jié)構(gòu)使用非常廣泛,一個(gè)常見的用途就是緩存用戶信息膀篮。我們將用戶信息結(jié)構(gòu)體
使用 JSON 序列化成字符串嘹狞,然后將序列化后的字符串塞進(jìn) Redis 來緩存。同樣誓竿,取用戶
信息會(huì)經(jīng)過一次反序列化的過程磅网。
Redis 的字符串實(shí)際分配的空間 一般要高于實(shí)際字符串長(zhǎng)度 len。當(dāng)字符串長(zhǎng)度小于 1M 時(shí)烤黍,
擴(kuò)容都是加倍現(xiàn)有的空間知市,如果超過 1M,擴(kuò)容時(shí)一次只會(huì)多擴(kuò) 1M 的空間速蕊。需要注意的是
字符串最大長(zhǎng)度為 512M嫂丙。
鍵值對(duì)
set name codehole
OK
get name
"codehole"
exists name
(integer) 1
del name
(integer) 1
get name
(nil)
批量鍵值對(duì)
可以批量對(duì)多個(gè)字符串進(jìn)行讀寫,節(jié)省網(wǎng)絡(luò)耗時(shí)開銷规哲。
set name1 codehole
OK
set name2 holycoder
OK
mget name1 name2 name3 # 返回一個(gè)列表
- "codehole"
- "holycoder"
- (nil)
mset name1 boy name2 girl name3 unknown
mget name1 name2 name3
- "boy"
- "girl"
- "unknown"
過期和 set 命令擴(kuò)展
可以對(duì) key 設(shè)置過期時(shí)間跟啤,到點(diǎn)自動(dòng)刪除,這個(gè)功能常用來控制緩存的失效時(shí)間唉锌。
set name codehole
get name "codehole"
expire name 5 # 5s 后過期
... # wait for 5s
get name
(nil)
setex name 5 codehole # 5s 后過期隅肥,等價(jià)于 set+expire
get name
"codehole"
... # wait for 5s
get name
(nil)
setnx name codehole # 如果 name 不存在就執(zhí)行 set 創(chuàng)建
(integer) 1
get name
"codehole"
setnx name holycoder
(integer) 0 # 因?yàn)?name 已經(jīng)存在,所以 set 創(chuàng)建不成功
get name
"codehole"# 沒有改變
計(jì)數(shù)
如果 value 值是一個(gè)整數(shù)袄简,還可以對(duì)它進(jìn)行自增操作腥放。自增是有范圍的,它的范圍是
signed long 的最大最小值绿语,超過了這個(gè)值秃症,Redis 會(huì)報(bào)錯(cuò)。
set age 30
OK
incr age
(integer) 31
incrby age 5
(integer) 36
incrby age -5
(integer) 31
set codehole 9223372036854775807# Long.Max
OK
incr codehole
(error) ERR increment or decrement would overflow
list ( 列表)
Redis 的列表相當(dāng)于 Java 語(yǔ)言里面的 LinkedList吕粹,注意它是鏈表而不是數(shù)組种柑。這意味著
list 的插入和刪除操作非常快匹耕,時(shí)間復(fù)雜度為 O(1)聚请,但是索引定位很慢,時(shí)間復(fù)雜度為
O(n)稳其。
當(dāng)列表彈出了最后一個(gè)元素之后驶赏,該數(shù)據(jù)結(jié)構(gòu)自動(dòng)被刪除,內(nèi)存被回收欢际。
Redis 的列表結(jié)構(gòu)常用來做異步隊(duì)列使用母市。將需要延后處理的任務(wù)結(jié)構(gòu)體序列化成字符
串塞進(jìn) Redis 的列表,另一個(gè)線程從這個(gè)列表中輪詢數(shù)據(jù)進(jìn)行處理损趋。
右邊進(jìn)左邊出:隊(duì)列
rpush books python java golang
(integer) 3
llen books
(integer) 3
lpop books
"python"
lpop books
"java"
lpop books
"golang"
lpop books
(nil)
右邊進(jìn)右邊出:棧
rpush books python java golang
(integer) 3
rpop books
"golang"
rpop books
"java"
rpop books
"python"
rpop books
(nil)
慢操作
lindex 相當(dāng)于 Java 鏈表的 get(int index)方法患久,它需要對(duì)鏈表進(jìn)行遍歷椅寺,性能隨著參數(shù)
index 增大而變差。 ltrim 和字面上的含義不太一樣蒋失,個(gè)人覺得它叫 lretain(保留) 更合適一
些返帕,因?yàn)?ltrim 跟的兩個(gè)參數(shù) start_index 和 end_index 定義了一個(gè)區(qū)間,在這個(gè)區(qū)間內(nèi)的值篙挽,
ltrim 要保留荆萤,區(qū)間之外統(tǒng)統(tǒng)砍掉。我們可以通過 ltrim 來實(shí)現(xiàn)一個(gè)定長(zhǎng)的鏈表铣卡,這一點(diǎn)非常
有用链韭。index 可以為負(fù)數(shù),index=-1 表示倒數(shù)第一個(gè)元素煮落,同樣 index=-2 表示倒數(shù)第二個(gè)元
素敞峭。
rpush books python java golang
(integer) 3
lindex books 1 # O(n) 慎用
"java"
lrange books 0 -1 # 獲取所有元素,O(n) 慎用
- "python"
- "java"
- "golang"
ltrim books 1 -1 # O(n) 慎用
OK
lrange books 0 -1
- "java"
- "golang"
ltrim books 1 0 # 這其實(shí)是清空了整個(gè)列表蝉仇,因?yàn)閰^(qū)間范圍長(zhǎng)度為負(fù)
OK
llen books
(integer) 0
hash ( 字典)
Redis 的字典相當(dāng)于 Java 語(yǔ)言里面的 HashMap旋讹,它是無序字典。內(nèi)部實(shí)現(xiàn)結(jié)構(gòu)上同
Java 的 HashMap 也是一致的轿衔,同樣的數(shù)組 + 鏈表二維結(jié)構(gòu)沉迹。
不同的是,Redis 的字典的值只能是字符串害驹,另外它們 rehash 的方式不一樣鞭呕,因?yàn)?br>
Java 的 HashMap 在字典很大時(shí),rehash 是個(gè)耗時(shí)的操作宛官,需要一次性全部 rehash琅拌。Redis
為了高性能,不能堵塞服務(wù)摘刑,所以采用了漸進(jìn)式 rehash 策略。
rehash :簡(jiǎn)單說 rehash 就是因?yàn)?HashMap 的空間不夠用了刻坊,所以需要分配一個(gè)大一點(diǎn)的空間枷恕,然后保存在里面的內(nèi)容需要重新計(jì)算 hash。隨著操作的不斷執(zhí)行谭胚, 哈希表保存的鍵值對(duì)會(huì)逐漸地增多或者減少徐块, 為了讓哈希表的負(fù)載因子(load factor)維持在一個(gè)合理的范圍之內(nèi), 當(dāng)哈希表保存的鍵值對(duì)數(shù)量太多或者太少時(shí)灾而, 程序需要對(duì)哈希表的大小進(jìn)行相應(yīng)的擴(kuò)展或者收縮胡控。
漸進(jìn)式 rehash 會(huì)在 rehash 的同時(shí),保留新舊兩個(gè) hash 結(jié)構(gòu)旁趟,查詢時(shí)會(huì)同時(shí)查詢兩個(gè)
hash 結(jié)構(gòu)昼激,然后在后續(xù)的定時(shí)任務(wù)中以及 hash 的子指令中,循序漸進(jìn)地將舊 hash 的內(nèi)容
一點(diǎn)點(diǎn)遷移到新的 hash 結(jié)構(gòu)中。
當(dāng) hash 移除了最后一個(gè)元素之后橙困,該數(shù)據(jù)結(jié)構(gòu)自動(dòng)被刪除瞧掺,內(nèi)存被回收。
hash 結(jié)構(gòu)也可以用來存儲(chǔ)用戶信息凡傅,不同于字符串一次性需要全部序列化整個(gè)對(duì)象辟狈,
hash 可以對(duì)用戶結(jié)構(gòu)中的每個(gè)字段單獨(dú)存儲(chǔ)。這樣當(dāng)我們需要獲取用戶信息時(shí)可以進(jìn)行部分
獲取夏跷。而以整個(gè)字符串的形式去保存用戶信息的話就只能一次性全部讀取哼转,這樣就會(huì)比較浪
費(fèi)網(wǎng)絡(luò)流量。
hash 也有缺點(diǎn)槽华,hash 結(jié)構(gòu)的存儲(chǔ)消耗要高于單個(gè)字符串壹蔓,到底該使用 hash 還是字符
串,需要根據(jù)實(shí)際情況再三權(quán)衡硼莽。
hset books java "think in java" # 命令行的字符串如果包含空格庶溶,要用引號(hào)括起來
(integer) 1
hset books golang "concurrency in go"
(integer) 1
hset books python "python cookbook"
(integer) 1
hgetall books # entries(),key 和 value 間隔出現(xiàn)
- "java"
- "think in java"
- "golang"
- "concurrency in go"
- "python"
- "python cookbook"
hlen books
(integer) 3
hget books java
"think in java"
hset books golang "learning go programming" # 因?yàn)槭歉虏僮鞫遥苑祷?0
(integer) 0
hget books golang
"learning go programming"
hmset books java "effective java" python "learning python" golang "modern golang
programming" # 批量 set
OK
同字符串一樣偏螺,hash 結(jié)構(gòu)中的單個(gè)子 key 也可以進(jìn)行計(jì)數(shù),它對(duì)應(yīng)的指令是 hincrby匆光,
和 incr 使用基本一樣套像。
hincrby user-laoqian age 1
(integer) 1
set ( 集合)
Redis 的集合相當(dāng)于 Java 語(yǔ)言里面的 HashSet,它內(nèi)部的鍵值對(duì)是無序的唯一的终息。它的
內(nèi)部實(shí)現(xiàn)相當(dāng)于一個(gè)特殊的字典夺巩,字典中所有的 value 都是一個(gè)值 NULL。
當(dāng)集合中最后一個(gè)元素移除之后周崭,數(shù)據(jù)結(jié)構(gòu)自動(dòng)刪除柳譬,內(nèi)存被回收。 set 結(jié)構(gòu)可以用來
存儲(chǔ)活動(dòng)中獎(jiǎng)的用戶 ID续镇,因?yàn)橛腥ブ毓δ苊腊模梢员WC同一個(gè)用戶不會(huì)中獎(jiǎng)兩次。
sadd books python
(integer) 1
sadd books python # 重復(fù)
(integer) 0
sadd books java golang
(integer) 2
smembers books # 注意順序摸航,和插入的并不一致制跟,因?yàn)?set 是無序的
- "java"
- "python"
- "golang"
sismember books java # 查詢某個(gè) value 是否存在,相當(dāng)于 contains(o)
(integer) 1
sismember books rust
(integer) 0
scard books # 獲取長(zhǎng)度相當(dāng)于 count()
(integer) 3
spop books # 彈出一個(gè)
"java"
zset ( 有序列表) )
zset 可能是 Redis 提供的最為特色的數(shù)據(jù)結(jié)構(gòu)酱虎。它類似于 Java 的 SortedSet 和 HashMap 的結(jié)合體雨膨,一方面它是一個(gè) set,保證了內(nèi)部
value 的唯一性读串,另一方面它可以給每個(gè) value 賦予一個(gè) score聊记,代表這個(gè) value 的排序權(quán)
重撒妈。它的內(nèi)部實(shí)現(xiàn)用的是一種叫著「跳躍列表」的數(shù)據(jù)結(jié)構(gòu)。
zset 中最后一個(gè) value 被移除后甥雕,數(shù)據(jù)結(jié)構(gòu)自動(dòng)刪除踩身,內(nèi)存被回收。 zset 可以用來存
粉絲列表社露,value 值是粉絲的用戶 ID挟阻,score 是關(guān)注時(shí)間。我們可以對(duì)粉絲列表按關(guān)注時(shí)間
進(jìn)行排序峭弟。
zset 還可以用來存儲(chǔ)學(xué)生的成績(jī)附鸽,value 值是學(xué)生的 ID,score 是他的考試成績(jī)瞒瘸。我們
可以對(duì)成績(jī)按分?jǐn)?shù)進(jìn)行排序就可以得到他的名次坷备。
zadd books 9.0 "think in java"
(integer) 1
zadd books 8.9 "java concurrency"
(integer) 1
zadd books 8.6 "java cookbook"
(integer) 1
zrange books 0 -1 # 按 score 排序列出,參數(shù)區(qū)間為排名范圍
- "java cookbook"
- "java concurrency"
- "think in java"
zrevrange books 0 -1 # 按 score 逆序列出情臭,參數(shù)區(qū)間為排名范圍
- "think in java"
- "java concurrency"
- "java cookbook"
zcard books # 相當(dāng)于 count()
(integer) 3
zscore books "java concurrency" # 獲取指定 value 的 score
"8.9"
zrank books "java concurrency" # 排名
(integer) 1
zrangebyscore books 0 8.91 # 根據(jù)分值區(qū)間遍歷 zset
- "java cookbook"
- "java concurrency"
zrangebyscore books -inf 8.91 withscores # 根據(jù)分值區(qū)間 (-∞, 8.91] 遍歷 zset省撑,同時(shí)返
回分值。inf 代表 infinite俯在,無窮大的意思竟秫。- "java cookbook"
- "8.6"
- "java concurrency"
- "8.9"
zrem books "java concurrency" # 刪除 value
(integer) 1
zrange books 0 -1- "java cookbook"
- "think in java"
容器型數(shù)據(jù)結(jié)構(gòu)的通用規(guī)則
list/set/hash/zset 這四種數(shù)據(jù)結(jié)構(gòu)是容器型數(shù)據(jù)結(jié)構(gòu),它們共享下面兩條通用規(guī)則:
1 跷乐、create if not exists
如果容器不存在肥败,那就創(chuàng)建一個(gè),再進(jìn)行操作愕提。比如 rpush 操作剛開始是沒有列表的馒稍,
Redis 就會(huì)自動(dòng)創(chuàng)建一個(gè),然后再 rpush 進(jìn)去新元素浅侨。
2 纽谒、drop if no elements
如果容器里元素沒有了,那么立即刪除元素如输,釋放內(nèi)存佛舱。這意味著 lpop 操作到最后一
個(gè)元素,列表就消失了挨决。
過期時(shí)間
Redis 所有的數(shù)據(jù)結(jié)構(gòu)都可以設(shè)置過期時(shí)間,時(shí)間到了订歪,Redis 會(huì)自動(dòng)刪除相應(yīng)的對(duì)象脖祈。
需要注意的是過期是以對(duì)象為單位,比如一個(gè) hash 結(jié)構(gòu)的過期是整個(gè) hash 對(duì)象的過期刷晋,
而不是其中的某個(gè)子 key盖高。
還有一個(gè)需要特別注意的地方是如果一個(gè)字符串已經(jīng)設(shè)置了過期時(shí)間慎陵,然后你調(diào)用了
set 方法修改了它,它的過期時(shí)間會(huì)消失喻奥。
127.0.0.1:6379> set codehole yoyo
OK
127.0.0.1:6379> expire codehole 600
(integer) 1
127.0.0.1:6379> ttl codehole
(integer) 597
127.0.0.1:6379> set codehole yoyo
OK
127.0.0.1:6379> ttl codehole
(integer) -1