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