【狂神说】Redis笔记
文章目錄
- 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 基礎(chǔ)的知識
- 4、五大數(shù)據(jù)類型
- 4.1 Redis-Key
- 4.2 String(字符串)
- 4.3 List(列表)
- 4.4 Set(集合)
- 4.5 Hash(哈希)
- 4.6 Zset(有序集合)
- 5、三種特殊數(shù)據(jù)類型
- 5.1 Geospatial 地理位置
- 5.2 Hyperloglog 基數(shù)統(tǒng)計
- 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發(fā)布訂閱
- 12、Redis主從復制
- 13、哨兵模式
- 14、Redis緩存穿透和雪崩
- 14.1 緩存穿透(查不到)
- 14.2 緩存擊穿(量太大,緩存過期!)
- 14.3 緩存雪崩
1、Nosql概述
1.1 為什么要用Nosql
1 、單機MySQL的年代!
90 年代,一個基本的網(wǎng)站訪問量一般不會太大,單個數(shù)據(jù)庫完全足夠!
那個時候,更多的去使用靜態(tài)網(wǎng)頁 Html ~ 服務器根本沒有太大的壓力!
思考一下,這種情況下:整個網(wǎng)站的瓶頸是什么?
1 、數(shù)據(jù)量如果太大、一個機器放不下了!
2 、數(shù)據(jù)的索引 (B+ Tree),一個機器內(nèi)存也放不下
3 、訪問量(讀寫混合),一個服務器承受不了~
只要你開始出現(xiàn)以上的三種情況之一,那么你就必須要晉級!
2 、Memcached(緩存) + MySQL + 垂直拆分 (讀寫分離)
網(wǎng)站80%的情況都是在讀,每次都要去查詢數(shù)據(jù)庫的話就十分的麻煩!所以說我們希望減輕數(shù)據(jù)的壓
力,我們可以使用緩存來保證效率!
發(fā)展過程: 優(yōu)化數(shù)據(jù)結(jié)構(gòu)和索引–> 文件緩存(IO)—> Memcached(當時最熱門的技術(shù)!)
3 、分庫分表 + 水平拆分 + MySQL集群
技術(shù)和業(yè)務在發(fā)展的同時,對人的要求也越來越高!
本質(zhì):數(shù)據(jù)庫(讀,寫)
早些年 MyISAM: 表鎖,十分影響效率!高并發(fā)下就會出現(xiàn)嚴重的鎖問題
轉(zhuǎn)戰(zhàn) InnoDB:行鎖
慢慢的就開始使用分庫分表來解決寫的壓力! MySQL 在那個年代推出了表分區(qū)!這個并沒有多少公司使用!
MySQL 的 集群,很好滿足那個年代的所有需求!
4、如今年代
2010–2020 十年之間,世界已經(jīng)發(fā)生了翻天覆地的變化;(定位,也是一種數(shù)據(jù),音樂,熱榜!)
MySQL 等關(guān)系型數(shù)據(jù)庫就不夠用了!數(shù)據(jù)量很多,變化很快~!
MySQL 有的使用它來村粗一些比較大的文件,博客,圖片!數(shù)據(jù)庫表很大,效率就低了!如果有一種數(shù)據(jù)庫來專門處理這種數(shù)據(jù),MySQL壓力就變得十分小(研究如何處理這些問題!)大數(shù)據(jù)的IO壓力下,表幾乎沒法更大!
目前一個基本的互聯(lián)網(wǎng)項目!
為什么要用NoSQL!
用戶的個人信息,社交網(wǎng)絡(luò),地理位置。用戶自己產(chǎn)生的數(shù)據(jù),用戶日志等等爆發(fā)式增長!
這時候我們就需要使用NoSQL數(shù)據(jù)庫的,Nosql 可以很好的處理以上的情況!
1.2 什么是NoSQL
NoSQL
NoSQL = Not Only SQL (不僅僅是SQL)
關(guān)系型數(shù)據(jù)庫:表格 ,行 ,列
泛指非關(guān)系型數(shù)據(jù)庫的,隨著web2.0互聯(lián)網(wǎng)的誕生!傳統(tǒng)的關(guān)系型數(shù)據(jù)庫很難對付web2.0時代!尤其
是超大規(guī)模的高并發(fā)的社區(qū)! 暴露出來很多難以克服的問題,NoSQL在當今大數(shù)據(jù)環(huán)境下發(fā)展的十分迅速,Redis是發(fā)展最快的,而且是我們當下必須要掌握的一個技術(shù)!
很多的數(shù)據(jù)類型用戶的個人信息,社交網(wǎng)絡(luò),地理位置。這些數(shù)據(jù)類型的存儲不需要一個固定的格式!不需要多余的操作就可以橫向擴展的 ! Map<String,Object> 使用鍵值對來控制!
NoSQL 特點
1 、方便擴展(數(shù)據(jù)之間沒有關(guān)系,很好擴展!)
2 、大數(shù)據(jù)量高性能(Redis 一秒寫 8 萬次,讀取 11 萬,NoSQL的緩存記錄級,是一種細粒度的緩存,性能會比較高!)
3 、數(shù)據(jù)類型是多樣型的!(不需要事先設(shè)計數(shù)據(jù)庫!隨取隨用!如果是數(shù)據(jù)量十分大的表,很多人就無法設(shè)計了!)
4 、高可用
傳統(tǒng)的RDBMS 和 NoSQL
傳統(tǒng) RDBMS -結(jié)構(gòu)化組織 -SQL -數(shù)據(jù)和關(guān)系都在單獨的表中 -嚴格的一致性 -操作數(shù)據(jù)定義語言 -基礎(chǔ)的事務 -....NoSQL -不僅僅是數(shù)據(jù) -沒有固定的查詢語言 -鍵值對存儲,列存儲,文檔存儲,圖形數(shù)據(jù)庫(社交關(guān)系) -最終一致性, -CAP定理和BASE理論 (異地多活) 初級架構(gòu)師! -高性能,高可用,高可擴 -....了解:3V+3高
大數(shù)據(jù)時代的3V:主要是描述問題的
大數(shù)據(jù)時代的 3 高:主要是對程序的要求
真正在公司中的實踐:NoSQL + RDBMS 一起使用才是最強的,阿里巴巴的架構(gòu)演進!
技術(shù)沒有高低之分,就看你如何去使用!(提升內(nèi)功,思維的提高!)
1.3 阿里巴巴演進分析
思考問題:這么多東西難道都是在一個數(shù)據(jù)庫中的嗎?
技術(shù)急不得,越是慢慢學,才能越扎實!
開源才是技術(shù)的王道!
任何一家互聯(lián)網(wǎng)的公司,都不可能只是簡簡單單讓用戶能用就好了!
大量公司做的都是相同的業(yè)務;(競品協(xié)議)
隨著這樣的競爭,業(yè)務是越來越完善,然后對于開發(fā)者的要求也是越來越高!
如果你未來相當一個架構(gòu)師: 沒有什么是加一層解決不了的!
# 1、商品的基本信息名稱、價格、商家信息;關(guān)系型數(shù)據(jù)庫就可以解決了! MySQL / Oracle (淘寶早年就去IOE了!- 王堅:推薦文章:阿里云的這群瘋子: 40 分鐘重要!)淘寶內(nèi)部的 MySQL 不是大家用的 MySQL# 2、商品的描述、評論(文字比較多)文檔型數(shù)據(jù)庫中,MongoDB# 3、圖片分布式文件系統(tǒng) FastDFS- 淘寶自己的 TFS- Gooale的 GFS- Hadoop HDFS- 阿里云的 oss# 4、商品的關(guān)鍵字 (搜索) - 搜索引擎 solr elasticsearch- ISerach:多隆(多去了解一下這些技術(shù)大佬!)所有牛逼的人都有一段苦逼的歲月!但是你只要像SB一樣的去堅持,終將牛逼!# 5、商品熱門的波段信息、- 內(nèi)存數(shù)據(jù)庫- Redis Tair、Memache...# 6、商品的交易,外部的支付接口- 三方應用要知道,一個簡單地網(wǎng)頁背后的技術(shù)一定不是大家所想的那么簡單!
大型互聯(lián)網(wǎng)應用問題:
數(shù)據(jù)類型太多了!
數(shù)據(jù)源繁多,經(jīng)常重構(gòu)!
數(shù)據(jù)要改造,大面積改造?
解決問題:
這里以上都是NoSQL入門概述,不僅能夠提高大家的知識,還可以幫助大家了解大廠的工作內(nèi)容!
2、NoSQL的四大分類
KV鍵值對:
- 新浪: Redis
- 美團:Redis + Tair
- 阿里、百度:Redis + memecache
文檔型數(shù)據(jù)庫(bson格式 和json一樣):
- MongoDB (一般必須要掌握)
- MongoDB 是一個基于分布式文件存儲的數(shù)據(jù)庫,C++ 編寫,主要用來處理大量的文檔!
- MongoDB 是一個介于關(guān)系型數(shù)據(jù)庫和非關(guān)系型數(shù)據(jù)庫中間的產(chǎn)品!MongoDB 是非關(guān)系型數(shù)
據(jù)庫中功能最豐富,最像關(guān)系型數(shù)據(jù)庫的!
- ConthDB
列存儲數(shù)據(jù)庫
- HBase
- 分布式文件系統(tǒng)
圖關(guān)系數(shù)據(jù)庫
- 他不是存圖形,放的是關(guān)系,比如:朋友圈社交網(wǎng)絡(luò),廣告推薦!
- Neo4j ,InfoGrid;
四者對比
| 鍵值(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 內(nèi)容緩存,主要用于處理大量數(shù)據(jù)的高訪問負載,也用于一些日志系統(tǒng)等等。 | Key 指向 Value 的鍵值對,通常用hash table來實現(xiàn) | 查找速度快 | 數(shù)據(jù)無結(jié)構(gòu)化,通常只被當作字符串或者二進制數(shù)據(jù) |
| 列存儲數(shù)據(jù)庫 | Cassandra, HBase, Riak | 分布式的文件系統(tǒng) | 以列簇式存儲,將同一列數(shù)據(jù)存在一起 | 查找速度快,可擴展性強,更容易進行分布式擴展 | 功能相對局限 |
| 文檔型數(shù)據(jù)庫 | CouchDB, MongoDb | Web應用(與Key-Value類似,Value是結(jié)構(gòu)化的,不同的是數(shù)據(jù)庫能夠了解Value的內(nèi)容) | Key-Value對應的鍵值對,Value為結(jié)構(gòu)化數(shù)據(jù) | 數(shù)據(jù)結(jié)構(gòu)要求不嚴格,表結(jié)構(gòu)可變,不需要像關(guān)系型數(shù)據(jù)庫一樣需要預先定義表結(jié)構(gòu) | 查詢性能不高,而且缺乏統(tǒng)一的查詢語法。 |
| 圖形(Graph)數(shù)據(jù)庫 | Neo4J, InfoGrid, Infinite Graph | 社交網(wǎng)絡(luò),推薦系統(tǒng)等。專注于構(gòu)建關(guān)系圖譜 | 圖結(jié)構(gòu) | 利用圖結(jié)構(gòu)相關(guān)算法。比如最短路徑尋址,N度關(guān)系查找等 | 很多時候需要對整個圖做計算才能得出需要的信息,而且這種結(jié)構(gòu)不太好做分布式的集群方案。 |
3、Redis入門
3.1 概述
Redis 是什么?
Redis(Remote Dictionary Server ),即遠程字典服務!
是一個開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。
redis會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎(chǔ)上實現(xiàn)了
master-slave(主從)同步。
免費和開源!是當下最熱門的 NoSQL 技術(shù)之一!也被人們稱之為結(jié)構(gòu)化數(shù)據(jù)庫!
Redis 能干嘛?
- 內(nèi)存存儲、持久化,內(nèi)存中是斷電即失、所以說持久化很重要(rdb、aof)
- 效率高,可以用于高速緩存
- 發(fā)布訂閱系統(tǒng)
- 地圖信息分析
- 計時器、計數(shù)器(瀏覽量!)
- …
特性
多樣的數(shù)據(jù)類型
持久化
集群
事務
…
缺點
不適合存儲重要的數(shù)據(jù)(財務類等)
不適合存儲經(jīng)常修改的數(shù)據(jù)
3.2 Windows安裝
1 、下載安裝包:https://github.com/dmajkic/redis/releases
2 、下載完畢得到壓縮包:
3 、解壓到自己電腦上的環(huán)境目錄下的就可以的!Redis 十分的小,只有5M
4 、開啟Redis,雙擊運行服務–redis-server.exe即可!
5 、使用redis客戶端–redis-cli.exe來連接redis
記住一句話,Window下使用確實簡單,但是Redis 推薦我們使用Linux去開發(fā)使用!
3.3 Linux安裝
1 、下載安裝包! redis-5.0.8.tar.gz ,用xftp把安裝包放到 /opt下
2 、解壓Redis的安裝包! 程序 /opt
tar -zxvf redis-5.0.8.tar.gz3 、進入解壓后的文件,可以看到我們redis的配置文件
4 、基本的環(huán)境安裝
yum install gcc-c++ # 安裝gcc,如果使用redis6.0以上的話需要gcc9.0版本以上才gcc -v # 查看安裝版本make # 自動進行配置,安裝后必須執(zhí)行,需要等待較長時間make install # 接著執(zhí)行5 、redis的默認安裝路徑 /usr/local/bin
cd /usr/local/bin # 進入目錄ls # 查看6 、將redis配置文件。復制到我們當前目錄下
mkdir RedisConfig # 創(chuàng)建目錄存放配置文件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 redis11 、如何關(guān)閉Redis服務呢?
shutdown # 關(guān)閉服務exit # 退出12 、再次查看進程是否存在
13 、后面我們會使用單機多Redis啟動集群測試!
3.4 測試性能
redis-benchmark 是一個壓力測試工具!
官方自帶的性能測試工具!
redis-benchmark 命令參數(shù)!
redis 性能測試工具可選參數(shù)如下所示:
| 1 | -h | 指定服務器主機名 | 127.0.0.1 |
| 2 | -p | 指定服務器端口 | 6379 |
| 3 | -s | 指定服務器 socket | |
| 4 | -c | 指定并發(fā)連接數(shù) | 50 |
| 5 | -n | 指定請求數(shù) | 10000 |
| 6 | -d | 以字節(jié)的形式指定 SET/GET 值的數(shù)據(jù)大小 | 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 | 生成循環(huán),永久執(zhí)行測試 | |
| 13 | -t | 僅運行以逗號分隔的測試命令列表。 | |
| 14 | -I | Idle 模式。僅打開 N 個 idle 連接并等待。 |
我們來簡單測試下:
# 測試: 100 個并發(fā)連接 每個并發(fā)100000 個請求,需要先開啟redis redis-benchmark -h localhost -p 6379 -c 100 -n 100000如何查看這些分析呢?
3.5 基礎(chǔ)的知識
redis默認有 16 個數(shù)據(jù)庫
默認使用的是第 0 個
# 可以使用 select 進行切換數(shù)據(jù)庫! 127 .0.0.1:6379> select 3 # 切換數(shù)據(jù)庫 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 * # 查看數(shù)據(jù)庫所有的key 1 ) "name" 127.0.0.1:6379[3]> FLUSHDB # 清除當前數(shù)據(jù)庫 OK 127.0.0.1:6379[3]> FLUSHALL # 清除全部數(shù)據(jù)庫的內(nèi)容 OK思考:為什么redis是 6379 !粉絲效應!(了解一下即可!)
Redis 是單線程的!
明白Redis是很快的,官方表示,Redis是基于內(nèi)存操作,CPU不是Redis性能瓶頸,Redis的瓶頸是根據(jù)機器的內(nèi)存和網(wǎng)絡(luò)帶寬來定,既然可以使用單線程來實現(xiàn),就使用單線程了!所以就使用了單線程了!
Redis 是C 語言寫的,官方提供的數(shù)據(jù)為 100000+ 的QPS,完全不比同樣是使用 key-vale的
Memecache差!
Redis 為什么單線程還這么快?
1 、誤區(qū) 1 :高性能的服務器一定是多線程的?
2 、誤區(qū) 2 :多線程(CPU上下文會切換!)一定比單線程效率高!
先去CPU>內(nèi)存>硬盤的速度要有所了解!
核心:redis 是將所有的數(shù)據(jù)全部放在內(nèi)存中的,所以說使用單線程去操作效率就是最高的,多線程
(CPU上下文會切換:耗時的操作!!!),對于內(nèi)存系統(tǒng)來說,如果沒有上下文切換效率就是最高的!多次讀寫都是在一個CPU上的,在內(nèi)存情況下,這個就是最佳的方案!
4、五大數(shù)據(jù)類型
Redis 是一個開源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),它可以用作數(shù)據(jù)庫、緩存和消息中間件MQ。 它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 與范圍查詢, bitmaps, hyperloglogs 和 地理空間
(geospatial) 索引半徑查詢。 Redis 內(nèi)置了 復制(replication),LUA腳本(Lua scripting), LRU 驅(qū)動事件(LRU eviction),事務(transactions) 和不同級別的 磁盤持久化(persistence), 并通過 Redis哨兵(Sentinel)和 自動分區(qū)(Cluster)提供高可用性(high availability)。
我們現(xiàn)在講解的所有命令大家一定要全部記住,后面我們使用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 # 設(shè)置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 string4.2 String(字符串)
90% 的 java程序員使用 redis 只會使用一個String類型!
########################################################################## 127 .0.0.1:6379> set key1 v1 # 設(shè)置值 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 瀏覽量變?yōu)?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 # 可以設(shè)置步長,指定增量! (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" # 設(shè)置 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) # 設(shè)置值同時設(shè)置過期時間setnx (set if not exist) # 不存在再設(shè)置,存在的話保持原值 (在分布式鎖中會常常使用!)127 .0.0.1:6379> setex key3 30 "hello" # 設(shè)置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 不存在,創(chuàng)建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存在,創(chuàng)建失敗! (integer) 0 127 .0.0.1:6379> get mykey "redis"########################################################################## mset # 同時設(shè)置多個值 mget # 同時獲取多個值127 .0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同時設(shè)置多個值 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 #設(shè)置失敗,k1已經(jīng)存在,msetnx是原子性操作 (integer) 0 127.0.0.1:6379> get k4 (nil)########################################################################## # 對象 set user:1 {name:zhangsan,age:3} # 設(shè)置一個user:1 對象 值為 json字符來保存一個對象!127.0.0.1:6379> set user:1 {name:xu,age:3} OK 127.0.0.1:6379> get user:1 #本質(zhì)還是鍵值對,user:1為key,"{name:xu,age:3}"為value "{name:xu,age:3}" # 這里的key是一個巧妙的設(shè)計: user:{id}:{filed} , 如此設(shè)計在Redis中是完全OK了! 127.0.0.1:6379> mset user:1:name xu1 user:1:age 4 # 這里本質(zhì)也是鍵值對 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 # 如果存在值,獲取原來的值,并設(shè)置新的值 "redis" 127 .0.0.1:6379> get db "mongodb"數(shù)據(jù)結(jié)構(gòu)是相同的!
String類似的使用場景:value除了是我們的字符串還可以是我們的數(shù)字!
-
計數(shù)器
-
統(tǒng)計多單位的數(shù)量
-
粉絲數(shù)
-
對象緩存存儲!
4.3 List(列表)
基本的數(shù)據(jù)類型,列表
在redis里面,我們可以把list玩成 ,棧、隊列、阻塞隊列!
所有的list命令都是用 L 開頭的,Redis不區(qū)分大小寫命令
########################################################################## 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 # 通過區(qū)間獲取具體的值! 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 # 移除指定的值!(從頭部開始的順序移除)# 取關(guān) 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集合中指定個數(shù)的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 # 移除的個數(shù)比列表擁有的個數(shù)多的話就全部移除 (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 # 截取指定開始下標和結(jié)束下標的列表 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 # 如果不存在列表我們?nèi)ジ戮蜁箦e(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"小結(jié)
- 他實際上是一個鏈表,before Node ,after Node , left,right 都可以插入值
- 如果key 不存在,創(chuàng)建新的鏈表
- 如果key存在,新增內(nèi)容
- 如果移除了所有值,空鏈表,也代表不存在!
- 在兩邊插入或者改動值,效率最高! 改變中間元素,相對來說效率會低一點~
消息排隊!消息隊列 (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集合中的內(nèi)容元素個數(shù)! (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!(可指定個數(shù))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站,共同關(guān)注!(并集)# 數(shù)字集合類:- 差集 - 交集- 并集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用戶將所有關(guān)注的人放在一個set集合中!將它的粉絲也放在一個集合中!
共同關(guān)注,共同愛好,二度好友,推薦好友!(六度分割理論)
4.5 Hash(哈希)
Map集合,key-map! 時候這個值是一個map集合! 本質(zhì)和String類型沒有太大區(qū)別,還是一個簡單的
key-vlaue!
hash變更的數(shù)據(jù) user name age,尤其是是用戶信息之類的,經(jīng)常變動的信息!hash 更適合于對象的存儲,String更加適合字符串存儲!
4.6 Zset(有序集合)
在set的基礎(chǔ)上,增加了一個值,set k1 v1 zset k1 score1 v1 ,根據(jù) 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"###########################################################################排序如何實現(xiàn) 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 # 獲取有序集合中的個數(shù) (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 # 獲取指定區(qū)間的成員數(shù)量! (integer) 3 127 .0.0.1:6379> zcount myset 1 2 (integer) 2其它的一些API,可以去查看官方文檔!
案例思路:set 排序 存儲班級成績表,工資表排序!
普通消息, 1 , 重要消息 2 ,帶權(quán)重進行判斷!
排行榜應用實現(xiàn),取Top N 測試!
5、三種特殊數(shù)據(jù)類型
5.1 Geospatial 地理位置
朋友的定位,附近的人,打車距離計算?
Redis 的 Geo 在Redis3.2 版本就推出了! 這個功能可以推算地理位置的信息,兩地之間的距離,方圓幾里的人!
可以查詢一些測試數(shù)據(jù):http://www.jsons.cn/lngcodeinfo/0706D99C19A781A3/
只有 六個命令:
Redis 地理位置(geo) 命令
| Redis GEOHASH 命令 | 返回一個或多個位置元素的 Geohash 表示 |
| Redis GEOPOS 命令 | 從key里返回所有給定位置元素的位置(經(jīng)度和緯度) |
| Redis GEODIST 命令 | 返回兩個給定位置之間的距離 |
| Redis GEORADIUS 命令 | 以給定的經(jīng)緯度為中心, 找出某一半徑內(nèi)的元素 |
| Redis GEOADD 命令 | 將指定的地理空間位置(緯度、經(jīng)度、名稱)添加到指定的key中 |
| Redis GEORADIUSBYMEMBER 命令 | 找出位于指定范圍內(nèi)的元素,中心點是由給定的位置元素決定 |
GEOADD 添加地理位置
# geoadd 添加地理位置 # 規(guī)則:兩極無法直接添加,我們一般會下載城市數(shù)據(jù),直接通過java程序一次性導入! # 有效的經(jīng)度從-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 # 參數(shù) 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) 2GEOPOS 獲取指定的城市的經(jīng)度和緯度
獲得指定位置定位:一定是一個坐標值!
127 .0.0.1:6379> geopos china:city beijing # 獲取指定的城市的經(jīng)度和緯度! 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 表示單位為英尺
GEORADIUS 以給定的經(jīng)緯度為中心, 找出某一半徑內(nèi)的位置元素
我附近的人? (獲得所有附近的人的地址,定位!)通過半徑來查詢!
獲得指定數(shù)量的人, 200
所有數(shù)據(jù)應該都錄入:china:city ,才會讓結(jié)果更加請求!
127 .0.0.1:6379> georadius china:city 110 30 1000 km # 以 110 , 30 這個經(jīng)緯度為中心,尋找方圓1000km內(nèi)的城市 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 # 篩選出指定數(shù)目的結(jié)果! 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為中心,找出某一半徑內(nèi)的位置元素
# 找出位于指定元素周圍的其他元素! 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字符串!
# 將二維的經(jīng)緯度轉(zhuǎn)換為一維的字符串,如果兩個字符串越接近,那么則距離越近! 127 .0.0.1:6379> geohash china:city beijing chongqing 1 ) "wx4fbxxfke0" 2 ) "wm5xzrybty0"GEO 底層的實現(xiàn)原理其實就是 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 基數(shù)統(tǒng)計
什么是基數(shù)?
A {1,3,5,7,8,7}
B{1,3,5,7,8}
基數(shù)(不重復的元素) = 5,可以接受誤差!
簡介
Redis 2.8.9 版本就更新了 Hyperloglog 數(shù)據(jù)結(jié)構(gòu)!
Redis Hyperloglog 基數(shù)統(tǒng)計的算法!
優(yōu)點:占用的內(nèi)存是固定,2^64 不同的元素的技術(shù),只需要廢 12KB內(nèi)存!如果要從內(nèi)存角度來比較的話 Hyperloglog 首選!
網(wǎng)頁的 UV (一個人訪問一個網(wǎng)站多次,但是還是算作一個人!)
傳統(tǒng)的方式, set 保存用戶的id,然后就可以統(tǒng)計 set 中的元素數(shù)量作為標準判斷!
這個方式如果保存大量的用戶id,就會比較麻煩!我們的目的是為了計數(shù),而不是保存用戶id;
0.81% 錯誤率! 統(tǒng)計UV任務,可以忽略不計的!
測試使用
127 .0.0.1:6379> pfadd mykey a b c d e f g h i j # 創(chuàng)建第一組元素 mykey (integer) 1 127 .0.0.1:6379> pfcount mykey # 統(tǒng)計 mykey 元素的基數(shù)數(shù)量 (integer) 10 127 .0.0.1:6379> PFadd mykey2 i j z x c v b n m # 創(chuàng)建第二組元素 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 # 看并集的數(shù)量! (integer) 15如果允許容錯,那么一定可以使用 Hyperloglog !
如果不允許容錯,就使用 set 或者自己的數(shù)據(jù)類型即可!
5.3 Bitmap 位圖
位存儲
統(tǒng)計用戶信息,活躍,不活躍! 登錄 、 未登錄! 打卡, 365 打卡! 兩個狀態(tài)的,都可以使用Bitmaps!
Bitmap 位圖,數(shù)據(jù)結(jié)構(gòu)! 都是操作二進制位來進行記錄,就只有 0 和 1 兩個狀態(tài)!
365 天 = 365 bit 1字節(jié) = 8bit 46 個字節(jié)左右!
# 使用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 # 統(tǒng)計操作,統(tǒng)計 打卡的天數(shù)! 127.0.0.1:6379> bitcount sign (integer) 36、事務
Redis 事務本質(zhì):一組命令的集合! 一個事務中的所有命令都會被序列化,在事務執(zhí)行過程的中,會按照順序執(zhí)行!
一次性、順序性、排他性!執(zhí)行一些列的命令!
------ 隊列 set set set 執(zhí)行 -------Redis事務沒有沒有隔離級別的概念!
所有的命令在事務中,并沒有直接被執(zhí)行!只有發(fā)起執(zhí)行命令的時候才會執(zhí)行!Exec
Redis單條命令是保證原子性的,但是事務不保證原子性!
redis的事務:
- 開啟事務(multi)
- 命令入隊(…)
- 執(zhí)行事務(exec)
正常執(zhí)行事務
# 正常執(zhí)行事務! 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 # 執(zhí)行事務 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 # 事務隊列中命令都不會被執(zhí)行! (nil)編譯型異常(代碼有問題! 命令有錯!) ,事務中所有的命令都不會被執(zhí)行!
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 # 執(zhí)行事務報錯! (error) EXECABORT Transaction discarded because of previous errors. 127 .0.0.1:6379> get k5 # 所有的命令都不會被執(zhí)行! (nil)運行時異常, 如果事務隊列中存在語法性,那么執(zhí)行命令的時候,其他命令是可以正常執(zhí)行的,錯誤命令拋出異常!
127 .0.0.1:6379> set k1 "v1" OK 127 .0.0.1:6379> multi OK 127 .0.0.1:6379> incr k1 # 會執(zhí)行的時候失敗! 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 # 雖然第一條命令報錯了,但是依舊正常執(zhí)行成功了! 2 ) OK 3 ) OK 4 ) "v3" 127 .0.0.1:6379> get k2 "v2" 127 .0.0.1:6379> get k3 "v3"監(jiān)控! Watch (面試常問!)
**悲觀鎖:**很悲觀,認為什么時候都會出問題,無論做什么都會加鎖!
**樂觀鎖:**很樂觀,認為什么時候都不會出問題,所以不會上鎖! 更新數(shù)據(jù)的時候去判斷一下,在此期間是否有人修改過這個數(shù)據(jù)
- 獲取version
- 更新的時候比較 version
Redis 監(jiān)視測試
單線程 正常執(zhí)行成功!
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 # 監(jiān)視 money 對象 OK 127 .0.0.1:6379> multi # 事務正常結(jié)束,數(shù)據(jù)期間沒有發(fā)生變動,這個時候就正常執(zhí)行成功! 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 # 監(jiān)視 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 # 執(zhí)行之前,另外一個線程修改了money的值,這個時候,就會導致事務執(zhí)行失敗! (nil)如果修改失敗,獲取最新的值就好
127.0.0.1:6379> unwatch # 事務執(zhí)行失敗,先解鎖 OK 127.0.0.1:6379> get money # 查看被修改后的值 "900" 127.0.0.1:6379> watch money # 獲取最新的值,再次監(jiān)視 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 # 對比監(jiān)視的值是否發(fā)生變化,如果沒有,執(zhí)行成功 1) (integer) 800 2) (integer) 1007、Jedis
什么是Jedis 是 Redis 官方推薦的 java連接開發(fā)工具! 使用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 、編碼測試:
- 連接數(shù)據(jù)庫
- 操作命令
- 斷開連接!
輸出:
常用的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(); //清除數(shù)據(jù)JSONObject jsonObject = new JSONObject(); //json對象jsonObject.put("name", "xu");jsonObject.put("age", 11);String jsonString = jsonObject.toJSONString(); //轉(zhuǎn)換為json字符串Transaction multi = jedis.multi();//開啟事務try {multi.set("key1", jsonString);multi.set("key2", jsonString);int i = 1 / 0; // 代碼拋出異常事務,執(zhí)行失敗!multi.exec(); //執(zhí)行事務} catch (Exception e) {multi.discard(); //出現(xiàn)異常,停止事務e.printStackTrace();} finally {System.out.println(jedis.get("key1"));System.out.println(jedis.get("key2"));jedis.close(); //關(guān)閉連接}} }8、SpringBoot整合
SpringBoot 操作數(shù)據(jù):spring-data jpa jdbc mongodb redis!
SpringData 也是和 SpringBoot 齊名的項目!
說明: 在 SpringBoot2.x 之后,原來使用的jedis 被替換為了 lettuce?
jedis : 采用的直連,多個線程操作的話,是不安全的,如果想要避免不安全的,使用 jedis pool 連接
池! 更像 BIO 模式
lettuce : 采用netty,實例可以在多個線程中進行共享,不存在線程不安全的情況!可以減少線程數(shù)據(jù)
了,更像 NIO 模式
源碼分析:
@Bean // 我們可以自己定義一個redisTemplate來替換這個默認的! @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory isConnectionFactory) throws UnknownHostException {// 默認的 RedisTemplate 沒有過多的設(shè)置,redis 對象都是需要序列化!// 兩個泛型都是 Object, Object 的類型,我們后使用需要強制轉(zhuǎn)換 <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= 63793 、測試!
@SpringBootTest class Redis02SpringbootApplicationTests {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid contextLoads() {// redisTemplate 操作不同的數(shù)據(jù)類型,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","關(guān)注狂神說公眾號");System.out.println(redisTemplate.opsForValue().get("mykey"));} }關(guān)于對象的保存:
我們來編寫一個自己的 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) {// 固定模板,在企業(yè)中,拿去就可以直接使用!// 我們?yōu)榱俗约洪_發(fā)方便,一般直接使用 <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開發(fā)人員來說,十分的簡單,更重要是要去理解redis的思想和每一種數(shù)據(jù)結(jié)構(gòu)的用處和作用場景!
9、Redis.conf詳解
啟動的時候,就通過配置文件來啟動!
單位
1 、配置文件 unit單位 對大小寫不敏感!
包含
就好比我們學習的Spring、Improt, include
網(wǎng)絡(luò)
bind 127 .0.0.1 # 綁定的ipprotected-mode yes # 保護模式port 6379 # 端口設(shè)置通用 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) 生產(chǎn)環(huán)境 # warning (only very important / critical messages are logged) loglevel noticelogfile "" # 日志的文件位置名databases 16 # 數(shù)據(jù)庫的數(shù)量,默認是 16 個數(shù)據(jù)庫always-show-logo yes # 是否總是顯示LOGO快照
持久化, 在規(guī)定的時間內(nèi),執(zhí)行了多少次操作,則會持久化到文件 .rdb 和 .aof
redis 是內(nèi)存數(shù)據(jù)庫,如果沒有持久化,那么數(shù)據(jù)斷電即失!
# 如果900s內(nèi),如果至少有一個1 key進行了修改,我們及進行持久化操作 save 900 1 # 如果300s內(nèi),如果至少10 key進行了修改,我們及進行持久化操作 save 300 10 # 如果60s內(nèi),如果至少10000 key進行了修改,我們及進行持久化操作 save 60 10000 # 我們之后學習持久化,會自己定義這個測試!stop-writes-on-bgsave-error yes # 持久化如果出錯,是否還需要繼續(xù)工作!rdbcompression yes # 是否壓縮 rdb 文件,需要消耗一些cpu資源!rdbchecksum yes # 保存rdb文件的時候,進行錯誤的檢查校驗!dir ./ # rdb 文件保存的目錄!REPLICATION 主從復制
SECURITY 安全
可以在這里設(shè)置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" # 設(shè)置redis的密碼 OK 127 .0.0.1:6379> config get requirepass # 發(fā)現(xiàn)所有的命令都沒有權(quán)限了 (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 # 設(shè)置能連接上redis的最大客戶端的數(shù)量maxmemory <bytes> # redis 配置最大的內(nèi)存容量maxmemory-policy noeviction # 內(nèi)存到達上限之后的處理策略1 、volatile-lru:只對設(shè)置了過期時間的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 # 每秒執(zhí)行一次 sync,可能會丟失這1s的數(shù)據(jù)! # appendfsync no # 不執(zhí)行 sync,這個時候操作系統(tǒng)自己同步數(shù)據(jù),速度最快!具體的配置,我們在 Redis持久化 中去給大家詳細詳解!
10、Redis持久化
面試和工作,持久化都是重點!
Redis 是內(nèi)存數(shù)據(jù)庫,如果不將內(nèi)存中的數(shù)據(jù)庫狀態(tài)保存到磁盤,那么一旦服務器進程退出,服務器中
的數(shù)據(jù)庫狀態(tài)也會消失。所以 Redis 提供了持久化功能!
10.1 RDB(Redis DataBase)
什么是RDB
在指定的時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤,也就是行話講的Snapshot快照,它恢復時是將快照文件直接讀到內(nèi)存里。
Redis會單獨創(chuàng)建(fork)一個子進程來進行持久化,會先將數(shù)據(jù)寫入到一個臨時文件中,待持久化過程都結(jié)束了,再用這個臨時文件替換上次持久化好的文件。整個過程中,主進程是不進行任何IO操作的。這就確保了極高的性能。如果需要進行大規(guī)模數(shù)據(jù)的恢復,且對于數(shù)據(jù)恢復的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。RDB 的缺點是最后一次持久化后的數(shù)據(jù)可能丟失。我們默認的就是 RDB,一般情況下不需要修改這個配置!
有時候在生產(chǎn)環(huán)境我們會將這個文件進行備份!
rdb保存的文件是dump.rdb 都是在我們的配置文件中快照中進行配置的!
觸發(fā)機制
1 、save的規(guī)則滿足的情況下,會自動觸發(fā)rdb規(guī)則
2 、執(zhí)行 flushall 命令,也會觸發(fā)我們的rdb規(guī)則!
3 、退出redis,也會產(chǎn)生 rdb 文件!
備份就自動生成一個 dump.rdb
如何恢復 rdb 文件
1 、只需要將rdb文件放在我們redis啟動目錄就可以,redis啟動的時候會自動檢查dump.rdb 恢復其中
的數(shù)據(jù)!
2 、查看需要存在的位置
127 .0.0.1:6379> config get dir 1 ) "dir" 2 ) "/usr/local/bin" # 如果在這個目錄下存在 dump.rdb 文件,啟動就會自動恢復其中的數(shù)據(jù)幾乎就它自己默認的配置就夠用了,但是我們還是需要去學習!
優(yōu)點:
1 、適合大規(guī)模的數(shù)據(jù)恢復!
2 、對數(shù)據(jù)的完整性要不高!
缺點:
1 、需要一定的時間間隔進行操作!如果redis意外宕機了,這個最后一次修改數(shù)據(jù)就沒有的了!
2 、fork進程的時候,會占用一定的內(nèi)容空間!!
10.2 AOF(Append Only File)
將我們的所有命令都記錄下來,history,恢復的時候就把這個文件全部在執(zhí)行一遍!
AOF是什么
以日志的形式來記錄每個寫操作,將Redis執(zhí)行過的所有指令記錄下來(讀操作不記錄),只許追加文件,但不可以改寫文件,redis啟動之初會讀取該文件重新構(gòu)建數(shù)據(jù),換言之,redis重啟的話就根據(jù)日志文件的內(nèi)容將寫指令從前到后執(zhí)行一次以完成數(shù)據(jù)的恢復工作
AOF保存的是 appendonly.aof 文件
默認是不開啟的,我們需要手動進行配置!我們只需要將 appendonly 改為yes就開啟了 aof!
重啟,redis 就可以生效了!
如果這個 aof 文件有錯誤,這時候 redis 是啟動不起來的,我們需要修復這個aof文件
redis 給我們提供了一個工具 redis-check-aof --fix
如果文件正常,重啟就可以直接恢復了!
重寫規(guī)則說明
aof 默認就是文件的無限追加,文件會越來越大!
如果 aof 文件大于 64m,太大了! fork一個新的進程來將我們的文件進行重寫!
優(yōu)點:
appendonly no # 默認是不開啟aof模式的,默認是使用rdb方式持久化的,在大部分所有的情況下, rdb完全夠用! appendfilename "appendonly.aof" # 持久化的文件的名字# appendfsync always # 每次修改都會 sync。消耗性能 appendfsync everysec # 每秒執(zhí)行一次 sync,可能會丟失這1s的數(shù)據(jù)! # appendfsync no # 不執(zhí)行 sync,這個時候操作系統(tǒng)自己同步數(shù)據(jù),速度最快!# rewrite 重寫,1 、每一次修改都同步,文件的完整會更加好!
2 、每秒同步一次,可能會丟失一秒的數(shù)據(jù)
3 、從不同步,效率最高的!
缺點:
1 、相對于數(shù)據(jù)文件來說,aof 遠遠大于 rdb,修復的速度也比 rdb 慢!
2 、Aof 運行效率也要比 rdb 慢,所以我們 redis 默認的配置就是rdb持久化!
擴展
1 、RDB 持久化方式能夠在指定的時間間隔內(nèi)對你的數(shù)據(jù)進行快照存儲
2 、AOF 持久化方式記錄每次對服務器寫的操作,當服務器重啟的時候會重新執(zhí)行這些命令來恢復原始的數(shù)據(jù),AOF命令以 Redis 協(xié)議追加保存每次寫的操作到文件末尾,Redis還能對AOF文件進行后臺重寫,使得AOF文件的體積不至于過大。
3 、只做緩存,如果你只希望你的數(shù)據(jù)在服務器運行的時候存在,你也可以不使用任何持久化
4 、同時開啟兩種持久化方式
- 在這種情況下,當redis重啟的時候會優(yōu)先載入AOF文件來恢復原始的數(shù)據(jù),因為在通常情況下AOF文件保存的數(shù)據(jù)集要比RDB文件保存的數(shù)據(jù)集要完整。
- RDB 的數(shù)據(jù)不實時,同時使用兩者時服務器重啟也只會找AOF文件,那要不要只使用AOF呢?作者建議不要,因為RDB更適合用于備份數(shù)據(jù)庫(AOF在不斷變化不好備份),快速重啟,而且不會有AOF可能潛在的Bug,留著作為一個萬一的手段。
5 、性能建議
- 因為RDB文件只用作后備用途,建議只在Slave上持久化RDB文件,而且只要 15 分鐘備份一次就夠了,只保留 save 900 1 這條規(guī)則。
- 如果Enable AOF ,好處是在最惡劣情況下也只會丟失不超過兩秒數(shù)據(jù),啟動腳本較簡單只load自
己的AOF文件就可以了,代價一是帶來了持續(xù)的IO,二是AOF rewrite 的最后將 rewrite 過程中產(chǎn)
生的新數(shù)據(jù)寫到新文件造成的阻塞幾乎是不可避免的。只要硬盤許可,應該盡量減少AOF rewrite
的頻率,AOF重寫的基礎(chǔ)大小默認值64M太小了,可以設(shè)到5G以上,默認超過原大小100%大小重寫可以改到適當?shù)臄?shù)值。 - 如果不Enable AOF ,僅靠 Master-Slave Repllcation 實現(xiàn)高可用性也可以,能省掉一大筆IO,也
減少了rewrite時帶來的系統(tǒng)波動。代價是如果Master/Slave 同時倒掉,會丟失十幾分鐘的數(shù)據(jù),
啟動腳本也要比較兩個 Master/Slave 中的 RDB文件,載入較新的那個,微博就是這種架構(gòu)。
11、Redis發(fā)布訂閱
Redis 發(fā)布訂閱(pub/sub)是一種消息通信模式:發(fā)送者(pub)發(fā)送消息,訂閱者(sub)接收消息。微信、 微博、關(guān)注系統(tǒng)!
Redis 客戶端可以訂閱任意數(shù)量的頻道。
訂閱/發(fā)布消息圖:
第一個:消息發(fā)送者, 第二個:頻道 , 第三個:消息訂閱者!
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的
關(guān)系:
當有新消息通過 PUBLISH 命令發(fā)送給頻道 channel1 時, 這個消息就會被發(fā)送給訂閱它的三個客戶
端:
命令
這些命令被廣泛用于構(gòu)建即時通信應用,比如網(wǎng)絡(luò)聊天室(chatroom)和實時廣播、實時提醒等。
| 1 | [PSUBSCRIBE pattern pattern …] 訂閱一個或多個符合給定模式的頻道。 |
| 2 | PUBSUB subcommand [argument [argument …]] 查看訂閱與發(fā)布系統(tǒng)狀態(tài)。 |
| 3 | PUBLISH channel message 將信息發(fā)送到指定的頻道。 |
| 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" # 消息的具體內(nèi)容1 ) "message" 2 ) "kuangshenshuo" 3 ) "hello,redis"發(fā)送端:
127 .0.0.1:6379> publish kuangshenshuo "hello,kuangshen" # 發(fā)布者發(fā)布消息到頻道! (integer) 1 127 .0.0.1:6379> PUBLISH kuangshenshuo "hello,redis" # 發(fā)布者發(fā)布消息到頻道! (integer) 1 127 .0.0.1:6379>原理
Redis是使用C實現(xiàn)的,通過分析 Redis 源碼里的 pubsub.c 文件,了解發(fā)布和訂閱機制的底層實現(xiàn),籍此加深對 Redis 的理解。
Redis 通過 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令實現(xiàn)發(fā)布和訂閱功能。
通過 SUBSCRIBE 命令訂閱某頻道后,redis-server 里維護了一個字典,字典的鍵就是一個個 頻道!而字典的值則是一個鏈表,鏈表中保存了所有訂閱這個 channel 的客戶端。SUBSCRIBE 命令的關(guān)鍵,就是將客戶端添加到給定 channel 的訂閱鏈表中。
通過 PUBLISH 命令向訂閱者發(fā)送消息,redis-server 會使用給定的頻道作為鍵,在它所維護的 channel 字典中查找記錄了訂閱這個頻道的所有客戶端的鏈表,遍歷這個鏈表,將消息發(fā)布給所有訂閱者。
Pub/Sub 從字面上理解就是發(fā)布(Publish)與訂閱(Subscribe),在Redis中,你可以設(shè)定對某一個
key值進行消息發(fā)布及消息訂閱,當一個key值上進行了消息發(fā)布后,所有訂閱它的客戶端都會收到相應的消息。這一功能最明顯的用法就是用作實時消息系統(tǒng),比如普通的即時聊天,群聊等功能。
使用場景:
1 、實時消息系統(tǒng)!
2 、實時聊天!(頻道當做聊天室,將信息回顯給所有人即可!)
3 、訂閱,關(guān)注系統(tǒng)都是可以的!
稍微復雜的場景我們就會使用 消息中間件 MQ
12、Redis主從復制
概念
主從復制,是指將一臺Redis服務器的數(shù)據(jù),復制到其他的Redis服務器。前者稱為主節(jié)點
(master/leader),后者稱為從節(jié)點(slave/follower);數(shù)據(jù)的復制是單向的,只能由主節(jié)點到從節(jié)點。
Master以寫為主,Slave 以讀為主。
默認情況下,每臺Redis服務器都是主節(jié)點;且一個主節(jié)點可以有多個從節(jié)點(或沒有從節(jié)點),但一個從節(jié)點只能有一個主節(jié)點。
主從復制的作用主要包括:
一般來說,要將Redis運用于工程項目中,只使用一臺Redis是萬萬不能的(宕機),原因如下:
- 從結(jié)構(gòu)上,單個Redis服務器會發(fā)生單點故障,并且一臺服務器需要處理所有的請求負載,壓力較
大; - 從容量上,單個Redis服務器內(nèi)存容量有限,就算一臺Redis服務器內(nèi)存容量為256G,也不能將所有內(nèi)存用作Redis存儲內(nèi)存,一般來說,單臺Redis最大使用內(nèi)存不應該超過20G。
電商網(wǎng)站上的商品,一般都是一次上傳,無數(shù)次瀏覽的,說專業(yè)點也就是"多讀少寫"。
對于這種場景,我們可以使如下這種架構(gòu):
主從復制,讀寫分離! 80% 的情況下都是在進行讀操作!減緩服務器的壓力!架構(gòu)中經(jīng)常使用! 一主二從!
只要在公司中,主從復制就是必須要使用的,因為在真實的項目中不可能單機使用Redis!
環(huán)境配置
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服務器都是主節(jié)點; 我們一般情況下只用配置從機就好了!
認老大! 一主 ( 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如果兩個都配置完了,就是有兩個從機的
真實的從主配置應該在配置文件中配置,這樣的話是永久的,我們這里使用的是命令,暫時的!
細節(jié)
主機可以寫,從機不能寫只能讀!主機中的所有信息和數(shù)據(jù),都會自動被從機保存!
主機寫:
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.測試:主機斷開連接,從機依舊連接到主機的,但是沒有寫操作,這個時候,主機如果回來了,從機依舊可以直接獲取到主機寫的信息!
如果是使用命令行來配置的主從,這個時候如果重啟了,就會變回主機!只要變?yōu)閺臋C,立馬就會從主機中獲取值!
復制原理
Slave 啟動成功連接到 master 后會發(fā)送一個sync同步命令
Master 接到命令,啟動后臺的存盤進程,同時收集所有接收到的用于修改數(shù)據(jù)集命令,在后臺進程執(zhí)行完畢之后,master將傳送整個數(shù)據(jù)文件到slave,并完成一次完全同步**(全量復制)**。
全量復制:而slave服務在接收到數(shù)據(jù)庫文件數(shù)據(jù)后,將其存盤并加載到內(nèi)存中。
增量復制:Master 繼續(xù)將新的所有收集到的修改命令依次傳給slave,完成同步
但是只要是重新連接master,一次完全同步**(全量復制)**將被自動執(zhí)行! 我們的數(shù)據(jù)一定可以在從機中看到!
層層鏈路
上一個Master鏈接下一個 Slave!
這時候也可以完成我們的主從復制!
如果沒有老大了,這個時候能不能選擇一個老大出來呢? 手動!
謀朝篡位
如果主機斷開了連接,我們可以使用 slaveof no one 讓自己變成主機!其他的節(jié)點就可以手動連
接到最新的這個主節(jié)點(手動)!如果這個時候老大修復了,那就重新連接老大!
13、哨兵模式
概述
主從切換技術(shù)的方法是:當主服務器宕機后,需要手動把一臺從服務器切換為主服務器,這就需要人工干預,費事費力,還會造成一段時間內(nèi)服務不可用。這不是一種推薦的方式,更多時候,我們優(yōu)先考慮哨兵模式。Redis從2.8開始正式提供了Sentinel(哨兵) 架構(gòu)來解決這個問題。謀朝篡位的自動版,能夠后臺監(jiān)控主機是否故障,如果故障了根據(jù)投票數(shù)自動將從庫轉(zhuǎn)換為主庫。
哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的進程,作為進程,它會獨立運行。其原理是 哨兵通過發(fā)送命令,等待Redis服務器響應,從而監(jiān)控運行的多個Redis實例。
這里的哨兵有兩個作用
- 通過發(fā)送命令,讓Redis服務器返回監(jiān)控其運行狀態(tài),包括主服務器和從服務器。
- 當哨兵監(jiān)測到master宕機,會自動將slave切換成master,然后通過 發(fā)布訂閱模式 通知其他的從服務器,修改配置文件,讓它們切換主機。
然而一個哨兵進程對Redis服務器進行監(jiān)控,可能會出現(xiàn)問題,為此,我們可以使用多個哨兵進行監(jiān)控。
各個哨兵之間還會進行監(jiān)控,這樣就形成了多哨兵模式。
假設(shè)主服務器宕機,哨兵 1 先檢測到這個結(jié)果,系統(tǒng)并不會馬上進行failover過程,僅僅是哨兵 1 主觀的認為主服務器不可用,這個現(xiàn)象成為 主觀下線 。當后面的哨兵也檢測到主服務器不可用,并且數(shù)量達到一定值時,那么哨兵之間就會進行一次投票,投票的結(jié)果由一個哨兵發(fā)起,進行failover[故障轉(zhuǎn)移]操作。切換成功后,就會通過發(fā)布訂閱模式,讓各個哨兵把自己監(jiān)控的從服務器實現(xiàn)切換主機,這個過程稱為客觀下線 。
測試
我們目前的狀態(tài)是 一主二從!
1 、配置哨兵配置文件 sentinel.conf 后面的這個數(shù)字 1 ,代表主機掛了,slave投票看讓誰接替成為主機,票數(shù)最多的,就會成為主機!
# sentinel monitor 被監(jiān)控的名稱 host port 1 sentinel monitor myredis 127.0.0.1 6379 12 、啟動哨兵!
[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 節(jié)點斷開了,這個時候就會從從機中隨機選擇一個服務器! (這里面有一個投票算法!)
如果主機此時回來了,只能歸并到新的主機下,當做從機,這就是哨兵模式的規(guī)則!
哨兵模式
優(yōu)點:
缺點:
哨兵模式的全部配置
# Example sentinel.conf # 哨兵sentinel實例運行的端口 默認 26379 port 26379 # 多個哨兵集群就得配置端口# 哨兵sentinel的工作目錄 dir /tmp# 哨兵sentinel監(jiān)控的redis主節(jié)點的 ip port # master-name 可以自己命名的主節(jié)點名字 只能由字母A-z、數(shù)字0-9 、這三個字符".-_"組成。 # quorum 配置多少個sentinel哨兵統(tǒng)一認為master主節(jié)點失聯(lián) 那么這時客觀上認為主節(jié)點失聯(lián)了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127 .0.0.1 6379 2# 當在Redis實例中開啟了requirepass foobared 授權(quán)密碼 這樣所有連接Redis實例的客戶端都要提供密碼 # 設(shè)置哨兵sentinel 連接主從的密碼 注意必須為主從設(shè)置一樣的驗證密碼 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster MySUPER--secret-0123passw0rd# 指定多少毫秒之后 主節(jié)點沒有應答哨兵sentinel 此時 哨兵主觀上認為主節(jié)點下線 默認 30 秒 # sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 30000# 這個配置項指定了在發(fā)生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,這個數(shù)字越小,完成failover所需的時間就越長,但是如果這個數(shù)字越大,就意味著越 多的slave因為replication而不可用。可以通過將這個值設(shè)為 1 來保證每次只有一個slave 處于不能處理命令請求的狀態(tài)。 # sentinel parallel-syncs <master-name> <numslaves> sentinel parallel-syncs mymaster 1# 故障轉(zhuǎn)移的超時時間 failover-timeout 可以用在以下這些方面: #1. 同一個sentinel對同一個master兩次failover之間的間隔時間。 #2. 當一個slave從一個錯誤的master那里同步數(shù)據(jù)開始計算時間。直到slave被糾正為向正確的master那里同步數(shù)據(jù)時。 #3.當想要取消一個正在進行的failover所需要的時間。 #4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置為指向master,但是就不按parallel-syncs所配置的規(guī)則來了 # 默認三分鐘 # sentinel failover-timeout <master-name> <milliseconds> sentinel failover-timeout mymaster 180000# SCRIPTS EXECUTION #配置當某一事件發(fā)生時所需要執(zhí)行的腳本,可以通過腳本來通知管理員,例如當系統(tǒng)運行不正常時發(fā)郵件通知 相關(guān)人員。 #對于腳本的運行結(jié)果有以下規(guī)則: #若腳本執(zhí)行后返回 1 ,那么該腳本稍后將會被再次執(zhí)行,重復次數(shù)目前默認為 10 #若腳本執(zhí)行后返回 2 ,或者比 2 更高的一個返回值,腳本將不會重復執(zhí)行。 #如果腳本在執(zhí)行過程中由于收到系統(tǒng)中斷信號被終止了,則同返回值為 1 時的行為相同。 #一個腳本的最大執(zhí)行時間為60s,如果超過這個時間,腳本將會被一個SIGKILL信號終止,之后重新執(zhí)行。 #通知型腳本:當sentinel有任何警告級別的事件發(fā)生時(比如說redis實例的主觀失效和客觀失效等等),將會去調(diào)用這個腳本,這時這個腳本應該通過郵件,SMS等方式去通知系統(tǒng)管理員關(guān)于系統(tǒng)不正常運行的信息。調(diào)用該腳本時,將傳給腳本兩個參數(shù),一個是事件的類型,一個是事件的描述。如果sentinel.conf配置文件中配置了這個腳本路徑,那么必須保證這個腳本存在于這個路徑,并且是可執(zhí)行的,否則sentinel無法正常啟動成功。 #通知腳本 # shell編程 # sentinel notification-script <master-name> <script-path> sentinel notification-script mymaster /var/redis/notify.sh# 客戶端重新配置主節(jié)點參數(shù)腳本 # 當一個master由于failover而發(fā)生改變時,這個腳本將會被調(diào)用,通知相關(guān)的客戶端關(guān)于master地址已經(jīng)發(fā)生改變的信息。 # 以下參數(shù)將會在調(diào)用腳本時傳給腳本: # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> # 目前<state>總是“failover”, # <role>是“l(fā)eader”或者“observer”中的一個。 # 參數(shù) from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的 # 這個腳本應該是通用的,能被多次調(diào)用,不是針對性的。 # sentinel client-reconfig-script <master-name> <script-path> sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由運維來配置!14、Redis緩存穿透和雪崩
服務的高可用問題!
在這里我們不會詳細的區(qū)分析解決方案的底層!
Redis緩存的使用,極大的提升了應用程序的性能和效率,特別是數(shù)據(jù)查詢方面。但同時,它也帶來了一些問題。其中,最要害的問題,就是數(shù)據(jù)的一致性問題,從嚴格意義上講,這個問題無解。如果對數(shù)據(jù)的一致性要求很高,那么就不能使用緩存。
另外的一些典型問題就是,緩存穿透、緩存雪崩和緩存擊穿。目前,業(yè)界也都有比較流行的解決方案。
14.1 緩存穿透(查不到)
概念
緩存穿透的概念很簡單,用戶想要查詢一個數(shù)據(jù),發(fā)現(xiàn)redis內(nèi)存數(shù)據(jù)庫沒有,也就是緩存沒有命中,于是向持久層數(shù)據(jù)庫查詢。發(fā)現(xiàn)也沒有,于是本次查詢失敗。當用戶很多的時候,緩存都沒有命中(秒殺!),于是都去請求了持久層數(shù)據(jù)庫。這會給持久層數(shù)據(jù)庫造成很大的壓力,這時候就相當于出現(xiàn)了緩存穿透。
解決方案
布隆過濾器
布隆過濾器是一種數(shù)據(jù)結(jié)構(gòu),對所有可能查詢的參數(shù)以hash形式存儲,在控制層先進行校驗,不符合則丟棄,從而避免了對底層存儲系統(tǒng)的查詢壓力;
緩存空對象
當存儲層不命中后,即使返回的空對象也將其緩存起來,同時會設(shè)置一個過期時間,之后再訪問這個數(shù)據(jù)將會從緩存中獲取,保護了后端數(shù)據(jù)源;
但是這種方法會存在兩個問題:
1 、如果空值能夠被緩存起來,這就意味著緩存需要更多的空間存儲更多的鍵,因為這當中可能會有很多的空值的鍵;
2 、即使對空值設(shè)置了過期時間,還是會存在緩存層和存儲層的數(shù)據(jù)會有一段時間窗口的不一致,這對于需要保持一致性的業(yè)務會有影響。
14.2 緩存擊穿(量太大,緩存過期!)
概述
這里需要注意和緩存擊穿的區(qū)別,緩存擊穿,是指一個key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進行訪問,當這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個屏障上鑿開了一個洞。
當某個key在過期的瞬間,有大量的請求并發(fā)訪問,這類數(shù)據(jù)一般是熱點數(shù)據(jù),由于緩存過期,會同時訪問數(shù)據(jù)庫來查詢最新數(shù)據(jù),并且回寫緩存,會導使數(shù)據(jù)庫瞬間壓力過大。
解決方案
設(shè)置熱點數(shù)據(jù)永不過期
從緩存層面來看,沒有設(shè)置過期時間,所以不會出現(xiàn)熱點 key 過期后產(chǎn)生的問題。
加互斥鎖
分布式鎖:使用分布式鎖,保證對于每個key同時只有一個線程去查詢后端服務,其他線程沒有獲得分布式鎖的權(quán)限,因此只需要等待即可。這種方式將高并發(fā)的壓力轉(zhuǎn)移到了分布式鎖,因此對分布式鎖的考驗很大。
14.3 緩存雪崩
概念
緩存雪崩,是指在某一個時間段,緩存集中過期失效。Redis 宕機!
產(chǎn)生雪崩的原因之一,比如在寫本文的時候,馬上就要到雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了緩存,假設(shè)緩存一個小時。那么到了凌晨一點鐘的時候,這批商品的緩存就都過期了。而對這批商品的訪問查詢,都落到了數(shù)據(jù)庫上,對于數(shù)據(jù)庫而言,就會產(chǎn)生周期性的壓力波峰。于是所有的請求都會達到存儲層,存儲層的調(diào)用量會暴增,造成存儲層也會掛掉的情況。
其實集中過期,倒不是非常致命,比較致命的緩存雪崩,是緩存服務器某個節(jié)點宕機或斷網(wǎng)。因為自然形成的緩存雪崩,一定是在某個時間段集中創(chuàng)建緩存,這個時候,數(shù)據(jù)庫也是可以頂住壓力的。無非就是對數(shù)據(jù)庫產(chǎn)生周期性的壓力而已。而緩存服務節(jié)點的宕機,對數(shù)據(jù)庫服務器造成的壓力是不可預知的,很有可能瞬間就把數(shù)據(jù)庫壓垮。
解決方案
redis高可用
這個思想的含義是,既然redis有可能掛掉,那我多增設(shè)幾臺redis,這樣一臺掛掉之后其他的還可以繼續(xù)工作,其實就是搭建的集群。(異地多活!)
限流降級(在SpringCloud講解過!)
這個解決方案的思想是,在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
數(shù)據(jù)預熱
數(shù)據(jù)加熱的含義就是在正式部署之前,我先把可能的數(shù)據(jù)先預先訪問一遍,這樣部分可能大量訪問的數(shù)據(jù)就會加載到緩存中。在即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存不同的key,設(shè)置不同的過期時間,讓緩存失效的時間點盡量均勻。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的【狂神说】Redis笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python根据财务指标寻找价值股票
- 下一篇: 时间修改,学习