日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis开发与运维

發布時間:2023/12/10 数据库 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis开发与运维 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本書全面講解了Redis基本功能及其使用,并結合線上開發與運維中的實際案例,深入分析并總結了實際工作中遇到的“陷進”,以及背后的原因,包含大規模集群開發與管理的場景、應用案例與開發技巧,為高效開發運維提供了大量實際經驗和建議。

本書不要求讀者有任何Redis使用經驗,對入門與進階DevOps的開發者提供有價值的幫助。

主要內容包括:

  • Redis的安裝配置
  • API
  • 各種高效功能
  • 客戶端
  • 持久化
  • 復制
  • 高可用
  • 內存
  • 哨兵
  • 集群
  • 緩存設計等
  • Redis高可用集群解決方案
  • Redis設計和使用中的問題
  • 最后提供了一個開源工具:Redis監控運維云平臺CacheCloud

前言

? Redis作為基于 鍵值對NoSQLNot Only Sql)數據庫,

具有 高性能豐富的數據結構持久化高可用分布式等特性,

同時Redis本身非常穩定,已經得到業界的廣泛認可和使用。

掌握Redis已經逐步稱為開發和運維人員的必備技能之一。

? 本書關注了Redis開發運維的方方面面,尤其對于開發運維中如何提高效率、減少可能遇到的問題進行詳細分析,但本書不單單介紹怎么解決這些問題,而是通過對Redis重要原理的解析,幫助開發運維人員學會找到問題的方法,以及理解背后的原理,從而讓開發運維人員不僅知其然,而且知其所以然

本書涵蓋內容

? 第1章

? 初始Redis,帶讀者進入Redis的世界,了解它的前世今生、眾多特性、應用場景、安裝配置、簡單使用,最后對Redis發展過程中的重要版本進行說明,可以讓讀者對Redis有一個全面的認識。

? 第2章

? API的理解和使用,全面介紹了Redis的 5種數據結構 字符串string)、哈希hash)、列表list)、集合set)、有序集合zset)的 數據模型、常用命令、典型應用場景,并且每個小結都會給出在Redis開發過程中可能要注意的坑和技巧。同時本章還會對Redis的 單線程處理機制、**鍵值管理 ** 做一個全面介紹,通過對這些原理的理解,讀者可以在合適的應用場景選擇合適的數據結構和命令進行開發,有效提高程序效率,降低可能產生的問題和隱患。

? 第3章

? 小功能大用處,除了 5種數據結構 外,Redis還提供了諸如 慢查詢Redis ShellPipelineLua腳本BitmapsHyperLogLog發布訂閱GEO 等附加功能,在這些功能的幫助下,Redis的應用場景更加豐富。

? 第4章

? 客戶端,本章重點關注Redis客戶端的開發,介紹了Redis的 客戶端通信協議、詳細講解了Java客戶端 Jedis 的使用技巧,同時通過從原理角度剖析在開發運維中,客戶端的監控和管理技巧,最后給出客戶端開發中常見問題以及案例講解。

? 第5章

? 持久化,Redis的 持久化 功能有效避免因進程退出造成的數據丟失問題,本章首先介紹 RDBAOF 兩種 持久化配置和運行流程,其次對常見的持久化問題進行定位和優化,最后結合Redis常見的單機多實例部署場景進行優化。

? 第6章

? 復制,在分布式系統中為了解決單點問題,通常會把數據復制多個副本部署到其他機器,用于故障恢復和負載均衡等需求,Redis也是如此。它為我們提供了 復制replication)功能,實現了多個相同數據的Redis副本。 復制功能是 高可用Redis的基礎,后面章節的 哨兵集群 都是在 復制的基礎上 實現高可用

? 第7章

? Redis的噩夢:阻塞,Redis是典型的單線程架構所有的讀寫操作 都在一條 主線程 中完成的。當Redis用于 高并發 場景時這條線程就變成了它的生命線。如果出現阻塞哪怕是很短時間對于我們的應用來說都是噩夢。導致阻塞問題的場景大致分為 內在原因外在原因,本章將進行詳細分析。

? 第8章

? 理解內存,Redis所有的數據在于內存中,如何高效利用Redis內存變得非常重要。高效利用Redis內存 首先需要理解 Redis內存消耗在哪里如何管理內存,最后再深入到 如何優化內存。掌握這些知識后相信讀者能夠實現 用更少的內存存儲更多的數據 從而 降低成本

? 第9章

? 哨兵,Redis從 2.8 版本開始正式提供了 Redis Sentinel,它有效解決了 主從模式故障轉移 的若干問題,為Redis提供了 高可用 功能。本章將一步步解析 Redis Sentinel 的相關概念安裝部署配置命令使用、原理解析,最后分析了 Redis Sentinel 運維中的一些問題。

? 第10章

? 集群,是本書的重頭戲, Redis ClusterRedis 3 提供的 Redis 分布式解決方案,有效解決了 Redis分布式 方面的需求,理解應用好 Redis Cluster 將極大地解放我們對 分布式Redis 的需求,同時它也是學習 分布式存儲 的絕佳案例。本章將對 Redis Cluster數據分布搭建集群節點通信請求路由集群伸縮故障轉移 等方面進行分析說明。

? 第11章

? 緩存設計緩存 能夠有效 加速應用的讀寫速度,以及 降低后端負載,對于開發人員進行日常應用的開發至關重要,但是將 緩存 加入應用架構后也會帶來一些問題,本章將介紹 緩存使用設計 中遇到的問題,具體包括:緩存的收益和成本緩存更新策略緩存粒度控制穿透問題優化無底洞問題優化雪崩問題優化熱點key優化

? 第12章

? 開發運維的“陷進”,介紹Redis開發運維中的一些棘手問題,具體包括:Linux配置優化flush誤操作數據恢復、如何讓Redis變得安全bigkey 問題、熱點key 問題。

? 第13章

? Redis監控運維云平臺 CacheCloud,介紹筆者所在團隊 開源Redis運維工具 CacheCloud,它有效解決了Redis監控和運維中的一些問題,本章將按照 快速部署機器部署接入應用用戶功能運維功能 多個維度全面的介紹 CacheCloud,相信在它的幫助下,讀者可以更好的監控和運維好Redis。

? 第14章

? Redis配置統計字典,會對Redis的 系統狀態信息 以及 全部配置 做一個全面的梳理,希望本章能夠成為 Redis配置統計字典,協助大家分析和解決日常開發和運維中遇到的問題。

第1章 初始Redis

1.1 盛贊Redis

1.2 Redis特性

1.3 Redis使用場景

? 1.3.1 Redis可以做什么

? 1.3.2 Redis不可以做什么

1.4 用好Redis的建議

1.5 正確安裝并啟動Redis

? 1.5.1 安裝Redis

? 1.5.2 配置、啟動、操作、關閉Redis

1.6 Redis重大版本

1.7 本章重點回顧

第1章 初始Redis

? 本章將帶領讀者進入Redis的世界,了解它的前世今生、眾多特性、典型應用場景安裝配置如何好用 等,最后會對Redis發展過程中的 重要版本 進行說明,本章主要內容如下:

  • 盛贊Redis
  • Redis特性
  • Redis使用場景
  • 用好Redis的建議
  • 正確安裝啟動Redis
  • Redis重大版本

1.1 盛贊Redis

Redis官網:http://redis.io

Redis 是一種基于 鍵值對key-value)的 NoSQL 數據庫

與很多 鍵值對數據庫 不同的是,

Redis 中的 可以是由 string字符串)、hash哈希)、list列表)、set(集合)、zset有序集合)、Bitmaps位圖)、HyperLogLogGEO地理信息定位) 等 多種數據結構和算法 組成,因此 Redis 可以 滿足很多的 應用場景

而且因為 Redis 會將 所有數據存放在 內存 中,所以它的 讀寫性能 非常 驚人

不僅如此,Redis 還可以將 內存的數據 利用 快照日志 的形式 保存到 硬盤 上,這樣在發生類似 斷電 或者 機器故障 的時候,內存中的數據 不會 “丟失”

除了上述功能以外,Redis 還提供了 鍵過期發布訂閱事務流水線Lua腳本 等附加功能。

總之,如果在合適的場景使用好 Redis,它就會像一把瑞士軍刀一樣所向披靡。

2008年,Redis 的作者 Salvatore Sanfilippo 在開發一個叫 LLOOGG 的網站時,需要實現一個 高性能隊列功能,最開始是使用 MySQL 來實現的,但后來發現無論怎么優化 SQL語句 都不能使 網站的性能 提高上去,再加上自己囊中羞澀,于是他決定自己做一個專屬于 LLOOGG 的數據庫,這個就是 Redis的前身

后來,Salvatore SanfilippoRedis 1.0 的源碼開放到 GitHub 上,可能連他自己都沒想到,Redis 后來如此受歡迎。

假如現在有人問 Redis的作者 都有誰在使用 Redis,我想他可以開句玩笑的回答:還有誰不使用Redis,當然這只是開玩笑,但是從 Redis 的官方公司統計來看,有很多重量級的公司都在使用 Redis,如果單單從體量來統計,新浪微博 可以說是全球最大的 Redis 使用者,除了 新浪微博,還有像 阿里巴巴、騰訊、百度、搜狐、優酷土豆、美團、小米、唯品會等公司都是 Redis 的使用者。

除此之外,許多開源技術想ELK等已經把 Redis 作為它們組件中的重要一環,而且 Redis 會在未來的版本中提供 模板系統 讓第三方人員 實現擴展功能,讓 Redis 發揮出更大的威力。

所以,可以這么說,熟練使用和運維 Redis 已經成為開發運維人員的一個必備技能。

1.2 Redis特性

? Redis 之所以受到如此多公司的青睞,必然有之過人之處,下面是關于 Redis 的 8個重要特性

  • 速度 快

    正常情況下,Redis 執行命令的速度 非常快,官方給出的數字是 讀寫性能 可以達到 10萬/秒,當然這也取決于 機器的性能,但這里先不討論 機器性能上的差異,只分析一下是什么造就了 Redis 除此之外的 速度,可以大致歸納為以下四點:

    • Redis 的 所有數據 都是 存放在 內存 中的,表1-1是谷歌公司2009年給出的各層級硬件執行速度,所以 把數據放在內存 中是 Redis速度快 的最主要原因。
    • Redis 使用 C語言 實現的,一般來說 C語言 實現的程序 “距離” 操作系統更近,執行速度 相對會更快。
    • Redis 使用了 單線程架構,預防了 多線程 可能產生的 競爭問題。
    • 作者對于 Redis源代碼 可以說是 精打細磨,曾經有人評價 Redis 是少有的 集性能和優雅于一身 的 開源代碼。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CZFzKuIB-1621301836851)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210513170503130.png)]

    表1-1 谷歌公司給出的 各層級 硬件 執行速度

    層級速度
    L1 cache reference0.5ns
    Branch mispredict5ns
    L2 cache reference7ns
    Mutex lock/unlock25ns
    Main memory reference100ns
    Compress 1K bytes with Zippy3 000ns
    Send 2K bytes over 1Gbps network20 000ns
    Read 1MB sequentially from Memory250 000ns
    Round trip within same datacenter500 000ns
    Disk seek10 000 000ns
    Read 1 MB sequentially from disk20 000 000ns
    Send packet CA->Netherlands->CA150 000 000ns
  • 基于 鍵值對 的數據結構 服務器

    幾乎所有的 編程語言 都提供了類似 字典 的功能,

    例如Java里的 map、Python里的 dict

    類似于這種 組織數據的方式 叫做 基于鍵值 的方式

    與很多 鍵值對 數據庫 不同的是,

    Redis 中的 不僅可以是 字符串,而且還可以是 具體的 數據結構

    這樣不僅能 便于在許多應用場景的開發,同時也能夠 提高開發效率

    Redis全稱Remote Dictionary Server

    它主要提供了 5種數據結構字符串哈希列表集合有序集合

    同時在 字符串基礎之上 演變 出了 位圖Bitmaps)和 HyperLogLog 兩種神奇的 “數據結構”

    并且隨著 LBSLocation Based Service基于位置服務)的不斷發展,Redis 3.2 版本 中加入有關 GEO地理信息定位)的功能,

    總之在這些數據結構的幫助下,開發者可以開發出各種“有意思”的應用。

  • 豐富的功能

    除了 5種數據結構Redis 還提供了許多額外的功能:

    • 提供了 鍵過期 功能,可以用來實現 緩存
    • 提供了 發布訂閱 功能,可以用來實現 消息系統
    • 支持 Lua腳本 功能,可以利用 Lua 創造出 新的Reds命令
    • 提供了簡單的 事務 功能,能在一定程度上保證 事務特性
    • 提供了 流水線Pipeline)功能,這樣 客戶端 能將 一批命令 一次性 傳到 Redis減少了 網絡的開銷
  • 簡單穩定

    Redis簡單 主要表現在 三個方面

    首先,Redis源碼 很少早期版本 的代碼只有 2萬行左右3.0版本 以后由于添加了 集群特性,代碼增至 5萬行左右,相對于很多 NoSQL數據庫 來說代碼量相對要少很多,也就意味著普通的開發和運維人員完全可以“吃透”它。

    其次,Redis 使用 單線程模型,這樣不僅使得 Redis服務端處理模型 變得簡單, 而且也使得 客戶端開發 變得簡單。

    最后,Redis 不需要依賴于 操作系統 中的類庫(例如 Memcache 需要 依賴 libevent 這樣的 系統類庫),Redis 自己 實現事件處理 的相關功能。

    Redis 雖然很簡單,但是不代表它不穩定

    以筆者維護的上千個Redis為例,沒有出現過因為Redis自身bug而宕掉的情況。

  • 客戶端語言 多

    Redis 提供了 簡單的 TCP通信協議,很多 編程語言 可以很 方便地 接入到 Redis,并且由于 Redis 受到 社區和各大公司的 廣泛認可,所以 支持Redis的客戶端語言 也非常 ,幾乎涵蓋了 主流的編程語言,例如Java、PHP、Python、C、C++、Nodejs等,第4章我們將對 Redis的客戶端 進行詳細說明。

  • 持久化

    通常看,將 數據 放在 內存 中是 不安全的,一旦發生 斷電 或者 機器故障重要的數據 可能就會 丟失,因此 Redis 提供了 兩種 持久化 方式RDBAOF,即可以用 兩種策略內存的數據 保存到 硬盤中(如圖1-1所示),這樣就 保證數據的可持久性,第5章我們將對 Redis的持久化 進行詳細說明。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1inDcMPh-1621301836855)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210513173702303.png)]

  • 主從復制

    Redis 提供了 復制 功能,實現了 多個相同數據的Redis副本(如圖1-2所示),復制 功能是 分布式Redis的基礎。第6章我們將會對 Redis的復制 進行詳細說明。

  • 高可用 和 分布式

    Redis2.8版本 正式提供了 高可用 實現Redis Sentinel,它能夠保證 Redis節點故障發現故障自動轉移

    Redis3.0版本 正式提供了 分布式 實現 Redis Cluster,它是 Redis 真正的 分布式實現,提供了 高可用讀寫容量的擴展性

  • 1.3 Redis 使用場景

    ? 上節我們已經了解了 Redis 的若干個 特性,本節來看一下 Redis典型應用場景 有哪些?

    1.3.1 Redis 可以做什么
  • 緩存

    緩存機制 幾乎在所有的大型網站 都有使用,合理地使用緩存 不僅可以 加快數據的訪問速度,而且能夠 有效地降低后端數據源的壓力

    Redis 提供了 鍵值過期時間設置,并且也提供了 靈活控制最大內存內存溢出后的淘汰策略

    可以這么說,一個 合理的緩存設計 能夠為 一個網站的穩定 保駕護航。第11章將對 緩存的設計與使用 進行詳細說明。

  • 排行榜系統

    排行榜系統 幾乎存在于所有的網站,例如 按照熱度排名的排行榜,按照發布時間的排行榜,按照各種復雜維度計算出的排行榜,Redis 提供了 列表有序集合 數據結構,合理地使用 這些 數據結構 可以很 方便地構建各種排行榜系統。

  • 計數器應用

    計數器 在網站中的作用 至關重要,例如 視頻網站有 播放數、電商網站有 瀏覽數,為了保證 數據的實時性每一次 播放和瀏覽 都要做 加1 的操作,如果 并發量很大 對于 傳統關系型數據庫的性能 是一種挑戰。

    Redis 天然支持 計數功能 而且 計數的性能 也非常,可以說是 計數器系統 的重要選擇。

  • 社交網絡

    贊/踩、粉絲、共同好友/喜好、推送、下拉刷新等是 社交網站的必備功能,由于社交網站 訪問量通常比較大,而且 傳統的關系型數據庫 不太適合保存這種類型的數據,Redis 提供的 數據結構 可以相對比較容易地實現這些功能。

  • 消息隊列系統

    消息隊列系統 可以說是 一個大型網站的必備基礎組件,因為其具有 業務 解耦非實時業務 削峰等特性。

    Redis 提供了 發布訂閱阻塞隊列 的功能,雖然和專業的消息隊列比還不夠足夠強大,但是對于一般的消息隊列基本可以滿足。

  • 1.3.2 Redis不可以做什么

    ? 實際上和任何一門技術一樣,每個技術都有自己的 應用場景邊界,也就是說 Redis不是 萬金油,有很多適合它解決的問題,但是也有很多不適合它解決的問題。

    我們可以站在 數據規模數據冷熱角度來進行分析。

    站在 數據規模 的角度看,數據 可以分為 大規模數據小規模數據

    我們知道 Redis數據存放在 內存 中的,雖然現在 內存 已經足夠 便宜,但是如果 數據量 非常大,例如每天有幾億的用戶行為數據,使用 Redis存儲的話,基本上是個 無底洞經濟成本 非常的

    站在 數據冷熱 的角度看,數據 分為 熱數據冷數據熱數據 通常是指需要 頻繁操作的數據,反之為冷數據,

    例如對于 視頻網站 來說,視頻基本信息 基本上在各個業務線都是 經常要操作 的數據,而 用戶的觀看記錄 不一定是經常需要訪問的數據,

    這里暫且不討論兩者數據規模的差異,

    單純站在 數據冷熱的角度 上看,視頻信息 屬于 熱數據用戶觀看記錄 屬于冷數據

    如果將這些 冷數據 放在 Redis 上,基本上是 對 內存的一種浪費

    但是對于一些 熱數據 可以放在 Redis加速讀寫,也可以 減輕后端存儲的負載,可以說是事半功倍。

    所以,Redis不是 萬金油,相信隨著我們對 Redis 的逐步學習,能夠清楚 Redis 真正的使用場景。

    1.4 用好Redis的建議

  • 切勿當做 黑盒 使用,開發與運維同樣重要

    很多使用 Redis 的開發者認為只要會用 API 開發相應的功能就可以,更有甚者認為 Redis 就是 get、set、del,不需要知道 Redis 的 原理。

    但是在我們實際運維和使用 Redis 的過程中發現,很多線上的故障和問題都是由于完全把 Redis 當做 黑盒 造成的,

    如果不了解 Redis的單線程模型,有些開發者會在 上千萬個鍵的Redis上 執行 keys * 操作,

    如果不了解 持久化的相關原理,會在一個 寫操作量很大的Redis上 配置自動保存RDB

    而且在很多公司內只有專職的 關系型數據庫DBA,并沒有 NoSQL 的相關運維人員,也就是說開發者很有可能會自己運維 Redis,對于 Redis 的開發者來說既是好事又是壞事。

    站在好的方面看,開發人員可以通過 運維 Redis 真正了解 Redis 的一些原理,不單純停留在開發上。

    站在壞的方面看,Redis 的開發人員不僅要支持開發,還要承擔運維的責任,而且由于運維經驗不足可能會造成線上故障。

    但是從實際經驗來看,運維足夠規模的 Redis 會對用好 Redis 更加有幫助。

  • 閱讀源碼

    我們在前面提到過,Redis開源項目,由于作者對 Redis代碼 的極致追求,Redis的代碼量 相對于許多 NoSQL數據庫 來說是非常 的,也就意味著作為普通的開發和運維人員也是可以 “吃透”的。

    通過閱讀優秀的源碼,不僅能夠加深我們對 Redis 的理解,而且還能提高自身的需求,例如新浪微博在 Redis的早期版本 上做了很多的 定制化 來滿足自身的需求,豌豆莢也開源基于 Proxy 的 Redis 分布式 實現 Codis。

  • 1.5 正確安裝并啟動Redis

    通常來說,學習一門技術最好的方法就是實戰,所以在學習 Redis 這樣一個實戰中產生的技術時,首先把它安裝部署起來,值得慶幸的是,相比于很多軟件和工具部署步驟繁雜,Redis的安裝 不得不說是非常 簡單,本節我們將學習如何 安裝Redis。

    注意

    在寫本書時,Redis 4.0 已經發布 RC版,但是大部分公司還都在使用 3.0 或更早的版本(2.6 或 2.8),本書所講的內容基于 Redis3.0。

    1.5.1 安裝Redis
  • 在Linux上安裝Redis

    Redis 能夠 兼容 絕大部分的 POSIX 系統,例如 LinuxOS XOpenBSDNetBSDFreeBSD

    其中比較典型的是 Linux 操作系統(例如 CentOS、Redhat、Ubuntu、Debian、OS X等)。

    在 Linux 安裝軟件通常有 兩種方法,

    第一種是通過 各個操作系統的軟件管理軟件 進行安裝,例如 CentOSyum 管理工具,Ubuntuapt

    但是由于 Redis 的更新速度相對較快,而這些管理工具不一定能更新到最新的版本,同時前面提到 Redis的安裝 本身不是很復雜,所以一般推薦使用第二種方式:源碼的方式 進行安裝,整個安裝只需一下 六步 即可完成,以 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 指定版本的 源碼壓縮包 到當前目錄。

  • 解壓縮 Redis 源碼壓縮包。

  • 建立一個 redis 目錄 的軟連接,指向 redis-3.0.7

  • 進入 redis 目錄

  • 編譯(編譯之前確保操作系統已經安裝 gcc)

  • 安裝

  • 這里有 兩點 需要注意:

    第一,第3步 中建立了一個 redis 目錄的 軟鏈接,這樣做是為了 不把 redis 目錄 固定在 指定版本上,有利于 Redis 未來版本升級,算是 安裝軟件 的一種好習慣。

    第二,第6步 中的 安裝 是將 Redis 的相關運行文件放到 /usr/local/bin/ 下,這樣就可以在任意目錄下執行 Redis 的命令。

    例如安裝后,可以在任何目錄執行 redis-cli -v 查看 Redis 版本。

    redis-cli -v redis-cli 3.0.7

    注意

    第12章將介紹更多 Linux配置優化 技巧,為 Redis 的良好運行保駕護航。

  • 在Windows上安裝Redis
  • Redis 的官方并不支持微軟的Windows操作系統,但是 Redis 作為一款優秀的開源技術吸引到了微軟公司的注意,微軟公司的開源技術組在 GitHub 上維護一個 Redis 的分支:https://github.com/MSOpenTech/redis。

    那為什么 Redis 的作者沒有開發和維護對Windows用戶的 Redis 版本呢?

    這里可以簡單分析一下:

    首先 Redis 的許多 特性 都是和 操作系統 相關的,Windows操作系統 和 Linux操作系統 有很大的區別,所以會 增加維護成本,而且更重要的是大部分公司都在使用 Linux 操作系統,而 Redis 在Linux 操作系統上的表現已經得到了實踐的驗證。

    對于使用 Windows操作系統 的讀者,可以通過安裝虛擬機來體驗 Redis 的諸多特性。

    注意

    對 Windows版本 的 Redis 感興趣的讀者,可以嘗試安裝和部署 Windows版本的Redis,但是本書中的知識和例子不能確保在Windows下能夠運行。

    1.5.2 配置、啟動、操作、關閉 Redis

    Redis 安裝之后,src 和 /usr/local/bin 目錄下多了幾個以 redis 開頭 可執行文件,我們稱之為 Redis Shell,這些 可執行文件 可以做很多事情,例如可以啟動和停止 Redis、可以 檢測和修復 Redis 的持久化文件,還可以 檢測 Redis 的性能

    表 1-2 中分別列出這些可執行文件的說明。

    表 1-2 Redis 可執行文件說明

    可執行文件作用
    redis-server啟動Redis
    redis-cliRedis命令行客戶端
    redis-benchmarkRedis基準測試工具
    redis-check-aofRedis AOF持久化文件檢測和修復工具
    redis-check-dumpRedis RDB持久化文件檢測和修復工具
    redis-sentinel啟動Redis Sentinel

    Redis 持久化Redis Sentinel 分別在第5章和第9章才會涉及,Redis基準測試 將在第3章介紹,所以本節只對 redis-serverredis-cli 進行介紹。

  • 啟動 Redis

    三種方法 啟動Redis默認配置運行配置配置文件啟動

    (1)默認配置

    這種方法會使用 Redis的默認配置 來啟動,下面就是 redis-server 執行后輸出的相關日志。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bRAOIMuK-1621301836858)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210514142427083.png)]

    可以看到直接使用 redis-server 啟動 Redis 后,會打印一些日志,通過日志可以看到一些信息,上例中可以看到:

    • 當前的 Redis版本 是 3.0.7。
    • Redis 的 默認端口 是 6379。
    • Redis 建議要 使用配置文件 來啟動。

    因為 直接啟動 無法自定義配置,所以這種方式是不會在生產環境中使用的。

    (2)運行啟動

    redis-server 加上 要修改配置名和值(可以是多對),沒有設置的配置將使用默認配置:

    # redis-server --configKey1 configValue1 --configKey2 configValue2

    例如,如果要用 6380 作為 端口 啟動 Redis,那么可以執行:

    # redis-server --port 6380

    雖然 運行配置 可以 自定義配置,但是如果 需要修改的配置較多 或者 希望將配置保存到文件中,不建議使用這種方式。

    (3)配置文件啟動

    將 配置 寫到 指定文件 里,例如我們將配置寫到了 /opt/redis/redis.conf 中,那么只需要執行如下命令即可啟動 Redis:

    # redis-server /opt/redis/redis.conf

    Redis 有 60 多個配置,這里只給出一些重要的配置(參見表 1-3),其他配置會隨著不斷深入學習進行介紹,第14章會將所有的配置說明進行匯總。

    表 1-3 Redis的基礎配置

    配置名配置說明
    port端口
    logfile日志文件
    dirRedis 工作目錄(存放持久化文件和日志文件)
    daemonize是否以守護進程的方式啟動Redis

    運維提示

    Redis 目錄下都會有一個 redis.conf 配置文件,里面就是 Redis的 默認配置,通常來講我們會在 一臺機器上啟動多個Redis,并且將 配置集中管理在指定目錄下,而且 配置不是完全手寫的,而是將 redis.conf 作為模板進行修改

    顯然通過 配置文件啟動的方式 提供了 更大的靈活性,所以大部分生產環境會使用這種方式啟動Redis。

  • Redis 命令行客戶端

    現在我們已經啟動了 Redis 服務,下面將介紹如何使用 redis-cli 連接,操作 Redis 服務。

    redis-cli 可以使用 兩種方式 連接Redis服務器。

    • 第一種是 交互式方式

      通過 redis-cli -h {host} -p {port}的方式連接到 Redis服務,之后所有的操作都是通過 交互的方式 實現,不需要再執行 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}就可以直接得到 命令的返回結果,例如:

      redis-cli -h 127.0.0.1 -p 6379 get hello "world"

      這里有 兩點 要注意:

      1)如果沒有 -h 參數,那么 默認連接 127.0.0.1 ;

      ? 如果沒有 -p ,那么 默認 6379 端口,也就是說如果 -h 和 -p 都沒寫就是連接 127.0.0.1:6379 這個Redis實例。

      2)redis-cli 是學習 Redis 的重要工具,后面的很多章節都是用它作講解,同時 redis-cli 還提供了很多有價值的參數,可以幫助解決很多問題,有關于 redis-cli 的強大功能將在第3章進行詳細介紹。

  • 停止 Redis 服務

    Redis 提供了 shutdown 命令來 停止Redis服務,例如要停掉 127.0.0.1 上 6379 端口上的 Redis 服務,可以執行如下操作:

    redis-cli shutdown

    可以看到 Redis 的日志輸出如下:

    # User requested shutdown... # 客戶端發出的shutdown命令* Saving the final RDB snapshot before exiting. #保存 RDB 持久化文件(有關 Redis持久化的特性在 1.2節 已經進行了簡單的介紹,RDB 是 Redis 的一種 持久化方式)* DB saved on disk # 將 RDB 文件保存在磁盤上# Redis is now ready to exit, bye bye... #關閉

    當使用 redis-cli 再次連接該 Redis 服務時,看到 Redis 已經 “失聯”。

    redis-cli Could not connect to Redis at 127.0.0.1:6379: Connection refused

    這里有 三點 需要注意一下:

    1)Redis 關閉的過程斷開與客戶端的連接持久化文件生成,是一種相對優雅的關閉方式。

    2)除了可以通過 shutdown 命令 關閉Redis服務 以外,還可以通過 kill 進程號 的方式關閉掉 Redis,但是不要 粗暴地 使用 kill -9 強制殺死 Redis服務,不但 不會做 持久化操作,還會 造成 緩沖區等資源不能被優雅關閉,極端情況會造成 AOF和復制丟失數據 的情況。

    3)shutdown 還有一個參數,代表 是否在關閉Redis前,生成持久化文件

    redis-cli shutdown nosave|save
  • 1.6 Redis 重大版本

    Redis 借鑒了 Linux 操作系統 對于 版本號的命名規則版本號 第二位數 如果是 奇數則為 非穩定版本(例如2.7、2.9、3.1),如果是 偶數則為 穩定版本(例如2.6、2.8、3.0、3.2)。

    當前 奇數版本 就是下一個 穩定版本 的 開發版本,例如 2.9版本 是 3.0版本 的開發版本。

    所以我們在生產環境通常 選取 偶數版本 的Redis,如果對于某些新的特性想提前了解和使用,可以選擇最新的奇數版本。

    介紹一門技術的版本是很多技術圖書的必備內容,通常讀者容易忽略,但隨著你對這門技術深入學習后,會覺得“倍感親切”,而且通常也會關注 新版本的特性,本小節將對 Redis 發展過程中的一些重要版本及特性進行說明。

  • Redis 2.6

    Redis 2.62012 年正式發布,經歷了 17 個版本,到 2.6.17 版本,相比于 Redis 2.4,主要特性如下:

    1)服務端 支持 Lua 腳本

    2)去掉 虛擬內存 相關功能。

    3)放開對 客戶端連接數 的硬編碼限制。

    4)鍵 的 過期時間 支持毫秒

    5)從結點 提供 只讀功能

    6)兩個新的 位圖命令:bitcount 和 bitop 。

    7)增強了 redis-benchmark 的功能:支持 定制化的壓測CSV輸出 等功能。

    8)基于 浮點數 自增命令:incrbyfloat 和 hincrbyfloat。

    9)redis-cli 可以使用 --eval 參數實現 Lua腳本執行

    10)shutdown 命令增強。

    11)info 可以按照 section 輸出,并且添加了一些統計項。

    12)重構了大量的核心代碼,所有 集群相關的代碼 都去掉了,cluster 功能 將會是 3.0版本 最大的亮點

    13)sort 命令優化。

  • Redis 2.8

    Redis 2.82013 年 11 月 22 日正式發布,經歷了 24 個版本,到 2.8.24 版本,相比于 Redis 2.6,主要特性如下:

    1)添加部分 主從復制 的功能,在一定程度上 降低了 由于 網絡問題造成頻繁 全量復制生成RDB 對系統造成的壓力。

    2)嘗試性地支持 IPv6

    3)可以通過 config set 命令設置 maxclients

    4)可以用 bind 命令 綁定 多個 IP地址

    5)Redis 設置 了明顯的 進程名,方便使用 ps 命令 查看系統進程。

    6)config rewrite 命令可以將 config set 持久化到 Redis配置文件 中。

    7)發布訂閱 添加了 pubsub 命令。

    8)Redis Sentinel 第二版,相比于 Redis 2.6 的 Redis Sentinel,此版本已經變成 生產可用

  • Redis 3.0

    Redis 3.02015 年 4 月 1 日 正式發布,截止到本書完成已經到 3.0.7 版本,相比于 Redis 2.8 主要特性如下:

    Redis 3.0 最大的改動就是添加 Redis的分布式 實現 Redis Cluster,填補了 Redis 官方沒有 分布式 實現的空白。

    Redis Cluster 經歷了 4 年才正式發布也是有原因的,具體可以參考 Redis Cluster 的開發日志(http://antirez.com/news/79)。

    1)Redis Cluster:Redis 的 官方 分布式實現

    2)全新的 embedded string 對象編碼結果,優化 小對象 內存訪問,在特定的工作負載下 速度大幅提升

    3)lur 算法大幅提升。

    4)migrate 連接緩存,大幅提升 鍵遷移的速度

    5)migrate 命令 兩個新的參數 copyreplace

    6)新的 client pause 命令,在指定時間內停止處理客戶端請求

    7)bitcount 命令性能提升。

    8)config set 設置 maxmemory 時候可以 設置不同的單位(之前只能是字節),例如 config set maxmemory 1gb。

    9)Redis 日志小做調整:日志中會反應 當前實例的角色master 或者 slave)。

    10)incr 命令 性能提升

  • Redis 3.2

    Redis 3.22016 年 5 月 6 日正式發布,相比于 Redis 3.0 主要特征如下:

    1)添加 GEO 相關功能。

    2)SDS速度節省空間 上都做了優化。

    3)支持用 upstart 或者 systemd 管理 Redis 進程

    4)新的 List 編碼類型:quicklist。

    5)從結點 讀取 過期數據 保證一致性

    6)添加了 hstrlen 命令。

    7)增強了 debug 命令,支持了更多的參數。

    8)Lua 腳本功能增強。

    9)添加了 Lua Debugger

    10)config set 支持更多的配置參數。

    11)優化了 Redis 崩潰 后的相關報告。

    12)新的 RDB 格式,但是仍然兼容舊的 RDB。

    13)加速 RDB 的加載速度。

    14)spop 命令支持 個數 參數。

    15)cluster nodes 命令得到 加速。

    16)Jemalloc 更新到 4.0.3 版本。

  • Redis 4.0

    可能出乎很多人的意料,Redis 3.2 之后的版本是 4.0,而不是 3.4、3.6、3.8。

    一般這種 重大版本號 的 升級 也意味著 軟件或者工具本身發生了重大變革,直到本書截稿前,Redis 發布了 4.0-RC2,下面列出 Redis 4.0 的新特性:

    1)提供了 模塊系統方便第三方開發者 拓展 Redis 的功能,更多模塊詳見:http://redismodules.com。

    2)PSYNC 2.0優化了之前版本中,主從節點切換 必然引起 全量復制 的問題

    3)提供了新的 緩存剔除 算法LFULast Frequently Used),并對已有算法進行了優化。

    4)提供了 非阻塞 del 和 flushall/flushdb 功能,有效解決 刪除 bigkey 可能造成的 Redis 阻塞

    5)提供了 RDB-AOF 混合持久式 格式,充分利用了 AOF 和 RDB 各自優勢。

    6)提供 memory 命令,實現 對內存更為全面的監控統計

    7)提供了 交互數據庫 功能,實現 Redis 內部數據庫之間的 數據置換

    8)Redis Cluster 兼容 NATDocker

  • 1.7 本章重點回顧

    1)Redis8個特性速度快基于鍵值對的數據結構服務器功能豐富簡單穩定客戶端語言多、持久化主從復制支持高可用和分布式

    2)Redis不是 萬金油,有些場景不適合 Redis 進行開發。

    3)開發運維結合 以及 閱讀源碼 是用好 Redis 的重要方法。

    4)生產環境 中使用 配置文件 啟動 Redis

    5)生產環境 選取 穩定版本的 Redis

    6)Redis 3.0 是重要的里程碑,發布了 Redis官方的 分布式實現 Redis Cluster

    第2章 API的理解和使用

    ? Redis 提供了 5種 數據結構,理解每種數據結構的特點 對于 Redis 開發運維 非常重要,同時掌握 Redis的單線程命令處理機制,會使 數據結構和命令的選擇事半功倍,本章內容如下:

    • 預備知識:幾個簡單的全局命令數據結構內部編碼單線程命令處理機制分析。
    • 5種 數據結構特點,命令使用應用場景
    • 鍵管理遍歷鍵數據庫管理

    2.1 預備

    ? 在正式介紹 5種數據結構 之前,了解一下 Redis 的一些 全局命令數據結構內部編碼單線程命令處理機制 是十分有必要的,它們能為后面內容的學習打下一個好的基礎,主要體現在兩個方面:

    ? 第一、Redis 的命令有上百個,如果純靠死記硬背比較困難,但是如果理解 Redis 的一些機制,會發現這些命令有很強的通用性

    ? 第二Redis不是萬金油,有些數據結構 和 命令 必須在特定場景下使用,一旦使用不當 可能對 Redis本身 或者 應用本身造成致命傷害。

    2.1.1 全局命令

    ? Redis5種數據結構,它們是 鍵值對中的 值,對于 來說有一些 通用的命令

  • 查看所有鍵

    keys *

    下面插入了 3對 字符串類型的鍵值對

    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 OK

    keys * 命令會 將所有的鍵輸出

    127.0.0.1:6379> keys * 1) "python" 2) "java" 3) "hello"
  • 鍵總數

    dbsize

    下面插入一個 列表類型的鍵值對(值 是多個元素組成)

    127.0.0.1:6379> rpush mylist a b c d e f g (integer) 7

    dbsize 命令會返回 當前數據庫中鍵的總數

    例如 當前數據庫有 4 個鍵,分別是 hello、java、python、mylist,所以 dbsize 的結果是 4:

    127.0.0.1:6379> dbsize (integer) 4

    dbsize 命令在 計算鍵總數 時 不會遍歷所有鍵,而是 直接獲取 Redis 內置的 鍵總數 變量,所以 dbsize 命令的 時間復雜度O(1)

    而 keys 命令會 遍歷所有鍵,所以它的 時間復雜度O(n)當 Redis 保存了 大量鍵時,線上環境 禁止使用

  • 檢查鍵是否存在

    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 是一個通用命令無論值是什么數據結構類型,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

    返回結果為 成功刪除鍵的個數假設刪除一個不存在的鍵,就會返回0

    127.0.0.1:6379> del not_exist_key (integer) 0

    同時 del 命令可以支持 刪除多個鍵

    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 支持對 鍵 添加過期時間當超過 過期時間 后,會自動刪除 鍵,例如為 鍵 hello 設置了 10秒過期時間:

    127.0.0.1:6379> set hello world OK 127.0.0.1:6379> expire hello 10 (integer) 1

    ttl 命令會返回 鍵的剩余過期時間,它有 3種返回值

    • 大于等于0 的整數鍵剩余的過期時間
    • -1鍵沒設置過期時間
    • -2鍵不存在

    可以通過 ttl 命令觀察鍵 hello 的剩余過期時間:

    # 還剩7秒 127.0.0.1:6379> ttl hello (integer) 7 ... # 還剩1秒 127.0.0.1:6379> ttl hello (integer) 1 # 返回結果為 -2 ,說明 鍵hello 已經被刪除 127.0.0.1:6379> ttl hello (integer) -2 127.0.0.1:6379> get hello (nil)

    有關 鍵過期 更為詳細的使用以及原理會在 2.7 節介紹。

  • 鍵的數據結構類型
  • type key

    例如鍵 hello 是字符串類型,返回結果為 string。

    鍵 mylist 是列表類型,返回結果為 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

    本小節只是拋磚引玉,給出幾個通用的命令,為 5種數據結構的使用 做一個熱身,2.7節 將對 鍵管理 做一個更為詳細的介紹。

    2.1.2 數據結構和內部編碼

    type 命令實際返回的就是 當前鍵的數據結構類型,它們分別是:string字符串)、hash哈希)、list列表)、set集合)、zset有序集合),但這些只是 Redis 對外的數據結構,如圖 2-1 所示。

    實際上 每種 數據結構 都有自己 底層的內部編碼 實現,而且是 多種實現,這樣 Redis 會在 合適的場景選擇合適的內部編碼,如圖 2-2 所示。

    可以看到 每種數據結構 都有 兩種以上的內部編碼實現,例如 list 數據結構包含了 linkedlistziplist 兩種內部編碼。同時 有些內部編碼,例如 ziplist可以作為 多種外部數據結構 的 內部實現,可以通過 object encoding 命令 查詢內部編碼

    127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> object encoding mylist "ziplist"

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BB8LhGU3-1621301836862)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517120419269.png)]

    可以看到 鍵hello 對應值的內部編碼是 embstr,鍵 mylist 對應值的內部編碼是 ziplist。

    Redis 這樣設計有兩個好處

    第一,可以改進內部編碼,而對外的數據結構和命令沒有影響,這樣一旦開發出更優秀的內部編碼,無需改動外部數據結構和命令,例如 Redis 3.2 提供了 quicklist,結合了 ziplistlinkedlist 兩者的優勢,為 列表類型 提供了 一種更為優秀的內部編碼實現,而對 外部用戶 來說基本感知不到。

    第二多種內部編碼實現 可以在 不同場景下發揮各自的優勢,例如 ziplist 比較節省內存,但是在 列表元素 比較多 的情況下,性能會有所下降,這時候 Redis 會根據 配置選項列表類型的內部實現 轉換為 linkedlist

    數據結構stringhashlistsetzset
    內部編碼rawhashtablelinkedlisthashtableskiplist
    intziplistziplistintsetziplist
    embstr
    2.1.3 單線程架構

    Redis 使用了 單線程架構I/O多路復用模型 來實現 高性能內存數據庫服務,本節首先通過 多個客戶端命令調用 的例子說明 Redis 單線程命令處理機制,接著分析 Redis 單線程模型 為什么性能如此之,最終給出為什么 理解單線程模型 是 使用和運維 Redis 的關鍵。

  • 引出單線程模型

    現在開啟了 三個 redis-cli 客戶端 同時執行命令、

    客戶端1 設置一個 字符串鍵值對:

    127.0.0.1:6379> set hello world

    客戶端2 對 counter 做自增操作:

    127.0.0.1:6379> incr counter

    客戶端3 對 counter 做自增操作:

    127.0.0.1:6379> incr counter

    Redis 客戶端 與 服務端 的模型可以簡化成 圖2-3,每次 客戶端調用都經歷了 發送命令執行命令返回結果 三個過程。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oAqoELf9-1621301836863)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517124231618.png)]

    其中 第2步 是重點要討論的,因為 Redis 是 單線程 來處理命令的,所以 一條命令從客戶端 到達 服務端 不會立刻被執行所有命令都會進入一個 隊列 中然后 逐個被執行

    所以上面 3個客戶端 命令的執行順序是不確定的(如圖2-4所示),但是可以確定 不會有兩條命令被同時執行(如圖2-5所示),所以 incr 命令無論怎么執行最終結果都是 2,不會產生并發問題,這就是 Redis 單線程的基本模型

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4uukxun5-1621301836864)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517124353808.png)]

    但是像 發送命令、返回結果、命令排隊 肯定不像描述的這么簡單,Redis 使用了 I/O多路復用技術 來解決 I/O 的問題,下一節將進行介紹。

  • 為什么單線程還能這么快

    通常來講,單線程處理能力 要比 多線程 差

    例如有 10 000 斤貨物,每輛車的運載能力是每次 200斤,那么要50次才能完成,

    但是如果有 50輛車,只要安排合理,只需一次就可以完成任務。

    那么為什么 Redis 使用 單線程模型 會達到每秒萬級別的處理能力 呢?

    可以將其歸結為三點

    第一純內存訪問,Redis 將 所有數據放在 內存 中內存 的 響應時長 大約為 100納秒,這是 Redis 達到每秒 萬級別 訪問的重要基礎

    第二非阻塞I/O,Redis 使用 epoll 作為 I/O 多路復用技術 的 實現,再加上 Redis 自身的 事件處理模型epoll 中的 連接、讀寫、關閉 都轉換為 事件不在 網絡 I/O 上浪費過多的時間,如圖 2-6 所示:

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8LbJKfu9-1621301836865)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517124935714.png)]

    第三單線程 避免了 線程切換 和 競態產生 的消耗

    既然采用 單線程 就能達到如此高的性能,那么也不失為一種不錯的選擇,

    因此單線程能帶來幾個好處

    ? 第一單線程 可以 簡化 數據結構和算法 的實現。如果對高級編程語言熟悉的讀者應該了解 并發數據結構實現 不但困難 而且 開發測試比較麻煩。

    ? 第二單線程 避免了 線程切換 和 競態產生 的消耗,對于 服務端開發 來說,鎖 和 線程切換 通常是 性能殺手

    但是 單線程 會有一個問題:對于 每個命令的執行時間 是有要求的。如果某個命令 執行過長,會造成 其他命令的 阻塞,對于 Redis 這種 高性能的服務 來說是致命的,所以 Redis 是面向 快速執行 場景的數據庫

    單線程機制 很容易被初學者忽視,但筆者認為 Redis 單線程機制 是 開發和運維人員 使用和理解 Redis 的核心之一,隨著后面的學習,相信讀者會逐步理解。

  • 2.2 字符串

    字符串類型 是 Redis 最基礎的數據結構

    首先 都是 字符串類型,而且 其他幾種數據結構 都是在 字符串類型 基礎上構建的,所以 字符串類型 能為 其他四種數據結構的學習 奠定基礎。

    如圖 2-7 所示,字符串類型的值 實際可以是 字符串(簡單的字符串、復雜的字符串(例如JSON、XML))、數字(整數、浮點數),甚至是二進制(圖片、音頻、視頻),但是 值最大 不能超過 512MB

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zYS8Dm8l-1621301836866)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517130333277.png)]

    2.2.1 命令

    ? 字符串類型 的 命令 比較多,本小節 將按照 常用和不常用 兩個維度 進行說明,但是這里 常用和不常用 是相對的,希望讀者盡可能都去了解和掌握。

  • 常用命令

    (1)設置值

    set key value [ex seconds] [px milliseconds] [nx|xx]

    下面操作 設置鍵為hello,值為world的鍵值對,返回結果為 OK 代表設置成功:

    127.0.0.1:6379> set hello world OK

    set 命令 有幾個選項:

    • ex seconds為 鍵 設置 秒級 過期時間
    • px milliseconds為 鍵 設置 毫秒級 過期時間
    • nx鍵 必須不存在,才可以設置成功,用于添加
    • xx與 nx 相反,鍵必須存在,才可以設置成功,用于更新

    除了 set 選項,Redis 還提供了 setex 和 setnx 兩個命令:

    setex key seconds value setnx key value

    它們的作用和 ex 和 nx 選項是一樣的。

    下面的例子說明了 set、setnx、set xx 的區別。

    當前 鍵 hello 不存在:

    127.0.0.1:6379> exists hello (integer) 0

    設置 鍵為hello, 值為world 的鍵值對:

    127.0.0.1:6379> set hello world OK

    因為鍵 hello 已存在,所以 setnx 失敗,返回結果為 0

    127.0.0.1:6379> setnx hello world (integer) 0

    因為鍵 hello 已存在,所以 set xx 成功,返回結果為 OK。

    127.0.0.1:6379> set hello jedis xx OK

    setnx 和 setxx 在實際使用中有什么應用場景嗎?

    以 setnx 命令為例子,由于 Redis 的 單線程命令處理機制,如果有 多個客戶端 同時執行 setnx key value,根據 setnx 的特性 只有一個客戶端能設置成功,setnx 可以作為 分布式鎖 的一種實現方案,Redis 官方給出了使用 setnx 實現分布式鎖 的方法: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)批量設置值

    mset key value [key value ...]

    下面操作通過 mset 命令一次性設置 4個鍵值對:

    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(空),結果是按照 傳入鍵 的順序 返回

    127.0.0.1:6379> mget a b c f 1) "1" 2) "2" 3) "3" 4) (nil)

    批量操作 命令 可以 有效提高開發效率,假如沒有 mget 這樣的命令,要執行 n 次 get 命令 需要按照圖 2-8 的方式來執行,具體耗時如下:

    n 次 get 時間 = n 次網絡時間 + n 次命令時間

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tpk8mJqK-1621301836867)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517133127643.png)]

    使用 mget 命令后,要執行 n 次 get 命令操作 只需要按照圖 2-9 的方式來完成,具體耗時如下:

    n 次 get 時間 = 1 次網絡時間 + n 次命令時間

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KrQD9scU-1621301836868)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517133254794.png)]

    Redis 可以支撐 每秒數萬 的讀寫操作,但是這指的是 Redis 服務端的處理能力

    對于客戶端來說,一次命令除了 命令時間 還有 網絡時間,假設 網絡時間為 1 毫秒,命令時間為 0.1 毫秒(按照每秒處理 1 萬條命令算),那么執行 1000次 get 命令 和 1次 mget 命令的區別 如表 2-1,因為 Redis 的處理能力已經足夠高,對于開發人員來說,網絡 可能會成為 性能 的瓶頸

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7z4vsnzX-1621301836869)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517133726771.png)]

    操作時間
    1 000 次 get1 000 × 1 + 1 000 × 0.1 = 1 100 毫秒 = 1.1 秒
    1 次 mget(組裝了 1 000個鍵值對)1 × 1 + 1 000 × 0.1 = 101 毫秒 = 0.101 秒

    學會使用 批量操作,有助于 提高業務處理效率,但是要注意的是 每次批量操作 所發送的命令數 不是無節制的,如果數量過多 可能造成 Redis 阻塞 或者 網絡擁塞

    (5)計數

    incr key

    incr 命令用于對值 做自增操作,返回結果分為三種情況:

    • 值不是整數,返回錯誤
    • 值是整數,返回自增后的結果
    • 鍵不存在,按照值為0 自增,返回結果為1

    例如對一個 不存在的鍵 執行 incr 操作后,返回結果是1:

    127.0.0.1:6379> exists key (integer) 0 127.0.0.1:6379> incr key (integer) 1

    再次對鍵 執行 incr 命令,返回結果是 2:

    127.0.0.1:6379> incr key (integer) 2

    如果值 不是整數,那么會 返回錯誤。

    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(自增指定數字)、decrby(自減指定數字)、incrbyfloat(自增浮點數):

    decr key incrby key increment decrby key decrement incrbyfloat key increment

    很多 存儲系統和編程語言內部 使用 CAS機制實現計數功能會有一定的CPU開銷,但在 Redis 中完全不存在這個問題,因為 Redis 是單線程架構任何命令到了 Redis 服務端 都要 順序執行

  • 不常用命令

    (1)追加值

    append key value

    append 可以向 字符串尾部 追加值,例如:

    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

    例如,當前值為 redisworld,所以返回值為10:

    127.0.0.1:6379> get key "redisworld" 127.0.0.1:6379> strlen key (integer) 10

    下面操作返回結果為 6,因為 每個中文占用 3個字節:

    127.0.0.1:6379> set hello "世界" OK 127.0.0.1:6379> strlen hello (integer) 6

    (3)設置并返回原值

    getset key value

    getset 和 set 一樣會 設置值,但是不同的是,它同時會 返回鍵原來的值,例如:

    127.0.0.1:6379> getset hello world (nil) 127.0.0.1:6379> getset hello redis "world"

    (4)設置 指定位置 的 字符

    setrange key offeset value

    下面操作將由 pest 變為了 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 end

    start 和 end 分別是 開始 和 結束的 偏移量([start, end]),偏移量 從 0 開始計算,例如下面操作獲取了 值 best 的 前兩個字符。

    127.0.0.1:6379> getrange redis 0 1 "be"

    表 2-2 是 字符串類型命令的 時間復雜度,開發人員可以參考此表,結合自身業務需求 和 數據大小 選擇合適的命令。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bA8PTUZ3-1621301836874)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517141143590.png)]

    命令時間復雜度
    set key valueO(1)
    get keyO(1)
    del key [key …]O(k),k 是鍵的個數
    mset key value [key value …]O(k),k 是鍵的個數
    mget key [key …]O(k),k 是鍵的個數
    incr keyO(1)
    decr keyO(1)
    incrby key incrementO(1)
    decrby key decrementO(1)
    incrbyfloat key incrementO(1)
    append key valueO(1)
    strlen keyO(1)
    setrange key offset valueO(1)
    getrange key start endO(n),n是字符串長度,由于獲取字符串非常快,所以如果字符串不是很長,可以視同為O(1)
  • 2.2.2 內部編碼

    字符串類型的 內部編碼 有3種

    • int8個字節的長整型
    • embstr小于等于39個字節的字符串
    • raw大于39個字節的字符串

    Redis 會根據 當前值的 類型 和 長度 決定使用哪種內部編碼 實現

    整數類型示例如下:

    127.0.0.1:6379> set key 8653 OK 127.0.0.1:6379> object encoding key "int"

    短字符串示例如下:

    # 小于等于 39 個字節 的字符串:embstr 127.0.0.1:6379> set key "hello world" OK 127.0.0.1:6379> object encoding key "embstr"

    長字符串示例如下:

    # 大于 39個字節 的字符串: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

    有關 字符串類型的內存優化技巧 將在 8.3 節詳細介紹。

    2.2.3 典型使用場景
  • 緩存功能

    圖 2-10 是比較典型的 緩存使用場景,其中 Redis 作為 緩存層MySQL 作為 存儲層絕大部分請求的數據都是從 Redis 中獲取

    由于 Redis 具有支撐 高并發 的特性,所以 緩存 通常能起到 加速讀寫降低后端壓力 的作用。

    下面偽代碼模擬了 圖 2-10 的訪問過程:

    1) 該函數用于 獲取用戶的基礎信息:

    UserInfo getUserInfo(long id){... }

    2)首先從 Redis 獲取用戶信息:

    //定義鍵 userRedisKey = "user:info:"+id;//從 Redis 獲取值 value = redis.get(userRedisKey); if(value != null){//將值 進行反序列化 為UserInfo 并返回結果userInfo = deserialize(value);return UserInfo; }

    開發提示

    與 MySQL 等關系型數據庫不同的是,Redis 沒有 命令空間,而且也沒有對 鍵名 有強制要求(除了 不能使用一些特殊字符)。

    設計合理的鍵名,有利于 防止鍵沖突項目的可維護性,比較推薦的方式是使用 “業務名 : 對象名 : id : [ 屬性 ]” 作為 鍵名(也可以不是分號)。

    例如 MySQL 的 數據庫名為 vs,用戶表名為 user,那么對應的鍵可以用 “vs:user:1”,“vs:user:1:name"來表示,例如"user : {uid} : friends : messages : {mid}”,可以在 能描述鍵含義的前提下 適當減少鍵的長度,例如變為 “u : {uid} : fr : m : {mid}”,從而 減少由于 鍵過長的內存浪費

    3)如果沒有從 Redis 獲取到用戶信息,需要從 MySQL 中進行獲取,并將結果回寫到 Redis,添加 1小時(3600秒)過期時間:

    //從 MySQL 獲取用戶信息 userInfo = mysql.get(id);//將 userInfo 序列化,并存入 redis redis.setex(userRedisKey, 3600, serialize(userInfo));//返回結果 return userInfo;

    整個功能的偽代碼如下:

    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; }
  • 計數

    許多應用都會使用 Redis 作為計數的 基礎工具,它可以實現 快速計數查詢緩存的功能,同時 數據 可以 異步落地 到 其他數據源

    例如筆者所在團隊的 視頻播放數系統 就是使用 Redis 作為視頻播放數 計數的基礎組件,用戶每播放一次視頻,相應的視頻播放數 就會 自增1。

    long incrVideoCounter(long id){key = "Video:playCount:"+id;return redis.incr(key); }

    開發提示

    實際上一個真實的 計數系統 要考慮的問題會很多:防作弊按照不同維度計數數據持久化到底層數據源等。

  • 共享Session

    如圖 2-11 所示,一個 分布式 Web 服務 將用戶的 Session信息 (例如用戶登錄信息)保存在各自服務器中,這樣會造成一個問題,出于負載均衡的考慮,分布式服務 會將 用戶的訪問 均衡到不同服務器上用戶刷新一次訪問 可能會發現 需要重新登錄,這個問題是 用戶無法容忍的。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cRZQHxM6-1621301836877)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517150020342.png)]

    為了解決這個問題,可以使用 Redis 將用戶的Session進行集中管理,如圖 2-12 所示,在這種模式下 只要保證 Redis是 高可用 和 擴展性 的,每次用戶更新或者查詢登錄信息 都是直接從 Redis 中集中獲取。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jG7RsGBG-1621301836878)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517150425297.png)]

  • 限速

    很多應用處于安全的考慮,會在每次進行登錄時,讓用戶輸入手機驗證碼,從而確定是否是用戶本人。

    但是為了 短信接口 不被頻繁訪問會限制 用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過 5次,如圖 2-13 所示。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZEaLrmw9-1621301836879)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517150617130.png)]

    此功能可以使用 Redis 來實現,下面的偽代碼給出了基本實現思路:

    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 實現了 限速功能,例如一些網站限制一個 IP地址 不能在一秒鐘之內訪問超過 n次 也可以采用類似的思路。

    除了上面介紹的幾種使用場景,字符串還有非常多的使用場景,開發人員可以結合字符串提供的相應命令充分發揮自己的想象力。

  • 2.3 哈希

    ? 幾乎所有的編程語言都提供了 哈希hash)類型,它們的叫法可能是 哈希字典關聯數組

    Redis 中,哈希類型是指 鍵本身 又是一個鍵值對結構,形如 value={{field1,value1}, ...{fieldN,valueN}},Redis鍵值對 和 哈希類型 兩者的關系 可以用圖 2-14來表示。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xJws3o4J-1621301836879)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517151327641.png)]

    注意

    哈希類型 中的 映射關系 叫做 field-value,

    注意這里的 value 是指 field 對應的值,

    不是 鍵對應的值,請注意 value 在不同上下文的作用。

    2.3.1 命令

    (1)設置值

    hset key field value

    下面為 user:1 添加一對 field-value:

    127.0.0.1:6379> hset user:1 name tom (integer) 1

    如果 設置成功 會返回1,反之會返回0

    此外 Redis 提供了 hsetnx 命令,它們的關系就像 set 和 setnx 命令一樣,只不過作用域 由 變為 field

    (2)獲取值

    hget key field

    例如,下面操作獲取 user:1 的 name 域(屬性)對應的值:

    127.0.0.1:6379> hget user:1 name "tom"

    如果 鍵 或 field 不存在,會返回 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 會刪除 一個 或 多個 field,返回結果為 成功刪除 field 的個數,例如:

    127.0.0.1:6379> hdel user:1 name (integer) 1 127.0.0.1:6379> hdel user:1 age (integer) 0

    (4)計算 field 個數

    hlen key

    例如 user:1 有 3 個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)批量設置 或 獲取 field-value

    hmget key field [field ...] hmset key field value [field value ...]

    hmset 和 hmget 分別是 批量設置 和 獲取 field-value,hmset 需要的參數是 key 和 多對 field-value,hmget 需要的參數是 key 和多個 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 域,所以 返回結果為 1,不包含時返回0

    127.0.0.1:6379> hexists user:1 name (integer) 1

    (7)獲取所有field

    hkeys key

    hkeys 命令應該叫 hfields 更為恰當,它返回 指定哈希鍵 所有的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"

    開發提示

    在使用 hgetall 時,如果 哈希元素個數比較多,會存在阻塞 Redis 的可能

    如果開發人員 只需要獲取部分field,可以使用 hmget,如果 一定要獲取 全部 field-value,可以使用 hscan 命令,該命令會 漸進式遍歷哈希類型hscan 將在 2.7節 介紹。

    (10) hincrby hincrbyfloat

    hincrby key field hincrbyfloat key field

    hincrbyhincrbyfloat,就像 incrbyincrbyfloat 命令一樣,但是它們的 作用域field

    (11)計算 value 的字符串長度(需要 Redis 3.2 以上)

    hstrlen key field

    例如 hget user:1 name 的 value 是 tom,那么 hstrlen 的返回結果是 3:

    127.0.0.1:6379> hstrlen user:1 name (integer) 3

    表 2-3 是 哈希類型命令的時間復雜度,開發人員可以參考此表選擇合適的命令。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rY0t7IhA-1621301836880)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517163340964.png)]

    命令時間復雜度
    hset key field valueO(1)
    hget key fieldO(1)
    hdel key field [field …]O(k),k 是field個數
    hlen keyO(1)
    hgetall keyO(n),n 是field總數
    hmget key field [field …]O(k),k 是field的個數
    hmset key field value [field value …]O(k),k 是field的個數
    hexists key fieldO(1)
    hkeys keyO(n),n 是field總數
    hvals keyO(n),n 是field 總數
    hsetnx key field valueO(1)
    hincrby key field incrementO(1)
    hincrbyfloat key field incrementO(1)
    hstrlen key fieldO(1)
    2.3.2 內部編碼

    ? 哈希類型內部編碼兩種

    • ziplist壓縮列表

      • 哈希類型元素個數 小于 hash-max-ziplist-entries 配置(默認 512 個)、同時 所有 值 都小于 hash-max-ziplist-value 配置(默認 64字節)時,Redis 會使用 ziplist 作為 哈希的 內部實現,ziplist 使用更加 緊湊的結構 實現 多個元素的連續存儲,所以在 節省內存方面 比 hashtable 更加優秀。
    • hashtable哈希表

      • 哈希類型 無法滿足 ziplist 的條件 時,Redis 會使用 hashtable 作為 哈希的內部實現,因為此時 ziplist 的 讀寫效率 會下降,而 hashtable 的 讀寫時間復雜度為O(1)

      下面的示例演示了 哈希類型的內部編碼,以及相應的變化。

      1)當 field 個數比較少 且 沒有大的 value 時,內部編碼為 ziplist:

      127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 OK 127.0.0.1:6379> object encoding hashkey "ziplist"

      2.1)當有 value 大于 64 字節內部編碼會由 ziplist 變為 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)當 field 個數超過 512內部編碼也會由 ziplist 變為 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"

      有關 哈希類型的內存優化技巧 將在8.3節中詳細介紹。

    2.3.3 使用場景

    圖 2-15 為 關系型數據表記錄 的兩條用戶信息,用戶的屬性 作為 表的列,每條用戶信息 作為 行。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-R2soFSmP-1621301836881)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517171153802.png)]

    idnameagecity
    1tom23beijing
    2mike30tianjin

    如果將其用 哈希類型 存儲,如圖 2-16所示。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LFYe6u71-1621301836881)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517171236539.png)]

    相比于使用 字符串序列化 緩存 用戶信息哈希類型 變得更加直觀,并且在 更新操作上 會更加便捷

    可以將 每個用戶的 id 定義為 鍵后綴,多對 field-value 對應 每個用戶的屬性,類似如下偽代碼:

    UserInfo getUserInfo(long id){//用戶id 作為 key 后綴userRedisKey = "user:info:" + id;//使用 hgetall 獲取所有用戶信息映射關系userInfoMap = redis.hgetall(userRedisKey);UserInfo userInfo;if (userInfoMap != null){//將映射關系轉換為 UserInfouserInfo = transferMapToUserInfo(userInfoMap);}else{// 從 MySQL 中獲取 用戶信息userInfo = mysql.get(id);//將UserInfo 變為映射關系 使用 hmset 保存到 Redis 中redis.hmset(userRedisKey,transferUserInfoToMap(userInfo));//添加過期時間redis.expire(userRedisKey, 3600);}return userInfo; }

    但是需要注意的是 哈希類型關系型數據庫兩點不同之處:

    • 哈希類型稀疏的,而 關系型數據庫 是完全 結構化的,例如 哈希類型 每個鍵 可以有不同的 field,而 關系型數據庫一旦 添加新的列,所有行 都要為其 設置值(即使為NULL),如圖2-17所示。
    • 關系型數據庫 可以做 復雜的關系查詢,而 Redis 去模擬關系型復雜查詢 開發困難,維護成本高

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6IYVNvHA-1621301836882)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517172939955.png)]

    開發人員需要將兩者的特點搞清楚,才能在合適的場景使用合適的技術。

    到目前為止,我們已經能夠 用三種方法緩存用戶信息,下面給出 三種方案的實現方法和優缺點分析。

    1)原生字符串類型:每個屬性一個鍵

    set user:1:name tom set user:1:age 23 set user:1:city beijing

    優點:簡單直觀每個屬性支持更新 操作。

    缺點:占用過多的鍵內存占用量較大,同時 用戶信息 內聚性 比較差,所以此種方案 一般不會在 生產環境使用。

    2)序列化 字符串類型:將 用戶信息 序列化后 用一個鍵保存

    set user:1 serialize(userInfo)

    優點:簡化編程,如果 合理的使用序列化 可以提高內存的使用率

    缺點:序列化和反序列化 有一定的開銷,同時每次更新屬性都需要把全部數據 取出進行 反序列化,更新后再序列化到 Redis 中。

    3)哈希類型每個用戶屬性使用一對 field-value,但是只用一個鍵保存

    hmset user:1 name tom age 23 city beijing

    優點:簡單直觀,如果 使用合理 可以減少 內存空間的使用

    缺點:要控制 哈希在 ziplist 和 hashtable 兩種內部編碼的轉換,hashtable 會消耗更多內存

    2.4 列表

    列表(list)類型 是用來 存儲多個 有序的 字符串

    如圖 2-18 所示,a、b、c、d、e 五個元素 從左到右 組成了一個 有序的列表,列表中的 每個字符串 稱為 元素element),

    一個列表 最多可以存儲 2的32次方-1 個元素。

    在 Redis 中,可以對 列表 兩端 插入push)和 彈出pop),還可以獲取 指定范圍的元素列表、獲取 指定索引下標的元素 等(如圖 2-18 和 圖 2-19 所示)。

    列表 是一種 比較靈活的 數據結構,它可以充當 隊列 的角色,在實際開發上 有很多應用場景。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NI5bMq4X-1621301872080)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517205903000.png)]

    列表類型兩個特點

    第一列表 中的 元素 是 有序的,這就意味著可以通過 索引下標 獲取某個元素 或者 某個范圍內的 元素列表,例如要獲取 圖 2-19的 第5個元素,可以執行 lindex user:1:message 4 (索引從 0 算起)就可以得到元素e。

    第二列表 中的 元素 可以是 重復的,例如圖 2-20所示 列表中 包含了 兩個字符串 a。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5Wnl6pK9-1621301872082)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517210257862.png)]

    這兩個特點在后面介紹 集合有序集合 后,會顯得更加突出,因此在考慮是否使用該數據結構前,首先需要弄清楚 列表 數據結構 的特點。

    2.4.1 命令

    下面將按照對 列表的 5種操作類型 對命令 進行介紹,命令如表2-4所示。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(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) 3

    lrange 0 -1 命令可以獲取 從左到右 獲取列表的所有元素

    127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a"

    (2)從左邊插入元素

    lpush key value [value ...]

    使用方法和 rpush 相同,只不過從左側插入,這里不再贅述。

    (3)向 某個元素 前 或者 后 插入元素

    linsert key before|after pivot value

    linsert 命令會從 列表 中 找到等于 pivot 的元素,在其 before) 或者 after插入一個新的元素 value,例如下面操作會在列表的元素 b 前 插入 java:

    127.0.0.1:6379> linsert listkey before b java (integer) 4

    返回結果為 4,代表 當前列表的長度,當前列表變為:

    127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "b" 4) "a"
  • 查找

    (1) 獲取 指定范圍內的元素列表

    lrange key start end

    lrange 操作會 獲取列表 指定索引范圍 所有的元素

    索引下標兩個特點

    第一索引下標 從左到右 分別是 0 到 N-1,但是 從右到左 分別是 -1 到 -N

    第二,lrange 中的 end 選項包含了自身,這個和很多編程語言不包含 end 不太相同,例如想獲取 列表的第2到第4個元素,可以執行如下操作:

    127.0.0.1:6379> lrange listkey 1 3 1) "java" 2) "b" 3) "a"

    (2)獲取 列表 指定索引下標 的元素

    lindex key index

    例如 當前列表 最后一個元素為a:

    127.0.0.1:6379> lindex listkey -1 "a"

    (3) 獲取列表長度

    llen key

    例如,下面示例 當前列表長度為 4:

    127.0.0.1:6379> llen listkey (integer) 4
  • 刪除

    (1)從列表左側 彈出 元素

    lpop key

    如下操作將 列表最左側的元素 c 會被彈出,彈出后列表變為 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)從列表右側彈出

    rpop key

    它的使用方法和 lpop 是一樣的,只不過從 列表 右側彈出,這里不再贅述。

    (3)刪除指定元素

    lrem key count value

    lrem 命令會從 列表中找到等于 value 的元素進行刪除,根據 count 的不同 分為 三種情況

    • count > 0從左到右刪除最多 count 個元素
    • count < 0從右到左刪除最多 count 絕對值個元素
    • count = 0刪除所有

    例如向 列表 從左向右 插入 5個a,那么當前列表變為 “a a a a a java b a”,下面操作將從 列表左邊開始刪除4個為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

    例如,下面操作會 只保留 列表 listkey 第2個到第4個元素:

    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"
  • 修改

    修改指定索引下標的元素

    lset key index newValue

    下面操作會將 列表 listkey 中的 第3個 元素設置為 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 ...] timeout

    blpop 和 brpop 是 lpop 和 rpop 的 阻塞版本,它們除了 彈出方向不同使用方法基本相同,所以下面以 brpop 命令進行說明,brpop 命令包含 兩個參數:

    • key [key …]多個列表的鍵
    • timeout阻塞時間(單位:

    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 ...阻塞...

    如果此期間添加了數據 element1,客戶端立即返回:

    127.0.0.1:6379> brpop list:test 3 1) "list:test" 2) "element1" (2.06s)

    2)列表不為空客戶端會立即返回

    127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1"

    在使用 brpop 時,有 兩點 需要注意。

    第一點,如果是 多個鍵,那么 brpop 會 從左至右 遍歷鍵一旦有一個鍵 能彈出元素,客戶端立即返回

    127.0.0.1:6379> brpop list:1 list:2 list:3 ...阻塞...

    此時 另一個客戶端 分別向 list:2 和 list:3 插入元素:

    client-lpush> lpush list:2 element2 (integer) 1 client-lpush> lpush list:3 element3 (integer) 1

    客戶端會 立即返回 list:2 中的element2 ,因為 list:2 最先有可以彈出的元素:

    127.0.0.1:6379> brpop list:1 list:2 list:3 1) "list:2" 2) "element2"

    第二點,如果 多個客戶端 對 同一個鍵執行 brpop,那么 最先執行 brpop 命令的客戶端 可以獲取到 彈出的值

    客戶端1:

    client-1> brpop list:test 0 ...阻塞...

    客戶端2:

    client-2> brpop list:test 0 ...阻塞...

    客戶端3:

    client-3> brpop list:test 0 ...阻塞...

    此時另一個客戶端 lpush 一個元素到 list:test 列表中:

    client-lpush> lpush list:test element (integer) 1

    那么 客戶端1 最先會獲取到元素,因為 客戶端1 最先執行 brpop,而客戶端2 和 客戶端3繼續阻塞:

    client> brpop list:test 0 1) "list:test" 2) "element"

    有關 列表 的 基礎命令 已經介紹完了,表 2-5 是這些命令的時間復雜度,開發人員可以參考此表 選擇合適的命令。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-55GHVUyL-1621301872089)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517214858071.png)]

    操作類型命令時間復雜度
    添加rpush key value [value …]O(k),k 是元素個數
    添加lpush key value [value …]O(k),k 是元素個數
    添加linsert key before|after pivot valueO(n),n 是pivot 距離列表頭或尾的舉例
    查找lrange key start endO(s+n),s 是 start 偏移量,n 是 start 到 end 的范圍
    查找lindex key indexO(n),n 是索引的偏移量
    查找llen keyO(1)
    刪除lpop keyO(1)
    刪除rpop keyO(1)
    刪除lrem count keyO(n),n 是列表長度
    刪除ltrim key start endO(n),n 是要裁剪的元素總數
    修改lset key index valueO(n),n 是索引的偏移量
    阻塞操作blpop brpopO(1)
  • 2.4.2 內部編碼

    列表類型內部編碼兩種

    • ziplist壓縮列表

      • 列表的 元素個數 小于 list-max-ziplist-entries 配置(默認 512 個),同時 列表中 每個元素的值 都小于 list-max-ziplist-value 配置時(默認 64 字節),Redis 會選用 ziplist 來作為 列表的內部實現減少內存的使用
    • linkedlist鏈表

      • 列表類型 無法滿足 ziplist 的條件時,Redis 會使用 linkedlist 作為 列表的內部實現

      下面的示例演示了 列表類型的內部編碼,以及相應的變化。

      1)當 元素個數 較少 且 沒有大元素 時,內部編碼為 ziplist:

      127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist"

      2.1)當 元素個數超過 512個內部編碼變為 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)或者當 某個元素超過 64 字節內部編碼也會變為 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"

      開發提示

      Redis 3.2 版本提供了 quicklist 內部編碼,

      簡單地說它是 以一個ziplist 為節點的 linkedlist,它結合了 ziplistlinkedlist 兩者的優勢,為 列表類型 提供了一種更為優秀的內部編碼實現,它的設計原理可以參考Redis的另一個作者Matt Stancliff 的博客:http://matt.sh/redis-quicklist。

      有關 列表類型的優化技巧 將在 8.3節 詳細介紹。

    2.4.3 使用場景
  • 消息隊列

    如圖 2-21所示,Redis的 lpush+brpop 命令組合即可實現 阻塞隊列,生產者客戶端 使用 lpush 從列表左側插入元素,多個消費者客戶端使用 brpop 命令阻塞式的 “搶” 列表尾部的元素,多個客戶端 保證了 消費的負載均衡 和 高可用性。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kzgBPzc3-1621301872090)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210517222037844.png)]

  • 文章列表

    每個用戶有屬于自己的 文章列表,現需要 分頁展示 文章列表。

    此時可以考慮使用 列表,因為 列表 不但是 有序的,同時 支持按照 索引范圍 獲取元素。

    1)每篇文章使用 哈希結構 存儲,例如每篇文章有 3個屬性 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)分頁 獲取 用戶文章列表,例如下面偽代碼獲取用戶 id=1 的前 10 篇文章:

    articles = lrange user:1:articles 0 9 for article in {articles}hgetall {article}

    使用 列表類型保存 和 獲取文章列表 會存在兩個問題。

    第一,如果 每次分頁獲取的文章個數較多,需要執行多次 hgetall 操作,此時可以考慮使用 Pipeline(第3章會介紹)批量獲取,或者考慮 將文章數據序列化為字符串,使用 mget 批量獲取。

    第二,分頁獲取文章列表時,lrange 命令在列表兩端性能較好,但是如果 列表較大,獲取列表中間范圍的元素 性能會變差,此時可以考慮將 列表做二級拆分,或者使用 Redis 3.2 的quicklist 內部編碼實現,它結合 ziplist 和 linkedlist 的特點,獲取 列表中間范圍的元素 時也可以高效完成。

  • 總結

    以上是生活随笔為你收集整理的Redis开发与运维的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。