文章目錄
- 1、Nosql概述
- 1.1 為什么要用Nosql
- 1.2 什么是NoSQL
- 1.3 阿里巴巴演進分析
- 2、NoSQL的四大分類
- 3、Redis入門
- 3.1 概述
- 3.2 Windows安裝
- 3.3 Linux安裝
- 3.4 測試性能
- 3.5 基礎的知識
- 4、五大數據類型
- 4.1 Redis-Key
- 4.2 String(字符串)
- 4.3 List(列表)
- 4.4 Set(集合)
- 4.5 Hash(哈希)
- 4.6 Zset(有序集合)
- 5、三種特殊數據類型
- 5.1 Geospatial 地理位置
- 5.2 Hyperloglog 基數統計
- 5.3 Bitmap 位圖
- 6、事務
- 7、Jedis
- 7.1 事務
- 8、SpringBoot整合
- 9、Redis.conf詳解
- 10、Redis持久化
- 10.1 RDB(Redis DataBase)
- 10.2 AOF(Append Only File)
- 11、Redis發布訂閱
- 12、Redis主從復制
- 13、哨兵模式
- 14、Redis緩存穿透和雪崩
- 14.1 緩存穿透(查不到)
- 14.2 緩存擊穿(量太大,緩存過期!)
- 14.3 緩存雪崩
1、Nosql概述
1.1 為什么要用Nosql
1 、單機MySQL的年代!
90 年代,一個基本的網站訪問量一般不會太大,單個數據庫完全足夠!
那個時候,更多的去使用靜態網頁 Html ~ 服務器根本沒有太大的壓力!
思考一下,這種情況下:整個網站的瓶頸是什么?
1 、數據量如果太大、一個機器放不下了!
2 、數據的索引 (B+ Tree),一個機器內存也放不下
3 、訪問量(讀寫混合),一個服務器承受不了~
只要你開始出現以上的三種情況之一,那么你就必須要晉級!
2 、Memcached(緩存) + MySQL + 垂直拆分 (讀寫分離)
網站80%的情況都是在讀,每次都要去查詢數據庫的話就十分的麻煩!所以說我們希望減輕數據的壓
力,我們可以使用緩存來保證效率!
發展過程: 優化數據結構和索引–> 文件緩存(IO)—> Memcached(當時最熱門的技術!)
3 、分庫分表 + 水平拆分 + MySQL集群
技術和業務在發展的同時,對人的要求也越來越高!
本質:數據庫(讀,寫)
早些年 MyISAM: 表鎖,十分影響效率!高并發下就會出現嚴重的鎖問題
轉戰 InnoDB:行鎖
慢慢的就開始使用分庫分表來解決寫的壓力! MySQL 在那個年代推出了表分區!這個并沒有多少公司使用!
MySQL 的 集群,很好滿足那個年代的所有需求!
4、如今年代
2010–2020 十年之間,世界已經發生了翻天覆地的變化;(定位,也是一種數據,音樂,熱榜!)
MySQL 等關系型數據庫就不夠用了!數據量很多,變化很快~!
MySQL 有的使用它來村粗一些比較大的文件,博客,圖片!數據庫表很大,效率就低了!如果有一種數據庫來專門處理這種數據,MySQL壓力就變得十分小(研究如何處理這些問題!)大數據的IO壓力下,表幾乎沒法更大!
目前一個基本的互聯網項目!
為什么要用NoSQL!
用戶的個人信息,社交網絡,地理位置。用戶自己產生的數據,用戶日志等等爆發式增長!
這時候我們就需要使用NoSQL數據庫的,Nosql 可以很好的處理以上的情況!
1.2 什么是NoSQL
NoSQL
NoSQL = Not Only SQL (不僅僅是SQL)
關系型數據庫:表格 ,行 ,列
泛指非關系型數據庫的,隨著web2.0互聯網的誕生!傳統的關系型數據庫很難對付web2.0時代!尤其
是超大規模的高并發的社區! 暴露出來很多難以克服的問題,NoSQL在當今大數據環境下發展的十分迅速,Redis是發展最快的,而且是我們當下必須要掌握的一個技術!
很多的數據類型用戶的個人信息,社交網絡,地理位置。這些數據類型的存儲不需要一個固定的格式!不需要多余的操作就可以橫向擴展的 ! Map<String,Object> 使用鍵值對來控制!
NoSQL 特點
1 、方便擴展(數據之間沒有關系,很好擴展!)
2 、大數據量高性能(Redis 一秒寫 8 萬次,讀取 11 萬,NoSQL的緩存記錄級,是一種細粒度的緩存,性能會比較高!)
3 、數據類型是多樣型的!(不需要事先設計數據庫!隨取隨用!如果是數據量十分大的表,很多人就無法設計了!)
4 、高可用
傳統的RDBMS 和 NoSQL
傳統 RDBMS
-結構化組織
-SQL
-數據和關系都在單獨的表中
-嚴格的一致性
-操作數據定義語言
-基礎的事務
-....NoSQL
-不僅僅是數據
-沒有固定的查詢語言
-鍵值對存儲,列存儲,文檔存儲,圖形數據庫(社交關系)
-最終一致性,
-CAP定理和BASE理論 (異地多活) 初級架構師!
-高性能,高可用,高可擴
-....
了解:3V+3高
大數據時代的3V:主要是描述問題的
- 海量Volume
- 多樣Variety
- 實時Velocity
大數據時代的 3 高:主要是對程序的要求
- 高并發
- 高可擴
- 高性能
真正在公司中的實踐:NoSQL + RDBMS 一起使用才是最強的,阿里巴巴的架構演進!
技術沒有高低之分,就看你如何去使用!(提升內功,思維的提高!)
1.3 阿里巴巴演進分析
思考問題:這么多東西難道都是在一個數據庫中的嗎?
技術急不得,越是慢慢學,才能越扎實!
開源才是技術的王道!
任何一家互聯網的公司,都不可能只是簡簡單單讓用戶能用就好了!
大量公司做的都是相同的業務;(競品協議)
隨著這樣的競爭,業務是越來越完善,然后對于開發者的要求也是越來越高!
如果你未來相當一個架構師: 沒有什么是加一層解決不了的!
# 1、商品的基本信息名稱、價格、商家信息;關系型數據庫就可以解決了! MySQL / Oracle (淘寶早年就去IOE了!- 王堅:推薦文章:阿里云的這群瘋子: 40 分鐘重要!)淘寶內部的 MySQL 不是大家用的 MySQL# 2、商品的描述、評論(文字比較多)文檔型數據庫中,MongoDB# 3、圖片分布式文件系統 FastDFS- 淘寶自己的 TFS- Gooale的 GFS- Hadoop HDFS- 阿里云的 oss# 4、商品的關鍵字 (搜索) - 搜索引擎 solr elasticsearch- ISerach:多隆(多去了解一下這些技術大佬!)所有牛逼的人都有一段苦逼的歲月!但是你只要像SB一樣的去堅持,終將牛逼!# 5、商品熱門的波段信息、- 內存數據庫- Redis Tair、Memache...# 6、商品的交易,外部的支付接口- 三方應用
要知道,一個簡單地網頁背后的技術一定不是大家所想的那么簡單!
大型互聯網應用問題:
數據類型太多了!
數據源繁多,經常重構!
數據要改造,大面積改造?
解決問題:
這里以上都是NoSQL入門概述,不僅能夠提高大家的知識,還可以幫助大家了解大廠的工作內容!
2、NoSQL的四大分類
KV鍵值對:
- 新浪: Redis
- 美團:Redis + Tair
- 阿里、百度:Redis + memecache
文檔型數據庫(bson格式 和json一樣):
- MongoDB (一般必須要掌握)
- MongoDB 是一個基于分布式文件存儲的數據庫,C++ 編寫,主要用來處理大量的文檔!
- MongoDB 是一個介于關系型數據庫和非關系型數據庫中間的產品!MongoDB 是非關系型數
據庫中功能最豐富,最像關系型數據庫的!
- ConthDB
列存儲數據庫
- HBase
- 分布式文件系統
圖關系數據庫

- 他不是存圖形,放的是關系,比如:朋友圈社交網絡,廣告推薦!
- Neo4j ,InfoGrid;
四者對比
分類 | Examples舉例 | 典型應用場景 | 數據模型 | 優點 | 缺點 |
---|---|---|---|---|---|
鍵值(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 內容緩存,主要用于處理大量數據的高訪問負載,也用于一些日志系統等等。 | Key 指向 Value 的鍵值對,通常用hash table來實現 | 查找速度快 | 數據無結構化,通常只被當作字符串或者二進制數據 |
列存儲數據庫 | Cassandra, HBase, Riak | 分布式的文件系統 | 以列簇式存儲,將同一列數據存在一起 | 查找速度快,可擴展性強,更容易進行分布式擴展 | 功能相對局限 |
文檔型數據庫 | CouchDB, MongoDb | Web應用(與Key-Value類似,Value是結構化的,不同的是數據庫能夠了解Value的內容) | Key-Value對應的鍵值對,Value為結構化數據 | 數據結構要求不嚴格,表結構可變,不需要像關系型數據庫一樣需要預先定義表結構 | 查詢性能不高,而且缺乏統一的查詢語法。 |
圖形(Graph)數據庫 | Neo4J, InfoGrid, Infinite Graph | 社交網絡,推薦系統等。專注于構建關系圖譜 | 圖結構 | 利用圖結構相關算法。比如最短路徑尋址,N度關系查找等 | 很多時候需要對整個圖做計算才能得出需要的信息,而且這種結構不太好做分布式的集群方案。 |
3、Redis入門
3.1 概述
Redis 是什么?
Redis(Remote Dictionary Server ),即遠程字典服務!
是一個開源的使用ANSI C語言編寫、支持網絡、可基于內存亦可持久化的日志型、Key-Value數據庫,并提供多種語言的API。
redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎上實現了
master-slave(主從)同步。
免費和開源!是當下最熱門的 NoSQL 技術之一!也被人們稱之為結構化數據庫!
Redis 能干嘛?
- 內存存儲、持久化,內存中是斷電即失、所以說持久化很重要(rdb、aof)
- 效率高,可以用于高速緩存
- 發布訂閱系統
- 地圖信息分析
- 計時器、計數器(瀏覽量!)
- …
特性
-
多樣的數據類型
-
持久化
-
集群
-
事務
-
…
缺點
-
不適合存儲重要的數據(財務類等)
-
不適合存儲經常修改的數據
3.2 Windows安裝
1 、下載安裝包:https://github.com/dmajkic/redis/releases
2 、下載完畢得到壓縮包:
3 、解壓到自己電腦上的環境目錄下的就可以的!Redis 十分的小,只有5M
4 、開啟Redis,雙擊運行服務–redis-server.exe即可!
5 、使用redis客戶端–redis-cli.exe來連接redis
記住一句話,Window下使用確實簡單,但是Redis 推薦我們使用Linux去開發使用!
3.3 Linux安裝
1 、下載安裝包! redis-5.0.8.tar.gz ,用xftp把安裝包放到 /opt
下
2 、解壓Redis的安裝包! 程序 /opt
tar -zxvf redis-5.0.8.tar.gz
3 、進入解壓后的文件,可以看到我們redis的配置文件
4 、基本的環境安裝
yum install gcc-c++ # 安裝gcc,如果使用redis6.0以上的話需要gcc9.0版本以上才gcc -v # 查看安裝版本make # 自動進行配置,安裝后必須執行,需要等待較長時間make install # 接著執行
5 、redis的默認安裝路徑 /usr/local/bin
cd /usr/local/bin # 進入目錄ls # 查看
6 、將redis配置文件。復制到我們當前目錄下
mkdir RedisConfig # 創建目錄存放配置文件cp /opt/redis-5.0.8/redis.conf RedisConfig/ # 將/opt/redis-5.0.8/下的配置文件拷貝過來
7 、redis默認不是后臺啟動的,修改配置文件!
vi redis.conf # 進行編輯
8 、啟動Redis服務!
cd /usr/local/bin # 回到bin目錄redis-server RedisConfig/redis.conf # 啟動redis服務
9 、使用redis-cli 進行連接測試!
redis-cli -p 6379 # -h 指定主機 -p指定端口ping # 測試連接
10 、復制連接,新開一個窗口,查看redis的進程是否開啟!
ps -ef|grep redis
11 、如何關閉Redis服務呢?
shutdown # 關閉服務exit # 退出
12 、再次查看進程是否存在
13 、后面我們會使用單機多Redis啟動集群測試!
3.4 測試性能
redis-benchmark 是一個壓力測試工具!
官方自帶的性能測試工具!
redis-benchmark 命令參數!
redis 性能測試工具可選參數如下所示:
序號 | 選項 | 描述 | 默認值 |
---|---|---|---|
1 | -h | 指定服務器主機名 | 127.0.0.1 |
2 | -p | 指定服務器端口 | 6379 |
3 | -s | 指定服務器 socket | |
4 | -c | 指定并發連接數 | 50 |
5 | -n | 指定請求數 | 10000 |
6 | -d | 以字節的形式指定 SET/GET 值的數據大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用隨機 key, SADD 使用隨機值 | |
9 | -P | 通過管道傳輸 請求 | 1 |
10 | -q | 強制退出 redis。僅顯示 query/sec 值 | |
11 | –csv | 以 CSV 格式輸出 | |
12 | -l | 生成循環,永久執行測試 | |
13 | -t | 僅運行以逗號分隔的測試命令列表。 | |
14 | -I | Idle 模式。僅打開 N 個 idle 連接并等待。 |
我們來簡單測試下:
# 測試: 100 個并發連接 每個并發100000 個請求,需要先開啟redis
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
如何查看這些分析呢?
3.5 基礎的知識
redis默認有 16 個數據庫
默認使用的是第 0 個
# 可以使用 select 進行切換數據庫!
127 .0.0.1:6379> select 3 # 切換數據庫
OK
127 .0.0.1:6379[3]> DBSIZE # 查看DB大小!
(integer) 0
127 .0.0.1:6379[3]> set name xu
127 .0.0.1:6379[3]> keys * # 查看數據庫所有的key
1 ) "name"
127.0.0.1:6379[3]> FLUSHDB # 清除當前數據庫
OK
127.0.0.1:6379[3]> FLUSHALL # 清除全部數據庫的內容
OK
思考:為什么redis是 6379 !粉絲效應!(了解一下即可!)
Redis 是單線程的!
明白Redis是很快的,官方表示,Redis是基于內存操作,CPU不是Redis性能瓶頸,Redis的瓶頸是根據機器的內存和網絡帶寬來定,既然可以使用單線程來實現,就使用單線程了!所以就使用了單線程了!
Redis 是C 語言寫的,官方提供的數據為 100000+ 的QPS,完全不比同樣是使用 key-vale的
Memecache差!
Redis 為什么單線程還這么快?
1 、誤區 1 :高性能的服務器一定是多線程的?
2 、誤區 2 :多線程(CPU上下文會切換!)一定比單線程效率高!
先去CPU>內存>硬盤的速度要有所了解!
核心:redis 是將所有的數據全部放在內存中的,所以說使用單線程去操作效率就是最高的,多線程
(CPU上下文會切換:耗時的操作!!!),對于內存系統來說,如果沒有上下文切換效率就是最高的!多次讀寫都是在一個CPU上的,在內存情況下,這個就是最佳的方案!
4、五大數據類型
Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它可以用作數據庫、緩存和消息中間件MQ。 它支持多種類型的數據結構,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與范圍查詢, bitmaps, hyperloglogs 和 地理空間
(geospatial) 索引半徑查詢。 Redis 內置了 復制(replication),LUA腳本(Lua scripting), LRU 驅動事件(LRU eviction),事務(transactions) 和不同級別的 磁盤持久化(persistence), 并通過 Redis哨兵(Sentinel)和 自動分區(Cluster)提供高可用性(high availability)。
我們現在講解的所有命令大家一定要全部記住,后面我們使用SpringBoot。Jedis,所有的方法就是
這些命令!——單點登錄
4.1 Redis-Key
127 .0.0.1:6379> keys * # 查看所有的key
(empty list or set)
127 .0.0.1:6379> set name kuangshen # set key
OK
127 .0.0.1:6379> keys *
1 ) "name"
127 .0.0.1:6379> set age 1
OK
127 .0.0.1:6379> keys *
1 ) "age"
2 ) "name"
127 .0.0.1:6379> EXISTS name # 判斷當前的key是否存在
(integer) 1
127 .0.0.1:6379> EXISTS name
(integer) 0
127 .0.0.1:6379> move name 1 # 移除當前的key
(integer) 1
127 .0.0.1:6379> keys *
1 ) "age"
127 .0.0.1:6379> set name qinjiang
OK
127 .0.0.1:6379> keys *
1 ) "age"
2 ) "name"
127 .0.0.1:6379> clear
127 .0.0.1:6379> keys *
1 ) "age"
2 ) "name"
127 .0.0.1:6379> get name
"qinjiang"
127 .0.0.1:6379> EXPIRE name 10 # 設置key的過期時間,單位是秒
(integer) 1
127 .0.0.1:6379> ttl name # 查看當前key的剩余時間
(integer) 4
127 .0.0.1:6379> ttl name
(integer) 3
127 .0.0.1:6379> ttl name
(integer) 2
127 .0.0.1:6379> ttl name
(integer) 1
127 .0.0.1:6379> ttl name
(integer) -2
127 .0.0.1:6379> get name
(nil)
127 .0.0.1:6379> type name # 查看當前key的一個類型!
string
127 .0.0.1:6379> type age
string
4.2 String(字符串)
90% 的 java程序員使用 redis 只會使用一個String類型!
##########################################################################
127 .0.0.1:6379> set key1 v1 # 設置值
OK
127 .0.0.1:6379> get key1 # 獲得值
"v1"
127 .0.0.1:6379> keys * # 獲得所有的key
1 ) "key1"
127 .0.0.1:6379> EXISTS key1 # 判斷某一個key是否存在
(integer) 1
127 .0.0.1:6379> APPEND key1 "hello" # 追加字符串,如果當前key不存在,就相當于setkey
(integer) 7
127 .0.0.1:6379> get key1
"v1hello"
127 .0.0.1:6379> STRLEN key1 # 獲取字符串的長度!
(integer) 7
127 .0.0.1:6379> APPEND key1 ",kaungshen"
(integer) 17
127 .0.0.1:6379> STRLEN key1
(integer) 17
127 .0.0.1:6379> get key1
"v1hello,kaungshen" ##########################################################################
# i++
# 步長 i+=
127 .0.0.1:6379> set views 0 # 初始瀏覽量為 0
OK
127 .0.0.1:6379> get views
"0"
127 .0.0.1:6379> incr views # 自增 1 瀏覽量變為 1
(integer) 1
127 .0.0.1:6379> incr views
(integer) 2
127 .0.0.1:6379> get views
"2"
127 .0.0.1:6379> decr views # 自減 1 瀏覽量-1
(integer) 1
127 .0.0.1:6379> decr views
(integer) 0
127 .0.0.1:6379> decr views
(integer) -1
127 .0.0.1:6379> get views
"-1"
127 .0.0.1:6379> INCRBY views 10 # 可以設置步長,指定增量!
(integer) 9
127 .0.0.1:6379> INCRBY views 10
(integer) 19
127 .0.0.1:6379> DECRBY views 5
(integer) 14##########################################################################
# 字符串范圍 getrange
127 .0.0.1:6379> set key1 "hello,kuangshen" # 設置 key1 的值
OK
127 .0.0.1:6379> get key1
"hello,kuangshen"
127 .0.0.1:6379> getrange key1 0 3 # 截取字符串 [0,3]
"hell"
127 .0.0.1:6379> GETRANGE key1 0 -1 # 獲取全部的字符串 和 get key是一樣的
"hello,kuangshen"##########################################################################
# 替換! setrange
127 .0.0.1:6379> set key2 abcdefg
OK
127 .0.0.1:6379> get key2
"abcdefg"
127 .0.0.1:6379> setrange key2 1 xx # 替換指定位置開始的字符串!
(integer) 7
127 .0.0.1:6379> get key2
"axxdefg"##########################################################################
setex (set with expire) # 設置值同時設置過期時間setnx (set if not exist) # 不存在再設置,存在的話保持原值 (在分布式鎖中會常常使用!)127 .0.0.1:6379> setex key3 30 "hello" # 設置key3 的值為 hello,30秒后過期
OK
127 .0.0.1:6379> ttl key3
(integer) 26
127 .0.0.1:6379> get key3
"hello"
127 .0.0.1:6379> setnx mykey "redis" # 如果mykey 不存在,創建mykey
(integer) 1
127 .0.0.1:6379> keys *
1 ) "key2"
2 ) "mykey"
3 ) "key1"
127 .0.0.1:6379> ttl key3
(integer) -2
127 .0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,創建失敗!
(integer) 0
127 .0.0.1:6379> get mykey
"redis"##########################################################################
mset # 同時設置多個值
mget # 同時獲取多個值127 .0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同時設置多個值
OK
127 .0.0.1:6379> keys *
1 ) "k1"
2 ) "k2"
3 ) "k3"
127 .0.0.1:6379> mget k1 k2 k3 # 同時獲取多個值
1 ) "v1"
2 ) "v2"
3 ) "v3"
127.0.0.1:6379> msetnx k1 v2 k4 v4 #設置失敗,k1已經存在,msetnx是原子性操作
(integer) 0
127.0.0.1:6379> get k4
(nil)##########################################################################
# 對象
set user:1 {name:zhangsan,age:3} # 設置一個user:1 對象 值為 json字符來保存一個對象!127.0.0.1:6379> set user:1 {name:xu,age:3}
OK
127.0.0.1:6379> get user:1 #本質還是鍵值對,user:1為key,"{name:xu,age:3}"為value
"{name:xu,age:3}"
# 這里的key是一個巧妙的設計: user:{id}:{filed} , 如此設計在Redis中是完全OK了!
127.0.0.1:6379> mset user:1:name xu1 user:1:age 4 # 這里本質也是鍵值對
OK
127.0.0.1:6379> get user:1
"{name:xu,age:3}"
127.0.0.1:6379> mget user:1:name user:1:age
1) "xu1"
2) "4"##########################################################################
getset # 先get然后再set
127 .0.0.1:6379> getset db redis # 如果不存在值,則返回 nil
(nil)
127 .0.0.1:6379> get db
"redis
127 .0.0.1:6379> getset db mongodb # 如果存在值,獲取原來的值,并設置新的值
"redis"
127 .0.0.1:6379> get db
"mongodb"
數據結構是相同的!
String類似的使用場景:value除了是我們的字符串還可以是我們的數字!
-
計數器
-
統計多單位的數量
-
粉絲數
-
對象緩存存儲!
4.3 List(列表)
基本的數據類型,列表
在redis里面,我們可以把list玩成 ,棧、隊列、阻塞隊列!
所有的list命令都是用 L 開頭的,Redis不區分大小寫命令
##########################################################################
lpush # 從左邊頭部插入
rpush # 從右邊尾部插入
127 .0.0.1:6379> LPUSH list one # 將一個值或者多個值,插入到列表頭部 (左)
(integer) 1
127 .0.0.1:6379> LPUSH list two
(integer) 2
127 .0.0.1:6379> LPUSH list three
(integer) 3
127 .0.0.1:6379> LRANGE list 0 -1 # 不能用get命令,獲取list中的所有值!
1 ) "three"
2 ) "two"
3 ) "one"
127 .0.0.1:6379> LRANGE list 0 1 # 通過區間獲取具體的值!
1 ) "three"
2 ) "two"
127 .0.0.1:6379> Rpush list right # 將一個值或者多個值,插入到列表尾部 (右)
(integer) 4
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "two"
3 ) "one"
4 ) "righr"##########################################################################
lpop # 從左邊頭部移除
rpop # 從右邊尾部移除
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "two"
3 ) "one"
4 ) "righr"
127 .0.0.1:6379> Lpop list # 移除list的第一個元素
"three"
127 .0.0.1:6379> Rpop list # 移除list的最后一個元素
"righr"
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "two"
2 ) "one"##########################################################################
lindex
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "two"
2 ) "one"
127 .0.0.1:6379> lindex list 1 # 通過下標獲得 list 中的某一個值!
"one"
127 .0.0.1:6379> lindex list 0
"two"##########################################################################
llen # 獲取列表長度
127 .0.0.1:6379> Lpush list one
(integer) 1
127 .0.0.1:6379> Lpush list two
(integer) 2127 .0.0.1:6379> Lpush list three(integer) 3127 .0.0.1:6379> Llen list # 返回列表的長度(integer) 3##########################################################################lrem # 移除指定的值!(從頭部開始的順序移除)# 取關 uid127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "3"
5) "3"
127.0.0.1:6379> lrem list 1 3 # 移除list集合中指定個數的value,精確匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
3) "3"
4) "3"
127.0.0.1:6379> lrem list 3 3 # 移除的個數比列表擁有的個數多的話就全部移除
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"##########################################################################
ltrim # 修剪 list被截斷!127.0.0.1:6379> lrange list 0 -1
1) "6"
2) "5"
3) "4"
4) "1"
5) "2"
6) "1"
127.0.0.1:6379> ltrim list 2 3 # 截取指定開始下標和結束下標的列表
OK
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "1"##########################################################################rpoplpush # 移除列表的最后一個元素,將他移動到新的列表的頭部中!127 .0.0.1:6379> rpush mylist "hello"
(integer) 1127 .0.0.1:6379> rpush mylist "hello1"(integer) 2127 .0.0.1:6379> rpush mylist "hello2"(integer) 3127 .0.0.1:6379> rpoplpush mylist myotherlist # 移除列表的最后一個元素,將他移動到新的列表頭部中!"hello2"127 .0.0.1:6379> lrange mylist 0 -1 # 查看原來的列表1 ) "hello"2 ) "hello1"127 .0.0.1:6379> lrange myotherlist 0 -1 # 查看目標列表中,確實存在改值!1 ) "hello2"##########################################################################lset # 將列表中指定下標的值替換為另外一個值,更新操作127 .0.0.1:6379> EXISTS list # 判斷這個列表是否存在(integer) 0127 .0.0.1:6379> lset list 0 item # 如果不存在列表我們去更新就會報錯(error) ERR no such key127 .0.0.1:6379> lpush list value1(integer) 1127 .0.0.1:6379> LRANGE list 0 01 ) "value1"127 .0.0.1:6379> lset list 0 item # 如果存在,更新當前下標的值OK127 .0.0.1:6379> LRANGE list 0 01 ) "item"127 .0.0.1:6379> lset list 1 other # 如果不存在,則會報錯!(error) ERR index out of range##########################################################################linsert # 將某個具體的value插入到列把你中某個元素(如果列表有多個符合的元素,從頭部開始找到的元素就是指定的元素)的前面或者后面!127 .0.0.1:6379> Rpush mylist "hello"(integer) 1127 .0.0.1:6379> Rpush mylist "world"(integer) 2127 .0.0.1:6379> LINSERT mylist before "world" "other"(integer) 3127 .0.0.1:6379> LRANGE mylist 0 -11 ) "hello"2 ) "other"3 ) "world"127 .0.0.1:6379> LINSERT mylist after world new(integer) 4127 .0.0.1:6379> LRANGE mylist 0 -11 ) "hello"2 ) "other"3 ) "world"4 ) "new"
小結
- 他實際上是一個鏈表,before Node ,after Node , left,right 都可以插入值
- 如果key 不存在,創建新的鏈表
- 如果key存在,新增內容
- 如果移除了所有值,空鏈表,也代表不存在!
- 在兩邊插入或者改動值,效率最高! 改變中間元素,相對來說效率會低一點~
消息排隊!消息隊列 (Lpush Rpop), 棧( Lpush Lpop)!
4.4 Set(集合)
set中的值是不重復的!
##########################################################################
sadd # set集合中添加多個元素
smembers # 查看指定set的所有值
sismembers # 判斷某一個值是不是在set集合中!127.0.0.1:6379> sadd myset 1 2 3 4 5 xu # set集合中添加元素
(integer) 6
127.0.0.1:6379> smembers myset # 查看指定set的所有值
1) "2"
2) "4"
3) "3"
4) "5"
5) "xu"
6) "1"
127 .0.0.1:6379> sismembers myset 1 # 判斷某一個值是不是在set集合中!
(integer) 1
127 .0.0.1:6379> SISMEMBER myset 6
(integer) 0##########################################################################
127.0.0.1:6379> scard myset # 獲取set集合中的內容元素個數!
(integer) 6##########################################################################
srem # 移除set集合中的指定元素(可以多個)
127.0.0.1:6379> srem myset 3 4 # 移除set集合中的指定元素(可以多個)
(integer) 2
127.0.0.1:6379> smembers myset
1) "2"
2) "5"
3) "xu"
4) "1"##########################################################################
set 無序不重復集合。
srandmember # 抽隨機元素!127.0.0.1:6379> srandmember myset # 隨機抽取一個元素
"xu"
127.0.0.1:6379> srandmember myset
"5"
127.0.0.1:6379> srandmember myset 5 # 抽取隨機5個元素
1) "2"
2) "5"
3) "1"
4) "xu"
127.0.0.1:6379> srandmember myset 3
1) "5"
2) "1"
3) "xu"##########################################################################spop # 隨機刪除key!(可指定個數)127.0.0.1:6379> spop myset
"1"
127.0.0.1:6379> spop myset
"5"
127.0.0.1:6379> smembers myset
1) "2"
2) "xu"
127.0.0.1:6379> spop myset 2 # 隨機刪除兩個元素
1) "2"
2) "xu"
127.0.0.1:6379> smembers myset
(empty list or set)##########################################################################將一個指定的值,移動到另外一個set集合!127 .0.0.1:6379> sadd myset "hello"(integer) 1127 .0.0.1:6379> sadd myset "world"(integer) 1127 .0.0.1:6379> sadd myset "kuangshen"(integer) 1127 .0.0.1:6379> sadd myset2 "set2"(integer) 1127 .0.0.1:6379> smove myset myset2 "kuangshen" # 將一個指定的值,移動到另外一個set集合!(integer) 1127 .0.0.1:6379> SMEMBERS myset1 ) "world"2 ) "hello"127 .0.0.1:6379> SMEMBERS myset21 ) "kuangshen"2 ) "set2"########################################################################### 微博,B站,共同關注!(并集)# 數字集合類:- 差集 - 交集- 并集127.0.0.1:6379> smembers myset
1) "1"
2) "3"
3) "4"
127.0.0.1:6379> smembers myset2
1) "1"
2) "2"
127.0.0.1:6379> sdiff myset myset2 # 差集,以第一個為主,做比較
1) "3"
2) "4"
127.0.0.1:6379> sinter myset myset2 # 交集 ,共同好友
1) "1"
127.0.0.1:6379> sunion myset myset2 # 并集
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sadd myset3 1 2 3 5
(integer) 4
127.0.0.1:6379> sdiff myset myset2 myset3 # 差集,以第一個為主,做比較
1) "4"
127.0.0.1:6379> sinter myset myset2 myset3
1) "1"
127.0.0.1:6379> sunion myset myset2 myset3
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
微博,A用戶將所有關注的人放在一個set集合中!將它的粉絲也放在一個集合中!
共同關注,共同愛好,二度好友,推薦好友!(六度分割理論)
4.5 Hash(哈希)
Map集合,key-map! 時候這個值是一個map集合! 本質和String類型沒有太大區別,還是一個簡單的
key-vlaue!
127 .0.0.1:6379> hset myhash field1 kuangshen # set一個具體 key-vlaue
(integer) 1
127 .0.0.1:6379> hget myhash field1 # 獲取一個字段值
"kuangshen"
127 .0.0.1:6379> hmset myhash field1 hello field2 world # set多個 key-vlaue
OK
127 .0.0.1:6379> hmget myhash field1 field2 # 獲取多個字段值
1 ) "hello"
2 ) "world"
127 .0.0.1:6379> hgetall myhash # 獲取全部的數據,
1 ) "field1"
2 ) "hello"
3 ) "field2"
4 ) "world"
127 .0.0.1:6379> hdel myhash field1 # 刪除hash指定key字段!對應的value值也就消失了!
(integer) 1
127 .0.0.1:6379> hgetall myhash
1 ) "field2"
2 ) "world"##########################################################################127 .0.0.1:6379> hmset myhash field1 hello field2 world
OK
127 .0.0.1:6379> HGETALL myhash
1 ) "field2"
2 ) "world"
3 ) "field1"
4 ) "hello"
127 .0.0.1:6379> hlen myhash # 獲取hash表的字段數量!
(integer) 2##########################################################################127 .0.0.1:6379> hexists myhash field1 # 判斷hash中指定字段是否存在!
(integer) 1
127 .0.0.1:6379> HEXISTS myhash field3
(integer) 0########################################################################### 只獲得所有field
# 只獲得所有value
127 .0.0.1:6379> hkeys myhash # 只獲得所有field
1 ) "field2"
2 ) "field1"
127 .0.0.1:6379> hvals myhash # 只獲得所有value
1 ) "world"
2 ) "hello"########################################################################### 沒有 hdecrb 和 hsetex 指令
127 .0.0.1:6379> hset myhash field3 5
(integer) 1
127 .0.0.1:6379> hincrby myhash field3 2 # 指定增量!
(integer) 7
127 .0.0.1:6379> HINCRBY myhash field3 -2
(integer) 5
127 .0.0.1:6379> hsetnx myhash field4 hello # 如果不存在則可以設置
(integer) 1
127 .0.0.1:6379> hsetnx myhash field4 world # 如果存在則不能設置
(integer) 0
hash變更的數據 user name age,尤其是是用戶信息之類的,經常變動的信息!hash 更適合于對象的存儲,String更加適合字符串存儲!
4.6 Zset(有序集合)
在set的基礎上,增加了一個值,set k1 v1 zset k1 score1 v1 ,根據 score 的大小進行排序
127 .0.0.1:6379> zadd myset 1 one # 添加一個值
(integer) 1
127 .0.0.1:6379> zadd myset 2 two 3 three # 添加多個值
(integer) 2
127 .0.0.1:6379> zrange myset 0 -1
1 ) "one"
2 ) "two"
3 ) "three"
127.0.0.1:6379> zrange myset 0 -1 withscores # 附帶成績(比較排序值)顯示
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"###########################################################################排序如何實現
127 .0.0.1:6379> zadd salary 2500 xiaohong # 添加三個用戶
(integer) 1
127 .0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127 .0.0.1:6379> zadd salary 500 xu
(integer) 1
# ZRANGEBYSCORE key min max
127 .0.0.1:6379> zrangebyscore salary -inf +inf # 顯示全部的用戶 從小到大! 這個指令跟 ‘ zrange salary 0 -1 ’一樣
1 ) "xu"
2 ) "xiaohong"
3 ) "zhangsan"
127.0.0.1:6379> zrevrange salary 0 -1 # 從大到小排序
1) "zhangsan"
2) "xiaohong"
3) "xu"
127 .0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 顯示全部的用戶并且附帶成績
1 ) "xu"
2 ) "500"
3 ) "xiaohong"
5 ) "zhangsan"
6 ) "5000"
127 .0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 顯示工資小于 2500 員工的升序排序!
1 ) "kaungshen"
2 ) "500"
3 ) "xiaohong"
4 ) "2500"########################################################################### 移除zset中的元素
127 .0.0.1:6379> zrange salary 0 -1
1 ) "xu"
2 ) "xiaohong"
3 ) "zhangsan"
127 .0.0.1:6379> zrem salary xiaohong # 移除有序集合中的指定元素
(integer) 1
127 .0.0.1:6379> zrange salary 0 -1
1 ) "kaungshen"
2 ) "zhangsan"
127 .0.0.1:6379> zcard salary # 獲取有序集合中的個數
(integer) 2##########################################################################127 .0.0.1:6379> zadd myset 1 hello
(integer) 1
127 .0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127 .0.0.1:6379> zcount myset 1 3 # 獲取指定區間的成員數量!
(integer) 3
127 .0.0.1:6379> zcount myset 1 2
(integer) 2
其它的一些API,可以去查看官方文檔!
案例思路:set 排序 存儲班級成績表,工資表排序!
普通消息, 1 , 重要消息 2 ,帶權重進行判斷!
排行榜應用實現,取Top N 測試!
5、三種特殊數據類型
5.1 Geospatial 地理位置
朋友的定位,附近的人,打車距離計算?
Redis 的 Geo 在Redis3.2 版本就推出了! 這個功能可以推算地理位置的信息,兩地之間的距離,方圓幾里的人!
可以查詢一些測試數據:http://www.jsons.cn/lngcodeinfo/0706D99C19A781A3/
只有 六個命令:
Redis 地理位置(geo) 命令
命令 | 描述 |
---|---|
Redis GEOHASH 命令 | 返回一個或多個位置元素的 Geohash 表示 |
Redis GEOPOS 命令 | 從key里返回所有給定位置元素的位置(經度和緯度) |
Redis GEODIST 命令 | 返回兩個給定位置之間的距離 |
Redis GEORADIUS 命令 | 以給定的經緯度為中心, 找出某一半徑內的元素 |
Redis GEOADD 命令 | 將指定的地理空間位置(緯度、經度、名稱)添加到指定的key中 |
Redis GEORADIUSBYMEMBER 命令 | 找出位于指定范圍內的元素,中心點是由給定的位置元素決定 |
GEOADD 添加地理位置
# geoadd 添加地理位置
# 規則:兩極無法直接添加,我們一般會下載城市數據,直接通過java程序一次性導入!
# 有效的經度從-180度到 180 度。
# 有效的緯度從-85.05112878度到85.05112878度。
# 當坐標位置超出上述指定范圍時,該命令將會返回一個錯誤。
# 127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
# (error) ERR invalid longitude,latitude pair 39 .900000,116.400000
# 參數 key 值()
127 .0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127 .0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127 .0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127 .0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
GEOPOS 獲取指定的城市的經度和緯度
獲得指定位置定位:一定是一個坐標值!
127 .0.0.1:6379> geopos china:city beijing # 獲取指定的城市的經度和緯度!
1 ) 1 ) "116.39999896287918091"2 ) "39.90000009167092543"
127 .0.0.1:6379> GEOPOS china:city beijing chongqing
1 ) 1 ) "116.39999896287918091"2 ) "39.90000009167092543"
2 ) 1 ) "106.49999767541885376"2 ) "29.52999957900659211"
GEODIST 返回兩個位置之間的距離
兩人之間的距離!
單位:
- m 表示單位為米 (默認單位)
- km 表示單位為千米
- mi 表示單位為英里
- ft 表示單位為英尺
127 .0.0.1:6379> geodist china:city beijing shanghai km # 查看上海到北京的直線距離
"1067.3788"
127 .0.0.1:6379> GEODIST china:city beijing chongqing km # 查看重慶到北京的直線距離
"1464.0708"
GEORADIUS 以給定的經緯度為中心, 找出某一半徑內的位置元素
我附近的人? (獲得所有附近的人的地址,定位!)通過半徑來查詢!
獲得指定數量的人, 200
所有數據應該都錄入:china:city ,才會讓結果更加請求!
127 .0.0.1:6379> georadius china:city 110 30 1000 km # 以 110 , 30 這個經緯度為中心,尋找方圓1000km內的城市
1 ) "chongqi"
2 ) "xian"
3 ) "shengzhen"
4 ) "hangzhou"
127 .0.0.1:6379> GEORADIUS china:city 110 30 500 km
1 ) "chongqi"
2 ) "xian"
127 .0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist # 顯示附帶位置距離
1 ) 1 ) "chongqi"2 ) "341.9374"
2 ) 1 ) "xian"2 ) "483.8340"
127 .0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord # 顯示他人的定位信息
1 ) 1 ) "chongqi"2 ) 1 ) "106.49999767541885376"2 ) "29.52999957900659211"
2 ) 1 ) "xian"2 ) 1 ) "108.96000176668167114"2 ) "34.25999964418929977"
127 .0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1
# 篩選出指定數目的結果!
1 ) 1 ) "chongqi"2 ) "341.9374"3 ) 1 ) "106.49999767541885376"2 ) "29.52999957900659211"
127 .0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"2) "341.9374"3) 1) "106.49999767541885376"2) "29.52999957900659211"
2) 1) "xian"2) "483.8340"3) 1) "108.96000176668167114"2) "34.25999964418929977"
GEORADIUSBYMEMBER 以指定的key為中心,找出某一半徑內的位置元素
# 找出位于指定元素周圍的其他元素!
127 .0.0.1:6379> georadiusbymember china:city beijing 1000 km
1 ) "beijing"
2 ) "xian"
127 .0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1 ) "hangzhou"
2 ) "shanghai"
GEOHASH 返回一個或多個位置元素的 Geohash 表示
該命令將返回 11 個字符的Geohash字符串!
# 將二維的經緯度轉換為一維的字符串,如果兩個字符串越接近,那么則距離越近!
127 .0.0.1:6379> geohash china:city beijing chongqing
1 ) "wx4fbxxfke0"
2 ) "wm5xzrybty0"
GEO 底層的實現原理其實就是 Zset!我們可以使用Zset命令來操作geo!
127 .0.0.1:6379> zrange china:city 0 -1 # 查看地圖中全部的元素
1 ) "chongqing"
2 ) "xian"
3 ) "shengzhen"
4 ) "hangzhou"
5 ) "shanghai"
6 ) "beijing"
127 .0.0.1:6379> zrem china:city beijing # 移除指定元素!
(integer) 1
127 .0.0.1:6379> ZRANGE china:city 0 -1
1 ) "chongqing"
2 ) "xian"
3 ) "shengzhen"
4 ) "hangzhou"
5 ) "shanghai"
5.2 Hyperloglog 基數統計
什么是基數?
A {1,3,5,7,8,7}
B{1,3,5,7,8}
基數(不重復的元素) = 5,可以接受誤差!
簡介
Redis 2.8.9 版本就更新了 Hyperloglog 數據結構!
Redis Hyperloglog 基數統計的算法!
優點:占用的內存是固定,2^64 不同的元素的技術,只需要廢 12KB內存!如果要從內存角度來比較的話 Hyperloglog 首選!
網頁的 UV (一個人訪問一個網站多次,但是還是算作一個人!)
傳統的方式, set 保存用戶的id,然后就可以統計 set 中的元素數量作為標準判斷!
這個方式如果保存大量的用戶id,就會比較麻煩!我們的目的是為了計數,而不是保存用戶id;
0.81% 錯誤率! 統計UV任務,可以忽略不計的!
測試使用
127 .0.0.1:6379> pfadd mykey a b c d e f g h i j # 創建第一組元素 mykey
(integer) 1
127 .0.0.1:6379> pfcount mykey # 統計 mykey 元素的基數數量
(integer) 10
127 .0.0.1:6379> PFadd mykey2 i j z x c v b n m # 創建第二組元素 mykey2
(integer) 1
127 .0.0.1:6379> PFCOUNT mykey2
(integer) 9
127 .0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并兩組 mykey mykey2 => mykey3 并集
OK
127 .0.0.1:6379> PFCOUNT mykey3 # 看并集的數量!
(integer) 15
如果允許容錯,那么一定可以使用 Hyperloglog !
如果不允許容錯,就使用 set 或者自己的數據類型即可!
5.3 Bitmap 位圖
位存儲
統計用戶信息,活躍,不活躍! 登錄 、 未登錄! 打卡, 365 打卡! 兩個狀態的,都可以使用Bitmaps!
Bitmap 位圖,數據結構! 都是操作二進制位來進行記錄,就只有 0 和 1 兩個狀態!
365 天 = 365 bit 1字節 = 8bit 46 個字節左右!
# 使用bitmap 來記錄 周一到周日的打卡!
# 周一: 1 周二: 0 周三: 1 周四:0 ......
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
# 查看某一天是否有打卡!
127.0.0.1:6379> getbit sign 5
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
# 統計操作,統計 打卡的天數!
127.0.0.1:6379> bitcount sign
(integer) 3
6、事務
Redis 事務本質:一組命令的集合! 一個事務中的所有命令都會被序列化,在事務執行過程的中,會按照順序執行!
一次性、順序性、排他性!執行一些列的命令!
------ 隊列 set set set 執行 -------
Redis事務沒有沒有隔離級別的概念!
所有的命令在事務中,并沒有直接被執行!只有發起執行命令的時候才會執行!Exec
Redis單條命令是保證原子性的,但是事務不保證原子性!
redis的事務:
- 開啟事務(multi)
- 命令入隊(…)
- 執行事務(exec)
正常執行事務
# 正常執行事務!
127 .0.0.1:6379> multi # 開啟事務
OK
# 命令入隊
127 .0.0.1:6379> set k1 v1
QUEUED
127 .0.0.1:6379> set k2 v2
QUEUED
127 .0.0.1:6379> get k2
QUEUED
127 .0.0.1:6379> set k3 v3
QUEUED
127 .0.0.1:6379> exec # 執行事務
1 ) OK
2 ) OK
3 ) "v2"
4 ) OK
放棄事務
127 .0.0.1:6379> multi # 開啟事務
OK
127 .0.0.1:6379> set k1 v1
QUEUED
127 .0.0.1:6379> set k2 v2
QUEUED
127 .0.0.1:6379> set k4 v4
QUEUED
127 .0.0.1:6379> discard # 取消事務
OK
127 .0.0.1:6379> get k4 # 事務隊列中命令都不會被執行!
(nil)
編譯型異常(代碼有問題! 命令有錯!) ,事務中所有的命令都不會被執行!
127 .0.0.1:6379> multi
OK
127 .0.0.1:6379> set k1 v1
QUEUED
127 .0.0.1:6379> set k2 v2
QUEUED
127 .0.0.1:6379> set k3 v3
QUEUED
127 .0.0.1:6379> getset k3 # 錯誤的命令
(error) ERR wrong number of arguments for 'getset' command
127 .0.0.1:6379> set k4 v4
QUEUED
127 .0.0.1:6379> set k5 v5
QUEUED
127 .0.0.1:6379> exec # 執行事務報錯!
(error) EXECABORT Transaction discarded because of previous errors.
127 .0.0.1:6379> get k5 # 所有的命令都不會被執行!
(nil)
運行時異常, 如果事務隊列中存在語法性,那么執行命令的時候,其他命令是可以正常執行的,錯誤命令拋出異常!
127 .0.0.1:6379> set k1 "v1"
OK
127 .0.0.1:6379> multi
OK
127 .0.0.1:6379> incr k1 # 會執行的時候失敗!
QUEUED
127 .0.0.1:6379> set k2 v2
QUEUED
127 .0.0.1:6379> set k3 v3
QUEUED
127 .0.0.1:6379> get k3
QUEUED
127 .0.0.1:6379> exec
1 ) (error) ERR value is not an integer or out of range # 雖然第一條命令報錯了,但是依舊正常執行成功了!
2 ) OK
3 ) OK
4 ) "v3"
127 .0.0.1:6379> get k2
"v2"
127 .0.0.1:6379> get k3
"v3"
監控! Watch (面試常問!)
**悲觀鎖:**很悲觀,認為什么時候都會出問題,無論做什么都會加鎖!
**樂觀鎖:**很樂觀,認為什么時候都不會出問題,所以不會上鎖! 更新數據的時候去判斷一下,在此期間是否有人修改過這個數據
- 獲取version
- 更新的時候比較 version
Redis 監視測試
單線程 正常執行成功!
127 .0.0.1:6379> set money 100
OK
127 .0.0.1:6379> set out 0
OK
127 .0.0.1:6379> watch money # 監視 money 對象
OK
127 .0.0.1:6379> multi # 事務正常結束,數據期間沒有發生變動,這個時候就正常執行成功!
OK
127 .0.0.1:6379> DECRBY money 20
QUEUED
127 .0.0.1:6379> INCRBY out 20
QUEUED
127 .0.0.1:6379> exec
1 ) (integer) 80
2 ) (integer) 20
測試多線程修改值 , 使用watch 可以當做redis的樂觀鎖操作!
127 .0.0.1:6379> watch money # 監視 money
OK
127 .0.0.1:6379> multi
OK
127 .0.0.1:6379> DECRBY money 10
QUEUED
127 .0.0.1:6379> INCRBY out 10
QUEUED
127 .0.0.1:6379> exec # 執行之前,另外一個線程修改了money的值,這個時候,就會導致事務執行失敗!
(nil)
如果修改失敗,獲取最新的值就好
127.0.0.1:6379> unwatch # 事務執行失敗,先解鎖
OK
127.0.0.1:6379> get money # 查看被修改后的值
"900"
127.0.0.1:6379> watch money # 獲取最新的值,再次監視
OK
127.0.0.1:6379> multi # 開啟事務
OK
127.0.0.1:6379> decrby money 100
QUEUED
127.0.0.1:6379> incrby out 100
QUEUED
127.0.0.1:6379> exec # 對比監視的值是否發生變化,如果沒有,執行成功
1) (integer) 800
2) (integer) 100
7、Jedis
什么是Jedis 是 Redis 官方推薦的 java連接開發工具! 使用Java 操作 Redis 中間件!如果你要使用
java 操作 redis,那么一定要對 Jedis 十分的熟悉!
測試
1 、導入對應的依賴
<!--導入jedis的包-->
<dependencies><!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version></dependency><!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency>
</dependencies>
2 、編碼測試:
- 連接數據庫
- 操作命令
- 斷開連接!
public class RedisPing {public static void main(String[] args) {Jedis jedis = new Jedis("127.0.0.1",6379);System.out.println(jedis.ping());jedis.close(); //關閉連接// jedis.shutdown(); //關閉服務}
}
輸出:
常用的API
String
List
Set
Hash
Zset
所有的api命令,就是我們對應的上面學習的指令,一個都沒有變化!
7.1 事務
package com.xu;import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;public class RedisPing {public static void main(String[] args) {Jedis jedis = new Jedis("127.0.0.1",6379);jedis.flushDB(); //清除數據JSONObject jsonObject = new JSONObject(); //json對象jsonObject.put("name", "xu");jsonObject.put("age", 11);String jsonString = jsonObject.toJSONString(); //轉換為json字符串Transaction multi = jedis.multi();//開啟事務try {multi.set("key1", jsonString);multi.set("key2", jsonString);int i = 1 / 0; // 代碼拋出異常事務,執行失敗!multi.exec(); //執行事務} catch (Exception e) {multi.discard(); //出現異常,停止事務e.printStackTrace();} finally {System.out.println(jedis.get("key1"));System.out.println(jedis.get("key2"));jedis.close(); //關閉連接}}
}
8、SpringBoot整合
SpringBoot 操作數據:spring-data jpa jdbc mongodb redis!
SpringData 也是和 SpringBoot 齊名的項目!
說明: 在 SpringBoot2.x 之后,原來使用的jedis 被替換為了 lettuce?
jedis : 采用的直連,多個線程操作的話,是不安全的,如果想要避免不安全的,使用 jedis pool 連接
池! 更像 BIO 模式
lettuce : 采用netty,實例可以在多個線程中進行共享,不存在線程不安全的情況!可以減少線程數據
了,更像 NIO 模式
源碼分析:
@Bean
// 我們可以自己定義一個redisTemplate來替換這個默認的!
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory isConnectionFactory)
throws UnknownHostException {// 默認的 RedisTemplate 沒有過多的設置,redis 對象都是需要序列化!// 兩個泛型都是 Object, Object 的類型,我們后使用需要強制轉換 <String, Object>RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return template;
}
@Bean
@ConditionalOnMissingBean
// 由于 String 是redis中最常使用的類型,所以說單獨提出來了一個bean!
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactoryredisConnectionFactory)
throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;
}
整合測試一下
1 、導入依賴
<!-- 操作redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 、配置連接
# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port= 6379
3 、測試!
@SpringBootTest
class Redis02SpringbootApplicationTests {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid contextLoads() {// redisTemplate 操作不同的數據類型,api和我們的指令是一樣的// opsForValue 操作字符串 類似String ,位圖在這個里面// opsForList 操作List 類似List// opsForSet// opsForHash// opsForZSet// opsForGeo// opsForHyperLogLog// 除了進本的操作,我們常用的方法都可以直接通過redisTemplate操作,比如事務,和基本的CRUD// 獲取redis的連接對象// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();// connection.flushDb();// connection.flushAll();redisTemplate.opsForValue().set("mykey","關注狂神說公眾號");System.out.println(redisTemplate.opsForValue().get("mykey"));}
}
關于對象的保存:
我們來編寫一個自己的 RedisTemplete
package com.kuang.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {// 自己定義了一個 RedisTemplate@Bean@SuppressWarnings("all")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactoryfactory) {// 固定模板,在企業中,拿去就可以直接使用!// 我們為了自己開發方便,一般直接使用 <String, Object>RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();template.setConnectionFactory(factory);// Json序列化配置Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = newJackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// String 的序列化StringRedisSerializer stringRedisSerializer = newStringRedisSerializer();// key采用String的序列化方式template.setKeySerializer(stringRedisSerializer);// hash的key也采用String的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}
所有的redis操作,其實對于java開發人員來說,十分的簡單,更重要是要去理解redis的思想和每一種數據結構的用處和作用場景!
9、Redis.conf詳解
啟動的時候,就通過配置文件來啟動!
單位
1 、配置文件 unit單位 對大小寫不敏感!
包含
就好比我們學習的Spring、Improt, include
網絡
bind 127 .0.0.1 # 綁定的ipprotected-mode yes # 保護模式port 6379 # 端口設置
通用 GENERAL
daemonize yes # 以守護進程的方式運行,默認是 no,我們需要自己開啟為yes!pidfile /var/run/redis_6379.pid # 如果以后臺的方式運行,我們就需要指定一個 pid 文件!# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生產環境
# warning (only very important / critical messages are logged)
loglevel noticelogfile "" # 日志的文件位置名databases 16 # 數據庫的數量,默認是 16 個數據庫always-show-logo yes # 是否總是顯示LOGO
快照
持久化, 在規定的時間內,執行了多少次操作,則會持久化到文件 .rdb 和 .aof
redis 是內存數據庫,如果沒有持久化,那么數據斷電即失!
# 如果900s內,如果至少有一個1 key進行了修改,我們及進行持久化操作
save 900 1
# 如果300s內,如果至少10 key進行了修改,我們及進行持久化操作
save 300 10
# 如果60s內,如果至少10000 key進行了修改,我們及進行持久化操作
save 60 10000
# 我們之后學習持久化,會自己定義這個測試!stop-writes-on-bgsave-error yes # 持久化如果出錯,是否還需要繼續工作!rdbcompression yes # 是否壓縮 rdb 文件,需要消耗一些cpu資源!rdbchecksum yes # 保存rdb文件的時候,進行錯誤的檢查校驗!dir ./ # rdb 文件保存的目錄!
REPLICATION 主從復制
SECURITY 安全
可以在這里設置redis的密碼,默認是沒有密碼!
127 .0.0.1:6379> ping
PONG
127 .0.0.1:6379> config get requirepass # 獲取redis的密碼
1 ) "requirepass"
2 ) ""
127 .0.0.1:6379> config set requirepass "123456" # 設置redis的密碼
OK
127 .0.0.1:6379> config get requirepass # 發現所有的命令都沒有權限了
(error) NOAUTH Authentication required.
127 .0.0.1:6379> ping
(error) NOAUTH Authentication required.
127 .0.0.1:6379> auth 123456 # 使用密碼進行登錄!
OK
127 .0.0.1:6379> config get requirepass
1 ) "requirepass"
2 ) "123456"
限制 CLIENTS
maxclients 10000 # 設置能連接上redis的最大客戶端的數量maxmemory <bytes> # redis 配置最大的內存容量maxmemory-policy noeviction # 內存到達上限之后的處理策略1 、volatile-lru:只對設置了過期時間的key進行LRU(默認值)2 、allkeys-lru : 刪除lru算法的key3 、volatile-random:隨機刪除即將過期key4 、allkeys-random:隨機刪除5 、volatile-ttl : 刪除即將過期的6 、noeviction : 永不過期,返回錯誤
APPEND ONLY 模式 aof配置
appendonly no # 默認是不開啟aof模式的,默認是使用rdb方式持久化的,在大部分所有的情況下,rdb完全夠用!appendfilename "appendonly.aof" # 持久化的文件的名字# appendfsync always # 每次修改都會 sync。消耗性能
appendfsync everysec # 每秒執行一次 sync,可能會丟失這1s的數據!
# appendfsync no # 不執行 sync,這個時候操作系統自己同步數據,速度最快!
具體的配置,我們在 Redis持久化 中去給大家詳細詳解!
10、Redis持久化
面試和工作,持久化都是重點!
Redis 是內存數據庫,如果不將內存中的數據庫狀態保存到磁盤,那么一旦服務器進程退出,服務器中
的數據庫狀態也會消失。所以 Redis 提供了持久化功能!
10.1 RDB(Redis DataBase)
什么是RDB
在指定的時間間隔內將內存中的數據集快照寫入磁盤,也就是行話講的Snapshot快照,它恢復時是將快照文件直接讀到內存里。
Redis會單獨創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,再用這個臨時文件替換上次持久化好的文件。整個過程中,主進程是不進行任何IO操作的。這就確保了極高的性能。如果需要進行大規模數據的恢復,且對于數據恢復的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。RDB 的缺點是最后一次持久化后的數據可能丟失。我們默認的就是 RDB,一般情況下不需要修改這個配置!
有時候在生產環境我們會將這個文件進行備份!
rdb保存的文件是dump.rdb 都是在我們的配置文件中快照中進行配置的!
觸發機制
1 、save的規則滿足的情況下,會自動觸發rdb規則
2 、執行 flushall 命令,也會觸發我們的rdb規則!
3 、退出redis,也會產生 rdb 文件!
備份就自動生成一個 dump.rdb
如何恢復 rdb 文件
1 、只需要將rdb文件放在我們redis啟動目錄就可以,redis啟動的時候會自動檢查dump.rdb 恢復其中
的數據!
2 、查看需要存在的位置
127 .0.0.1:6379> config get dir
1 ) "dir"
2 ) "/usr/local/bin" # 如果在這個目錄下存在 dump.rdb 文件,啟動就會自動恢復其中的數據
幾乎就它自己默認的配置就夠用了,但是我們還是需要去學習!
優點:
1 、適合大規模的數據恢復!
2 、對數據的完整性要不高!
缺點:
1 、需要一定的時間間隔進行操作!如果redis意外宕機了,這個最后一次修改數據就沒有的了!
2 、fork進程的時候,會占用一定的內容空間!!
10.2 AOF(Append Only File)
將我們的所有命令都記錄下來,history,恢復的時候就把這個文件全部在執行一遍!
AOF是什么
以日志的形式來記錄每個寫操作,將Redis執行過的所有指令記錄下來(讀操作不記錄),只許追加文件,但不可以改寫文件,redis啟動之初會讀取該文件重新構建數據,換言之,redis重啟的話就根據日志文件的內容將寫指令從前到后執行一次以完成數據的恢復工作
AOF保存的是 appendonly.aof 文件
默認是不開啟的,我們需要手動進行配置!我們只需要將 appendonly 改為yes就開啟了 aof!
重啟,redis 就可以生效了!
如果這個 aof 文件有錯誤,這時候 redis 是啟動不起來的,我們需要修復這個aof文件
redis 給我們提供了一個工具 redis-check-aof --fix
如果文件正常,重啟就可以直接恢復了!
重寫規則說明
aof 默認就是文件的無限追加,文件會越來越大!
如果 aof 文件大于 64m,太大了! fork一個新的進程來將我們的文件進行重寫!
優點:
appendonly no # 默認是不開啟aof模式的,默認是使用rdb方式持久化的,在大部分所有的情況下,
rdb完全夠用!
appendfilename "appendonly.aof" # 持久化的文件的名字# appendfsync always # 每次修改都會 sync。消耗性能
appendfsync everysec # 每秒執行一次 sync,可能會丟失這1s的數據!
# appendfsync no # 不執行 sync,這個時候操作系統自己同步數據,速度最快!# rewrite 重寫,
1 、每一次修改都同步,文件的完整會更加好!
2 、每秒同步一次,可能會丟失一秒的數據
3 、從不同步,效率最高的!
缺點:
1 、相對于數據文件來說,aof 遠遠大于 rdb,修復的速度也比 rdb 慢!
2 、Aof 運行效率也要比 rdb 慢,所以我們 redis 默認的配置就是rdb持久化!
擴展
1 、RDB 持久化方式能夠在指定的時間間隔內對你的數據進行快照存儲
2 、AOF 持久化方式記錄每次對服務器寫的操作,當服務器重啟的時候會重新執行這些命令來恢復原始的數據,AOF命令以 Redis 協議追加保存每次寫的操作到文件末尾,Redis還能對AOF文件進行后臺重寫,使得AOF文件的體積不至于過大。
3 、只做緩存,如果你只希望你的數據在服務器運行的時候存在,你也可以不使用任何持久化
4 、同時開啟兩種持久化方式
- 在這種情況下,當redis重啟的時候會優先載入AOF文件來恢復原始的數據,因為在通常情況下AOF文件保存的數據集要比RDB文件保存的數據集要完整。
- RDB 的數據不實時,同時使用兩者時服務器重啟也只會找AOF文件,那要不要只使用AOF呢?作者建議不要,因為RDB更適合用于備份數據庫(AOF在不斷變化不好備份),快速重啟,而且不會有AOF可能潛在的Bug,留著作為一個萬一的手段。
5 、性能建議
- 因為RDB文件只用作后備用途,建議只在Slave上持久化RDB文件,而且只要 15 分鐘備份一次就夠了,只保留 save 900 1 這條規則。
- 如果Enable AOF ,好處是在最惡劣情況下也只會丟失不超過兩秒數據,啟動腳本較簡單只load自
己的AOF文件就可以了,代價一是帶來了持續的IO,二是AOF rewrite 的最后將 rewrite 過程中產
生的新數據寫到新文件造成的阻塞幾乎是不可避免的。只要硬盤許可,應該盡量減少AOF rewrite
的頻率,AOF重寫的基礎大小默認值64M太小了,可以設到5G以上,默認超過原大小100%大小重寫可以改到適當的數值。 - 如果不Enable AOF ,僅靠 Master-Slave Repllcation 實現高可用性也可以,能省掉一大筆IO,也
減少了rewrite時帶來的系統波動。代價是如果Master/Slave 同時倒掉,會丟失十幾分鐘的數據,
啟動腳本也要比較兩個 Master/Slave 中的 RDB文件,載入較新的那個,微博就是這種架構。
11、Redis發布訂閱
Redis 發布訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。微信、 微博、關注系統!
Redis 客戶端可以訂閱任意數量的頻道。
訂閱/發布消息圖:
第一個:消息發送者, 第二個:頻道 , 第三個:消息訂閱者!
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的
關系:

當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被發送給訂閱它的三個客戶
端:

命令
這些命令被廣泛用于構建即時通信應用,比如網絡聊天室(chatroom)和實時廣播、實時提醒等。
序號 | 命令及描述 |
---|---|
1 | [PSUBSCRIBE pattern pattern …] 訂閱一個或多個符合給定模式的頻道。 |
2 | PUBSUB subcommand [argument [argument …]] 查看訂閱與發布系統狀態。 |
3 | PUBLISH channel message 將信息發送到指定的頻道。 |
4 | PUNSUBSCRIBE [pattern [pattern …]] 退訂所有給定模式的頻道。 |
5 | [SUBSCRIBE channel channel …] 訂閱給定的一個或多個頻道的信息。 |
6 | UNSUBSCRIBE [channel [channel …]] 指退訂給定的頻道。 |
測試
訂閱端:
127 .0.0.1:6379> subscribe kuangshenshuo # 訂閱一個頻道 kuangshenshuo
Reading messages... (press Ctrl-C to quit)
1 ) "subscribe"
2 ) "kuangshenshuo"
3 ) (integer) 1
# 等待讀取推送的信息
1 ) "message" # 消息
2 ) "kuangshenshuo" # 那個頻道的消息
3 ) "hello,kuangshen" # 消息的具體內容1 ) "message"
2 ) "kuangshenshuo"
3 ) "hello,redis"
發送端:
127 .0.0.1:6379> publish kuangshenshuo "hello,kuangshen" # 發布者發布消息到頻道!
(integer) 1
127 .0.0.1:6379> PUBLISH kuangshenshuo "hello,redis" # 發布者發布消息到頻道!
(integer) 1
127 .0.0.1:6379>
原理
Redis是使用C實現的,通過分析 Redis 源碼里的 pubsub.c 文件,了解發布和訂閱機制的底層實現,籍此加深對 Redis 的理解。
Redis 通過 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令實現發布和訂閱功能。
通過 SUBSCRIBE 命令訂閱某頻道后,redis-server 里維護了一個字典,字典的鍵就是一個個 頻道!而字典的值則是一個鏈表,鏈表中保存了所有訂閱這個 channel 的客戶端。SUBSCRIBE 命令的關鍵,就是將客戶端添加到給定 channel 的訂閱鏈表中。
通過 PUBLISH 命令向訂閱者發送消息,redis-server 會使用給定的頻道作為鍵,在它所維護的 channel 字典中查找記錄了訂閱這個頻道的所有客戶端的鏈表,遍歷這個鏈表,將消息發布給所有訂閱者。
Pub/Sub 從字面上理解就是發布(Publish)與訂閱(Subscribe),在Redis中,你可以設定對某一個
key值進行消息發布及消息訂閱,當一個key值上進行了消息發布后,所有訂閱它的客戶端都會收到相應的消息。這一功能最明顯的用法就是用作實時消息系統,比如普通的即時聊天,群聊等功能。
使用場景:
1 、實時消息系統!
2 、實時聊天!(頻道當做聊天室,將信息回顯給所有人即可!)
3 、訂閱,關注系統都是可以的!
稍微復雜的場景我們就會使用 消息中間件 MQ
12、Redis主從復制
概念
主從復制,是指將一臺Redis服務器的數據,復制到其他的Redis服務器。前者稱為主節點
(master/leader),后者稱為從節點(slave/follower);數據的復制是單向的,只能由主節點到從節點。
Master以寫為主,Slave 以讀為主。
默認情況下,每臺Redis服務器都是主節點;且一個主節點可以有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。
主從復制的作用主要包括:
- 數據冗余:主從復制實現了數據的熱備份,是持久化之外的一種數據冗余方式。
- 故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗余。
- 負載均衡:在主從復制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis數據時應用連接主節點,讀Redis數據時應用連接從節點),分擔服務器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis服務器的并發量。
- 高可用(集群)基石:除了上述作用以外,主從復制還是哨兵和集群能夠實施的基礎,因此說主從復制是Redis高可用的基礎。
一般來說,要將Redis運用于工程項目中,只使用一臺Redis是萬萬不能的(宕機),原因如下:
- 從結構上,單個Redis服務器會發生單點故障,并且一臺服務器需要處理所有的請求負載,壓力較
大; - 從容量上,單個Redis服務器內存容量有限,就算一臺Redis服務器內存容量為256G,也不能將所有內存用作Redis存儲內存,一般來說,單臺Redis最大使用內存不應該超過20G。
電商網站上的商品,一般都是一次上傳,無數次瀏覽的,說專業點也就是"多讀少寫"。
對于這種場景,我們可以使如下這種架構:

主從復制,讀寫分離! 80% 的情況下都是在進行讀操作!減緩服務器的壓力!架構中經常使用! 一主二從!
只要在公司中,主從復制就是必須要使用的,因為在真實的項目中不可能單機使用Redis!
環境配置
127 .0.0.1:6379> info replication # 查看當前庫的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 沒有從機
master_replid:b63c90e6c501143759cb0e7f450bd1eb0c70882a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
只配置從庫,不用配置主庫!
復制 3 個配置文件,然后修改對應的信息
1 、端口
2 、pid文件名字
3 、log文件名字
4 、dump.rdb 文件名字
修改完畢之后,啟動我們的 3 個redis服務器,可以通過進程信息查看!ps -ef|grep redis
一主二從
默認情況下,每臺Redis服務器都是主節點; 我們一般情況下只用配置從機就好了!
認老大! 一主 ( 79 )二從( 80 , 81 )
127.0.0.1:6380> slaveof 127.0.0.1 6379 # SLAVEOF host 6379 找誰當自己的老大!
OK
127.0.0.1:6380> info replication
# Replication
role:slave # 當前角色是從機
master_host:127.0.0.1 # 可以的看到主機的信息
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:c63801d05f947eb101e2d46cfd8209a41653d49b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14# 主機信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=112,lag=1 # 可以看到從機信息
master_replid:c63801d05f947eb101e2d46cfd8209a41653d49b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:112
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:112
如果兩個都配置完了,就是有兩個從機的
真實的從主配置應該在配置文件中配置,這樣的話是永久的,我們這里使用的是命令,暫時的!
細節
主機可以寫,從機不能寫只能讀!主機中的所有信息和數據,都會自動被從機保存!
主機寫:
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
從機只能讀:
127.0.0.1:6380> keys *
1) "k1"
127.0.0.1:6380> get k1
"v1"
127.0.0.1:6380> set k2 v2
(error) READONLY You can't write against a read only replica.
測試:主機斷開連接,從機依舊連接到主機的,但是沒有寫操作,這個時候,主機如果回來了,從機依舊可以直接獲取到主機寫的信息!
如果是使用命令行來配置的主從,這個時候如果重啟了,就會變回主機!只要變為從機,立馬就會從主機中獲取值!
復制原理
Slave 啟動成功連接到 master 后會發送一個sync同步命令
Master 接到命令,啟動后臺的存盤進程,同時收集所有接收到的用于修改數據集命令,在后臺進程執行完畢之后,master將傳送整個數據文件到slave,并完成一次完全同步**(全量復制)**。
全量復制:而slave服務在接收到數據庫文件數據后,將其存盤并加載到內存中。
增量復制:Master 繼續將新的所有收集到的修改命令依次傳給slave,完成同步
但是只要是重新連接master,一次完全同步**(全量復制)**將被自動執行! 我們的數據一定可以在從機中看到!
層層鏈路
上一個Master鏈接下一個 Slave!
這時候也可以完成我們的主從復制!
如果沒有老大了,這個時候能不能選擇一個老大出來呢? 手動!
謀朝篡位
如果主機斷開了連接,我們可以使用 slaveof no one
讓自己變成主機!其他的節點就可以手動連
接到最新的這個主節點(手動)!如果這個時候老大修復了,那就重新連接老大!
13、哨兵模式
概述
主從切換技術的方法是:當主服務器宕機后,需要手動把一臺從服務器切換為主服務器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。這不是一種推薦的方式,更多時候,我們優先考慮哨兵模式。Redis從2.8開始正式提供了Sentinel(哨兵) 架構來解決這個問題。謀朝篡位的自動版,能夠后臺監控主機是否故障,如果故障了根據投票數自動將從庫轉換為主庫。
哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的進程,作為進程,它會獨立運行。其原理是 哨兵通過發送命令,等待Redis服務器響應,從而監控運行的多個Redis實例。

這里的哨兵有兩個作用
- 通過發送命令,讓Redis服務器返回監控其運行狀態,包括主服務器和從服務器。
- 當哨兵監測到master宕機,會自動將slave切換成master,然后通過 發布訂閱模式 通知其他的從服務器,修改配置文件,讓它們切換主機。
然而一個哨兵進程對Redis服務器進行監控,可能會出現問題,為此,我們可以使用多個哨兵進行監控。
各個哨兵之間還會進行監控,這樣就形成了多哨兵模式。

假設主服務器宕機,哨兵 1 先檢測到這個結果,系統并不會馬上進行failover過程,僅僅是哨兵 1 主觀的認為主服務器不可用,這個現象成為 主觀下線 。當后面的哨兵也檢測到主服務器不可用,并且數量達到一定值時,那么哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行failover[故障轉移]操作。切換成功后,就會通過發布訂閱模式,讓各個哨兵把自己監控的從服務器實現切換主機,這個過程稱為客觀下線 。
測試
我們目前的狀態是 一主二從!
1 、配置哨兵配置文件 sentinel.conf 后面的這個數字 1 ,代表主機掛了,slave投票看讓誰接替成為主機,票數最多的,就會成為主機!
# sentinel monitor 被監控的名稱 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
2 、啟動哨兵!
[root@Linux-xu bin]# redis-sentinel RedisConfig/sentinel.conf
61063:X 27 Jul 2020 17:17:09.768 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
61063:X 27 Jul 2020 17:17:09.768 # Redis version=5.0.8, bits=64, commit=00000000, modified=0, pid=61063, just started
61063:X 27 Jul 2020 17:17:09.768 # Configuration loaded
61063:X 27 Jul 2020 17:17:09.770 * Increased maximum number of open files to 10032 (it was originally set to 1024)._._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.8 (00000000/0) 64 bit.-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379| `-._ `._ / _.-' | PID: 61063`-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 61063:X 27 Jul 2020 17:17:09.772 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
61063:X 27 Jul 2020 17:17:09.774 # Sentinel ID is 40074a476dbe27d420e78ec7eac1ba0b6d5266cb
61063:X 27 Jul 2020 17:17:09.774 # +monitor master myredis 127.0.0.1 6379 quorum 1
61063:X 27 Jul 2020 17:17:09.783 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
61063:X 27 Jul 2020 17:17:09.786 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
如果Master 節點斷開了,這個時候就會從從機中隨機選擇一個服務器! (這里面有一個投票算法!)
如果主機此時回來了,只能歸并到新的主機下,當做從機,這就是哨兵模式的規則!
哨兵模式
優點:
- 哨兵集群,基于主從復制模式,所有的主從配置優點,它全有
- 主從可以切換,故障可以轉移,系統的可用性就會更好
- 哨兵模式就是主從模式的升級,手動到自動,更加健壯!
缺點:
- Redis 不好在線擴容,集群容量一旦到達上限,在線擴容就十分麻煩!
- 實現哨兵模式的配置其實是很麻煩的,里面有很多選擇!
哨兵模式的全部配置
# Example sentinel.conf
# 哨兵sentinel實例運行的端口 默認 26379
port 26379 # 多個哨兵集群就得配置端口# 哨兵sentinel的工作目錄
dir /tmp# 哨兵sentinel監控的redis主節點的 ip port
# master-name 可以自己命名的主節點名字 只能由字母A-z、數字0-9 、這三個字符".-_"組成。
# quorum 配置多少個sentinel哨兵統一認為master主節點失聯 那么這時客觀上認為主節點失聯了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127 .0.0.1 6379 2# 當在Redis實例中開啟了requirepass foobared 授權密碼 這樣所有連接Redis實例的客戶端都要提供密碼
# 設置哨兵sentinel 連接主從的密碼 注意必須為主從設置一樣的驗證密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd# 指定多少毫秒之后 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認為主節點下線 默認 30 秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,這個數字越小,完成failover所需的時間就越長,但是如果這個數字越大,就意味著越 多的slave因為replication而不可用。可以通過將這個值設為 1 來保證每次只有一個slave 處于不能處理命令請求的狀態。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1# 故障轉移的超時時間 failover-timeout 可以用在以下這些方面:
#1. 同一個sentinel對同一個master兩次failover之間的間隔時間。
#2. 當一個slave從一個錯誤的master那里同步數據開始計算時間。直到slave被糾正為向正確的master那里同步數據時。
#3.當想要取消一個正在進行的failover所需要的時間。
#4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置為指向master,但是就不按parallel-syncs所配置的規則來了
# 默認三分鐘
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000# SCRIPTS EXECUTION
#配置當某一事件發生時所需要執行的腳本,可以通過腳本來通知管理員,例如當系統運行不正常時發郵件通知
相關人員。
#對于腳本的運行結果有以下規則:
#若腳本執行后返回 1 ,那么該腳本稍后將會被再次執行,重復次數目前默認為 10
#若腳本執行后返回 2 ,或者比 2 更高的一個返回值,腳本將不會重復執行。
#如果腳本在執行過程中由于收到系統中斷信號被終止了,則同返回值為 1 時的行為相同。
#一個腳本的最大執行時間為60s,如果超過這個時間,腳本將會被一個SIGKILL信號終止,之后重新執行。
#通知型腳本:當sentinel有任何警告級別的事件發生時(比如說redis實例的主觀失效和客觀失效等等),將會去調用這個腳本,這時這個腳本應該通過郵件,SMS等方式去通知系統管理員關于系統不正常運行的信息。調用該腳本時,將傳給腳本兩個參數,一個是事件的類型,一個是事件的描述。如果sentinel.conf配置文件中配置了這個腳本路徑,那么必須保證這個腳本存在于這個路徑,并且是可執行的,否則sentinel無法正常啟動成功。
#通知腳本
# shell編程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh# 客戶端重新配置主節點參數腳本
# 當一個master由于failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關于master地址已經發生改變的信息。
# 以下參數將會在調用腳本時傳給腳本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>總是“failover”,
# <role>是“leader”或者“observer”中的一個。
# 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的
# 這個腳本應該是通用的,能被多次調用,不是針對性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由運維來配置!
14、Redis緩存穿透和雪崩
服務的高可用問題!
在這里我們不會詳細的區分析解決方案的底層!
Redis緩存的使用,極大的提升了應用程序的性能和效率,特別是數據查詢方面。但同時,它也帶來了一些問題。其中,最要害的問題,就是數據的一致性問題,從嚴格意義上講,這個問題無解。如果對數據的一致性要求很高,那么就不能使用緩存。
另外的一些典型問題就是,緩存穿透、緩存雪崩和緩存擊穿。目前,業界也都有比較流行的解決方案。
14.1 緩存穿透(查不到)
概念
緩存穿透的概念很簡單,用戶想要查詢一個數據,發現redis內存數據庫沒有,也就是緩存沒有命中,于是向持久層數據庫查詢。發現也沒有,于是本次查詢失敗。當用戶很多的時候,緩存都沒有命中(秒殺!),于是都去請求了持久層數據庫。這會給持久層數據庫造成很大的壓力,這時候就相當于出現了緩存穿透。
解決方案
布隆過濾器
布隆過濾器是一種數據結構,對所有可能查詢的參數以hash形式存儲,在控制層先進行校驗,不符合則丟棄,從而避免了對底層存儲系統的查詢壓力;

緩存空對象
當存儲層不命中后,即使返回的空對象也將其緩存起來,同時會設置一個過期時間,之后再訪問這個數據將會從緩存中獲取,保護了后端數據源;

但是這種方法會存在兩個問題:
1 、如果空值能夠被緩存起來,這就意味著緩存需要更多的空間存儲更多的鍵,因為這當中可能會有很多的空值的鍵;
2 、即使對空值設置了過期時間,還是會存在緩存層和存儲層的數據會有一段時間窗口的不一致,這對于需要保持一致性的業務會有影響。
14.2 緩存擊穿(量太大,緩存過期!)
概述
這里需要注意和緩存擊穿的區別,緩存擊穿,是指一個key非常熱點,在不停的扛著大并發,大并發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大并發就穿破緩存,直接請求數據庫,就像在一個屏障上鑿開了一個洞。
當某個key在過期的瞬間,有大量的請求并發訪問,這類數據一般是熱點數據,由于緩存過期,會同時訪問數據庫來查詢最新數據,并且回寫緩存,會導使數據庫瞬間壓力過大。
解決方案
設置熱點數據永不過期
從緩存層面來看,沒有設置過期時間,所以不會出現熱點 key 過期后產生的問題。
加互斥鎖
分布式鎖:使用分布式鎖,保證對于每個key同時只有一個線程去查詢后端服務,其他線程沒有獲得分布式鎖的權限,因此只需要等待即可。這種方式將高并發的壓力轉移到了分布式鎖,因此對分布式鎖的考驗很大。
14.3 緩存雪崩
概念
緩存雪崩,是指在某一個時間段,緩存集中過期失效。Redis 宕機!
產生雪崩的原因之一,比如在寫本文的時候,馬上就要到雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了緩存,假設緩存一個小時。那么到了凌晨一點鐘的時候,這批商品的緩存就都過期了。而對這批商品的訪問查詢,都落到了數據庫上,對于數據庫而言,就會產生周期性的壓力波峰。于是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會掛掉的情況。

其實集中過期,倒不是非常致命,比較致命的緩存雪崩,是緩存服務器某個節點宕機或斷網。因為自然形成的緩存雪崩,一定是在某個時間段集中創建緩存,這個時候,數據庫也是可以頂住壓力的。無非就是對數據庫產生周期性的壓力而已。而緩存服務節點的宕機,對數據庫服務器造成的壓力是不可預知的,很有可能瞬間就把數據庫壓垮。
解決方案
redis高可用
這個思想的含義是,既然redis有可能掛掉,那我多增設幾臺redis,這樣一臺掛掉之后其他的還可以繼續工作,其實就是搭建的集群。(異地多活!)
限流降級(在SpringCloud講解過!)
這個解決方案的思想是,在緩存失效后,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。
數據預熱
數據加熱的含義就是在正式部署之前,我先把可能的數據先預先訪問一遍,這樣部分可能大量訪問的數據就會加載到緩存中。在即將發生大并發訪問前手動觸發加載緩存不同的key,設置不同的過期時間,讓緩存失效的時間點盡量均勻。