Redis
之基礎知識總結
一、支持的基本的數據類型
1.1. 五大數據類型
Redis
有5個基本數據結構,string
、list
、hash
、set
和zset
。它們是日常開發中使用頻率非常高應用最爲廣泛的數據結構,
-
String類型
-
String
是redis
最基本的類型,一個key對應一個value,sring
類型是二進制安全的。redis
的string
可以包含任何數據。Sring
類型是Redis
最基本的數據類型,一個鍵最大能存儲512MB
。 -
Redis
的字符串是動態字符串,是可以修改的字符串,內部結構實現上類似於Java
的ArrayList
,採用預分配冗餘空間的方式來減少內存的頻繁分配。 -
內部爲當前字符串實際分配的空間
capacity
一般要高於實際字符串長度len
。當字符串長度小於1M
時,擴容都是加倍現有的空間,如果超過1M
,擴容時一次只會多擴1M
的空間。 -
針對
String
的一些操作:127.0.0.1:6379> set name zhangsan # 初始化字符串 OK 127.0.0.1:6379> get name # 獲取字符串 "zhangsan" 127.0.0.1:6379> strlen name # 字符串長度 (integer) 8 127.0.0.1:6379> getrange name 2 4 # 獲取子串 key值和開始和結束位置 "ang" 127.0.0.1:6379> setrange name 2 6666 # 覆蓋子串 將會把子串 6666 從2位置開始覆蓋 (integer) 8 127.0.0.1:6379> get name # 覆蓋之後的子串 "zh6666an" 127.0.0.1:6379> append name 123456789 # 追加子串 (integer) 17 127.0.0.1:6379> get name # 追加之後的子串 "zh6666an123456789" 127.0.0.1:6379>
-
將作爲計數器使用
127.0.0.1:6379> set index 10 # 設置 index 值爲 10 OK 127.0.0.1:6379> get index "10" 127.0.0.1:6379> incrby index 1 # 增加一 (integer) 11 127.0.0.1:6379> get index # 增加一後爲11 "11" 127.0.0.1:6379> decrby index 1 # 減去一 (integer) 10 127.0.0.1:6379> get index # 減去一位10 "10" 127.0.0.1:6379> incr index # 加一 (integer) 11 127.0.0.1:6379> decr index # 減一 (integer) 10 127.0.0.1:6379>
-
過期刪除操作
127.0.0.1:6379> expire index 5 # 設置過期時間 (integer) 1 # 1 成功 0 key不存在 127.0.0.1:6379> ttl index # 獲取還有多久過期 (integer) -2 # -2 表示變量不存在, -1 表示設置過期時間 返回正常正數爲還有多少秒過期 127.0.0.1:6379> del name # 刪除 (integer) 1 # 刪除成功返回1 127.0.0.1:6379> get name # 獲取 (nil) # b變量不存在 127.0.0.1:6379> ```
-
-
list類型
-
Redis
將列表數據結構命名爲list
而不是array
,是因爲列表的存儲結構用的是鏈表而不是數組,而且鏈表還是雙向鏈表。因爲它是鏈表,所以隨機定位性能較弱,首尾插入刪除性能較優。如果list
的列表長度很長,使用時我們一定要關注鏈表相關操作的時間複雜度。 -
負下標 鏈表元素的位置使用自然數
0,1,2,....n-1
表示,還可以使用負數-1,-2,...-n
來表示,-1
表示「倒數第一」,-2
表示「倒數第二」,那麼-n
就表示第一個元素,對應的下標爲0
。 -
隊列/堆棧 鏈表可以從表頭和表尾追加和移除元素,結合使用
rpush/rpop/lpush/lpop
四條指令,可以將鏈表作爲隊列或堆棧使用,左向右向進行都可以 -
list的一些操作:
在日常應用中,列表常用來作爲異步隊列來使用
127.0.0.1:6379> rpush code go java # 將值value插入到列表key的表尾,當且僅當key存在並且是一個列表。 (integer) 2 127.0.0.1:6379> rpush code python php c c++ (integer) 6 127.0.0.1:6379> rpop code # 移除並返回列表key的尾元素 "c++" 127.0.0.1:6379> rpop code "c" 127.0.0.1:6379> lpop code # 移除並返回列表key的頭元素 "go" 127.0.0.1:6379> lpop code "java" 127.0.0.1:6379> lrange code 0 -1 # 查看列表中的元素 0 開始位置, -1爲最後一個位置 1) "python" 2) "php" 127.0.0.1:6379> lpush code 12345 # 往表頭插入數據 (integer) 3 127.0.0.1:6379> lrange code 0 -1 # 將12345 插入到了表頭 1) "12345" 2) "python" 3) "php" 127.0.0.1:6379> lpush code java1 java2 # 插入多個值 (integer) 6 127.0.0.1:6379> lrange code 0 -1 1) "java2" 2) "java1" 3) "rust" 4) "12345" 5) "python" 6) "php" 127.0.0.1:6379> llen code # 獲取列表的長度 (integer) 6 127.0.0.1:6379> lindex code 2 # 訪問位置2的數據 "rust" 127.0.0.1:6379> lset code 1 js # 修改位置1的數據 OK 127.0.0.1:6379> lrange code 0 -1 1) "java2" 2) "js" 3) "rust" 4) "12345" 5) "python" 6) "php" 127.0.0.1:6379> linsert code after js js1 # 在js數據之後插入數據 (integer) 7 127.0.0.1:6379> lrange code 0 -1 1) "java2" 2) "js" 3) "js1" 4) "rust" 5) "12345" 6) "python" 7) "php" 127.0.0.1:6379> linsert code before js js2 # 在js數據之前插入數據 (integer) 8 127.0.0.1:6379> lrange code 0 -1 1) "java2" 2) "js2" 3) "js" 4) "js1" 5) "rust" 6) "12345" 7) "python" 8) "php" 127.0.0.1:6379> lrem code 1 js # 元素刪除 (integer) 1 127.0.0.1:6379> lrange code 0 -1 1) "java2" 2) "js2" 3) "js1" 4) "rust" 5) "12345" 6) "python" 7) "php" 127.0.0.1:6379> ltrim code -4 -1 # 截取定長數據, 其他部分將被捨去 OK 127.0.0.1:6379> lrange code 0 -1 1) "rust" 2) "12345" 3) "python" 4) "php" 127.0.0.1:6379>
-
快速列表
如果再深入一點,你會發現
Redis
底層存儲的還不是一個簡單的linkedlist
,而是稱之爲快速鏈表quicklist
的一個結構。首先在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是ziplist
,也即是壓縮列表。它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存。當數據量比較多的時候纔會改成quicklist
。因爲普通的鏈表需要的附加指針空間太大,會比較浪費空間。比如這個列表裏存的只是int
類型的數據,結構上還需要兩個額外的指針prev
和next
。所以Redis
將鏈表和ziplist
結合起來組成了quicklist
。也就是將多個ziplist
使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗餘。
-
-
hash
類型-
哈希等價於
Java
語言的HashMap
或者是Python
語言的dict
,在實現結構上它使用二維結構,第一維是數組,第二維是鏈表,hash
的內容key
和value
存放在鏈表中,數組裏存放的是鏈表的頭指針。通過key
查找元素時,先計算key
的hashcode
,然後用hashcode
對數組的長度進行取模定位到鏈表的表頭,再對鏈表進行遍歷獲取到相應的value
值,鏈表的作用就是用來將產生了「hash
碰撞」的元素串起來。Java
語言開發者會感到非常熟悉,因爲這樣的結構和HashMap
是沒有區別的。哈希的第一維數組的長度也是2^n
。 -
一些常規操作:
127.0.0.1:6379> hset code name zhangsan # 增加一個元素 (integer) 1 127.0.0.1:6379> hmset code lisi threeClass wangwu fourClass # 增加多個元素 OK 127.0.0.1:6379> hget code name # 通過key獲取值 "zhangsan" 127.0.0.1:6379> hgetall code # 獲取所有的值 1) "name" 2) "zhangsan" 3) "lisi" 4) "threeClass" 5) "wangwu" 6) "fourClass" 127.0.0.1:6379> hkeys code # 獲取所有的key 1) "name" 2) "lisi" 3) "wangwu" 127.0.0.1:6379> hvals code # 獲取所有的value 1) "zhangsan" 2) "threeClass" 3) "fourClass" 127.0.0.1:6379> hdel code name # 獲取key 爲name的數據 (integer) 1 127.0.0.1:6379> hgetall code 1) "lisi" 2) "threeClass" 3) "wangwu" 4) "fourClass" 127.0.0.1:6379> hdel code lisi wangwu # 刪除多個 (integer) 2 127.0.0.1:6379> hgetall code (empty list or set) 127.0.0.1:6379> hexists code name # 查看name存在, 存在返回1 不存在返回0 (integer) 1 127.0.0.1:6379> hexists code naaa (integer) 0 127.0.0.1:6379> hset code name lisi # 計數器,使用,不能使字符串 (integer) 1 127.0.0.1:6379> hincrby code name 1 # 加一操作,這樣會報錯 (error) ERR hash value is not an integer 127.0.0.1:6379> hset code name 1 # 將name值設置爲1 (integer) 0 127.0.0.1:6379> hincrby code name 1 # 加一操作之後就成爲 2 (integer) 2 127.0.0.1:6379> hgetall code 1) "name" 2) "2" 127.0.0.1:6379> hdel code name # 刪除元素 (integer) 1 127.0.0.1:6379> hexists code name # 是否存在 存在1 不存在0 (integer) 0 127.0.0.1:6379> ``` 3. **擴容** 當`hash`內部的元素比較擁擠時(`hash`碰撞比較頻繁),就需要進行擴容。擴容需要申請新的兩倍大小的數組,然後將所有的鍵值對重新分配到新的數組下標對應的鏈表中(`rehash`)。如果`hash`結構很大,比如有上百萬個鍵值對,那麼一次完整`rehash`的過程就會耗時很長。這對於單線程的`Redis`裏來說有點壓力山大。所以`Redis`採用了漸進式`rehash`的方案。它會同時保留兩個新舊`hash`結構,在後續的定時任務以及`hash`結構的讀寫指令中將舊結構的元素逐漸遷移到新的結構中。這樣就可以避免因擴容導致的線程卡頓現象。 4. **縮容** `Redis`的`hash`結構不但有擴容還有縮容,從這一點出發,它要比`Java`的`HashMap`要厲害一些,`Java`的`HashMap`只有擴容。縮容的原理和擴容是一致的,只不過新的數組大小要比舊數組小一倍。
-
-
set
類型-
Java程序員都知道
HashSet
的內部實現使用的是HashMap
,只不過所有的value
都指向同一個對象。Redis
的set
結構也是一樣,它的內部也使用hash
結構,所有的value
都指向同一個內部值。 -
一些常用的方法:
127.0.0.1:6379> sadd index name sex age height # 添加元素 (integer) 4 127.0.0.1:6379> smembers index # 列出所有元素 1) "sex" 2) "name" 3) "height" 4) "age" 127.0.0.1:6379> scard index # 統計元素 (integer) 4 127.0.0.1:6379> srandmember index # 隨機獲取某一元素 "age" 127.0.0.1:6379> srem index sex # 刪除元素 (integer) 1 127.0.0.1:6379> spop index # 隨機刪除一個元素 "height" 127.0.0.1:6379> sismember index sex # 檢驗某一元素是否存在,0 不存在 1存在 (integer) 0 127.0.0.1:6379> sismember index name (integer) 1 127.0.0.1:6379>
-
-
sortset
類型-
SortedSet(zset)
是Redis
提供的一個非常特別的數據結構,一方面它等價於Java
的數據結構Map
,可以給每一個元素value
賦予一個權重score
,另一方面它又類似於TreeSet
,內部的元素會按照權重score
進行排序,可以得到每個元素的名次,還可以通過score
的範圍來獲取元素的列表。 -
zset
底層實現使用了兩個數據結構,第一個是hash
,第二個是跳躍列表,hash
的作用就是關聯元素value
和權重score
,保障元素value
的唯一性,可以通過元素value
找到相應的score
值。跳躍列表的目的在於給元素value
排序,根據score
的範圍獲取元素列表。 -
一些常用操作:
127.0.0.1:6379> zadd info 14 age # 增加一元素 (integer) 1 127.0.0.1:6379> zadd info 15 name 17 height # 增加多個元素 (integer) 2 127.0.0.1:6379> zcard info # 獲取元素個數 (integer) 3 127.0.0.1:6379> zrem info name # 刪除元素 (integer) 1 127.0.0.1:6379> zadd info 5 sex (integer) 1 127.0.0.1:6379> zincrby info 1 sex # 加一操作 "6" 127.0.0.1:6379> zincrby info 3 sex # 加三操作 "9" 127.0.0.1:6379> zrange info 0 -1 # 獲取所有元素 1) "lisi" 2) "sex" 3) "age" 4) "height" 127.0.0.1:6379> zrank info sex # 指定元素的正向排名 (integer) 1 127.0.0.1:6379> zrevrank info sex # zrevrank指令獲取指定元素的反向排名[倒數第一名] (integer) 2 127.0.0.1:6379> zscore info sex # 獲取元素的權重 "9" 127.0.0.1:6379> zrange info 0 -1 withscores # 攜帶權重正向輸出 1) "lisi" 2) "7" 3) "sex" 4) "9" 5) "age" 6) "14" 7) "height" 8) "17" 127.0.0.1:6379> zrevrange info 0 -1 withscores # 攜帶權重反向輸出 1) "height" 2) "17" 3) "age" 4) "14" 5) "sex" 6) "9" 7) "lisi" 8) "7" 127.0.0.1:6379> zrangebyscore info -inf +inf withscores # 通過zrangebyscore指令指定score範圍獲取對應的元素列表 -inf 負無窮 +inf 正無窮 1) "lisi" 2) "7" 3) "sex" 4) "9" 5) "age" 6) "14" 7) "height" 8) "17" 127.0.0.1:6379> zrevrangebyscore info +inf -inf withscores # 通過zrevrangebyscore指令獲取倒排元素列表 1) "height" 2) "17" 3) "age" 4) "14" 5) "sex" 6) "9" 7) "lisi" 8) "7" 127.0.0.1:6379>
-
二、簡述機制原理
Redis
使用的是NIO
中的多路IO複用的機制,能夠非常好的支持併發,從而保持線程安全。
Redis
單線程,也就是底層採用一個線程維護多個不同的客戶端io
操作。
但是NIO
在不同的操作系統上實現的方式有所不同,在我們windows
操作系統使用select
實現輪訓時間複雜度是爲o(n),而且還存在空輪訓的情況,效率非常低, 其次是默認對我們輪訓的數據有一定限制,所以支持上萬的tcp
連接是非常難。
在linux
操作系統採用epoll
實現事件驅動回調,不會存在空輪訓的情況,只對活躍的 socket
連接實現主動回調這樣在性能上有大大的提升,所以時間複雜度是爲o(1)
。
nginx
、redis
都能夠非常高支持高併發,最終都是linux
中的IO
多路複用機制epoll
, Redis
底層採用nio epoll
實現。
三、使用場景
- Token令牌的生成
- 短信驗證碼Code
- 緩存查詢數據
- 網頁計數器
- 分佈式鎖
- 延遲操作
四、 SpringBoot
整合
4.1. 依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 需要引入pools,否則會報類無法找到 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
4.2. 配置文件
# Redis服務器地址
spring.redis.host=192.168.252.135
# Redis數據庫索引
spring.redis.database=0
# Redis密碼
spring.redis.password=123456
# 端口
spring.redis.port=6379
# 連接池最大連接數,負值無限制
spring.redis.lettuce.pool.max-active=8
# 連接池最大阻塞等待時間 負值無限制
spring.redis.lettuce.pool.max-wait=-1ms
# 關機超時時間
spring.redis.lettuce.shutdown-timeout=100ms
# 最小空閒連接數
spring.redis.lettuce.pool.min-idle=0
4.3. 使用例子
@RestController
public class RedisController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/getData")
public String load(@RequestParam("name") String name) {
stringRedisTemplate.opsForValue().set("name", name, TimeUnit.SECONDS.toSeconds(60));
return "success";
}
}
4.4. Redis
客戶端區別
-
Jedis
:是Redis
的Java
實現客戶端,提供了比較全面的Redis
命令的支持, -
Redisson
:實現了分佈式和可擴展的Java
數據結構。 -
Lettuce
:高級Redis
客戶端,用於線程安全同步,異步和響應使用,支持集羣,Sentinel
,管道和編碼器。
總體來說:優先使用Lettuce
,如果需要分佈式鎖,分佈式集合等分佈式的高級特性,添加Redisson
結合使用,因爲Redisson
本身對字符串的操作支持很差。
文章第二部分的Redis數據類型參考文章地址:https://juejin.im/post/5b53ee7e5188251aaa2d2e16