文章目錄
前言
redis(全稱:Remote Dictionary Server,即遠程字典服務),近年來可以說是火遍了大江南北,它能幹的事兒是真不少。最基本的,做爲系統緩存我們可以使用它;我們還可以使用它維護一個高效的消息隊列。然而Redis能夠實現的功能遠遠不止於此,小小的鍵值對被玩出了無數的花樣(當然這和Redis本身提供的豐富功能也脫不了關係)。
如果你已經迫不及待的想要使用Redis了,那麼,這裏有一些關於Redis你不得不知道的事兒
本篇不着重闡述原理,而是從最基本的API使用上進行較爲詳細的介紹,方便快速代入使用場景
爲什麼要使用Redis
- 最爲重要的一點就是Redis速度夠快,能夠明顯的提高系統性能。
- Redis能夠非常簡單的實現特定的業務場景,比如,排行榜,好友關係等
- 能夠實現簡單的消息隊列
速度
Redis速度快的最主要的原因是它是“完全”基於內存的數據庫(當然也提供了持久化),這使得 它避免了使用寫磁盤的格式對內存數據接口編碼的開銷(磁盤的數據依舊是要讀到內存中才能夠被使用,這裏面存在着很大的開銷),故而直接從內存讀取數據是要比讀磁盤要快非常多。
另外,其他次要一些原因還有:
- Redis是由C語言實現的(語言級別速度的保證(´・ω・`) )
- 其線程模型爲單線程,避免了線程切換的開銷
- 網絡方面使用epoll解決了高併發問題
- …
根據官方說法,Redis最高能夠達到 10w OPS(operation per second)!
高效、豐富的數據結構
除了最基本的鍵值對,Redis提供了豐富的數據結構,包括
- 字符串(String)
- 哈希(Hash Tables)
- 列表(Linked List)
- 集合(Sets)
- 有序集合(Sorted Set)
其他還有一些基於上述結構實現的數據結構
- 位圖 (Bit Map)
- HyperLogLog(使用超小內存(12k)實現唯一值,本質是字符串)
- GEO (地理信息定位)
持久化
雖然內存只要一斷電就嗝屁,但是Redis也是提供持久化功能的,在內存上操作保證速度的同時,也提供了RDB和AOF兩種方式進行持久化,將數據的更新異步的保存到磁盤上,無需擔心數據的丟失了:)
其他
豐富的功能
- Redis支持多種編程語言
- 提供發佈訂閱,事務功能
- 支持編寫Lua腳本
- 支持pipline
- 支持主從複製,分佈式(Redis-Cluster),高可用(Redis-Sentinel)
簡單
使用簡約但不簡單!
Redis API
下面通過介紹Redis API來多方面的展現Redis的強大功能
字符串
字符串是redis最基本的結構,很多redis的高級數據結構底層都是由字符串實現的,下面來看看redis字符串的API。
字符串的使用場景很多,最爲基本的是作爲緩存,另外還有計數器(字符串提供的自增),分佈式鎖等等。
- get/del操作 O(1)
# 獲取key對應的value
get key
# 刪除key
del key
- set操作 O(1)
# 設置一個值(無論是否存在)
set key value
# key不存在時才做設置
setnx key value
# key存在時才設置
set key value xx
- mget O(n)
# 批量獲取key值
mget key1 key2 key3
對於redis來說,所有的key都是字符串,value則類型多樣
對於value字符串來說,value可以是字符串,也可以是數字和二進制類型,這兩種類型是在redis內部自動轉換的,之後將要提到的位圖(bitmp)即是使用字符串的二進制形式所實現的。
單個字符串value的大小小於等於512MB,通常一個value限制在100k以內是比較合適的。
對於批量獲取操作mget來說,如果有需要多次取值的操作,不妨使用mget進行批量獲取,相比於多次get更加的高效。
- 整型操作
# key自增1,如果key不存在,自增後get(key)= 1
incr key
# 同上相反 ,自減
decr key
# key自增k,如果key不存在,自增後get(key)= k
incrby key k
# key 自減k,其他同incrby相反
decrby key k
前面說的計數器,就可以用上面的自增API進行實現,非常的方便:)
- 其他一些操作
# set key newvalue並返回舊的value
getset key newvalue
# 將value追加到舊的value
append key value
# 返回字符串長度(注意中文)
strlen key
# 增加key對應的值
incrbyfloat key value
# 獲取字符串指定下標所有的值
getrange key start end
# 設置,其他同上
setrange key index value
Hash
redis的hash是一個mapMap的結構,其結構長這樣
hash的key依舊是字符串,其value分爲兩個部分:field,val,其中field的值不能相同,val值可以相同(如果理解哈希概念的話,這個地方應該不會陌生),你可以把它理解爲編程語言中的哈希表,字典等數據結構。其實redis本身就是一個大字典,hash相當於一個嵌套字典了。
舉個🌰, 我們可以用hash來存儲一個用戶的相關信息,像這樣
下面我們來看看redis hash結構的相關API
# 獲取hash key對應的field的value
hget key field
# 設置hash key對應的field的value
hset key field value
# 刪除hash key對應的field的value
hdel key field
# 判斷hash key是否有field
hexists key field
# 獲取hash key field的數量
hlen key
# 批量獲取hash key的一批field對應的值
hmget key field1 field2 ... fieldN
# 批量設置hash key的一批field value
hmset key field1 value field2 value2...fieldN valueN
# 返回hash key 對應所有的field 和 value(謹慎使用), 數據量大時會阻塞
hgetall key
# 返回hash key對應所有field的value
hvals key
# 返回hash key對應的所有field
hkeys key
# 設置hash key對應的field的value(如field已經存在則失敗)
hsetnx key filed value
# hash key對應的field的value自增inCounter
hincrby key field intCounter
# hincrby浮點數版
hincrbyfloat key field floatCounter
使用hash保存複雜信息
涉及到複雜信息更新時,我們通常可以有限考慮使用redis的hash結構,從上面的用戶信息例子可以看出,hash結構存儲往往更加直觀,並且能夠節省內存(這是由其數據結構決定的),最關鍵的是,它可以部分更新。同樣的實現用戶信息存儲則必須全量更新纔行。
但是需要注意的是,我們可以爲redis數據設置過期時間,而hash結構的過期精度只支持到key級別,不能爲field單獨設置過期時間
list
直觀一點,就是一個列表結構,像這樣
你可以直接把它當成編程語言中的一個列表結構(儘管內部實現可能不一致):有序、內部元素可重複,可以在列表左邊和右邊插入和彈出元素。
看看redis list的API
# 從列表右端插入值(1-N個)
# e.g. rpush listkey c b a: 結果 c—>b—>a
rpush key value1 value2... valueN
# 和rpush相反
lpush
# 從列表左側彈出一個item
lpop key
# 與lpop相反
rpop
# 根據count值,從列表中刪除所有value相等的項
# count > 0,從左到右,刪除最多count個value相等的項
lrem key count value
# 在list指定的值前 | 後插入newValue
linsert key before | after value newValue
# 按照索引範圍修剪列表(start end爲需要保留的範圍)
ltrim key start end
# 獲取列表指定索引範圍所有item(包含end)
lrange key start end
# 獲取列表指定索引的item
lindex key
# 獲取列表長度
llen key
# 設置列表指定索引值爲newValue
lset key index newValue
# lpop阻塞版本,timeout是阻塞超時時間,timeout=0爲永遠不阻塞(對生產者消費者模型,消息隊列實現有幫助)
blpop key timeout
# 與blpop相反
brpop key timeout
通過使用redis的list API,我們可以非常方便的實現一些功能,例如
list的使用場景(棧、隊列、消息隊列…)
- 使用
lpush
+lpop
可以實現一個簡單的棧(後進先出) - 使用
lpush
+rpop
可以實現一個簡單的隊列(先進先出) - 使用
lpush
+ltrim
可以實現一個固定數量的列表 - 使用
lpush
+brpop
可以實現一個消息隊列
set 集合
我們直接類比到編程語言的集合數據結構(比如python的set),它長這樣
集合內部的元素是無序的且不重複的,多個集合直接我們可以進行一些類似並集,交集的操作,話不多說,直接來看看redis提供的set API
=====================集合內操作===========================
# 添加集合元素
sadd key value1 value2
# 刪除集合元素
srem key value1 value2
# 計算集合大小
scard key
# 判斷value是否在集合中(返回1表示存在)
sismember key value
# 從集合中隨機挑count個元素
srandmember key count
# 從集合中隨機彈出一個元素
spop key
# 獲取集合所有元素(小心使用, 可能會阻塞)
smembers key
=====================集合間操作===========================
# 取value交集
sinter key1 key2
# 取所有不同value
sdiff key1 key2
# 取所有value
sunion key1 key2
# 將上述集合間操作結果保存至destkey
sinter|sdiff|sunion + store destkey
set的使用場景(抽獎,點贊,共同關注…)
這是微博上隨便截取的一個抽獎微博截圖,如果讓我們用redis來實現一個抽獎,那麼,使用set就是一個非常好的選擇。
假設我們的有一個key叫users,他對應的value是一個set,裏面存儲了所有用戶的id,那麼,我們就可以使用spop
和srandmember
來很方便的實現抽獎功能。
其他的業務場景比如知乎文章的贊同,反對,我們可以使用集合的API來實現(對於每一篇文章,當用戶點贊時,調用sadd將用戶id推進集合當中)
還有類似於共同關注的功能,我們也可以很方便的使用集合的取交集和並集的操作來完成。
zset(有序集合)
zset同樣也是一個集合,但是與上節的set相比,zset是有序的,redis中的zset結構長這樣:
如果說set的value是一個個遊離的單獨元素的話,那麼zset就是給這些元素打上了一個序號(score)
老規矩,先來看看API
# 添加socre和value(可以是多對)
zadd key score1 value1 score2 value2
# 刪除元素(可以是多個)
zrem key value1 value2
# 返回元素的score
zscore key value
# 增加或減少元素的socre
zincrby key increSocre value
# 返回元素的總個數
zcard key
# 返回value的排名(類似索引id)
zrank key value
# 返回範圍內的 value score ,-1代表最後一個
zrange key start end [WITHSCORES]
# 返回指定分數範圍內的升序元素[分值]
zrangebyscore key minScore maxScore [WITHSCORES]
# 獲取有序集合內在指定分數範圍內的個數
zcount key minScore maxScore
# 刪除指定排名內的升序元素
zremrangebyrank key start end
# 刪除指定分數內的升序元素
zremrangebyscore key minScore maxScore
使用zset實現一個排行榜
這是一個微博的熱搜截圖,在這裏熱搜數據相當於一個zset,圖上的4685796, 4384536,4368888…就是zset的score(搜索量),具體的信息就是一個個value,zset內部會根據socore進行排序, 這裏的排名跟就是根據這個socre來的。你看,通過對zset API的靈活運用,一個基本的排名功能是不是就出來了呢:)
後記
本篇只是對redis中幾種典型的數據結構進行了一番介紹,在下一章中,我們還將進行一些redis高級特性的討論(比如redis的慢查詢,管道pipline,發佈訂閱,位圖,HyperLoglog以及GEO等等),這些特性會給我們在日常開發當中提供許多的便利~