Redis开发与运维
本書全面講解了Redis基本功能及其使用,并結(jié)合線上開發(fā)與運(yùn)維中的實(shí)際案例,深入分析并總結(jié)了實(shí)際工作中遇到的“陷進(jìn)”,以及背后的原因,包含大規(guī)模集群開發(fā)與管理的場景、應(yīng)用案例與開發(fā)技巧,為高效開發(fā)運(yùn)維提供了大量實(shí)際經(jīng)驗(yàn)和建議。
本書不要求讀者有任何Redis使用經(jīng)驗(yàn),對(duì)入門與進(jìn)階DevOps的開發(fā)者提供有價(jià)值的幫助。
主要內(nèi)容包括:
- Redis的安裝配置
- API
- 各種高效功能
- 客戶端
- 持久化
- 復(fù)制
- 高可用
- 內(nèi)存
- 哨兵
- 集群
- 緩存設(shè)計(jì)等
- Redis高可用集群解決方案
- Redis設(shè)計(jì)和使用中的問題
- 最后提供了一個(gè)開源工具:Redis監(jiān)控運(yùn)維云平臺(tái)CacheCloud
前言
? Redis作為基于 鍵值對(duì) 的 NoSQL(Not Only Sql)數(shù)據(jù)庫,
具有 高性能、豐富的數(shù)據(jù)結(jié)構(gòu)、持久化、高可用、分布式等特性,
同時(shí)Redis本身非常穩(wěn)定,已經(jīng)得到業(yè)界的廣泛認(rèn)可和使用。
掌握Redis已經(jīng)逐步稱為開發(fā)和運(yùn)維人員的必備技能之一。
? 本書關(guān)注了Redis開發(fā)運(yùn)維的方方面面,尤其對(duì)于開發(fā)運(yùn)維中如何提高效率、減少可能遇到的問題進(jìn)行詳細(xì)分析,但本書不單單介紹怎么解決這些問題,而是通過對(duì)Redis重要原理的解析,幫助開發(fā)運(yùn)維人員學(xué)會(huì)找到問題的方法,以及理解背后的原理,從而讓開發(fā)運(yùn)維人員不僅知其然,而且知其所以然。
本書涵蓋內(nèi)容
? 第1章
? 初始Redis,帶讀者進(jìn)入Redis的世界,了解它的前世今生、眾多特性、應(yīng)用場景、安裝配置、簡單使用,最后對(duì)Redis發(fā)展過程中的重要版本進(jìn)行說明,可以讓讀者對(duì)Redis有一個(gè)全面的認(rèn)識(shí)。
? 第2章
? API的理解和使用,全面介紹了Redis的 5種數(shù)據(jù)結(jié)構(gòu) 字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)的 數(shù)據(jù)模型、常用命令、典型應(yīng)用場景,并且每個(gè)小結(jié)都會(huì)給出在Redis開發(fā)過程中可能要注意的坑和技巧。同時(shí)本章還會(huì)對(duì)Redis的 單線程處理機(jī)制、**鍵值管理 ** 做一個(gè)全面介紹,通過對(duì)這些原理的理解,讀者可以在合適的應(yīng)用場景選擇合適的數(shù)據(jù)結(jié)構(gòu)和命令進(jìn)行開發(fā),有效提高程序效率,降低可能產(chǎn)生的問題和隱患。
? 第3章
? 小功能大用處,除了 5種數(shù)據(jù)結(jié)構(gòu) 外,Redis還提供了諸如 慢查詢、Redis Shell、Pipeline、Lua腳本、Bitmaps、HyperLogLog、發(fā)布訂閱、GEO 等附加功能,在這些功能的幫助下,Redis的應(yīng)用場景更加豐富。
? 第4章
? 客戶端,本章重點(diǎn)關(guān)注Redis客戶端的開發(fā),介紹了Redis的 客戶端通信協(xié)議、詳細(xì)講解了Java客戶端 Jedis 的使用技巧,同時(shí)通過從原理角度剖析在開發(fā)運(yùn)維中,客戶端的監(jiān)控和管理技巧,最后給出客戶端開發(fā)中常見問題以及案例講解。
? 第5章
? 持久化,Redis的 持久化 功能有效避免因進(jìn)程退出造成的數(shù)據(jù)丟失問題,本章首先介紹 RDB 和 AOF 兩種 持久化配置和運(yùn)行流程,其次對(duì)常見的持久化問題進(jìn)行定位和優(yōu)化,最后結(jié)合Redis常見的單機(jī)多實(shí)例部署場景進(jìn)行優(yōu)化。
? 第6章
? 復(fù)制,在分布式系統(tǒng)中為了解決單點(diǎn)問題,通常會(huì)把數(shù)據(jù)復(fù)制多個(gè)副本部署到其他機(jī)器,用于故障恢復(fù)和負(fù)載均衡等需求,Redis也是如此。它為我們提供了 復(fù)制(replication)功能,實(shí)現(xiàn)了多個(gè)相同數(shù)據(jù)的Redis副本。 復(fù)制功能是 高可用Redis的基礎(chǔ),后面章節(jié)的 哨兵 和 集群 都是在 復(fù)制的基礎(chǔ)上 實(shí)現(xiàn)高可用。
? 第7章
? Redis的噩夢:阻塞,Redis是典型的單線程架構(gòu),所有的讀寫操作 都在一條 主線程 中完成的。當(dāng)Redis用于 高并發(fā) 場景時(shí)這條線程就變成了它的生命線。如果出現(xiàn)阻塞哪怕是很短時(shí)間對(duì)于我們的應(yīng)用來說都是噩夢。導(dǎo)致阻塞問題的場景大致分為 內(nèi)在原因 和 外在原因,本章將進(jìn)行詳細(xì)分析。
? 第8章
? 理解內(nèi)存,Redis所有的數(shù)據(jù)存在于內(nèi)存中,如何高效利用Redis內(nèi)存變得非常重要。高效利用Redis內(nèi)存 首先需要理解 Redis內(nèi)存消耗在哪里,如何管理內(nèi)存,最后再深入到 如何優(yōu)化內(nèi)存。掌握這些知識(shí)后相信讀者能夠?qū)崿F(xiàn) 用更少的內(nèi)存存儲(chǔ)更多的數(shù)據(jù) 從而 降低成本 。
? 第9章
? 哨兵,Redis從 2.8 版本開始正式提供了 Redis Sentinel,它有效解決了 主從模式 下 故障轉(zhuǎn)移 的若干問題,為Redis提供了 高可用 功能。本章將一步步解析 Redis Sentinel 的相關(guān)概念、安裝部署、配置、命令使用、原理解析,最后分析了 Redis Sentinel 運(yùn)維中的一些問題。
? 第10章
? 集群,是本書的重頭戲, Redis Cluster 是 Redis 3 提供的 Redis 分布式解決方案,有效解決了 Redis分布式 方面的需求,理解應(yīng)用好 Redis Cluster 將極大地解放我們對(duì) 分布式Redis 的需求,同時(shí)它也是學(xué)習(xí) 分布式存儲(chǔ) 的絕佳案例。本章將對(duì) Redis Cluster 的 數(shù)據(jù)分布、搭建集群、節(jié)點(diǎn)通信、請(qǐng)求路由、集群伸縮、故障轉(zhuǎn)移 等方面進(jìn)行分析說明。
? 第11章
? 緩存設(shè)計(jì),緩存 能夠有效 加速應(yīng)用的讀寫速度,以及 降低后端負(fù)載,對(duì)于開發(fā)人員進(jìn)行日常應(yīng)用的開發(fā)至關(guān)重要,但是將 緩存 加入應(yīng)用架構(gòu)后也會(huì)帶來一些問題,本章將介紹 緩存使用 和 設(shè)計(jì) 中遇到的問題,具體包括:緩存的收益和成本、緩存更新策略、緩存粒度控制、穿透問題優(yōu)化、無底洞問題優(yōu)化、雪崩問題優(yōu)化、熱點(diǎn)key優(yōu)化。
? 第12章
? 開發(fā)運(yùn)維的“陷進(jìn)”,介紹Redis開發(fā)運(yùn)維中的一些棘手問題,具體包括:Linux配置優(yōu)化、flush誤操作數(shù)據(jù)恢復(fù)、如何讓Redis變得安全、bigkey 問題、熱點(diǎn)key 問題。
? 第13章
? Redis監(jiān)控運(yùn)維云平臺(tái) CacheCloud,介紹筆者所在團(tuán)隊(duì) 開源 的 Redis運(yùn)維工具 CacheCloud,它有效解決了Redis監(jiān)控和運(yùn)維中的一些問題,本章將按照 快速部署、機(jī)器部署、接入應(yīng)用、用戶功能、運(yùn)維功能 多個(gè)維度全面的介紹 CacheCloud,相信在它的幫助下,讀者可以更好的監(jiān)控和運(yùn)維好Redis。
? 第14章
? Redis配置統(tǒng)計(jì)字典,會(huì)對(duì)Redis的 系統(tǒng)狀態(tài)信息 以及 全部配置 做一個(gè)全面的梳理,希望本章能夠成為 Redis配置統(tǒng)計(jì)字典,協(xié)助大家分析和解決日常開發(fā)和運(yùn)維中遇到的問題。
第1章 初始Redis
1.1 盛贊Redis
1.2 Redis特性
1.3 Redis使用場景
? 1.3.1 Redis可以做什么
? 1.3.2 Redis不可以做什么
1.4 用好Redis的建議
1.5 正確安裝并啟動(dòng)Redis
? 1.5.1 安裝Redis
? 1.5.2 配置、啟動(dòng)、操作、關(guān)閉Redis
1.6 Redis重大版本
1.7 本章重點(diǎn)回顧
第1章 初始Redis
? 本章將帶領(lǐng)讀者進(jìn)入Redis的世界,了解它的前世今生、眾多特性、典型應(yīng)用場景、安裝配置、如何好用 等,最后會(huì)對(duì)Redis發(fā)展過程中的 重要版本 進(jìn)行說明,本章主要內(nèi)容如下:
- 盛贊Redis
- Redis特性
- Redis使用場景
- 用好Redis的建議
- 正確安裝啟動(dòng)Redis
- Redis重大版本
1.1 盛贊Redis
Redis官網(wǎng):http://redis.io
Redis 是一種基于 鍵值對(duì)(key-value)的 NoSQL 數(shù)據(jù)庫,
與很多 鍵值對(duì)數(shù)據(jù)庫 不同的是,
Redis 中的 值 可以是由 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位圖)、HyperLogLog、GEO(地理信息定位) 等 多種數(shù)據(jù)結(jié)構(gòu)和算法 組成,因此 Redis 可以 滿足很多的 應(yīng)用場景,
而且因?yàn)?Redis 會(huì)將 所有數(shù)據(jù) 都 存放在 內(nèi)存 中,所以它的 讀寫性能 非常 驚人。
不僅如此,Redis 還可以將 內(nèi)存的數(shù)據(jù) 利用 快照 和 日志 的形式 保存到 硬盤 上,這樣在發(fā)生類似 斷電 或者 機(jī)器故障 的時(shí)候,內(nèi)存中的數(shù)據(jù) 不會(huì) “丟失”。
除了上述功能以外,Redis 還提供了 鍵過期、發(fā)布訂閱、事務(wù)、流水線、Lua腳本 等附加功能。
總之,如果在合適的場景使用好 Redis,它就會(huì)像一把瑞士軍刀一樣所向披靡。
2008年,Redis 的作者 Salvatore Sanfilippo 在開發(fā)一個(gè)叫 LLOOGG 的網(wǎng)站時(shí),需要實(shí)現(xiàn)一個(gè) 高性能 的 隊(duì)列功能,最開始是使用 MySQL 來實(shí)現(xiàn)的,但后來發(fā)現(xiàn)無論怎么優(yōu)化 SQL語句 都不能使 網(wǎng)站的性能 提高上去,再加上自己囊中羞澀,于是他決定自己做一個(gè)專屬于 LLOOGG 的數(shù)據(jù)庫,這個(gè)就是 Redis的前身。
后來,Salvatore Sanfilippo 將 Redis 1.0 的源碼開放到 GitHub 上,可能連他自己都沒想到,Redis 后來如此受歡迎。
假如現(xiàn)在有人問 Redis的作者 都有誰在使用 Redis,我想他可以開句玩笑的回答:還有誰不使用Redis,當(dāng)然這只是開玩笑,但是從 Redis 的官方公司統(tǒng)計(jì)來看,有很多重量級(jí)的公司都在使用 Redis,如果單單從體量來統(tǒng)計(jì),新浪微博 可以說是全球最大的 Redis 使用者,除了 新浪微博,還有像 阿里巴巴、騰訊、百度、搜狐、優(yōu)酷土豆、美團(tuán)、小米、唯品會(huì)等公司都是 Redis 的使用者。
除此之外,許多開源技術(shù)想ELK等已經(jīng)把 Redis 作為它們組件中的重要一環(huán),而且 Redis 會(huì)在未來的版本中提供 模板系統(tǒng) 讓第三方人員 實(shí)現(xiàn)擴(kuò)展功能,讓 Redis 發(fā)揮出更大的威力。
所以,可以這么說,熟練使用和運(yùn)維 Redis 已經(jīng)成為開發(fā)運(yùn)維人員的一個(gè)必備技能。
1.2 Redis特性
? Redis 之所以受到如此多公司的青睞,必然有之過人之處,下面是關(guān)于 Redis 的 8個(gè)重要特性。
速度 快
正常情況下,Redis 執(zhí)行命令的速度 非常快,官方給出的數(shù)字是 讀寫性能 可以達(dá)到 10萬/秒,當(dāng)然這也取決于 機(jī)器的性能,但這里先不討論 機(jī)器性能上的差異,只分析一下是什么造就了 Redis 除此之外的 速度,可以大致歸納為以下四點(diǎn):
- Redis 的 所有數(shù)據(jù) 都是 存放在 內(nèi)存 中的,表1-1是谷歌公司2009年給出的各層級(jí)硬件執(zhí)行速度,所以 把數(shù)據(jù)放在內(nèi)存 中是 Redis速度快 的最主要原因。
- Redis 使用 C語言 實(shí)現(xiàn)的,一般來說 C語言 實(shí)現(xiàn)的程序 “距離” 操作系統(tǒng)更近,執(zhí)行速度 相對(duì)會(huì)更快。
- Redis 使用了 單線程架構(gòu),預(yù)防了 多線程 可能產(chǎn)生的 競爭問題。
- 作者對(duì)于 Redis源代碼 可以說是 精打細(xì)磨,曾經(jīng)有人評(píng)價(jià) Redis 是少有的 集性能和優(yōu)雅于一身 的 開源代碼。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-CZFzKuIB-1621301836851)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210513170503130.png)]
表1-1 谷歌公司給出的 各層級(jí) 硬件 執(zhí)行速度
| L1 cache reference | 0.5ns |
| Branch mispredict | 5ns |
| L2 cache reference | 7ns |
| Mutex lock/unlock | 25ns |
| Main memory reference | 100ns |
| Compress 1K bytes with Zippy | 3 000ns |
| Send 2K bytes over 1Gbps network | 20 000ns |
| Read 1MB sequentially from Memory | 250 000ns |
| Round trip within same datacenter | 500 000ns |
| Disk seek | 10 000 000ns |
| Read 1 MB sequentially from disk | 20 000 000ns |
| Send packet CA->Netherlands->CA | 150 000 000ns |
基于 鍵值對(duì) 的數(shù)據(jù)結(jié)構(gòu) 服務(wù)器
幾乎所有的 編程語言 都提供了類似 字典 的功能,
例如Java里的 map、Python里的 dict,
類似于這種 組織數(shù)據(jù)的方式 叫做 基于鍵值 的方式,
與很多 鍵值對(duì) 數(shù)據(jù)庫 不同的是,
Redis 中的 值 不僅可以是 字符串,而且還可以是 具體的 數(shù)據(jù)結(jié)構(gòu),
這樣不僅能 便于在許多應(yīng)用場景的開發(fā),同時(shí)也能夠 提高開發(fā)效率。
Redis 的 全稱 是 Remote Dictionary Server,
它主要提供了 5種數(shù)據(jù)結(jié)構(gòu):字符串、哈希、列表、集合、有序集合,
同時(shí)在 字符串 的 基礎(chǔ)之上 演變 出了 位圖(Bitmaps)和 HyperLogLog 兩種神奇的 “數(shù)據(jù)結(jié)構(gòu)”,
并且隨著 LBS(Location Based Service,基于位置服務(wù))的不斷發(fā)展,Redis 3.2 版本 中加入有關(guān) GEO(地理信息定位)的功能,
總之在這些數(shù)據(jù)結(jié)構(gòu)的幫助下,開發(fā)者可以開發(fā)出各種“有意思”的應(yīng)用。
豐富的功能
除了 5種數(shù)據(jù)結(jié)構(gòu),Redis 還提供了許多額外的功能:
- 提供了 鍵過期 功能,可以用來實(shí)現(xiàn) 緩存。
- 提供了 發(fā)布訂閱 功能,可以用來實(shí)現(xiàn) 消息系統(tǒng)。
- 支持 Lua腳本 功能,可以利用 Lua 創(chuàng)造出 新的Reds命令。
- 提供了簡單的 事務(wù) 功能,能在一定程度上保證 事務(wù)特性。
- 提供了 流水線(Pipeline)功能,這樣 客戶端 能將 一批命令 一次性 傳到 Redis,減少了 網(wǎng)絡(luò)的開銷。
簡單穩(wěn)定
Redis 的 簡單 主要表現(xiàn)在 三個(gè)方面。
首先,Redis 的 源碼 很少,早期版本 的代碼只有 2萬行左右,3.0版本 以后由于添加了 集群特性,代碼增至 5萬行左右,相對(duì)于很多 NoSQL數(shù)據(jù)庫 來說代碼量相對(duì)要少很多,也就意味著普通的開發(fā)和運(yùn)維人員完全可以“吃透”它。
其次,Redis 使用 單線程模型,這樣不僅使得 Redis服務(wù)端處理模型 變得簡單, 而且也使得 客戶端開發(fā) 變得簡單。
最后,Redis 不需要依賴于 操作系統(tǒng) 中的類庫(例如 Memcache 需要 依賴 libevent 這樣的 系統(tǒng)類庫),Redis 自己 實(shí)現(xiàn) 了 事件處理 的相關(guān)功能。
Redis 雖然很簡單,但是不代表它不穩(wěn)定。
以筆者維護(hù)的上千個(gè)Redis為例,沒有出現(xiàn)過因?yàn)镽edis自身bug而宕掉的情況。
客戶端語言 多
Redis 提供了 簡單的 TCP通信協(xié)議,很多 編程語言 可以很 方便地 接入到 Redis,并且由于 Redis 受到 社區(qū)和各大公司的 廣泛認(rèn)可,所以 支持Redis的客戶端語言 也非常 多,幾乎涵蓋了 主流的編程語言,例如Java、PHP、Python、C、C++、Nodejs等,第4章我們將對(duì) Redis的客戶端 進(jìn)行詳細(xì)說明。
持久化
通常看,將 數(shù)據(jù) 放在 內(nèi)存 中是 不安全的,一旦發(fā)生 斷電 或者 機(jī)器故障,重要的數(shù)據(jù) 可能就會(huì) 丟失,因此 Redis 提供了 兩種 持久化 方式:RDB 和 AOF,即可以用 兩種策略 將 內(nèi)存的數(shù)據(jù) 保存到 硬盤中(如圖1-1所示),這樣就 保證 了 數(shù)據(jù)的可持久性,第5章我們將對(duì) Redis的持久化 進(jìn)行詳細(xì)說明。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-1inDcMPh-1621301836855)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210513173702303.png)]
主從復(fù)制
Redis 提供了 復(fù)制 功能,實(shí)現(xiàn)了 多個(gè)相同數(shù)據(jù)的Redis副本(如圖1-2所示),復(fù)制 功能是 分布式Redis的基礎(chǔ)。第6章我們將會(huì)對(duì) Redis的復(fù)制 進(jìn)行詳細(xì)說明。
高可用 和 分布式
Redis 從 2.8版本 正式提供了 高可用 實(shí)現(xiàn)Redis Sentinel,它能夠保證 Redis節(jié)點(diǎn) 的 故障發(fā)現(xiàn) 和 故障自動(dòng)轉(zhuǎn)移。
Redis 從 3.0版本 正式提供了 分布式 實(shí)現(xiàn) Redis Cluster,它是 Redis 真正的 分布式實(shí)現(xiàn),提供了 高可用、讀寫 和 容量的擴(kuò)展性。
1.3 Redis 使用場景
? 上節(jié)我們已經(jīng)了解了 Redis 的若干個(gè) 特性,本節(jié)來看一下 Redis 的 典型應(yīng)用場景 有哪些?
1.3.1 Redis 可以做什么
緩存
緩存機(jī)制 幾乎在所有的大型網(wǎng)站 都有使用,合理地使用緩存 不僅可以 加快數(shù)據(jù)的訪問速度,而且能夠 有效地降低后端數(shù)據(jù)源的壓力。
Redis 提供了 鍵值過期時(shí)間設(shè)置,并且也提供了 靈活控制最大內(nèi)存 和 內(nèi)存溢出后的淘汰策略。
可以這么說,一個(gè) 合理的緩存設(shè)計(jì) 能夠?yàn)?一個(gè)網(wǎng)站的穩(wěn)定 保駕護(hù)航。第11章將對(duì) 緩存的設(shè)計(jì)與使用 進(jìn)行詳細(xì)說明。
排行榜系統(tǒng)
排行榜系統(tǒng) 幾乎存在于所有的網(wǎng)站,例如 按照熱度排名的排行榜,按照發(fā)布時(shí)間的排行榜,按照各種復(fù)雜維度計(jì)算出的排行榜,Redis 提供了 列表 和 有序集合 數(shù)據(jù)結(jié)構(gòu),合理地使用 這些 數(shù)據(jù)結(jié)構(gòu) 可以很 方便地構(gòu)建各種排行榜系統(tǒng)。
計(jì)數(shù)器應(yīng)用
計(jì)數(shù)器 在網(wǎng)站中的作用 至關(guān)重要,例如 視頻網(wǎng)站有 播放數(shù)、電商網(wǎng)站有 瀏覽數(shù),為了保證 數(shù)據(jù)的實(shí)時(shí)性,每一次 播放和瀏覽 都要做 加1 的操作,如果 并發(fā)量很大 對(duì)于 傳統(tǒng)關(guān)系型數(shù)據(jù)庫的性能 是一種挑戰(zhàn)。
Redis 天然支持 計(jì)數(shù)功能 而且 計(jì)數(shù)的性能 也非常好,可以說是 計(jì)數(shù)器系統(tǒng) 的重要選擇。
社交網(wǎng)絡(luò)
贊/踩、粉絲、共同好友/喜好、推送、下拉刷新等是 社交網(wǎng)站的必備功能,由于社交網(wǎng)站 訪問量通常比較大,而且 傳統(tǒng)的關(guān)系型數(shù)據(jù)庫 不太適合保存這種類型的數(shù)據(jù),Redis 提供的 數(shù)據(jù)結(jié)構(gòu) 可以相對(duì)比較容易地實(shí)現(xiàn)這些功能。
消息隊(duì)列系統(tǒng)
消息隊(duì)列系統(tǒng) 可以說是 一個(gè)大型網(wǎng)站的必備基礎(chǔ)組件,因?yàn)槠渚哂?業(yè)務(wù) 解耦、非實(shí)時(shí)業(yè)務(wù) 削峰等特性。
Redis 提供了 發(fā)布訂閱 和 阻塞隊(duì)列 的功能,雖然和專業(yè)的消息隊(duì)列比還不夠足夠強(qiáng)大,但是對(duì)于一般的消息隊(duì)列基本可以滿足。
1.3.2 Redis不可以做什么
? 實(shí)際上和任何一門技術(shù)一樣,每個(gè)技術(shù)都有自己的 應(yīng)用場景 和 邊界,也就是說 Redis 并 不是 萬金油,有很多適合它解決的問題,但是也有很多不適合它解決的問題。
我們可以站在 數(shù)據(jù)規(guī)模 和 數(shù)據(jù)冷熱 的角度來進(jìn)行分析。
站在 數(shù)據(jù)規(guī)模 的角度看,數(shù)據(jù) 可以分為 大規(guī)模數(shù)據(jù) 和 小規(guī)模數(shù)據(jù),
我們知道 Redis 的 數(shù)據(jù) 是 存放在 內(nèi)存 中的,雖然現(xiàn)在 內(nèi)存 已經(jīng)足夠 便宜,但是如果 數(shù)據(jù)量 非常大,例如每天有幾億的用戶行為數(shù)據(jù),使用 Redis 來存儲(chǔ)的話,基本上是個(gè) 無底洞,經(jīng)濟(jì)成本 非常的 高。
站在 數(shù)據(jù)冷熱 的角度看,數(shù)據(jù) 分為 熱數(shù)據(jù) 和 冷數(shù)據(jù),熱數(shù)據(jù) 通常是指需要 頻繁操作的數(shù)據(jù),反之為冷數(shù)據(jù),
例如對(duì)于 視頻網(wǎng)站 來說,視頻基本信息 基本上在各個(gè)業(yè)務(wù)線都是 經(jīng)常要操作 的數(shù)據(jù),而 用戶的觀看記錄 不一定是經(jīng)常需要訪問的數(shù)據(jù),
這里暫且不討論兩者數(shù)據(jù)規(guī)模的差異,
單純站在 數(shù)據(jù)冷熱的角度 上看,視頻信息 屬于 熱數(shù)據(jù),用戶觀看記錄 屬于冷數(shù)據(jù)。
如果將這些 冷數(shù)據(jù) 放在 Redis 上,基本上是 對(duì) 內(nèi)存的一種浪費(fèi),
但是對(duì)于一些 熱數(shù)據(jù) 可以放在 Redis 中 加速讀寫,也可以 減輕后端存儲(chǔ)的負(fù)載,可以說是事半功倍。
所以,Redis 并不是 萬金油,相信隨著我們對(duì) Redis 的逐步學(xué)習(xí),能夠清楚 Redis 真正的使用場景。
1.4 用好Redis的建議
切勿當(dāng)做 黑盒 使用,開發(fā)與運(yùn)維同樣重要
很多使用 Redis 的開發(fā)者認(rèn)為只要會(huì)用 API 開發(fā)相應(yīng)的功能就可以,更有甚者認(rèn)為 Redis 就是 get、set、del,不需要知道 Redis 的 原理。
但是在我們實(shí)際運(yùn)維和使用 Redis 的過程中發(fā)現(xiàn),很多線上的故障和問題都是由于完全把 Redis 當(dāng)做 黑盒 造成的,
如果不了解 Redis的單線程模型,有些開發(fā)者會(huì)在 上千萬個(gè)鍵的Redis上 執(zhí)行 keys * 操作,
如果不了解 持久化的相關(guān)原理,會(huì)在一個(gè) 寫操作量很大的Redis上 配置自動(dòng)保存RDB。
而且在很多公司內(nèi)只有專職的 關(guān)系型數(shù)據(jù)庫DBA,并沒有 NoSQL 的相關(guān)運(yùn)維人員,也就是說開發(fā)者很有可能會(huì)自己運(yùn)維 Redis,對(duì)于 Redis 的開發(fā)者來說既是好事又是壞事。
站在好的方面看,開發(fā)人員可以通過 運(yùn)維 Redis 真正了解 Redis 的一些原理,不單純停留在開發(fā)上。
站在壞的方面看,Redis 的開發(fā)人員不僅要支持開發(fā),還要承擔(dān)運(yùn)維的責(zé)任,而且由于運(yùn)維經(jīng)驗(yàn)不足可能會(huì)造成線上故障。
但是從實(shí)際經(jīng)驗(yàn)來看,運(yùn)維足夠規(guī)模的 Redis 會(huì)對(duì)用好 Redis 更加有幫助。
閱讀源碼
我們在前面提到過,Redis 是 開源項(xiàng)目,由于作者對(duì) Redis代碼 的極致追求,Redis的代碼量 相對(duì)于許多 NoSQL數(shù)據(jù)庫 來說是非常 小 的,也就意味著作為普通的開發(fā)和運(yùn)維人員也是可以 “吃透”的。
通過閱讀優(yōu)秀的源碼,不僅能夠加深我們對(duì) Redis 的理解,而且還能提高自身的需求,例如新浪微博在 Redis的早期版本 上做了很多的 定制化 來滿足自身的需求,豌豆莢也開源基于 Proxy 的 Redis 分布式 實(shí)現(xiàn) Codis。
1.5 正確安裝并啟動(dòng)Redis
通常來說,學(xué)習(xí)一門技術(shù)最好的方法就是實(shí)戰(zhàn),所以在學(xué)習(xí) Redis 這樣一個(gè)實(shí)戰(zhàn)中產(chǎn)生的技術(shù)時(shí),首先把它安裝部署起來,值得慶幸的是,相比于很多軟件和工具部署步驟繁雜,Redis的安裝 不得不說是非常 簡單,本節(jié)我們將學(xué)習(xí)如何 安裝Redis。
注意
在寫本書時(shí),Redis 4.0 已經(jīng)發(fā)布 RC版,但是大部分公司還都在使用 3.0 或更早的版本(2.6 或 2.8),本書所講的內(nèi)容基于 Redis3.0。
1.5.1 安裝Redis
在Linux上安裝Redis
Redis 能夠 兼容 絕大部分的 POSIX 系統(tǒng),例如 Linux、OS X、OpenBSD、NetBSD 和 FreeBSD,
其中比較典型的是 Linux 操作系統(tǒng)(例如 CentOS、Redhat、Ubuntu、Debian、OS X等)。
在 Linux 安裝軟件通常有 兩種方法,
第一種是通過 各個(gè)操作系統(tǒng)的軟件管理軟件 進(jìn)行安裝,例如 CentOS 有 yum 管理工具,Ubuntu 有 apt。
但是由于 Redis 的更新速度相對(duì)較快,而這些管理工具不一定能更新到最新的版本,同時(shí)前面提到 Redis的安裝 本身不是很復(fù)雜,所以一般推薦使用第二種方式:源碼的方式 進(jìn)行安裝,整個(gè)安裝只需一下 六步 即可完成,以 3.0.7 版本 為例:
wget http://download.redis.io/release/redis-3.0.7.tar.gztar xzf redis-3.0.7.tar.gzln -s redis-3.0.7 rediscd redismakemake install下載 Redis 指定版本的 源碼壓縮包 到當(dāng)前目錄。
解壓縮 Redis 源碼壓縮包。
建立一個(gè) redis 目錄 的軟連接,指向 redis-3.0.7
進(jìn)入 redis 目錄
編譯(編譯之前確保操作系統(tǒng)已經(jīng)安裝 gcc)
安裝
這里有 兩點(diǎn) 需要注意:
第一,第3步 中建立了一個(gè) redis 目錄的 軟鏈接,這樣做是為了 不把 redis 目錄 固定在 指定版本上,有利于 Redis 未來版本升級(jí),算是 安裝軟件 的一種好習(xí)慣。
第二,第6步 中的 安裝 是將 Redis 的相關(guān)運(yùn)行文件放到 /usr/local/bin/ 下,這樣就可以在任意目錄下執(zhí)行 Redis 的命令。
例如安裝后,可以在任何目錄執(zhí)行 redis-cli -v 查看 Redis 版本。
redis-cli -v redis-cli 3.0.7注意
第12章將介紹更多 Linux配置優(yōu)化 技巧,為 Redis 的良好運(yùn)行保駕護(hù)航。
Redis 的官方并不支持微軟的Windows操作系統(tǒng),但是 Redis 作為一款優(yōu)秀的開源技術(shù)吸引到了微軟公司的注意,微軟公司的開源技術(shù)組在 GitHub 上維護(hù)一個(gè) Redis 的分支:https://github.com/MSOpenTech/redis。
那為什么 Redis 的作者沒有開發(fā)和維護(hù)對(duì)Windows用戶的 Redis 版本呢?
這里可以簡單分析一下:
首先 Redis 的許多 特性 都是和 操作系統(tǒng) 相關(guān)的,Windows操作系統(tǒng) 和 Linux操作系統(tǒng) 有很大的區(qū)別,所以會(huì) 增加維護(hù)成本,而且更重要的是大部分公司都在使用 Linux 操作系統(tǒng),而 Redis 在Linux 操作系統(tǒng)上的表現(xiàn)已經(jīng)得到了實(shí)踐的驗(yàn)證。
對(duì)于使用 Windows操作系統(tǒng) 的讀者,可以通過安裝虛擬機(jī)來體驗(yàn) Redis 的諸多特性。
注意
對(duì) Windows版本 的 Redis 感興趣的讀者,可以嘗試安裝和部署 Windows版本的Redis,但是本書中的知識(shí)和例子不能確保在Windows下能夠運(yùn)行。
1.5.2 配置、啟動(dòng)、操作、關(guān)閉 Redis
Redis 安裝之后,src 和 /usr/local/bin 目錄下多了幾個(gè)以 redis 開頭 可執(zhí)行文件,我們稱之為 Redis Shell,這些 可執(zhí)行文件 可以做很多事情,例如可以啟動(dòng)和停止 Redis、可以 檢測和修復(fù) Redis 的持久化文件,還可以 檢測 Redis 的性能。
表 1-2 中分別列出這些可執(zhí)行文件的說明。
表 1-2 Redis 可執(zhí)行文件說明
| redis-server | 啟動(dòng)Redis |
| redis-cli | Redis命令行客戶端 |
| redis-benchmark | Redis基準(zhǔn)測試工具 |
| redis-check-aof | Redis AOF持久化文件檢測和修復(fù)工具 |
| redis-check-dump | Redis RDB持久化文件檢測和修復(fù)工具 |
| redis-sentinel | 啟動(dòng)Redis Sentinel |
Redis 持久化 和 Redis Sentinel 分別在第5章和第9章才會(huì)涉及,Redis基準(zhǔn)測試 將在第3章介紹,所以本節(jié)只對(duì) redis-server、redis-cli 進(jìn)行介紹。
啟動(dòng) Redis
有 三種方法 啟動(dòng)Redis:默認(rèn)配置、運(yùn)行配置、配置文件啟動(dòng)。
(1)默認(rèn)配置
這種方法會(huì)使用 Redis的默認(rèn)配置 來啟動(dòng),下面就是 redis-server 執(zhí)行后輸出的相關(guān)日志。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-bRAOIMuK-1621301836858)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210514142427083.png)]
可以看到直接使用 redis-server 啟動(dòng) Redis 后,會(huì)打印一些日志,通過日志可以看到一些信息,上例中可以看到:
- 當(dāng)前的 Redis版本 是 3.0.7。
- Redis 的 默認(rèn)端口 是 6379。
- Redis 建議要 使用配置文件 來啟動(dòng)。
因?yàn)?直接啟動(dòng) 無法自定義配置,所以這種方式是不會(huì)在生產(chǎn)環(huán)境中使用的。
(2)運(yùn)行啟動(dòng)
redis-server 加上 要修改配置名和值(可以是多對(duì)),沒有設(shè)置的配置將使用默認(rèn)配置:
# redis-server --configKey1 configValue1 --configKey2 configValue2例如,如果要用 6380 作為 端口 啟動(dòng) Redis,那么可以執(zhí)行:
# redis-server --port 6380雖然 運(yùn)行配置 可以 自定義配置,但是如果 需要修改的配置較多 或者 希望將配置保存到文件中,不建議使用這種方式。
(3)配置文件啟動(dòng)
將 配置 寫到 指定文件 里,例如我們將配置寫到了 /opt/redis/redis.conf 中,那么只需要執(zhí)行如下命令即可啟動(dòng) Redis:
# redis-server /opt/redis/redis.confRedis 有 60 多個(gè)配置,這里只給出一些重要的配置(參見表 1-3),其他配置會(huì)隨著不斷深入學(xué)習(xí)進(jìn)行介紹,第14章會(huì)將所有的配置說明進(jìn)行匯總。
表 1-3 Redis的基礎(chǔ)配置
| port | 端口 |
| logfile | 日志文件 |
| dir | Redis 工作目錄(存放持久化文件和日志文件) |
| daemonize | 是否以守護(hù)進(jìn)程的方式啟動(dòng)Redis |
運(yùn)維提示
Redis 目錄下都會(huì)有一個(gè) redis.conf 配置文件,里面就是 Redis的 默認(rèn)配置,通常來講我們會(huì)在 一臺(tái)機(jī)器上啟動(dòng)多個(gè)Redis,并且將 配置集中管理在指定目錄下,而且 配置不是完全手寫的,而是將 redis.conf 作為模板進(jìn)行修改。
顯然通過 配置文件啟動(dòng)的方式 提供了 更大的靈活性,所以大部分生產(chǎn)環(huán)境會(huì)使用這種方式啟動(dòng)Redis。
Redis 命令行客戶端
現(xiàn)在我們已經(jīng)啟動(dòng)了 Redis 服務(wù),下面將介紹如何使用 redis-cli 連接,操作 Redis 服務(wù)。
redis-cli 可以使用 兩種方式 連接Redis服務(wù)器。
-
第一種是 交互式方式
通過 redis-cli -h {host} -p {port}的方式連接到 Redis服務(wù),之后所有的操作都是通過 交互的方式 實(shí)現(xiàn),不需要再執(zhí)行 redis-cli 了,例如:
redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> get hello "world" -
第二種是 命令方式
用 redis-cli -h ip {host} -p {port} {command}就可以直接得到 命令的返回結(jié)果,例如:
redis-cli -h 127.0.0.1 -p 6379 get hello "world"這里有 兩點(diǎn) 要注意:
1)如果沒有 -h 參數(shù),那么 默認(rèn)連接 127.0.0.1 ;
? 如果沒有 -p ,那么 默認(rèn) 6379 端口,也就是說如果 -h 和 -p 都沒寫就是連接 127.0.0.1:6379 這個(gè)Redis實(shí)例。
2)redis-cli 是學(xué)習(xí) Redis 的重要工具,后面的很多章節(jié)都是用它作講解,同時(shí) redis-cli 還提供了很多有價(jià)值的參數(shù),可以幫助解決很多問題,有關(guān)于 redis-cli 的強(qiáng)大功能將在第3章進(jìn)行詳細(xì)介紹。
停止 Redis 服務(wù)
Redis 提供了 shutdown 命令來 停止Redis服務(wù),例如要停掉 127.0.0.1 上 6379 端口上的 Redis 服務(wù),可以執(zhí)行如下操作:
redis-cli shutdown可以看到 Redis 的日志輸出如下:
# User requested shutdown... # 客戶端發(fā)出的shutdown命令* Saving the final RDB snapshot before exiting. #保存 RDB 持久化文件(有關(guān) Redis持久化的特性在 1.2節(jié) 已經(jīng)進(jìn)行了簡單的介紹,RDB 是 Redis 的一種 持久化方式)* DB saved on disk # 將 RDB 文件保存在磁盤上# Redis is now ready to exit, bye bye... #關(guān)閉當(dāng)使用 redis-cli 再次連接該 Redis 服務(wù)時(shí),看到 Redis 已經(jīng) “失聯(lián)”。
redis-cli Could not connect to Redis at 127.0.0.1:6379: Connection refused這里有 三點(diǎn) 需要注意一下:
1)Redis 關(guān)閉的過程:斷開與客戶端的連接、持久化文件生成,是一種相對(duì)優(yōu)雅的關(guān)閉方式。
2)除了可以通過 shutdown 命令 關(guān)閉Redis服務(wù) 以外,還可以通過 kill 進(jìn)程號(hào) 的方式關(guān)閉掉 Redis,但是不要 粗暴地 使用 kill -9 強(qiáng)制殺死 Redis服務(wù),不但 不會(huì)做 持久化操作,還會(huì) 造成 緩沖區(qū)等資源不能被優(yōu)雅關(guān)閉,極端情況會(huì)造成 AOF和復(fù)制丟失數(shù)據(jù) 的情況。
3)shutdown 還有一個(gè)參數(shù),代表 是否在關(guān)閉Redis前,生成持久化文件:
redis-cli shutdown nosave|save1.6 Redis 重大版本
Redis 借鑒了 Linux 操作系統(tǒng) 對(duì)于 版本號(hào)的命名規(guī)則:版本號(hào) 第二位數(shù) 如果是 奇數(shù),則為 非穩(wěn)定版本(例如2.7、2.9、3.1),如果是 偶數(shù),則為 穩(wěn)定版本(例如2.6、2.8、3.0、3.2)。
當(dāng)前 奇數(shù)版本 就是下一個(gè) 穩(wěn)定版本 的 開發(fā)版本,例如 2.9版本 是 3.0版本 的開發(fā)版本。
所以我們在生產(chǎn)環(huán)境通常 選取 偶數(shù)版本 的Redis,如果對(duì)于某些新的特性想提前了解和使用,可以選擇最新的奇數(shù)版本。
介紹一門技術(shù)的版本是很多技術(shù)圖書的必備內(nèi)容,通常讀者容易忽略,但隨著你對(duì)這門技術(shù)深入學(xué)習(xí)后,會(huì)覺得“倍感親切”,而且通常也會(huì)關(guān)注 新版本的特性,本小節(jié)將對(duì) Redis 發(fā)展過程中的一些重要版本及特性進(jìn)行說明。
Redis 2.6
Redis 2.6 在 2012 年正式發(fā)布,經(jīng)歷了 17 個(gè)版本,到 2.6.17 版本,相比于 Redis 2.4,主要特性如下:
1)服務(wù)端 支持 Lua 腳本。
2)去掉 虛擬內(nèi)存 相關(guān)功能。
3)放開對(duì) 客戶端連接數(shù) 的硬編碼限制。
4)鍵 的 過期時(shí)間 支持毫秒。
5)從結(jié)點(diǎn) 提供 只讀功能。
6)兩個(gè)新的 位圖命令:bitcount 和 bitop 。
7)增強(qiáng)了 redis-benchmark 的功能:支持 定制化的壓測,CSV輸出 等功能。
8)基于 浮點(diǎn)數(shù) 自增命令:incrbyfloat 和 hincrbyfloat。
9)redis-cli 可以使用 --eval 參數(shù)實(shí)現(xiàn) Lua腳本執(zhí)行。
10)shutdown 命令增強(qiáng)。
11)info 可以按照 section 輸出,并且添加了一些統(tǒng)計(jì)項(xiàng)。
12)重構(gòu)了大量的核心代碼,所有 集群相關(guān)的代碼 都去掉了,cluster 功能 將會(huì)是 3.0版本 最大的亮點(diǎn)。
13)sort 命令優(yōu)化。
Redis 2.8
Redis 2.8 在 2013 年 11 月 22 日正式發(fā)布,經(jīng)歷了 24 個(gè)版本,到 2.8.24 版本,相比于 Redis 2.6,主要特性如下:
1)添加部分 主從復(fù)制 的功能,在一定程度上 降低了 由于 網(wǎng)絡(luò)問題,造成頻繁 全量復(fù)制生成RDB 對(duì)系統(tǒng)造成的壓力。
2)嘗試性地支持 IPv6。
3)可以通過 config set 命令設(shè)置 maxclients。
4)可以用 bind 命令 綁定 多個(gè) IP地址。
5)Redis 設(shè)置 了明顯的 進(jìn)程名,方便使用 ps 命令 查看系統(tǒng)進(jìn)程。
6)config rewrite 命令可以將 config set 持久化到 Redis配置文件 中。
7)發(fā)布訂閱 添加了 pubsub 命令。
8)Redis Sentinel 第二版,相比于 Redis 2.6 的 Redis Sentinel,此版本已經(jīng)變成 生產(chǎn)可用。
Redis 3.0
Redis 3.0 在 2015 年 4 月 1 日 正式發(fā)布,截止到本書完成已經(jīng)到 3.0.7 版本,相比于 Redis 2.8 主要特性如下:
Redis 3.0 最大的改動(dòng)就是添加 Redis的分布式 實(shí)現(xiàn) Redis Cluster,填補(bǔ)了 Redis 官方?jīng)]有 分布式 實(shí)現(xiàn)的空白。
Redis Cluster 經(jīng)歷了 4 年才正式發(fā)布也是有原因的,具體可以參考 Redis Cluster 的開發(fā)日志(http://antirez.com/news/79)。
1)Redis Cluster:Redis 的 官方 分布式實(shí)現(xiàn)。
2)全新的 embedded string 對(duì)象編碼結(jié)果,優(yōu)化 小對(duì)象 內(nèi)存訪問,在特定的工作負(fù)載下 速度大幅提升。
3)lur 算法大幅提升。
4)migrate 連接緩存,大幅提升 鍵遷移的速度。
5)migrate 命令 兩個(gè)新的參數(shù) copy 和 replace。
6)新的 client pause 命令,在指定時(shí)間內(nèi)停止處理客戶端請(qǐng)求。
7)bitcount 命令性能提升。
8)config set 設(shè)置 maxmemory 時(shí)候可以 設(shè)置不同的單位(之前只能是字節(jié)),例如 config set maxmemory 1gb。
9)Redis 日志小做調(diào)整:日志中會(huì)反應(yīng) 當(dāng)前實(shí)例的角色(master 或者 slave)。
10)incr 命令 性能提升。
Redis 3.2
Redis 3.2 在 2016 年 5 月 6 日正式發(fā)布,相比于 Redis 3.0 主要特征如下:
1)添加 GEO 相關(guān)功能。
2)SDS 在 速度 和 節(jié)省空間 上都做了優(yōu)化。
3)支持用 upstart 或者 systemd 管理 Redis 進(jìn)程。
4)新的 List 編碼類型:quicklist。
5)從結(jié)點(diǎn) 讀取 過期數(shù)據(jù) 保證一致性。
6)添加了 hstrlen 命令。
7)增強(qiáng)了 debug 命令,支持了更多的參數(shù)。
8)Lua 腳本功能增強(qiáng)。
9)添加了 Lua Debugger。
10)config set 支持更多的配置參數(shù)。
11)優(yōu)化了 Redis 崩潰 后的相關(guān)報(bào)告。
12)新的 RDB 格式,但是仍然兼容舊的 RDB。
13)加速 RDB 的加載速度。
14)spop 命令支持 個(gè)數(shù) 參數(shù)。
15)cluster nodes 命令得到 加速。
16)Jemalloc 更新到 4.0.3 版本。
Redis 4.0
可能出乎很多人的意料,Redis 3.2 之后的版本是 4.0,而不是 3.4、3.6、3.8。
一般這種 重大版本號(hào) 的 升級(jí) 也意味著 軟件或者工具本身發(fā)生了重大變革,直到本書截稿前,Redis 發(fā)布了 4.0-RC2,下面列出 Redis 4.0 的新特性:
1)提供了 模塊系統(tǒng),方便第三方開發(fā)者 拓展 Redis 的功能,更多模塊詳見:http://redismodules.com。
2)PSYNC 2.0:優(yōu)化了之前版本中,主從節(jié)點(diǎn)切換 必然引起 全量復(fù)制 的問題。
3)提供了新的 緩存剔除 算法:LFU(Last Frequently Used),并對(duì)已有算法進(jìn)行了優(yōu)化。
4)提供了 非阻塞 del 和 flushall/flushdb 功能,有效解決 刪除 bigkey 可能造成的 Redis 阻塞。
5)提供了 RDB-AOF 混合持久式 格式,充分利用了 AOF 和 RDB 各自優(yōu)勢。
6)提供 memory 命令,實(shí)現(xiàn) 對(duì)內(nèi)存更為全面的監(jiān)控統(tǒng)計(jì)。
7)提供了 交互數(shù)據(jù)庫 功能,實(shí)現(xiàn) Redis 內(nèi)部數(shù)據(jù)庫之間的 數(shù)據(jù)置換。
8)Redis Cluster 兼容 NAT 和 Docker。
1.7 本章重點(diǎn)回顧
1)Redis 的 8個(gè)特性:速度快、基于鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu)服務(wù)器、功能豐富、簡單穩(wěn)定、客戶端語言多、持久化、主從復(fù)制、支持高可用和分布式。
2)Redis 并不是 萬金油,有些場景不適合 Redis 進(jìn)行開發(fā)。
3)開發(fā)運(yùn)維結(jié)合 以及 閱讀源碼 是用好 Redis 的重要方法。
4)生產(chǎn)環(huán)境 中使用 配置文件 啟動(dòng) Redis。
5)生產(chǎn)環(huán)境 選取 穩(wěn)定版本的 Redis。
6)Redis 3.0 是重要的里程碑,發(fā)布了 Redis官方的 分布式實(shí)現(xiàn) Redis Cluster。
第2章 API的理解和使用
? Redis 提供了 5種 數(shù)據(jù)結(jié)構(gòu),理解每種數(shù)據(jù)結(jié)構(gòu)的特點(diǎn) 對(duì)于 Redis 開發(fā)運(yùn)維 非常重要,同時(shí)掌握 Redis的單線程命令處理機(jī)制,會(huì)使 數(shù)據(jù)結(jié)構(gòu)和命令的選擇事半功倍,本章內(nèi)容如下:
- 預(yù)備知識(shí):幾個(gè)簡單的全局命令,數(shù)據(jù)結(jié)構(gòu)和內(nèi)部編碼,單線程命令處理機(jī)制分析。
- 5種 數(shù)據(jù)結(jié)構(gòu) 的特點(diǎn),命令使用、應(yīng)用場景。
- 鍵管理、遍歷鍵、數(shù)據(jù)庫管理。
2.1 預(yù)備
? 在正式介紹 5種數(shù)據(jù)結(jié)構(gòu) 之前,了解一下 Redis 的一些 全局命令、數(shù)據(jù)結(jié)構(gòu) 和 內(nèi)部編碼、單線程命令處理機(jī)制 是十分有必要的,它們能為后面內(nèi)容的學(xué)習(xí)打下一個(gè)好的基礎(chǔ),主要體現(xiàn)在兩個(gè)方面:
? 第一、Redis 的命令有上百個(gè),如果純靠死記硬背比較困難,但是如果理解 Redis 的一些機(jī)制,會(huì)發(fā)現(xiàn)這些命令有很強(qiáng)的通用性。
? 第二、Redis不是萬金油,有些數(shù)據(jù)結(jié)構(gòu) 和 命令 必須在特定場景下使用,一旦使用不當(dāng) 可能對(duì) Redis本身 或者 應(yīng)用本身造成致命傷害。
2.1.1 全局命令
? Redis 有 5種數(shù)據(jù)結(jié)構(gòu),它們是 鍵值對(duì)中的 值,對(duì)于 鍵 來說有一些 通用的命令。
查看所有鍵
keys *
下面插入了 3對(duì) 字符串類型的鍵值對(duì):
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 OKkeys * 命令會(huì) 將所有的鍵輸出:
127.0.0.1:6379> keys * 1) "python" 2) "java" 3) "hello"鍵總數(shù)
dbsize
下面插入一個(gè) 列表類型的鍵值對(duì)(值 是多個(gè)元素組成)
127.0.0.1:6379> rpush mylist a b c d e f g (integer) 7dbsize 命令會(huì)返回 當(dāng)前數(shù)據(jù)庫中鍵的總數(shù)。
例如 當(dāng)前數(shù)據(jù)庫有 4 個(gè)鍵,分別是 hello、java、python、mylist,所以 dbsize 的結(jié)果是 4:
127.0.0.1:6379> dbsize (integer) 4dbsize 命令在 計(jì)算鍵總數(shù) 時(shí) 不會(huì)遍歷所有鍵,而是 直接獲取 Redis 內(nèi)置的 鍵總數(shù) 變量,所以 dbsize 命令的 時(shí)間復(fù)雜度 是 O(1)。
而 keys 命令會(huì) 遍歷所有鍵,所以它的 時(shí)間復(fù)雜度 是 O(n),當(dāng) Redis 保存了 大量鍵時(shí),線上環(huán)境 禁止使用。
檢查鍵是否存在
exists key
如果 鍵存在 則返回1,不存在則返回0。
127.0.0.1:6379> exists java (integer) 1 127.0.0.1:6379> exists not_exist_key (integer) 0刪除鍵
del key [key ...]
del 是一個(gè)通用命令,無論值是什么數(shù)據(jù)結(jié)構(gòu)類型,del 命令 都可以將其刪除,例如下面將 字符串類型的鍵 java 和 列表類型的鍵 mylist 分別刪除:
127.0.0.1:6379> del java (integer) 1 127.0.0.1:6379> exists java (integer) 0 127.0.0.1:6379> del mylist (integer) 1 127.0.0.1:6379> exists mylist (integer) 0返回結(jié)果為 成功刪除鍵的個(gè)數(shù),假設(shè)刪除一個(gè)不存在的鍵,就會(huì)返回0:
127.0.0.1:6379> del not_exist_key (integer) 0同時(shí) del 命令可以支持 刪除多個(gè)鍵:
127.0.0.1:6379> set a 1 OK 127.0.0.1:6379> set b 2 OK 127.0.0.1:6379> set c 3 OK 127.0.0.1:6379> del a b c (integer) 3鍵過期
expire key seconds
Redis 支持對(duì) 鍵 添加過期時(shí)間,當(dāng)超過 過期時(shí)間 后,會(huì)自動(dòng)刪除 鍵,例如為 鍵 hello 設(shè)置了 10秒過期時(shí)間:
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> expire hello 10 (integer) 1ttl 命令會(huì)返回 鍵的剩余過期時(shí)間,它有 3種返回值:
- 大于等于0 的整數(shù):鍵剩余的過期時(shí)間。
- -1:鍵沒設(shè)置過期時(shí)間。
- -2:鍵不存在。
可以通過 ttl 命令觀察鍵 hello 的剩余過期時(shí)間:
# 還剩7秒 127.0.0.1:6379> ttl hello (integer) 7 ... # 還剩1秒 127.0.0.1:6379> ttl hello (integer) 1 # 返回結(jié)果為 -2 ,說明 鍵hello 已經(jīng)被刪除 127.0.0.1:6379> ttl hello (integer) -2 127.0.0.1:6379> get hello (nil)有關(guān) 鍵過期 更為詳細(xì)的使用以及原理會(huì)在 2.7 節(jié)介紹。
type key
例如鍵 hello 是字符串類型,返回結(jié)果為 string。
鍵 mylist 是列表類型,返回結(jié)果為 list。
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如果 鍵不存在,則返回 none :
127.0.0.1:6379> type not_exist_key none本小節(jié)只是拋磚引玉,給出幾個(gè)通用的命令,為 5種數(shù)據(jù)結(jié)構(gòu)的使用 做一個(gè)熱身,2.7節(jié) 將對(duì) 鍵管理 做一個(gè)更為詳細(xì)的介紹。
2.1.2 數(shù)據(jù)結(jié)構(gòu)和內(nèi)部編碼
type 命令實(shí)際返回的就是 當(dāng)前鍵的數(shù)據(jù)結(jié)構(gòu)類型,它們分別是:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),但這些只是 Redis 對(duì)外的數(shù)據(jù)結(jié)構(gòu),如圖 2-1 所示。
實(shí)際上 每種 數(shù)據(jù)結(jié)構(gòu) 都有自己 底層的內(nèi)部編碼 實(shí)現(xiàn),而且是 多種實(shí)現(xiàn),這樣 Redis 會(huì)在 合適的場景選擇合適的內(nèi)部編碼,如圖 2-2 所示。
可以看到 每種數(shù)據(jù)結(jié)構(gòu) 都有 兩種以上的內(nèi)部編碼實(shí)現(xiàn),例如 list 數(shù)據(jù)結(jié)構(gòu)包含了 linkedlist 和 ziplist 兩種內(nèi)部編碼。同時(shí) 有些內(nèi)部編碼,例如 ziplist,可以作為 多種外部數(shù)據(jù)結(jié)構(gòu) 的 內(nèi)部實(shí)現(xiàn),可以通過 object encoding 命令 查詢內(nèi)部編碼:
127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> object encoding mylist "ziplist"[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-BB8LhGU3-1621301836862)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517120419269.png)]
可以看到 鍵hello 對(duì)應(yīng)值的內(nèi)部編碼是 embstr,鍵 mylist 對(duì)應(yīng)值的內(nèi)部編碼是 ziplist。
Redis 這樣設(shè)計(jì)有兩個(gè)好處:
第一,可以改進(jìn)內(nèi)部編碼,而對(duì)外的數(shù)據(jù)結(jié)構(gòu)和命令沒有影響,這樣一旦開發(fā)出更優(yōu)秀的內(nèi)部編碼,無需改動(dòng)外部數(shù)據(jù)結(jié)構(gòu)和命令,例如 Redis 3.2 提供了 quicklist,結(jié)合了 ziplist 和 linkedlist 兩者的優(yōu)勢,為 列表類型 提供了 一種更為優(yōu)秀的內(nèi)部編碼實(shí)現(xiàn),而對(duì) 外部用戶 來說基本感知不到。
第二,多種內(nèi)部編碼實(shí)現(xiàn) 可以在 不同場景下發(fā)揮各自的優(yōu)勢,例如 ziplist 比較節(jié)省內(nèi)存,但是在 列表元素 比較多 的情況下,性能會(huì)有所下降,這時(shí)候 Redis 會(huì)根據(jù) 配置選項(xiàng) 將 列表類型的內(nèi)部實(shí)現(xiàn) 轉(zhuǎn)換為 linkedlist。
| 內(nèi)部編碼 | raw | hashtable | linkedlist | hashtable | skiplist |
| int | ziplist | ziplist | intset | ziplist | |
| embstr |
2.1.3 單線程架構(gòu)
Redis 使用了 單線程架構(gòu) 和 I/O多路復(fù)用模型 來實(shí)現(xiàn) 高性能 的 內(nèi)存數(shù)據(jù)庫服務(wù),本節(jié)首先通過 多個(gè)客戶端命令調(diào)用 的例子說明 Redis 單線程命令處理機(jī)制,接著分析 Redis 單線程模型 為什么性能如此之高,最終給出為什么 理解單線程模型 是 使用和運(yùn)維 Redis 的關(guān)鍵。
引出單線程模型
現(xiàn)在開啟了 三個(gè) redis-cli 客戶端 同時(shí)執(zhí)行命令、
客戶端1 設(shè)置一個(gè) 字符串鍵值對(duì):
127.0.0.1:6379> set hello world客戶端2 對(duì) counter 做自增操作:
127.0.0.1:6379> incr counter客戶端3 對(duì) counter 做自增操作:
127.0.0.1:6379> incr counterRedis 客戶端 與 服務(wù)端 的模型可以簡化成 圖2-3,每次 客戶端調(diào)用都經(jīng)歷了 發(fā)送命令、執(zhí)行命令、返回結(jié)果 三個(gè)過程。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-oAqoELf9-1621301836863)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517124231618.png)]
其中 第2步 是重點(diǎn)要討論的,因?yàn)?Redis 是 單線程 來處理命令的,所以 一條命令從客戶端 到達(dá) 服務(wù)端 不會(huì)立刻被執(zhí)行,所有命令都會(huì)進(jìn)入一個(gè) 隊(duì)列 中,然后 逐個(gè)被執(zhí)行。
所以上面 3個(gè)客戶端 命令的執(zhí)行順序是不確定的(如圖2-4所示),但是可以確定 不會(huì)有兩條命令被同時(shí)執(zhí)行(如圖2-5所示),所以 incr 命令無論怎么執(zhí)行最終結(jié)果都是 2,不會(huì)產(chǎn)生并發(fā)問題,這就是 Redis 單線程的基本模型。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-4uukxun5-1621301836864)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517124353808.png)]
但是像 發(fā)送命令、返回結(jié)果、命令排隊(duì) 肯定不像描述的這么簡單,Redis 使用了 I/O多路復(fù)用技術(shù) 來解決 I/O 的問題,下一節(jié)將進(jìn)行介紹。
為什么單線程還能這么快
通常來講,單線程處理能力 要比 多線程 差,
例如有 10 000 斤貨物,每輛車的運(yùn)載能力是每次 200斤,那么要50次才能完成,
但是如果有 50輛車,只要安排合理,只需一次就可以完成任務(wù)。
那么為什么 Redis 使用 單線程模型 會(huì)達(dá)到每秒萬級(jí)別的處理能力 呢?
可以將其歸結(jié)為三點(diǎn):
第一,純內(nèi)存訪問,Redis 將 所有數(shù)據(jù)放在 內(nèi)存 中,內(nèi)存 的 響應(yīng)時(shí)長 大約為 100納秒,這是 Redis 達(dá)到每秒 萬級(jí)別 訪問的重要基礎(chǔ)。
第二,非阻塞I/O,Redis 使用 epoll 作為 I/O 多路復(fù)用技術(shù) 的 實(shí)現(xiàn),再加上 Redis 自身的 事件處理模型 將 epoll 中的 連接、讀寫、關(guān)閉 都轉(zhuǎn)換為 事件,不在 網(wǎng)絡(luò) I/O 上浪費(fèi)過多的時(shí)間,如圖 2-6 所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-8LbJKfu9-1621301836865)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517124935714.png)]
第三,單線程 避免了 線程切換 和 競態(tài)產(chǎn)生 的消耗。
既然采用 單線程 就能達(dá)到如此高的性能,那么也不失為一種不錯(cuò)的選擇,
因此單線程能帶來幾個(gè)好處:
? 第一,單線程 可以 簡化 數(shù)據(jù)結(jié)構(gòu)和算法 的實(shí)現(xiàn)。如果對(duì)高級(jí)編程語言熟悉的讀者應(yīng)該了解 并發(fā)數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn) 不但困難 而且 開發(fā)測試比較麻煩。
? 第二,單線程 避免了 線程切換 和 競態(tài)產(chǎn)生 的消耗,對(duì)于 服務(wù)端開發(fā) 來說,鎖 和 線程切換 通常是 性能殺手。
但是 單線程 會(huì)有一個(gè)問題:對(duì)于 每個(gè)命令的執(zhí)行時(shí)間 是有要求的。如果某個(gè)命令 執(zhí)行過長,會(huì)造成 其他命令的 阻塞,對(duì)于 Redis 這種 高性能的服務(wù) 來說是致命的,所以 Redis 是面向 快速執(zhí)行 場景的數(shù)據(jù)庫。
單線程機(jī)制 很容易被初學(xué)者忽視,但筆者認(rèn)為 Redis 單線程機(jī)制 是 開發(fā)和運(yùn)維人員 使用和理解 Redis 的核心之一,隨著后面的學(xué)習(xí),相信讀者會(huì)逐步理解。
2.2 字符串
字符串類型 是 Redis 最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)。
首先 鍵 都是 字符串類型,而且 其他幾種數(shù)據(jù)結(jié)構(gòu) 都是在 字符串類型 基礎(chǔ)上構(gòu)建的,所以 字符串類型 能為 其他四種數(shù)據(jù)結(jié)構(gòu)的學(xué)習(xí) 奠定基礎(chǔ)。
如圖 2-7 所示,字符串類型的值 實(shí)際可以是 字符串(簡單的字符串、復(fù)雜的字符串(例如JSON、XML))、數(shù)字(整數(shù)、浮點(diǎn)數(shù)),甚至是二進(jìn)制(圖片、音頻、視頻),但是 值最大 不能超過 512MB。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-zYS8Dm8l-1621301836866)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517130333277.png)]
2.2.1 命令
? 字符串類型 的 命令 比較多,本小節(jié) 將按照 常用和不常用 兩個(gè)維度 進(jìn)行說明,但是這里 常用和不常用 是相對(duì)的,希望讀者盡可能都去了解和掌握。
常用命令
(1)設(shè)置值
set key value [ex seconds] [px milliseconds] [nx|xx]下面操作 設(shè)置鍵為hello,值為world的鍵值對(duì),返回結(jié)果為 OK 代表設(shè)置成功:
127.0.0.1:6379> set hello world OKset 命令 有幾個(gè)選項(xiàng):
- ex seconds :為 鍵 設(shè)置 秒級(jí) 過期時(shí)間。
- px milliseconds:為 鍵 設(shè)置 毫秒級(jí) 過期時(shí)間。
- nx:鍵 必須不存在,才可以設(shè)置成功,用于添加。
- xx:與 nx 相反,鍵必須存在,才可以設(shè)置成功,用于更新。
除了 set 選項(xiàng),Redis 還提供了 setex 和 setnx 兩個(gè)命令:
setex key seconds value setnx key value它們的作用和 ex 和 nx 選項(xiàng)是一樣的。
下面的例子說明了 set、setnx、set xx 的區(qū)別。
當(dāng)前 鍵 hello 不存在:
127.0.0.1:6379> exists hello (integer) 0設(shè)置 鍵為hello, 值為world 的鍵值對(duì):
127.0.0.1:6379> set hello world OK因?yàn)殒I hello 已存在,所以 setnx 失敗,返回結(jié)果為 0:
127.0.0.1:6379> setnx hello world (integer) 0因?yàn)殒I hello 已存在,所以 set xx 成功,返回結(jié)果為 OK。
127.0.0.1:6379> set hello jedis xx OKsetnx 和 setxx 在實(shí)際使用中有什么應(yīng)用場景嗎?
以 setnx 命令為例子,由于 Redis 的 單線程命令處理機(jī)制,如果有 多個(gè)客戶端 同時(shí)執(zhí)行 setnx key value,根據(jù) setnx 的特性 只有一個(gè)客戶端能設(shè)置成功,setnx 可以作為 分布式鎖 的一種實(shí)現(xiàn)方案,Redis 官方給出了使用 setnx 實(shí)現(xiàn)分布式鎖 的方法:http://redis.io/topics/distlock。
(2)獲取值
get key下面操作 獲取鍵hello的值:
127.0.0.1:6379> get hello "world"如果 要獲取的鍵 不存在,則返回 nil(空):
127.0.0.1:6379> get not_exist_key (nil)(3)批量設(shè)置值
mset key value [key value ...]下面操作通過 mset 命令一次性設(shè)置 4個(gè)鍵值對(duì):
127.0.0.1:6379> mset a 1 b 2 c 3 d 4 OK(4)批量獲取值
mget key [key ...]下面操作批量獲取了鍵 a、b、c、d的值:
127.0.0.1:6379> mget a b c d 1) "1" 2) "2" 3) "3" 4) "4"如果有些 鍵 不存在,那么它的值 為 nil(空),結(jié)果是按照 傳入鍵 的順序 返回:
127.0.0.1:6379> mget a b c f 1) "1" 2) "2" 3) "3" 4) (nil)批量操作 命令 可以 有效提高開發(fā)效率,假如沒有 mget 這樣的命令,要執(zhí)行 n 次 get 命令 需要按照?qǐng)D 2-8 的方式來執(zhí)行,具體耗時(shí)如下:
n 次 get 時(shí)間 = n 次網(wǎng)絡(luò)時(shí)間 + n 次命令時(shí)間[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-tpk8mJqK-1621301836867)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517133127643.png)]
使用 mget 命令后,要執(zhí)行 n 次 get 命令操作 只需要按照?qǐng)D 2-9 的方式來完成,具體耗時(shí)如下:
n 次 get 時(shí)間 = 1 次網(wǎng)絡(luò)時(shí)間 + n 次命令時(shí)間[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-KrQD9scU-1621301836868)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517133254794.png)]
Redis 可以支撐 每秒數(shù)萬 的讀寫操作,但是這指的是 Redis 服務(wù)端的處理能力,
對(duì)于客戶端來說,一次命令除了 命令時(shí)間 還有 網(wǎng)絡(luò)時(shí)間,假設(shè) 網(wǎng)絡(luò)時(shí)間為 1 毫秒,命令時(shí)間為 0.1 毫秒(按照每秒處理 1 萬條命令算),那么執(zhí)行 1000次 get 命令 和 1次 mget 命令的區(qū)別 如表 2-1,因?yàn)?Redis 的處理能力已經(jīng)足夠高,對(duì)于開發(fā)人員來說,網(wǎng)絡(luò) 可能會(huì)成為 性能 的瓶頸。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-7z4vsnzX-1621301836869)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517133726771.png)]
| 1 000 次 get | 1 000 × 1 + 1 000 × 0.1 = 1 100 毫秒 = 1.1 秒 |
| 1 次 mget(組裝了 1 000個(gè)鍵值對(duì)) | 1 × 1 + 1 000 × 0.1 = 101 毫秒 = 0.101 秒 |
學(xué)會(huì)使用 批量操作,有助于 提高業(yè)務(wù)處理效率,但是要注意的是 每次批量操作 所發(fā)送的命令數(shù) 不是無節(jié)制的,如果數(shù)量過多 可能造成 Redis 阻塞 或者 網(wǎng)絡(luò)擁塞。
(5)計(jì)數(shù)
incr keyincr 命令用于對(duì)值 做自增操作,返回結(jié)果分為三種情況:
- 值不是整數(shù),返回錯(cuò)誤。
- 值是整數(shù),返回自增后的結(jié)果。
- 鍵不存在,按照值為0 自增,返回結(jié)果為1。
例如對(duì)一個(gè) 不存在的鍵 執(zhí)行 incr 操作后,返回結(jié)果是1:
127.0.0.1:6379> exists key (integer) 0 127.0.0.1:6379> incr key (integer) 1再次對(duì)鍵 執(zhí)行 incr 命令,返回結(jié)果是 2:
127.0.0.1:6379> incr key (integer) 2如果值 不是整數(shù),那么會(huì) 返回錯(cuò)誤。
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> incr hello (error) ERR value is not an integer or out of range除了 incr 命令,Redis 提供了 decr(自減)、incrby(自增指定數(shù)字)、decrby(自減指定數(shù)字)、incrbyfloat(自增浮點(diǎn)數(shù)):
decr key incrby key increment decrby key decrement incrbyfloat key increment很多 存儲(chǔ)系統(tǒng)和編程語言內(nèi)部 使用 CAS機(jī)制實(shí)現(xiàn)計(jì)數(shù)功能,會(huì)有一定的CPU開銷,但在 Redis 中完全不存在這個(gè)問題,因?yàn)?Redis 是單線程架構(gòu),任何命令到了 Redis 服務(wù)端 都要 順序執(zhí)行。
不常用命令
(1)追加值
append key valueappend 可以向 字符串尾部 追加值,例如:
127.0.0.1:6379> get key "redis" 127.0.0.1:6379> append key world (integer) 10 127.0.0.1:6379> get key "redisworld"(2)字符串長度
strlen key例如,當(dāng)前值為 redisworld,所以返回值為10:
127.0.0.1:6379> get key "redisworld" 127.0.0.1:6379> strlen key (integer) 10下面操作返回結(jié)果為 6,因?yàn)?每個(gè)中文占用 3個(gè)字節(jié):
127.0.0.1:6379> set hello "世界" OK 127.0.0.1:6379> strlen hello (integer) 6(3)設(shè)置并返回原值
getset key valuegetset 和 set 一樣會(huì) 設(shè)置值,但是不同的是,它同時(shí)會(huì) 返回鍵原來的值,例如:
127.0.0.1:6379> getset hello world (nil) 127.0.0.1:6379> getset hello redis "world"(4)設(shè)置 指定位置 的 字符
setrange key offeset value下面操作將由 pest 變?yōu)榱?best:
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"(5)獲取 部分字符串
getrange key start endstart 和 end 分別是 開始 和 結(jié)束的 偏移量([start, end]),偏移量 從 0 開始計(jì)算,例如下面操作獲取了 值 best 的 前兩個(gè)字符。
127.0.0.1:6379> getrange redis 0 1 "be"表 2-2 是 字符串類型命令的 時(shí)間復(fù)雜度,開發(fā)人員可以參考此表,結(jié)合自身業(yè)務(wù)需求 和 數(shù)據(jù)大小 選擇合適的命令。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-bA8PTUZ3-1621301836874)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517141143590.png)]
| set key value | O(1) |
| get key | O(1) |
| del key [key …] | O(k),k 是鍵的個(gè)數(shù) |
| mset key value [key value …] | O(k),k 是鍵的個(gè)數(shù) |
| mget key [key …] | O(k),k 是鍵的個(gè)數(shù) |
| incr key | O(1) |
| decr key | O(1) |
| incrby key increment | O(1) |
| decrby key decrement | O(1) |
| incrbyfloat key increment | O(1) |
| append key value | O(1) |
| strlen key | O(1) |
| setrange key offset value | O(1) |
| getrange key start end | O(n),n是字符串長度,由于獲取字符串非???#xff0c;所以如果字符串不是很長,可以視同為O(1) |
2.2.2 內(nèi)部編碼
字符串類型的 內(nèi)部編碼 有3種:
- int:8個(gè)字節(jié)的長整型。
- embstr:小于等于39個(gè)字節(jié)的字符串。
- raw:大于39個(gè)字節(jié)的字符串。
Redis 會(huì)根據(jù) 當(dāng)前值的 類型 和 長度 決定使用哪種內(nèi)部編碼 實(shí)現(xiàn)。
整數(shù)類型示例如下:
127.0.0.1:6379> set key 8653 OK 127.0.0.1:6379> object encoding key "int"短字符串示例如下:
# 小于等于 39 個(gè)字節(jié) 的字符串:embstr 127.0.0.1:6379> set key "hello world" OK 127.0.0.1:6379> object encoding key "embstr"長字符串示例如下:
# 大于 39個(gè)字節(jié) 的字符串:raw 127.0.0.1:6379> set key "one string greater than 39 byte........." OK 127.0.0.1:6379> object encoding key "raw" 127.0.0.1:6379> strlen key (integer) 40有關(guān) 字符串類型的內(nèi)存優(yōu)化技巧 將在 8.3 節(jié)詳細(xì)介紹。
2.2.3 典型使用場景
緩存功能
圖 2-10 是比較典型的 緩存使用場景,其中 Redis 作為 緩存層,MySQL 作為 存儲(chǔ)層,絕大部分請(qǐng)求的數(shù)據(jù)都是從 Redis 中獲取。
由于 Redis 具有支撐 高并發(fā) 的特性,所以 緩存 通常能起到 加速讀寫 和 降低后端壓力 的作用。
下面?zhèn)未a模擬了 圖 2-10 的訪問過程:
1) 該函數(shù)用于 獲取用戶的基礎(chǔ)信息:
UserInfo getUserInfo(long id){... }2)首先從 Redis 獲取用戶信息:
//定義鍵 userRedisKey = "user:info:"+id;//從 Redis 獲取值 value = redis.get(userRedisKey); if(value != null){//將值 進(jìn)行反序列化 為UserInfo 并返回結(jié)果userInfo = deserialize(value);return UserInfo; }開發(fā)提示
與 MySQL 等關(guān)系型數(shù)據(jù)庫不同的是,Redis 沒有 命令空間,而且也沒有對(duì) 鍵名 有強(qiáng)制要求(除了 不能使用一些特殊字符)。
但 設(shè)計(jì)合理的鍵名,有利于 防止鍵沖突 和 項(xiàng)目的可維護(hù)性,比較推薦的方式是使用 “業(yè)務(wù)名 : 對(duì)象名 : id : [ 屬性 ]” 作為 鍵名(也可以不是分號(hào))。
例如 MySQL 的 數(shù)據(jù)庫名為 vs,用戶表名為 user,那么對(duì)應(yīng)的鍵可以用 “vs:user:1”,“vs:user:1:name"來表示,例如"user : {uid} : friends : messages : {mid}”,可以在 能描述鍵含義的前提下 適當(dāng)減少鍵的長度,例如變?yōu)?“u : {uid} : fr : m : {mid}”,從而 減少由于 鍵過長的內(nèi)存浪費(fèi)。
3)如果沒有從 Redis 獲取到用戶信息,需要從 MySQL 中進(jìn)行獲取,并將結(jié)果回寫到 Redis,添加 1小時(shí)(3600秒)過期時(shí)間:
//從 MySQL 獲取用戶信息 userInfo = mysql.get(id);//將 userInfo 序列化,并存入 redis redis.setex(userRedisKey, 3600, serialize(userInfo));//返回結(jié)果 return userInfo;整個(gè)功能的偽代碼如下:
UserInfo getUserInfo(long id){userRedisKey = "user:info:"+id;value = redis.get(userRedisKey);UserInfo userInfo;if(value != null){userInfo = deserialize(value);}else{userInfo = mysql.get(id);if(userInfo != null)redis.setex(userRedisKey, 3600, serialize(userInfo));}return userInfo; }計(jì)數(shù)
許多應(yīng)用都會(huì)使用 Redis 作為計(jì)數(shù)的 基礎(chǔ)工具,它可以實(shí)現(xiàn) 快速計(jì)數(shù)、查詢緩存的功能,同時(shí) 數(shù)據(jù) 可以 異步落地 到 其他數(shù)據(jù)源。
例如筆者所在團(tuán)隊(duì)的 視頻播放數(shù)系統(tǒng) 就是使用 Redis 作為視頻播放數(shù) 計(jì)數(shù)的基礎(chǔ)組件,用戶每播放一次視頻,相應(yīng)的視頻播放數(shù) 就會(huì) 自增1。
long incrVideoCounter(long id){key = "Video:playCount:"+id;return redis.incr(key); }開發(fā)提示
實(shí)際上一個(gè)真實(shí)的 計(jì)數(shù)系統(tǒng) 要考慮的問題會(huì)很多:防作弊、按照不同維度計(jì)數(shù),數(shù)據(jù)持久化到底層數(shù)據(jù)源等。
共享Session
如圖 2-11 所示,一個(gè) 分布式 Web 服務(wù) 將用戶的 Session信息 (例如用戶登錄信息)保存在各自服務(wù)器中,這樣會(huì)造成一個(gè)問題,出于負(fù)載均衡的考慮,分布式服務(wù) 會(huì)將 用戶的訪問 均衡到不同服務(wù)器上,用戶刷新一次訪問 可能會(huì)發(fā)現(xiàn) 需要重新登錄,這個(gè)問題是 用戶無法容忍的。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-cRZQHxM6-1621301836877)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517150020342.png)]
為了解決這個(gè)問題,可以使用 Redis 將用戶的Session進(jìn)行集中管理,如圖 2-12 所示,在這種模式下 只要保證 Redis是 高可用 和 擴(kuò)展性 的,每次用戶更新或者查詢登錄信息 都是直接從 Redis 中集中獲取。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-jG7RsGBG-1621301836878)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517150425297.png)]
限速
很多應(yīng)用處于安全的考慮,會(huì)在每次進(jìn)行登錄時(shí),讓用戶輸入手機(jī)驗(yàn)證碼,從而確定是否是用戶本人。
但是為了 短信接口 不被頻繁訪問,會(huì)限制 用戶每分鐘獲取驗(yàn)證碼的頻率,例如一分鐘不能超過 5次,如圖 2-13 所示。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-ZEaLrmw9-1621301836879)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517150617130.png)]
此功能可以使用 Redis 來實(shí)現(xiàn),下面的偽代碼給出了基本實(shí)現(xiàn)思路:
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 實(shí)現(xiàn)了 限速功能,例如一些網(wǎng)站限制一個(gè) IP地址 不能在一秒鐘之內(nèi)訪問超過 n次 也可以采用類似的思路。
除了上面介紹的幾種使用場景,字符串還有非常多的使用場景,開發(fā)人員可以結(jié)合字符串提供的相應(yīng)命令充分發(fā)揮自己的想象力。
2.3 哈希
? 幾乎所有的編程語言都提供了 哈希(hash)類型,它們的叫法可能是 哈希、字典、關(guān)聯(lián)數(shù)組。
在 Redis 中,哈希類型是指 鍵本身 又是一個(gè)鍵值對(duì)結(jié)構(gòu),形如 value={{field1,value1}, ...{fieldN,valueN}},Redis鍵值對(duì) 和 哈希類型 兩者的關(guān)系 可以用圖 2-14來表示。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-xJws3o4J-1621301836879)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517151327641.png)]
注意
哈希類型 中的 映射關(guān)系 叫做 field-value,
注意這里的 value 是指 field 對(duì)應(yīng)的值,
不是 鍵對(duì)應(yīng)的值,請(qǐng)注意 value 在不同上下文的作用。
2.3.1 命令
(1)設(shè)置值
hset key field value下面為 user:1 添加一對(duì) field-value:
127.0.0.1:6379> hset user:1 name tom (integer) 1如果 設(shè)置成功 會(huì)返回1,反之會(huì)返回0。
此外 Redis 提供了 hsetnx 命令,它們的關(guān)系就像 set 和 setnx 命令一樣,只不過作用域 由 鍵 變?yōu)?field。
(2)獲取值
hget key field例如,下面操作獲取 user:1 的 name 域(屬性)對(duì)應(yīng)的值:
127.0.0.1:6379> hget user:1 name "tom"如果 鍵 或 field 不存在,會(huì)返回 nil:
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 ...]hdel 會(huì)刪除 一個(gè) 或 多個(gè) field,返回結(jié)果為 成功刪除 field 的個(gè)數(shù),例如:
127.0.0.1:6379> hdel user:1 name (integer) 1 127.0.0.1:6379> hdel user:1 age (integer) 0(4)計(jì)算 field 個(gè)數(shù)
hlen key例如 user:1 有 3 個(gè)field:
127.0.0.1:6379> hset user:1 name tom (integer) 1 127.0.0.1:6379> hset user:1 age 23 (integer) 1 127.0.0.1:6379> hset user:1 city tianjin (integer) 1 127.0.0.1:6379> hlen user:1 (integer) 3(5)批量設(shè)置 或 獲取 field-value
hmget key field [field ...] hmset key field value [field value ...]hmset 和 hmget 分別是 批量設(shè)置 和 獲取 field-value,hmset 需要的參數(shù)是 key 和 多對(duì) field-value,hmget 需要的參數(shù)是 key 和多個(gè) field。
例如:
127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin OK 127.0.0.1:6379> hmget user:1 name city 1) "mike" 2) "tianjin"(6)判斷 field 是否存在
hexists key field例如,user:1 包含 name 域,所以 返回結(jié)果為 1,不包含時(shí)返回0:
127.0.0.1:6379> hexists user:1 name (integer) 1(7)獲取所有field
hkeys keyhkeys 命令應(yīng)該叫 hfields 更為恰當(dāng),它返回 指定哈希鍵 所有的field,例如:
127.0.0.1:6379> hkeys user:1 1) "name" 2) "age" 3) "city"(8)獲取所有 value
hvals key下面操作 獲取 user:1 全部 value:
127.0.0.1:6379> hvals user:1 1) "mike" 2) "12" 3) "tianjin"(9)獲取所有的field-value
hgetall key下面操作獲取 user:1 所有的field-value
127.0.0.1:6379> hgetall user:1 1) "name" 2) "mike" 3) "age" 4) "12" 5) "city" 6) "tianjin"開發(fā)提示
在使用 hgetall 時(shí),如果 哈希元素個(gè)數(shù)比較多,會(huì)存在阻塞 Redis 的可能。
如果開發(fā)人員 只需要獲取部分field,可以使用 hmget,如果 一定要獲取 全部 field-value,可以使用 hscan 命令,該命令會(huì) 漸進(jìn)式遍歷哈希類型,hscan 將在 2.7節(jié) 介紹。
(10) hincrby hincrbyfloat
hincrby key field hincrbyfloat key fieldhincrby 和 hincrbyfloat,就像 incrby 和 incrbyfloat 命令一樣,但是它們的 作用域 是 field。
(11)計(jì)算 value 的字符串長度(需要 Redis 3.2 以上)
hstrlen key field例如 hget user:1 name 的 value 是 tom,那么 hstrlen 的返回結(jié)果是 3:
127.0.0.1:6379> hstrlen user:1 name (integer) 3表 2-3 是 哈希類型命令的時(shí)間復(fù)雜度,開發(fā)人員可以參考此表選擇合適的命令。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-rY0t7IhA-1621301836880)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517163340964.png)]
| hset key field value | O(1) |
| hget key field | O(1) |
| hdel key field [field …] | O(k),k 是field個(gè)數(shù) |
| hlen key | O(1) |
| hgetall key | O(n),n 是field總數(shù) |
| hmget key field [field …] | O(k),k 是field的個(gè)數(shù) |
| hmset key field value [field value …] | O(k),k 是field的個(gè)數(shù) |
| hexists key field | O(1) |
| hkeys key | O(n),n 是field總數(shù) |
| hvals key | O(n),n 是field 總數(shù) |
| hsetnx key field value | O(1) |
| hincrby key field increment | O(1) |
| hincrbyfloat key field increment | O(1) |
| hstrlen key field | O(1) |
2.3.2 內(nèi)部編碼
? 哈希類型 的 內(nèi)部編碼 有兩種:
-
ziplist(壓縮列表)
- 當(dāng) 哈希類型元素個(gè)數(shù) 小于 hash-max-ziplist-entries 配置(默認(rèn) 512 個(gè))、同時(shí) 所有 值 都小于 hash-max-ziplist-value 配置(默認(rèn) 64字節(jié))時(shí),Redis 會(huì)使用 ziplist 作為 哈希的 內(nèi)部實(shí)現(xiàn),ziplist 使用更加 緊湊的結(jié)構(gòu) 實(shí)現(xiàn) 多個(gè)元素的連續(xù)存儲(chǔ),所以在 節(jié)省內(nèi)存方面 比 hashtable 更加優(yōu)秀。
-
hashtable(哈希表)
- 當(dāng) 哈希類型 無法滿足 ziplist 的條件 時(shí),Redis 會(huì)使用 hashtable 作為 哈希的內(nèi)部實(shí)現(xiàn),因?yàn)榇藭r(shí) ziplist 的 讀寫效率 會(huì)下降,而 hashtable 的 讀寫時(shí)間復(fù)雜度為O(1)。
下面的示例演示了 哈希類型的內(nèi)部編碼,以及相應(yīng)的變化。
1)當(dāng) field 個(gè)數(shù)比較少 且 沒有大的 value 時(shí),內(nèi)部編碼為 ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 OK 127.0.0.1:6379> object encoding hashkey "ziplist"2.1)當(dāng)有 value 大于 64 字節(jié),內(nèi)部編碼會(huì)由 ziplist 變?yōu)?hashtable:
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64bute...忽略..." OK 127.0.0.1:6379> object encoding hashkey "hashtable"2.2)當(dāng) field 個(gè)數(shù)超過 512,內(nèi)部編碼也會(huì)由 ziplist 變?yōu)?hashtable:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ...忽略... f513 v513 OK 127.0.0.1:6379> object encoding hashkey "hashtable"有關(guān) 哈希類型的內(nèi)存優(yōu)化技巧 將在8.3節(jié)中詳細(xì)介紹。
2.3.3 使用場景
圖 2-15 為 關(guān)系型數(shù)據(jù)表記錄 的兩條用戶信息,用戶的屬性 作為 表的列,每條用戶信息 作為 行。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-R2soFSmP-1621301836881)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517171153802.png)]
| 1 | tom | 23 | beijing |
| 2 | mike | 30 | tianjin |
如果將其用 哈希類型 存儲(chǔ),如圖 2-16所示。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-LFYe6u71-1621301836881)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517171236539.png)]
相比于使用 字符串序列化 緩存 用戶信息,哈希類型 變得更加直觀,并且在 更新操作上 會(huì)更加便捷。
可以將 每個(gè)用戶的 id 定義為 鍵后綴,多對(duì) field-value 對(duì)應(yīng) 每個(gè)用戶的屬性,類似如下偽代碼:
UserInfo getUserInfo(long id){//用戶id 作為 key 后綴userRedisKey = "user:info:" + id;//使用 hgetall 獲取所有用戶信息映射關(guān)系userInfoMap = redis.hgetall(userRedisKey);UserInfo userInfo;if (userInfoMap != null){//將映射關(guān)系轉(zhuǎn)換為 UserInfouserInfo = transferMapToUserInfo(userInfoMap);}else{// 從 MySQL 中獲取 用戶信息userInfo = mysql.get(id);//將UserInfo 變?yōu)橛成潢P(guān)系 使用 hmset 保存到 Redis 中redis.hmset(userRedisKey,transferUserInfoToMap(userInfo));//添加過期時(shí)間redis.expire(userRedisKey, 3600);}return userInfo; }但是需要注意的是 哈希類型 和 關(guān)系型數(shù)據(jù)庫 有 兩點(diǎn)不同之處:
- 哈希類型 是 稀疏的,而 關(guān)系型數(shù)據(jù)庫 是完全 結(jié)構(gòu)化的,例如 哈希類型 每個(gè)鍵 可以有不同的 field,而 關(guān)系型數(shù)據(jù)庫一旦 添加新的列,所有行 都要為其 設(shè)置值(即使為NULL),如圖2-17所示。
- 關(guān)系型數(shù)據(jù)庫 可以做 復(fù)雜的關(guān)系查詢,而 Redis 去模擬關(guān)系型復(fù)雜查詢 開發(fā)困難,維護(hù)成本高。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-6IYVNvHA-1621301836882)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517172939955.png)]
開發(fā)人員需要將兩者的特點(diǎn)搞清楚,才能在合適的場景使用合適的技術(shù)。
到目前為止,我們已經(jīng)能夠 用三種方法緩存用戶信息,下面給出 三種方案的實(shí)現(xiàn)方法和優(yōu)缺點(diǎn)分析。
1)原生字符串類型:每個(gè)屬性一個(gè)鍵。
set user:1:name tom set user:1:age 23 set user:1:city beijing優(yōu)點(diǎn):簡單直觀,每個(gè)屬性 都 支持更新 操作。
缺點(diǎn):占用過多的鍵,內(nèi)存占用量較大,同時(shí) 用戶信息 內(nèi)聚性 比較差,所以此種方案 一般不會(huì)在 生產(chǎn)環(huán)境使用。
2)序列化 字符串類型:將 用戶信息 序列化后 用一個(gè)鍵保存。
set user:1 serialize(userInfo)優(yōu)點(diǎn):簡化編程,如果 合理的使用序列化 可以提高內(nèi)存的使用率。
缺點(diǎn):序列化和反序列化 有一定的開銷,同時(shí)每次更新屬性都需要把全部數(shù)據(jù) 取出進(jìn)行 反序列化,更新后再序列化到 Redis 中。
3)哈希類型:每個(gè)用戶屬性使用一對(duì) field-value,但是只用一個(gè)鍵保存。
hmset user:1 name tom age 23 city beijing優(yōu)點(diǎn):簡單直觀,如果 使用合理 可以減少 內(nèi)存空間的使用。
缺點(diǎn):要控制 哈希在 ziplist 和 hashtable 兩種內(nèi)部編碼的轉(zhuǎn)換,hashtable 會(huì)消耗更多內(nèi)存。
2.4 列表
列表(list)類型 是用來 存儲(chǔ)多個(gè) 有序的 字符串,
如圖 2-18 所示,a、b、c、d、e 五個(gè)元素 從左到右 組成了一個(gè) 有序的列表,列表中的 每個(gè)字符串 稱為 元素(element),
一個(gè)列表 最多可以存儲(chǔ) 2的32次方-1 個(gè)元素。
在 Redis 中,可以對(duì) 列表 兩端 插入(push)和 彈出(pop),還可以獲取 指定范圍的元素列表、獲取 指定索引下標(biāo)的元素 等(如圖 2-18 和 圖 2-19 所示)。
列表 是一種 比較靈活的 數(shù)據(jù)結(jié)構(gòu),它可以充當(dāng) 棧 和 隊(duì)列 的角色,在實(shí)際開發(fā)上 有很多應(yīng)用場景。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-NI5bMq4X-1621301872080)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517205903000.png)]
列表類型 有 兩個(gè)特點(diǎn):
第一、列表 中的 元素 是 有序的,這就意味著可以通過 索引下標(biāo) 獲取某個(gè)元素 或者 某個(gè)范圍內(nèi)的 元素列表,例如要獲取 圖 2-19的 第5個(gè)元素,可以執(zhí)行 lindex user:1:message 4 (索引從 0 算起)就可以得到元素e。
第二、列表 中的 元素 可以是 重復(fù)的,例如圖 2-20所示 列表中 包含了 兩個(gè)字符串 a。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-5Wnl6pK9-1621301872082)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517210257862.png)]
這兩個(gè)特點(diǎn)在后面介紹 集合 和 有序集合 后,會(huì)顯得更加突出,因此在考慮是否使用該數(shù)據(jù)結(jié)構(gòu)前,首先需要弄清楚 列表 數(shù)據(jù)結(jié)構(gòu) 的特點(diǎn)。
2.4.1 命令
下面將按照對(duì) 列表的 5種操作類型 對(duì)命令 進(jìn)行介紹,命令如表2-4所示。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-PLEeBqM9-1621301872085)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517210707928.png)]
| 添加 | rpush lpush linsert |
| 查 | lrange lindex llen |
| 刪除 | lpop rpop lrem ltrim |
| 修改 | lset |
| 阻塞操作 | blpop brpop |
添加操作
(1)從右邊插入元素
rpush key value [value ...]下面代碼 從右向左 插入元素 c、b、a:
127.0.0.1:6379> rpush listkey c b a (integer) 3lrange 0 -1 命令可以獲取 從左到右 獲取列表的所有元素:
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a"(2)從左邊插入元素
lpush key value [value ...]使用方法和 rpush 相同,只不過從左側(cè)插入,這里不再贅述。
(3)向 某個(gè)元素 前 或者 后 插入元素
linsert key before|after pivot valuelinsert 命令會(huì)從 列表 中 找到等于 pivot 的元素,在其 前(before) 或者 后(after) 插入一個(gè)新的元素 value,例如下面操作會(huì)在列表的元素 b 前 插入 java:
127.0.0.1:6379> linsert listkey before b java (integer) 4返回結(jié)果為 4,代表 當(dāng)前列表的長度,當(dāng)前列表變?yōu)?#xff1a;
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "b" 4) "a"查找
(1) 獲取 指定范圍內(nèi)的元素列表
lrange key start endlrange 操作會(huì) 獲取列表 指定索引范圍 所有的元素。
索引下標(biāo) 有 兩個(gè)特點(diǎn):
第一,索引下標(biāo) 從左到右 分別是 0 到 N-1,但是 從右到左 分別是 -1 到 -N。
第二,lrange 中的 end 選項(xiàng)包含了自身,這個(gè)和很多編程語言不包含 end 不太相同,例如想獲取 列表的第2到第4個(gè)元素,可以執(zhí)行如下操作:
127.0.0.1:6379> lrange listkey 1 3 1) "java" 2) "b" 3) "a"(2)獲取 列表 指定索引下標(biāo) 的元素
lindex key index例如 當(dāng)前列表 最后一個(gè)元素為a:
127.0.0.1:6379> lindex listkey -1 "a"(3) 獲取列表長度
llen key例如,下面示例 當(dāng)前列表長度為 4:
127.0.0.1:6379> llen listkey (integer) 4刪除
(1)從列表左側(cè) 彈出 元素
lpop key如下操作將 列表最左側(cè)的元素 c 會(huì)被彈出,彈出后列表變?yōu)?java、b、a:
127.0.0.1:6379> lpop listkey "c" 127.0.0.1:6379> lrange listkey 0 -1 1) "java" 2) "b" 3) "a"(2)從列表右側(cè)彈出
rpop key它的使用方法和 lpop 是一樣的,只不過從 列表 右側(cè)彈出,這里不再贅述。
(3)刪除指定元素
lrem key count valuelrem 命令會(huì)從 列表中找到等于 value 的元素進(jìn)行刪除,根據(jù) count 的不同 分為 三種情況:
- count > 0 ,從左到右,刪除最多 count 個(gè)元素。
- count < 0,從右到左,刪除最多 count 絕對(duì)值個(gè)元素。
- count = 0,刪除所有。
例如向 列表 從左向右 插入 5個(gè)a,那么當(dāng)前列表變?yōu)?“a a a a a java b a”,下面操作將從 列表左邊開始刪除4個(gè)為a 的元素:
127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "java" 3) "b" 4) "a"(4)按照索引范圍 修剪 列表
ltrim key start end例如,下面操作會(huì) 只保留 列表 listkey 第2個(gè)到第4個(gè)元素:
127.0.0.1:6379> ltrim listkey 1 3 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "java" 2) "b" 3) "a"修改
修改指定索引下標(biāo)的元素:
lset key index newValue下面操作會(huì)將 列表 listkey 中的 第3個(gè) 元素設(shè)置為 python:
127.0.0.1:6379> lset listkey 2 python OK 127.0.0.1:6379> lrange listkey 0 -1 1) "java" 2) "b" 3) "python"阻塞操作
阻塞式彈出 如下:
blpop key [key ...] timeout brpop key [key ...] timeoutblpop 和 brpop 是 lpop 和 rpop 的 阻塞版本,它們除了 彈出方向不同,使用方法基本相同,所以下面以 brpop 命令進(jìn)行說明,brpop 命令包含 兩個(gè)參數(shù):
- key [key …]:多個(gè)列表的鍵。
- timeout:阻塞時(shí)間(單位:秒)
1)列表為空:
如果 timeout = 3,那么 客戶端 要等到 3 秒后返回,
如果 timeout = 0,那么 客戶端 一直阻塞等下去:
127.0.0.1:6379> brpop list:test 3 (nil) (3.10s) 127.0.0.1:6379> brpop list:test 0 ...阻塞...如果此期間添加了數(shù)據(jù) element1,客戶端立即返回:
127.0.0.1:6379> brpop list:test 3 1) "list:test" 2) "element1" (2.06s)2)列表不為空:客戶端會(huì)立即返回。
127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1"在使用 brpop 時(shí),有 兩點(diǎn) 需要注意。
第一點(diǎn),如果是 多個(gè)鍵,那么 brpop 會(huì) 從左至右 遍歷鍵,一旦有一個(gè)鍵 能彈出元素,客戶端立即返回:
127.0.0.1:6379> brpop list:1 list:2 list:3 ...阻塞...此時(shí) 另一個(gè)客戶端 分別向 list:2 和 list:3 插入元素:
client-lpush> lpush list:2 element2 (integer) 1 client-lpush> lpush list:3 element3 (integer) 1客戶端會(huì) 立即返回 list:2 中的element2 ,因?yàn)?list:2 最先有可以彈出的元素:
127.0.0.1:6379> brpop list:1 list:2 list:3 1) "list:2" 2) "element2"第二點(diǎn),如果 多個(gè)客戶端 對(duì) 同一個(gè)鍵執(zhí)行 brpop,那么 最先執(zhí)行 brpop 命令的客戶端 可以獲取到 彈出的值。
客戶端1:
client-1> brpop list:test 0 ...阻塞...客戶端2:
client-2> brpop list:test 0 ...阻塞...客戶端3:
client-3> brpop list:test 0 ...阻塞...此時(shí)另一個(gè)客戶端 lpush 一個(gè)元素到 list:test 列表中:
client-lpush> lpush list:test element (integer) 1那么 客戶端1 最先會(huì)獲取到元素,因?yàn)?客戶端1 最先執(zhí)行 brpop,而客戶端2 和 客戶端3繼續(xù)阻塞:
client> brpop list:test 0 1) "list:test" 2) "element"有關(guān) 列表 的 基礎(chǔ)命令 已經(jīng)介紹完了,表 2-5 是這些命令的時(shí)間復(fù)雜度,開發(fā)人員可以參考此表 選擇合適的命令。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-55GHVUyL-1621301872089)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517214858071.png)]
| 添加 | rpush key value [value …] | O(k),k 是元素個(gè)數(shù) |
| 添加 | lpush key value [value …] | O(k),k 是元素個(gè)數(shù) |
| 添加 | linsert key before|after pivot value | O(n),n 是pivot 距離列表頭或尾的舉例 |
| 查找 | lrange key start end | O(s+n),s 是 start 偏移量,n 是 start 到 end 的范圍 |
| 查找 | lindex key index | O(n),n 是索引的偏移量 |
| 查找 | llen key | O(1) |
| 刪除 | lpop key | O(1) |
| 刪除 | rpop key | O(1) |
| 刪除 | lrem count key | O(n),n 是列表長度 |
| 刪除 | ltrim key start end | O(n),n 是要裁剪的元素總數(shù) |
| 修改 | lset key index value | O(n),n 是索引的偏移量 |
| 阻塞操作 | blpop brpop | O(1) |
2.4.2 內(nèi)部編碼
列表類型 的 內(nèi)部編碼 有兩種。
-
ziplist(壓縮列表)
- 當(dāng) 列表的 元素個(gè)數(shù) 小于 list-max-ziplist-entries 配置(默認(rèn) 512 個(gè)),同時(shí) 列表中 每個(gè)元素的值 都小于 list-max-ziplist-value 配置時(shí)(默認(rèn) 64 字節(jié)),Redis 會(huì)選用 ziplist 來作為 列表的內(nèi)部實(shí)現(xiàn) 來 減少內(nèi)存的使用。
-
linkedlist(鏈表)
- 當(dāng) 列表類型 無法滿足 ziplist 的條件時(shí),Redis 會(huì)使用 linkedlist 作為 列表的內(nèi)部實(shí)現(xiàn)。
下面的示例演示了 列表類型的內(nèi)部編碼,以及相應(yīng)的變化。
1)當(dāng) 元素個(gè)數(shù) 較少 且 沒有大元素 時(shí),內(nèi)部編碼為 ziplist:
127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist"2.1)當(dāng) 元素個(gè)數(shù)超過 512個(gè),內(nèi)部編碼變?yōu)?linkedlist:
127.0.0.1:6379> rpush listkey e4 e5 ...忽略... e512 e 513 (integer) 513 127.0.0.1:6379> object encoding listkey "linkedlist"2.2)或者當(dāng) 某個(gè)元素超過 64 字節(jié),內(nèi)部編碼也會(huì)變?yōu)?linkedlist:
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte ......................................." (integer) 4 127.0.0.1:6379> object encoding listkey "linkedlist"開發(fā)提示
Redis 3.2 版本提供了 quicklist 內(nèi)部編碼,
簡單地說它是 以一個(gè)ziplist 為節(jié)點(diǎn)的 linkedlist,它結(jié)合了 ziplist 和 linkedlist 兩者的優(yōu)勢,為 列表類型 提供了一種更為優(yōu)秀的內(nèi)部編碼實(shí)現(xiàn),它的設(shè)計(jì)原理可以參考Redis的另一個(gè)作者M(jìn)att Stancliff 的博客:http://matt.sh/redis-quicklist。
有關(guān) 列表類型的優(yōu)化技巧 將在 8.3節(jié) 詳細(xì)介紹。
2.4.3 使用場景
消息隊(duì)列
如圖 2-21所示,Redis的 lpush+brpop 命令組合即可實(shí)現(xiàn) 阻塞隊(duì)列,生產(chǎn)者客戶端 使用 lpush 從列表左側(cè)插入元素,多個(gè)消費(fèi)者客戶端使用 brpop 命令阻塞式的 “搶” 列表尾部的元素,多個(gè)客戶端 保證了 消費(fèi)的負(fù)載均衡 和 高可用性。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-kzgBPzc3-1621301872090)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517222037844.png)]
文章列表
每個(gè)用戶有屬于自己的 文章列表,現(xiàn)需要 分頁展示 文章列表。
此時(shí)可以考慮使用 列表,因?yàn)?列表 不但是 有序的,同時(shí) 支持按照 索引范圍 獲取元素。
1)每篇文章使用 哈希結(jié)構(gòu) 存儲(chǔ),例如每篇文章有 3個(gè)屬性 title、timestamp、content:
hmset article:1 title xx timestamp 1476536196 content xxxx ... hmset article:k title yy timestamp 1476512536 content yyyy ....2)向 用戶文章列表 添加文章,user : {id} : articles 作為 用戶文章列表 的鍵:
lpush user:1:articles article:1 article3 ... lpush user:k:articles article:5 ...3)分頁 獲取 用戶文章列表,例如下面?zhèn)未a獲取用戶 id=1 的前 10 篇文章:
articles = lrange user:1:articles 0 9 for article in {articles}hgetall {article}使用 列表類型保存 和 獲取文章列表 會(huì)存在兩個(gè)問題。
第一,如果 每次分頁獲取的文章個(gè)數(shù)較多,需要執(zhí)行多次 hgetall 操作,此時(shí)可以考慮使用 Pipeline(第3章會(huì)介紹)批量獲取,或者考慮 將文章數(shù)據(jù)序列化為字符串,使用 mget 批量獲取。
第二,分頁獲取文章列表時(shí),lrange 命令在列表兩端性能較好,但是如果 列表較大,獲取列表中間范圍的元素 性能會(huì)變差,此時(shí)可以考慮將 列表做二級(jí)拆分,或者使用 Redis 3.2 的quicklist 內(nèi)部編碼實(shí)現(xiàn),它結(jié)合 ziplist 和 linkedlist 的特點(diǎn),獲取 列表中間范圍的元素 時(shí)也可以高效完成。
總結(jié)
以上是生活随笔為你收集整理的Redis开发与运维的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用Spark学习FP Tree算法和Pr
- 下一篇: python 运维自动化之路 Day2