Redis系列之基礎(chǔ)篇
前言
redis用了一段時(shí)間了,卻只會(huì)簡單的增刪改查.近期決定通過《Redis深度歷險(xiǎn)》這本書系統(tǒng)學(xué)習(xí)下.記下這份筆記以供回顧和網(wǎng)友學(xué)習(xí).
1. Redis簡介
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
——————來自官網(wǎng)
? 翻譯下官網(wǎng)說法: Redis是一個(gè)開源的,基于內(nèi)存的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ),用于做數(shù)據(jù)庫,緩存,消息中間件.它支持多種數(shù)據(jù)結(jié)構(gòu)string,hash,list,set,zset巴拉巴拉巴拉(英語不好勿噴...)
? 經(jīng)驗(yàn)來說Redis一般用來做緩存,分布式鎖呀等等...
1.1 為什么要用Redis?
兩句話可以概括Redis:
是一個(gè)完全開源免費(fèi)的key-value內(nèi)存數(shù)據(jù)庫
通常用于做緩存.
突然發(fā)現(xiàn)Redis和Java中的Map的功能是很相似的.既然這樣的話,我們?yōu)槭裁床挥肕ap呢?
- Redis可以用幾十G內(nèi)存做緩存,Map不行,一般JVM分幾個(gè)G就很大了
- Redis的緩存可以持久化,Map是內(nèi)存對(duì)象,程序一重啟數(shù)據(jù)就沒了
- Redis可以實(shí)現(xiàn)分布式緩存,Map的緩存只是本地的,分布式下不具備緩存一致性
- Redis可以處理每秒百萬級(jí)的并發(fā),是專業(yè)的緩存服務(wù),Map只是普通的Java對(duì)象
- Redis緩存有過期機(jī)制,Map本身無此功能
- Redis有豐富的API,而Map相比下過于單調(diào)
1.2 Redis可以做什么?
Redis的業(yè)務(wù)范圍非常廣泛,來看看Redis可以用在哪些地方
- 記錄帖子的點(diǎn)贊數(shù)、評(píng)論數(shù)和點(diǎn)擊數(shù)(hash)
- 記錄用戶的帖子ID列表(排序),便于快速顯示用戶的帖子列表(zset)
- 記錄帖子的標(biāo)題凉夯、摘要劲够、作者和封面信息,用于列表頁展示(hash)
- 記錄帖子的點(diǎn)贊用戶ID列表,評(píng)論ID列表,用于顯示和去重計(jì)數(shù)(zset)
- 緩存近期熱帖內(nèi)容(帖子內(nèi)容的空間占用較大),減少數(shù)據(jù)庫壓力(hash)
- 記錄帖子的相關(guān)文章ID,根據(jù)內(nèi)容推薦相關(guān)帖子(list)
- 若帖子ID是整數(shù)自增的征绎,可以使用Redis來分配帖子ID(計(jì)數(shù)器)
- 收藏夾和帖子之間的關(guān)系(zset)
- 記錄熱榜帖子ID列表人柿、總熱榜和分類熱榜(zset)
- 緩存用戶行為歷史凫岖,過濾惡意行為(zset哥放、hash)
當(dāng)然甥雕,實(shí)際上可能并沒有這么多犀农,畢竟請(qǐng)求壓力不大的情況下宰掉,很多數(shù)據(jù)都是在數(shù)據(jù)庫中直接查詢的轨奄,只有在請(qǐng)求壓力很大挨务,才會(huì)把數(shù)據(jù)挪到緩存中來操作。
1.3 Redis的歷史(擴(kuò)展)
Redis由意大利人Salvatore Sanfilippo(Antirez)開發(fā)丁侄。
Antirez出生于非英語系國家鸿摇,英語能力長期以來是一個(gè)短板拙吉,他曾經(jīng)專門為自己蹩腳的英語能力寫過一篇博客《英語傷痛15年》筷黔,用自己的成長經(jīng)歷來鼓勵(lì)那些英語不好的技術(shù)開發(fā)者們努力攻克英語難關(guān)佛舱。
Redis的默認(rèn)端口是6379名眉,這個(gè)端口號(hào)并不是隨機(jī)選的凰棉,而是由于手機(jī)鍵盤字母“MERZ”的位置決定的。
“MERZ”在Antirez的朋友圈語言中是“愚蠢”的代名詞掏秩,它由意大利廣告女郎“Alessia Merz”在電視節(jié)目上說了一堆愚蠢的話而被人熟知蒙幻。
2. Redis數(shù)據(jù)結(jié)構(gòu)
本文不會(huì)講解Redis的安裝诈豌,具體安裝方法請(qǐng)百度矫渔。
本文簡述常用命令摧莽,其余請(qǐng)查閱官方文檔油够。
2.1 五種基本數(shù)據(jù)結(jié)構(gòu)
? Redis有五種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),分別為:string(字符串)撕阎、list(列表)虏束、hash(字典)厦章、set(集合)和zset(有序集合)汗侵。這五種數(shù)據(jù)結(jié)構(gòu)的熟練使用群发,是Redis的相關(guān)知識(shí)中最基礎(chǔ)雪猪、最重要的部分起愈,也是Redis面試題中被問到最多的知識(shí)點(diǎn)官觅。
2.1.1 string(字符串)
? string是Redis中最簡單的數(shù)據(jù)結(jié)構(gòu)休涤,它的內(nèi)部表示就是一個(gè)字符數(shù)組功氨。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)使用非常廣泛,常用于緩存用戶信息黎比。Redis的字符串是動(dòng)態(tài)字符串阅虫,是可以修改的字符串颓帝,內(nèi)部結(jié)構(gòu)類似于Java的ArrayList吕座,采用預(yù)分配冗余空間的方式來減少內(nèi)存的頻繁分配。
? 內(nèi)部為當(dāng)前字符串分配的實(shí)際空間capacity一般要高于實(shí)際字符串長度len吴趴。當(dāng)字符串長度小于1MB時(shí)篷帅,擴(kuò)容都是加倍現(xiàn)有的空間。若長度超過1MB箭昵,擴(kuò)容時(shí)一次最多擴(kuò)容1MB空間家制。==字符串最大長度為512MB颤殴。==
【鍵值對(duì)】
相當(dāng)于字典的key和value,支持簡單的增刪改查操作矮瘟。
> set name keben # 添加/修改值
> get name # 獲取值
> exists name # 檢測是否存在
> del name # 刪除值
【批量鍵值對(duì)】
可以對(duì)多個(gè)字符串進(jìn)行批量讀寫劫侧,節(jié)省網(wǎng)絡(luò)耗時(shí)開銷烧栋。
> mset name keben age 6 desc cool # 批量添加/修改值
> mget name age desc # 返回一個(gè)列表
【過期和set命令擴(kuò)展】
可以對(duì)key設(shè)置過期時(shí)間拳球,到時(shí)間會(huì)被自動(dòng)刪除劲弦,常用來控制緩存的時(shí)效時(shí)間。
> expire name 5 # 5s后過期
> setex name 5 keben # 5s后過期醇坝,相當(dāng)于set+expire
> setnx name keben # 若不存在就創(chuàng)建邑跪,存在則不創(chuàng)建
【計(jì)數(shù)】
若value值是一個(gè)整數(shù),還可以對(duì)它進(jìn)行自增操作呼猪。自增是有范圍的画畅,它的范圍在signed long的最大值和最小值之間,超過這個(gè)范圍宋距,Redis會(huì)報(bào)錯(cuò)轴踱。
> incr age # 自增1
> incrby age # 自減1
2.1.2 list(列表)
? Redis的列表相當(dāng)于Java中的LinkedList,==注意它是鏈表而不是數(shù)組==。這意味著list的插入和刪除操作非常快,時(shí)間復(fù)雜度為O(1),但是索引定位很慢,時(shí)間復(fù)雜度為O(n)快压。列表中每個(gè)元素都使用了雙向指針順序拦宣,是為雙向鏈表豆瘫。
? 當(dāng)列表中最后一個(gè)元素彈出后,該數(shù)據(jù)結(jié)構(gòu)會(huì)被自動(dòng)刪除,內(nèi)存被回收。
? Redis的列表結(jié)構(gòu)常用于做異步隊(duì)列使用也切。將需要延后處理的任務(wù)結(jié)構(gòu)體序列化成字符串,塞入Redis的列表附井,另一個(gè)線程從列表中輪詢數(shù)據(jù)進(jìn)行處理。
【右進(jìn)左出:隊(duì)列】
隊(duì)列是先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),常用于消息排隊(duì)和異步邏輯處理,它會(huì)確保元素的訪問順序性。
> rpush books java python kotlin php # books列表批量添加值
> llen books # 查看books大小
> lpop books # 彈出隊(duì)列的第一個(gè)
【右進(jìn)右出:棿姥兀】
棧是先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),跟隊(duì)列正好相反。拿Redis的列表數(shù)據(jù)結(jié)構(gòu)來做棧使用的業(yè)務(wù)場景并不多見搁吓。
> rpop books # 彈出最后一個(gè)入棧的
【慢操作】
lindex相當(dāng)于Java鏈表的get(int index)方法通贞,它需要對(duì)鏈表進(jìn)行遍歷茎用,性能隨著參數(shù)index增大而變差琉预。
ltrim有兩個(gè)參數(shù) start_index,end_index,定義一個(gè)區(qū)間昙楚,截取保留區(qū)間內(nèi)數(shù)據(jù)析砸。
> lindex books 1
> lrange books 0 -1 # 獲取所有元素
> ltrim books 1 0 # 清空了整個(gè)列表,因?yàn)閰^(qū)間范圍長度為負(fù)
? list底層存儲(chǔ)的不是一個(gè)簡單的linkedlist,而是稱為“快速鏈表”(quicklist)的一個(gè)結(jié)構(gòu)斥季。
? 在列表元素少時(shí)映之,會(huì)用一塊連續(xù)的內(nèi)存存儲(chǔ),這個(gè)結(jié)構(gòu)是ziplist僵刮,即壓縮列表。當(dāng)數(shù)據(jù)量較多時(shí)才會(huì)改為quicklist钥屈。因?yàn)槠胀ǖ逆湵硇枰母郊又羔樋臻g太大(prev窟绷、next),會(huì)浪費(fèi)空間姊途,還會(huì)加重內(nèi)存的碎片化涉瘾。Redis將鏈表和ziplist結(jié)合組成quicklist,就是用鏈表將ziplist使用雙向指針串起來使用捷兰。
2.1.3 hash(字典)
? Redis里的字典相當(dāng)于Java中的HashMap立叛,它是無序的,內(nèi)部存儲(chǔ)了很多鍵值對(duì)贡茅,實(shí)現(xiàn)結(jié)構(gòu)上與HashMap也是一樣的秘蛇,都是“數(shù)組+鏈表”二維結(jié)構(gòu)。而存放的位置是根據(jù)key的hashCode來計(jì)算的顶考,若兩個(gè)key計(jì)算出來的在數(shù)組的同一位赁还,則在這個(gè)下標(biāo)下使用鏈表串接起來。
? 但是Redis中的hash只能存字符串驹沿,而且它們r(jià)ehash的方式也不一樣艘策。因?yàn)镠ashMap在字典很大時(shí),rehash是很耗時(shí)的渊季,需要一次性全部rehash朋蔫。Redis為了追求高性能,不能堵塞服務(wù)却汉,采用了漸進(jìn)式rehash策略驯妄。
? 漸進(jìn)式rehash的時(shí)候,不會(huì)一次全部rehash合砂,而是將舊hash里的內(nèi)容慢慢遷移(遷移后舊的里面就沒有了)到新的hash結(jié)構(gòu)中青扔,在徹底遷移完之前,查詢時(shí)會(huì)同時(shí)查詢兩個(gè)hash結(jié)構(gòu)。當(dāng)遷移完成后微猖,就會(huì)用新的hash結(jié)構(gòu)谈息,舊的數(shù)據(jù)結(jié)構(gòu)被自動(dòng)刪除。==注意励两,hash結(jié)構(gòu)的存儲(chǔ)消耗要高于單個(gè)字符串黎茎。==
> hset books java "Head first Java" # 值中若是有空格,要用引號(hào)括起來
> hgetall books # 獲取所有key当悔,value key和value間隔出現(xiàn)
> hlen books # size
> hget books java # get
> hmset books java "Hello java" php "hello pnp" #batch set
> hincrby user age 10 # add 10
2.1.4 set(集合)
? Redis中的set和list都是字符串序列,非常相似踢代,不同于set是用hash來實(shí)現(xiàn)值的唯一性盲憎,無序的,不可重復(fù)的胳挎。它的內(nèi)部實(shí)現(xiàn)相當(dāng)于一個(gè)特殊的字典饼疙,字典中所有的value都是一個(gè)值NULL。
? 當(dāng)集合中的最后一個(gè)元素被移除之后慕爬,數(shù)據(jù)結(jié)構(gòu)被自動(dòng)刪除窑眯,內(nèi)存被回收。
> sadd books java # add
> smembers books # 獲取全部
> sismeber books java # 查詢值是否存在
> scard books # 獲取大小
> spop books # 彈出一個(gè)
2.1.5 zset(有序集合)
? zset是Redis提供的最有特色的數(shù)據(jù)結(jié)構(gòu)医窿,也是面試中最常出現(xiàn)的角色磅甩。它是ziplist和skiplist(跳躍列表)的結(jié)合體,一方面它是一個(gè)set姥卢,保證了值的唯一性卷要,另一方面它可以給每個(gè)值賦予一個(gè)score,代表著這個(gè)值的排序權(quán)重独榴。當(dāng)存儲(chǔ)元素的數(shù)量小于128個(gè)僧叉,所有member的長度都小于64字節(jié),就會(huì)使用ziplist棺榔。
> zadd game 256 "keben" # 添加 游戲計(jì)分 用戶keben 分?jǐn)?shù) 256
> zrange game 0 -1 # 按score排序列出瓶堕,參數(shù)區(qū)間為排名范圍
> zrevrange game 0 -1 # 按score逆序列出,參數(shù)區(qū)間為排名范圍
> zcard game # size
> zscore game "keben" # 獲取 keben 的 分?jǐn)?shù)(內(nèi)部score使用double類型存儲(chǔ)症歇,可能存在小數(shù)點(diǎn)精度問題)
> zrank game "keben" # 獲取 keben 的 排名
> zrangebyscore game 0 100 # 根據(jù)分值區(qū)間遍歷 zset
> zrangebyscore game -inf 256 withscores # 根據(jù)分值區(qū)間(-無窮大郎笆,256]
> zrem game kobe # 刪除
? 跳躍列表的結(jié)構(gòu)非常特殊,也比較復(fù)雜当船。zset要支持隨機(jī)的插入和刪除题画,不宜于使用數(shù)組來表示。而普通鏈表又不適用于查找定位插入點(diǎn)德频。
? 跳躍列表是一種層級(jí)關(guān)系苍息。?
? 隨機(jī)生成層級(jí),把每一層級(jí)的串聯(lián)起來。查找時(shí)先從最上層級(jí)(lv.3)查找竞思。
? 如若新增一個(gè) 21表谊, 會(huì)先從lv.3進(jìn)行對(duì)比,21<24,然后從lv.2(9)至 lv.2(24)中查找.,最后在lv.1(9,16,24)中找到合適位置,進(jìn)行插入,并隨機(jī)生成層數(shù)盖喷。生成的幾率也是越高層幾率越小爆办。
2.2 容器形數(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 exit: 若容器不存在跨算,就會(huì)創(chuàng)建一個(gè)再進(jìn)行操作。
2. drop if no elements: 如果容器里的元素沒有了椭懊,那么立即刪除容器诸蚕,釋放內(nèi)存。
2.3 過期時(shí)間
? Redis所有的數(shù)據(jù)結(jié)構(gòu)都可以設(shè)置過期時(shí)間氧猬,時(shí)間到了背犯,Redis會(huì)自動(dòng)刪除相應(yīng)的對(duì)象。==注意盅抚,過期是以對(duì)象為單位==漠魏,例hash結(jié)構(gòu)的過期是整個(gè)hash對(duì)象的過期,而不是其中的某個(gè)子key的過期泉哈。
? 若一個(gè)字符串已經(jīng)設(shè)置了過期時(shí)間蛉幸,再用set修改它,那么它的過期時(shí)間會(huì)消失丛晦。
> set name keben
> expire name 500 # 設(shè)置過期時(shí)間 (秒)
> ttl name # 查看剩余存活時(shí)間
最后
? 總結(jié)下下~
? 本文講解了Redis的一些基本使用和五種數(shù)據(jù)結(jié)構(gòu)奕纫。
- string --> 簡單的key-value,數(shù)組結(jié)構(gòu)
- list --> 雙向鏈表結(jié)構(gòu),底層采用“ziplist+quicklist”
- hash --> "數(shù)組+鏈表"結(jié)構(gòu)
- set --> 無序列表
- zset --> 采用“ziplist+skiplist”結(jié)構(gòu)
如果有什么遺漏或錯(cuò)誤烫沙,希望大家在留言區(qū)指出匹层,加以補(bǔ)充~
轉(zhuǎn)載請(qǐng)注明