狂神筆記:https://files.cnblogs.com/files/henuliulei/%E7%8B%82%E7%A5%9E%E8%AF%B4Redis%E7%AC%94%E8%AE%B0.zip
一、Nosql概述
爲什麼使用Nosql
1、單機Mysql時代
90年代,一個網站的訪問量一般不會太大,單個數據庫完全夠用。隨着用戶增多,網站出現以下問題
- 數據量增加到一定程度,單機數據庫就放不下了
- 數據的索引(B+ Tree),一個機器內存也存放不下
- 訪問量變大後(讀寫混合),一臺服務器承受不住。
2、Memcached(緩存) + Mysql + 垂直拆分(讀寫分離)
網站80%的情況都是在讀,每次都要去查詢數據庫的話就十分的麻煩!所以說我們希望減輕數據庫的壓力,我們可以使用緩存來保證效率!
優化過程經歷了以下幾個過程:
-
優化數據庫的數據結構和索引(難度大)
-
文件緩存,通過IO流獲取比每次都訪問數據庫效率略高,但是流量爆炸式增長時候,IO流也承受不了
-
MemCache,當時最熱門的技術,通過在數據庫和數據庫訪問層之間加上一層緩存,第一次訪問時查詢數據庫,將結果保存到緩存,後續的查詢先檢查緩存,若有直接拿去使用,效率顯著提升。
3、分庫分表 + 水平拆分 + Mysql集羣
4、如今最近的年代
如今信息量井噴式增長,各種各樣的數據出現(用戶定位數據,圖片數據等),大數據的背景下關係型數據庫(RDBMS)無法滿足大量數據要求。Nosql數據庫就能輕鬆解決這些問題。
目前一個基本的互聯網項目
爲什麼要用NoSQL ?
用戶的個人信息,社交網絡,地理位置。用戶自己產生的數據,用戶日誌等等爆發式增長!
這時候我們就需要使用NoSQL數據庫的,Nosql可以很好的處理以上的情況!
什麼是Nosql
NoSQL = Not Only SQL(不僅僅是SQL)
Not Only Structured Query Language
關係型數據庫:列+行,同一個表下數據的結構是一樣的。
非關係型數據庫:數據存儲沒有固定的格式,並且可以進行橫向擴展。
NoSQL泛指非關係型數據庫,隨着web2.0互聯網的誕生,傳統的關係型數據庫很難對付web2.0時代!尤其是超大規模的高併發的社區,暴露出來很多難以克服的問題,NoSQL在當今大數據環境下發展的十分迅速,Redis是發展最快的。
Nosql特點
-
方便擴展(數據之間沒有關係,很好擴展!)
-
大數據量高性能(Redis一秒可以寫8萬次,讀11萬次,NoSQL的緩存記錄級,是一種細粒度的緩存,性能會比較高!)
-
數據類型是多樣型的!(不需要事先設計數據庫,隨取隨用)
-
傳統的 RDBMS 和 NoSQL
傳統的 RDBMS(關係型數據庫) - 結構化組織 - SQL - 數據和關係都存在單獨的表中 row col - 操作,數據定義語言 - 嚴格的一致性 - 基礎的事務
-
Nosql - 不僅僅是數據 - 沒有固定的查詢語言 - 鍵值對存儲,列存儲,文檔存儲,圖形數據庫(社交關係) - 最終一致性 - CAP定理和BASE - 高性能,高可用,高擴展 - ..
瞭解:3V + 3高
大數據時代的3V :主要是描述問題的
-
海量Velume
-
多樣Variety
-
實時Velocity
大數據時代的3高 : 主要是對程序的要求
-
高併發
-
高可擴
-
高性能
真正在公司中的實踐:NoSQL + RDBMS 一起使用纔是最強的。
阿里巴巴演進分析
推薦閱讀:阿里雲的這羣瘋子https://yq.aliyun.com/articles/653511
# 商品信息
- 一般存放在關係型數據庫:Mysql,阿里巴巴使用的Mysql都是經過內部改動的。
# 商品描述、評論(文字居多)
- 文檔型數據庫:MongoDB
# 圖片
- 分佈式文件系統 FastDFS
- 淘寶:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里雲: oss
# 商品關鍵字 用於搜索
- 搜索引擎:solr,elasticsearch
- 阿里:Isearch 多隆
# 商品熱門的波段信息
- 內存數據庫:Redis,Memcache
# 商品交易,外部支付接口
- 第三方應用
Nosql的四大分類
KV鍵值對
- 新浪:Redis
- 美團:Redis + Tair
- 阿里、百度:Redis + Memcache
文檔型數據庫(bson數據格式):
-
MongoDB(掌握)
- 基於分佈式文件存儲的數據庫。C++編寫,用於處理大量文檔。
- MongoDB是RDBMS和NoSQL的中間產品。MongoDB是非關係型數據庫中功能最豐富的,NoSQL中最像關係型數據庫的數據庫。
-
ConthDB
列存儲數據庫
- HBase(大數據必學)
- 分佈式文件系統
圖關係數據庫
用於廣告推薦,社交網絡
- Neo4j、InfoGrid
分類 | Examples舉例 | 典型應用場景 | 數據模型 | 優點 | 缺點 |
---|---|---|---|---|---|
鍵值對(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 內容緩存,主要用於處理大量數據的高訪問負載,也用於一些日誌系統等等。 | Key 指向 Value 的鍵值對,通常用hash table來實現 | 查找速度快 | 數據無結構化,通常只被當作字符串或者二進制數據 |
列存儲數據庫 | Cassandra, HBase, Riak | 分佈式的文件系統 | 以列簇式存儲,將同一列數據存在一起 | 查找速度快,可擴展性強,更容易進行分佈式擴展 | 功能相對侷限 |
文檔型數據庫 | CouchDB, MongoDb | Web應用(與Key-Value類似,Value是結構化的,不同的是數據庫能夠了解Value的內容) | Key-Value對應的鍵值對,Value爲結構化數據 | 數據結構要求不嚴格,表結構可變,不需要像關係型數據庫一樣需要預先定義表結構 | 查詢性能不高,而且缺乏統一的查詢語法。 |
圖形(Graph)數據庫 | Neo4J, InfoGrid, Infinite Graph | 社交網絡,推薦系統等。專注於構建關係圖譜 | 圖結構 | 利用圖結構相關算法。比如最短路徑尋址,N度關係查找等 | 很多時候需要對整個圖做計算才能得出需要的信息,而且這種結構不太好做分佈式的集羣 |
讀寫分離和註冊複製的概念
數據庫表如何水平拆分和垂直拆分
MyISAM與InnoDB 的區別(9個不同點)
區別:
1. InnoDB支持事務,MyISAM不支持,對於InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,所以最好把多條SQL語言放在begin和commit之間,組成一個事務;
2. InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB錶轉爲MYISAM會失敗;
3. InnoDB是聚集索引,使用B+Tree作爲索引結構,數據文件是和(主鍵)索引綁在一起的(表數據文件本身就是按B+Tree組織的一個索引結構),必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然後再通過主鍵查詢到數據。因此,主鍵不應該過大,因爲主鍵太大,其他索引也都會很大。
MyISAM是非聚集索引,也是使用B+Tree作爲索引結構,索引和數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
也就是說:InnoDB的B+樹主鍵索引的葉子節點就是數據文件,輔助索引的葉子節點是主鍵的值;而MyISAM的B+樹主鍵索引和輔助索引的葉子節點都是數據文件的地址指針。
4. InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快(注意不能加有任何WHERE條件);
那麼爲什麼InnoDB沒有了這個變量呢?
因爲InnoDB的事務特性,在同一時刻表中的行數對於不同的事務而言是不一樣的,因此count統計會計算對於當前事務而言可以統計到的行數,而不是將總行數儲存起來方便快速查詢。InnoDB會嘗試遍歷一個儘可能小的索引除非優化器提示使用別的索引。如果二級索引不存在,InnoDB還會嘗試去遍歷其他聚簇索引。
如果索引並沒有完全處於InnoDB維護的緩衝區(Buffer Pool)中,count操作會比較費時。可以建立一個記錄總行數的表並讓你的程序在INSERT/DELETE時更新對應的數據。和上面提到的問題一樣,如果此時存在多個事務的話這種方案也不太好用。如果得到大致的行數值已經足夠滿足需求可以嘗試SHOW TABLE STATUS
5. Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引領域的查詢效率上MyISAM速度更快高;PS:5.7以後的InnoDB支持全文索引了
6. MyISAM表格可以被壓縮後進行查詢操作
7. InnoDB支持表、行(默認)級鎖,而MyISAM支持表級鎖
InnoDB的行鎖是實現在索引上的,而不是鎖在物理行記錄上。潛臺詞是,如果訪問沒有命中索引,也無法使用行鎖,將要退化爲表鎖。
例如:
t_user(uid, uname, age, sex) innodb;
uid PK
無其他索引
update t_user set age=10 where uid=1; 命中索引,行鎖。
update t_user set age=10 where uid != 1; 未命中索引,表鎖。
update t_user set age=10 where name='chackca'; 無索引,表鎖。
8、InnoDB表必須有唯一索引(如主鍵)(用戶沒有指定的話會自己找/生產一個隱藏列Row_id來充當默認主鍵),而Myisam可以沒有
9、Innodb存儲文件有frm、ibd,而Myisam是frm、MYD、MYI
Innodb:frm是表定義文件,ibd是數據文件
Myisam:frm是表定義文件,myd是數據文件,myi是索引文件
如何選擇:
1. 是否要支持事務,如果要請選擇innodb,如果不需要可以考慮MyISAM;
2. 如果表中絕大多數都只是讀查詢,可以考慮MyISAM,如果既有讀也有寫,請使用InnoDB。
3. 系統奔潰後,MyISAM恢復起來更困難,能否接受;
4. MySQL5.5版本開始Innodb已經成爲Mysql的默認引擎(之前是MyISAM),說明其優勢是有目共睹的,如果你不知道用什麼,那就用InnoDB,至少不會差。
InnoDB爲什麼推薦使用自增ID作爲主鍵?
答:自增ID可以保證每次插入時B+索引是從右邊擴展的,可以避免B+樹和頻繁合併和分裂(對比使用UUID)。如果使用字符串主鍵和隨機主鍵,會使得數據隨機插入,效率比較差。
innodb引擎的4大特性
插入緩衝(insert buffer),二次寫(double write),自適應哈希索引(ahi),預讀(read ahead)
二、Redis入門
概述
Redis是什麼?
Redis(Remote Dictionary Server ),即遠程字典服務。
是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。
與memcached一樣,爲了保證效率,數據都是緩存在內存中。區別的是redis會週期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
Redis能該幹什麼?
- 內存存儲、持久化,內存是斷電即失的,所以需要持久化(RDB、AOF)
- 高效率、用於高速緩衝
- 發佈訂閱系統
- 地圖信息分析
- 計時器、計數器(eg:瀏覽量)
- 。。。
特性
-
多樣的數據類型
-
持久化
-
集羣
-
事務
…
環境搭建
官網:https://redis.io/
推薦使用Linux服務器學習。
windows版本的Redis已經停更很久了…
Windows安裝
https://github.com/dmajkic/redis
-
解壓安裝包
-
開啓redis-server.exe
-
啓動redis-cli.exe測試
Linux安裝
-
下載安裝包!
redis-5.0.8.tar.gz
-
解壓Redis的安裝包!程序一般放在
/opt
目錄下 -
基本環境安裝
yum install gcc-c++ # 然後進入redis目錄下執行 make # 然後執行 make install
-
redis默認安裝路徑
/usr/local/bin
-
將redis的配置文件複製到 程序安裝目錄
/usr/local/bin/kconfig
下 -
redis默認不是後臺啓動的,需要修改配置文件!
-
通過制定的配置文件啓動redis服務
-
使用redis-cli連接指定的端口號測試,Redis的默認端口6379
-
查看redis進程是否開啓
-
關閉Redis服務
shutdown
-
再次查看進程是否存在
-
後面我們會使用單機多Redis啓動集羣測試
測試性能
**redis-benchmark:**Redis官方提供的性能測試工具,參數選項如下:
簡單測試:
# 測試:100個併發連接 100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
- 1
- 2
基礎知識
redis默認有16個數據庫
默認使用的第0個;
16個數據庫爲:DB 0~DB 15
默認使用DB 0 ,可以使用select n
切換到DB n,dbsize
可以查看當前數據庫的大小,與key數量相關。
127.0.0.1:6379> config get databases # 命令行查看數據庫數量databases 1) "databases" 2) "16" 127.0.0.1:6379> select 8 # 切換數據庫 DB 8 OK 127.0.0.1:6379[8]> dbsize # 查看數據庫大小 (integer) 0
# 不同數據庫之間 數據是不能互通的,並且dbsize 是根據庫中key的個數。
127.0.0.1:6379> set name sakura OK 127.0.0.1:6379> SELECT 8 OK 127.0.0.1:6379[8]> get name # db8中並不能獲取db0中的鍵值對。 (nil) 127.0.0.1:6379[8]> DBSIZE (integer) 0 127.0.0.1:6379[8]> SELECT 0 OK 127.0.0.1:6379> keys * 1) "counter:__rand_int__" 2) "mylist" 3) "name" 4) "key:__rand_int__" 5) "myset:__rand_int__" 127.0.0.1:6379> DBSIZE # size和key個數相關 (integer) 5
keys *
:查看當前數據庫中所有的key。
flushdb
:清空當前數據庫中的鍵值對。
flushall
:清空所有數據庫的鍵值對。
Redis是單線程的,Redis是基於內存操作的。
所以Redis的性能瓶頸不是CPU,而是機器內存和網絡帶寬。
那麼爲什麼Redis的速度如此快呢,性能這麼高呢?QPS達到10W+
Redis爲什麼單線程還這麼快?
- 誤區1:高性能的服務器一定是多線程的?
- 誤區2:多線程(CPU上下文會切換!)一定比單線程效率高!
核心:Redis是將所有的數據放在內存中的,所以說使用單線程去操作效率就是最高的,多線程(CPU上下文會切換:耗時的操作!),對於內存系統來說,如果沒有上下文切換效率就是最高的,多次讀寫都是在一個CPU上的,在內存存儲數據情況下,單線程就是最佳的方案。
三、五大數據類型
Redis是一個開源(BSD許可),內存存儲的數據結構服務器,可用作數據庫,高速緩存和消息隊列代理。它支持字符串、哈希表、列表、集合、有序集合,位圖,hyperloglogs等數據類型。內置複製、Lua腳本、LRU收回、事務以及不同級別磁盤持久化功能,同時通過Redis Sentinel提供高可用,通過Redis Cluster提供自動分區。
Redis-key
在redis中無論什麼數據類型,在數據庫中都是以key-value形式保存,通過進行對Redis-key的操作,來完成對數據庫中數據的操作。
下面學習的命令:
exists key
:判斷鍵是否存在del key
:刪除鍵值對move key db
:將鍵值對移動到指定數據庫expire key second
:設置鍵值對的過期時間type key
:查看value的數據類型
127.0.0.1:6379> keys * # 查看當前數據庫所有key (empty list or set) 127.0.0.1:6379> set name qinjiang # set key OK 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> move age 1 # 將鍵值對移動到指定數據庫 (integer) 1 127.0.0.1:6379> EXISTS age # 判斷鍵是否存在 (integer) 0 # 不存在 127.0.0.1:6379> EXISTS name (integer) 1 # 存在 127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> keys * 1) "age" 127.0.0.1:6379[1]> del age # 刪除鍵值對 (integer) 1 # 刪除個數 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> EXPIRE age 15 # 設置鍵值對的過期時間 (integer) 1 # 設置成功 開始計數 127.0.0.1:6379> ttl age # 查看key的過期剩餘時間 (integer) 13 127.0.0.1:6379> ttl age (integer) 11 127.0.0.1:6379> ttl age (integer) 9 127.0.0.1:6379> ttl age (integer) -2 # -2 表示key過期,-1表示key未設置過期時間 127.0.0.1:6379> get age # 過期的key 會被自動delete (nil) 127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> type name # 查看value的數據類型 string
關於TTL
命令
Redis的key,通過TTL命令返回key的過期時間,一般來說有3種:
- 當前key沒有設置過期時間,所以會返回-1.
- 當前key有設置過期時間,而且key已經過期,所以會返回-2.
- 當前key有設置過期時間,且key還沒有過期,故會返回key的正常剩餘時間.
關於重命名RENAME
和RENAMENX
RENAME key newkey
修改 key 的名稱RENAMENX key newkey
僅當 newkey 不存在時,將 key 改名爲 newkey 。
更多命令學習:https://www.redis.net.cn/order/
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wBVZtGVm-1597890996517)(狂神說 Redis.assets/image-20200813114228439.png)]
String(字符串)
普通的set、get直接略過。
命令 | 描述 | 示例 |
---|---|---|
APPEND key value |
向指定的key的value後追加字符串 | 127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> append msg " world" (integer) 11 127.0.0.1:6379> get msg “hello world” |
DECR/INCR key |
將指定key的value數值進行+1/-1(僅對於數字) | 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> incr age (integer) 21 127.0.0.1:6379> decr age (integer) 20 |
INCRBY/DECRBY key n |
按指定的步長對數值進行加減 | 127.0.0.1:6379> INCRBY age 5 (integer) 25 127.0.0.1:6379> DECRBY age 10 (integer) 15 |
INCRBYFLOAT key n |
爲數值加上浮點型數值 | 127.0.0.1:6379> INCRBYFLOAT age 5.2 “20.2” |
STRLEN key |
獲取key保存值的字符串長度 | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> STRLEN msg (integer) 11 |
GETRANGE key start end |
按起止位置獲取字符串(閉區間,起止位置都取) | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 3 9 “lo worl” |
SETRANGE key offset value |
用指定的value 替換key中 offset開始的值 | 127.0.0.1:6379> SETRANGE msg 2 hello (integer) 7 127.0.0.1:6379> get msg “tehello” |
GETSET key value |
將給定 key 的值設爲 value ,並返回 key 的舊值(old value)。 | 127.0.0.1:6379> GETSET msg test “hello world” |
SETNX key value |
僅當key不存在時進行set | 127.0.0.1:6379> SETNX msg test (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1 |
SETEX key seconds value |
set 鍵值對並設置過期時間 | 127.0.0.1:6379> setex name 10 root OK 127.0.0.1:6379> get name (nil) |
MSET key1 value1 [key2 value2..] |
批量set鍵值對 | 127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 OK |
MSETNX key1 value1 [key2 value2..] |
批量設置鍵值對,僅當參數中所有的key都不存在時執行 | 127.0.0.1:6379> MSETNX k1 v1 k4 v4 (integer) 0 |
MGET key1 [key2..] |
批量獲取多個key保存的值 | 127.0.0.1:6379> MGET k1 k2 k3 1) “v1” 2) “v2” 3) “v3” |
PSETEX key milliseconds value |
和 SETEX 命令相似,但它以毫秒爲單位設置 key 的生存時間, | |
getset key value |
如果不存在值,則返回nil,如果存在值,獲取原來的值,並設置新的值 |
String類似的使用場景:value除了是字符串還可以是數字,用途舉例:
- 計數器
- 統計多單位的數量:uid:123666:follow 0
- 粉絲數
- 對象存儲緩存
List(列表)
Redis列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)
一個列表最多可以包含 232 - 1 個元素 (4294967295, 每個列表超過40億個元素)。
首先我們列表,可以經過規則定義將其變爲隊列、棧、雙端隊列等
正如圖Redis中List是可以進行雙端操作的,所以命令也就分爲了LXXX和RLLL兩類,有時候L也表示List例如LLEN
命令 | 描述 |
---|---|
LPUSH/RPUSH key value1[value2..] |
從左邊/右邊向列表中PUSH值(一個或者多個)。 |
LRANGE key start end |
獲取list 起止元素==(索引從左往右 遞增)== |
LPUSHX/RPUSHX key value |
向已存在的列名中push值(一個或者多個) |
LINSERT key BEFORE|AFTER pivot value |
在指定列表元素的前/後 插入value |
LLEN key |
查看列表長度 |
LINDEX key index |
通過索引獲取列表元素 |
LSET key index value |
通過索引爲元素設值 |
LPOP/RPOP key |
從最左邊/最右邊移除值 並返回 |
RPOPLPUSH source destination |
將列表的尾部(右)最後一個值彈出,並返回,然後加到另一個列表的頭部 |
LTRIM key start end |
通過下標截取指定範圍內的列表 |
LREM key count value |
List中是允許value重複的 count > 0 :從頭部開始搜索 然後刪除指定的value 至多刪除count個 count < 0 :從尾部開始搜索… count = 0 :刪除列表中所有的指定value。 |
BLPOP/BRPOP key1[key2] timout |
移出並獲取列表的第一個/最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素爲止。 |
BRPOPLPUSH source destination timeout |
和RPOPLPUSH 功能相同,如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素爲止。 |
---------------------------LPUSH---RPUSH---LRANGE--------------------------------
127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1} (integer) 1 127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1} (integer) 2 127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3} (integer) 3 127.0.0.1:6379> get mylist # 普通的get是無法獲取list值的 (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 獲取起止位置範圍內的元素 1) "k2" 2) "k1" 3) "k3" 127.0.0.1:6379> LRANGE mylist 0 2 1) "k2" 2) "k1" 3) "k3" 127.0.0.1:6379> LRANGE mylist 0 1 1) "k2" 2) "k1" 127.0.0.1:6379> LRANGE mylist 0 -1 # 獲取全部元素 1) "k2" 2) "k1" 3) "k3" ---------------------------LPUSHX---RPUSHX----------------------------------- 127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失敗 (integer) 0 127.0.0.1:6379> LPUSHX list v1 v2 (integer) 0 127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左邊 PUSH k4 k5 (integer) 5 127.0.0.1:6379> LRANGE mylist 0 -1 1) "k5" 2) "k4" 3) "k2" 4) "k1" 5) "k3" ---------------------------LINSERT--LLEN--LINDEX--LSET---------------------------- 127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素後 插入ins_key1 (integer) 6 127.0.0.1:6379> LRANGE mylist 0 -1 1) "k5" 2) "k4" 3) "k2" 4) "ins_key1" 5) "k1" 6) "k3" 127.0.0.1:6379> LLEN mylist # 查看mylist的長度 (integer) 6 127.0.0.1:6379> LINDEX mylist 3 # 獲取下標爲3的元素 "ins_key1" 127.0.0.1:6379> LINDEX mylist 0 "k5" 127.0.0.1:6379> LSET mylist 3 k6 # 將下標3的元素 set值爲k6 OK 127.0.0.1:6379> LRANGE mylist 0 -1 1) "k5" 2) "k4" 3) "k2" 4) "k6" 5) "k1" 6) "k3" ---------------------------LPOP--RPOP-------------------------- 127.0.0.1:6379> LPOP mylist # 左側(頭部)彈出 "k5" 127.0.0.1:6379> RPOP mylist # 右側(尾部)彈出 "k3" ---------------------------RPOPLPUSH-------------------------- 127.0.0.1:6379> LRANGE mylist 0 -1 1) "k4" 2) "k2" 3) "k6" 4) "k1" 127.0.0.1:6379> RPOPLPUSH mylist newlist # 將mylist的最後一個值(k1)彈出,加入到newlist的頭部 "k1" 127.0.0.1:6379> LRANGE newlist 0 -1 1) "k1" 127.0.0.1:6379> LRANGE mylist 0 -1 1) "k4" 2) "k2" 3) "k6" ---------------------------LTRIM-------------------------- 127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分 OK 127.0.0.1:6379> LRANGE mylist 0 -1 1) "k4" 2) "k2" # 初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2 ---------------------------LREM-------------------------- 127.0.0.1:6379> LREM mylist 3 k2 # 從頭部開始搜索 至多刪除3個 k2 (integer) 3 # 刪除後:mylist: k2,k2,k2,k4,k2,k2,k2,k2 127.0.0.1:6379> LREM mylist -2 k2 #從尾部開始搜索 至多刪除2個 k2 (integer) 2 # 刪除後:mylist: k2,k2,k2,k4,k2,k2 ---------------------------BLPOP--BRPOP-------------------------- mylist: k2,k2,k2,k4,k2,k2 newlist: k1 127.0.0.1:6379> BLPOP newlist mylist 30 # 從newlist中彈出第一個值,mylist作爲候選 1) "newlist" # 彈出 2) "k1" 127.0.0.1:6379> BLPOP newlist mylist 30 1) "mylist" # 由於newlist空了 從mylist中彈出 2) "k2" 127.0.0.1:6379> BLPOP newlist 30 (30.10s) # 超時了 127.0.0.1:6379> BLPOP newlist 30 # 我們連接另一個客戶端向newlist中push了test, 阻塞被解決。 1) "newlist" 2) "test" (12.54s)
小結
-
list實際上是一個鏈表,before Node after , left, right 都可以插入值
-
如果key不存在,則創建新的鏈表
-
如果key存在,新增內容
-
如果移除了所有值,空鏈表,也代表不存在
-
在兩邊插入或者改動值,效率最高!修改中間元素,效率相對較低
應用:
消息排隊!消息隊列(Lpush Rpop),棧(Lpush Lpop)
Set(集合)
Redis的Set是string類型的無序集合。集合成員是唯一的,這就意味着集合中不能出現重複的數據。
Redis 中 集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是O(1)。
集合中最大的成員數爲 232 - 1 (4294967295, 每個集合可存儲40多億個成員)。
命令 | 描述 |
---|---|
SADD key member1[member2..] |
向集合中無序增加一個/多個成員 |
SCARD key |
獲取集合的成員數 |
SMEMBERS key |
返回集合中所有的成員 |
SISMEMBER key member |
查詢member元素是否是集合的成員,結果是無序的 |
SRANDMEMBER key [count] |
隨機返回集合中count個成員,count缺省值爲1 |
SPOP key [count] |
隨機移除並返回集合中count個成員,count缺省值爲1 |
SMOVE source destination member |
將source集合的成員member移動到destination集合 |
SREM key member1[member2..] |
移除集合中一個/多個成員 |
SDIFF key1[key2..] |
返回所有集合的差集 key1- key2 - … |
SDIFFSTORE destination key1[key2..] |
在SDIFF的基礎上,將結果保存到集合中==(覆蓋)==。不能保存到其他類型key噢! |
SINTER key1 [key2..] |
返回所有集合的交集 |
SINTERSTORE destination key1[key2..] |
在SINTER的基礎上,存儲結果到集合中。覆蓋 |
SUNION key1 [key2..] |
返回所有集合的並集 |
SUNIONSTORE destination key1 [key2..] |
在SUNION的基礎上,存儲結果到及和張。覆蓋 |
SSCAN KEY [MATCH pattern] [COUNT count] |
在大量數據環境下,使用此命令遍歷集合中元素,每次遍歷部分 |
---------------SADD--SCARD--SMEMBERS--SISMEMBER-------------------- 127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成員 m1~m4 (integer) 4 127.0.0.1:6379> SCARD myset # 獲取集合的成員數目 (integer) 4 127.0.0.1:6379> smembers myset # 獲取集合中所有成員 1) "m4" 2) "m3" 3) "m2" 4) "m1" 127.0.0.1:6379> SISMEMBER myset m5 # 查詢m5是否是myset的成員 (integer) 0 # 不是,返回0 127.0.0.1:6379> SISMEMBER myset m2 (integer) 1 # 是,返回1 127.0.0.1:6379> SISMEMBER myset m3 (integer) 1 ---------------------SRANDMEMBER--SPOP---------------------------------- 127.0.0.1:6379> SRANDMEMBER myset 3 # 隨機返回3個成員 1) "m2" 2) "m3" 3) "m4" 127.0.0.1:6379> SRANDMEMBER myset # 隨機返回1個成員 "m3" 127.0.0.1:6379> SPOP myset 2 # 隨機移除並返回2個成員 1) "m1" 2) "m4" # 將set還原到{m1,m2,m3,m4} ---------------------SMOVE--SREM---------------------------------------- 127.0.0.1:6379> SMOVE myset newset m3 # 將myset中m3成員移動到newset集合 (integer) 1 127.0.0.1:6379> SMEMBERS myset 1) "m4" 2) "m2" 3) "m1" 127.0.0.1:6379> SMEMBERS newset 1) "m3" 127.0.0.1:6379> SREM newset m3 # 從newset中移除m3元素 (integer) 1 127.0.0.1:6379> SMEMBERS newset (empty list or set) # 下面開始是多集合操作,多集合操作中若只有一個參數默認和自身進行運算 # setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6} -----------------------------SDIFF------------------------------------ 127.0.0.1:6379> SDIFF setx sety setz # 等價於setx-sety-setz 1) "m4" 127.0.0.1:6379> SDIFF setx sety # setx - sety 1) "m4" 2) "m1" 127.0.0.1:6379> SDIFF sety setx # sety - setx 1) "m5" -------------------------SINTER--------------------------------------- # 共同關注(交集) 127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集 1) "m6" 127.0.0.1:6379> SINTER setx sety # 求setx sety的交集 1) "m2" 2) "m6" -------------------------SUNION--------------------------------------- 127.0.0.1:6379> SUNION setx sety setz # setx sety setz的並集 1) "m4" 2) "m6" 3) "m3" 4) "m2" 5) "m1" 6) "m5" 127.0.0.1:6379> SUNION setx sety # setx sety 並集 1) "m4" 2) "m6" 3) "m2" 4) "m1" 5) "m5"
Hash(哈希)
Redis hash 是一個string類型的field和value的映射表,hash特別適合用於存儲對象。
Set就是一種簡化的Hash,只變動key,而value使用默認值填充。可以將一個Hash表作爲一個對象進行存儲,表中存放對象的信息。
命令 | 描述 |
---|---|
HSET key field value |
將哈希表 key 中的字段 field 的值設爲 value 。重複設置同一個field會覆蓋,返回0 |
HMSET key field1 value1 [field2 value2..] |
同時將多個 field-value (域-值)對設置到哈希表 key 中。 |
HSETNX key field value |
只有在字段 field 不存在時,設置哈希表字段的值。 |
HEXISTS key field |
查看哈希表 key 中,指定的字段是否存在。 |
HGET key field value |
獲取存儲在哈希表中指定字段的值 |
HMGET key field1 [field2..] |
獲取所有給定字段的值 |
HGETALL key |
獲取在哈希表key 的所有字段和值 |
HKEYS key |
獲取哈希表key中所有的字段 |
HLEN key |
獲取哈希表中字段的數量 |
HVALS key |
獲取哈希表中所有值 |
HDEL key field1 [field2..] |
刪除哈希表key中一個/多個field字段 |
HINCRBY key field n |
爲哈希表 key 中的指定字段的整數值加上增量n,並返回增量後結果 一樣只適用於整數型字段 |
HINCRBYFLOAT key field n |
爲哈希表 key 中的指定字段的浮點數值加上增量 n。 |
HSCAN key cursor [MATCH pattern] [COUNT count] |
迭代哈希表中的鍵值對。 |
------------------------HSET--HMSET--HSETNX---------------- 127.0.0.1:6379> HSET studentx name sakura # 將studentx哈希表作爲一個對象,設置name爲sakura (integer) 1 127.0.0.1:6379> HSET studentx name gyc # 重複設置field進行覆蓋,並返回0 (integer) 0 127.0.0.1:6379> HSET studentx age 20 # 設置studentx的age爲20 (integer) 1 127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 設置sex爲1,tel爲15623667886 OK 127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 設置已存在的field (integer) 0 # 失敗 127.0.0.1:6379> HSETNX studentx email [email protected] (integer) 1 # 成功 ----------------------HEXISTS-------------------------------- 127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在 (integer) 1 # 存在 127.0.0.1:6379> HEXISTS studentx addr (integer) 0 # 不存在 -------------------HGET--HMGET--HGETALL----------- 127.0.0.1:6379> HGET studentx name # 獲取studentx中name字段的value "gyc" 127.0.0.1:6379> HMGET studentx name age tel # 獲取studentx中name、age、tel字段的value 1) "gyc" 2) "20" 3) "15623667886" 127.0.0.1:6379> HGETALL studentx # 獲取studentx中所有的field及其value 1) "name" 2) "gyc" 3) "age" 4) "20" 5) "sex" 6) "1" 7) "tel" 8) "15623667886" 9) "email" 10) "[email protected]" --------------------HKEYS--HLEN--HVALS-------------- 127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field 1) "name" 2) "age" 3) "sex" 4) "tel" 5) "email" 127.0.0.1:6379> HLEN studentx # 查看studentx中的字段數量 (integer) 5 127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value 1) "gyc" 2) "20" 3) "1" 4) "15623667886" 5) "[email protected]" -------------------------HDEL-------------------------- 127.0.0.1:6379> HDEL studentx sex tel # 刪除studentx 中的sex、tel字段 (integer) 2 127.0.0.1:6379> HKEYS studentx 1) "name" 2) "age" 3) "email" -------------HINCRBY--HINCRBYFLOAT------------------------ 127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段數值+1 (integer) 21 127.0.0.1:6379> HINCRBY studentx name 1 # 非整數字型字段不可用 (error) ERR hash value is not an integer 127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6 "90.8"
Hash變更的數據user name age,尤其是用戶信息之類的,經常變動的信息!Hash更適合於對象的存儲,Sring更加適合字符串存儲!
Zset(有序集合)
不同的是每個元素都會關聯一個double類型的分數(score)。redis正是通過分數來爲集合中的成員進行從小到大的排序。
score相同:按字典順序排序
有序集合的成員是唯一的,但分數(score)卻可以重複。
命令 | 描述 |
---|---|
ZADD key score member1 [score2 member2] |
向有序集合添加一個或多個成員,或者更新已存在成員的分數 |
ZCARD key |
獲取有序集合的成員數 |
ZCOUNT key min max |
計算在有序集合中指定區間score的成員數 |
ZINCRBY key n member |
有序集合中對指定成員的分數加上增量 n |
ZSCORE key member |
返回有序集中,成員的分數值 |
ZRANK key member |
返回有序集合中指定成員的索引 |
ZRANGE key start end |
通過索引區間返回有序集合成指定區間內的成員 |
ZRANGEBYLEX key min max |
通過字典區間返回有序集合的成員 |
ZRANGEBYSCORE key min max |
通過分數返回有序集合指定區間內的成員==-inf 和 +inf分別表示最小最大值,只支持開區間()== |
ZLEXCOUNT key min max |
在有序集合中計算指定字典區間內成員數量 |
ZREM key member1 [member2..] |
移除有序集合中一個/多個成員 |
ZREMRANGEBYLEX key min max |
移除有序集合中給定的字典區間的所有成員 |
ZREMRANGEBYRANK key start stop |
移除有序集合中給定的排名區間的所有成員 |
ZREMRANGEBYSCORE key min max |
移除有序集合中給定的分數區間的所有成員 |
ZREVRANGE key start end |
返回有序集中指定區間內的成員,通過索引,分數從高到底 |
ZREVRANGEBYSCORRE key max min |
返回有序集中指定分數區間內的成員,分數從高到低排序 |
ZREVRANGEBYLEX key max min |
返回有序集中指定字典區間內的成員,按字典順序倒序 |
ZREVRANK key member |
返回有序集合中指定成員的排名,有序集成員按分數值遞減(從大到小)排序 |
ZINTERSTORE destination numkeys key1 [key2 ..] |
計算給定的一個或多個有序集的交集並將結果集存儲在新的有序集合 key 中,numkeys:表示參與運算的集合數,將score相加作爲結果的score |
ZUNIONSTORE destination numkeys key1 [key2..] |
計算給定的一個或多個有序集的交集並將結果集存儲在新的有序集合 key 中 |
ZSCAN key cursor [MATCH pattern\] [COUNT count] |
迭代有序集合中的元素(包括元素成員和元素分值) |
-------------------ZADD--ZCARD--ZCOUNT-------------- 127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中添加成員m1 score=1 以及成員m2 score=2.. (integer) 2 127.0.0.1:6379> ZCARD myzset # 獲取有序集合的成員數 (integer) 2 127.0.0.1:6379> ZCOUNT myzset 0 1 # 獲取score在 [0,1]區間的成員數量 (integer) 1 127.0.0.1:6379> ZCOUNT myzset 0 2 (integer) 2 ----------------ZINCRBY--ZSCORE-------------------------- 127.0.0.1:6379> ZINCRBY myzset 5 m2 # 將成員m2的score +5 "7" 127.0.0.1:6379> ZSCORE myzset m1 # 獲取成員m1的score "1" 127.0.0.1:6379> ZSCORE myzset m2 "7" --------------ZRANK--ZRANGE----------------------------------- 127.0.0.1:6379> ZRANK myzset m1 # 獲取成員m1的索引,索引按照score排序,score相同索引值按字典順序順序增加 (integer) 0 127.0.0.1:6379> ZRANK myzset m2 (integer) 2 127.0.0.1:6379> ZRANGE myzset 0 1 # 獲取索引在 0~1的成員 1) "m1" 2) "m3" 127.0.0.1:6379> ZRANGE myzset 0 -1 # 獲取全部成員 1) "m1" 2) "m3" 3) "m2" #testset=>{abc,add,amaze,apple,back,java,redis} score均爲0 ------------------ZRANGEBYLEX--------------------------------- 127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成員 1) "abc" 2) "add" 3) "amaze" 4) "apple" 5) "back" 6) "java" 7) "redis" 127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分頁 按索引顯示查詢結果的 0,1,2條記錄 1) "abc" 2) "add" 3) "amaze" 127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 顯示 3,4,5條記錄 1) "apple" 2) "back" 3) "java" 127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 顯示 (-,apple] 區間內的成員 1) "abc" 2) "add" 3) "amaze" 4) "apple" 127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 顯示 [apple,java]字典區間的成員 1) "apple" 2) "back" 3) "java" -----------------------ZRANGEBYSCORE--------------------- 127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之間的的成員 1) "m1" 2) "m3" 3) "m2" 127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5 1) "m1" 2) "m3" --------------------ZLEXCOUNT----------------------------- 127.0.0.1:6379> ZLEXCOUNT testset - + (integer) 7 127.0.0.1:6379> ZLEXCOUNT testset [apple [java (integer) 3 ------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE-------------------------------- 127.0.0.1:6379> ZREM testset abc # 移除成員abc (integer) 1 127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典區間[apple,java]中的所有成員 (integer) 3 127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成員 (integer) 2 127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成員 (integer) 2 # testset=> {abc,add,apple,amaze,back,java,redis} score均爲0 # myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)} ----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX----------- 127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score遞減排序,然後按索引,返回結果的 0~3 1) "m9" 2) "m7" 3) "m4" 4) "m3" 127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序結果的 索引的2~4 1) "m4" 2) "m3" 3) "m2" 127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score遞減順序 返回集合中分數在[2,6]之間的成員 1) "m4" 2) "m3" 3) "m2" 127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典區間的成員 1) "java" 2) "back" 3) "apple" 4) "amaze" -------------------------ZREVRANK------------------------------ 127.0.0.1:6379> ZREVRANK myzset m7 # 按score遞減順序,返回成員m7索引 (integer) 1 127.0.0.1:6379> ZREVRANK myzset m2 (integer) 4 # mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小紅、小剛的數學成績 # enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小紅、小剛的英語成績 -------------------ZINTERSTORE--ZUNIONSTORE----------------------------------- 127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 將mathscore enscore進行合併 結果存放到sumscore (integer) 3 127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合併後的score是之前集合中所有score的和 1) "xm" 2) "160" 3) "xg" 4) "177" 5) "xh" 6) "188" 127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取兩個集合的成員score最小值作爲結果的 (integer) 3 127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores 1) "xm" 2) "70" 3) "xg" 4) "87" 5) "xh" 6) "93"
應用案例:
- set排序 存儲班級成績表 工資表排序!
- 普通消息,1.重要消息 2.帶權重進行判斷
- 排行榜應用實現,取Top N測試
四、三種特殊數據類型
Geospatial(地理位置)
使用經緯度定位地理座標並用一個有序集合zset保存,所以zset命令也可以使用
命令 | 描述 |
---|---|
geoadd key longitud(經度) latitude(緯度) member [..] |
將具體經緯度的座標存入一個有序集合 |
geopos key member [member..] |
獲取集合中的一個/多個成員座標 |
geodist key member1 member2 [unit] |
返回兩個給定位置之間的距離。默認以米作爲單位。 |
georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count] |
以給定的經緯度爲中心, 返回集合包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素。 |
GEORADIUSBYMEMBER key member radius... |
功能與GEORADIUS相同,只是中心位置不是具體的經緯度,而是使用結合中已有的成員作爲中心點。 |
geohash key member1 [member2..] |
返回一個或多個位置元素的Geohash表示。使用Geohash位置52點整數編碼。 |
有效經緯度
- 有效的經度從-180度到180度。
- 有效的緯度從-85.05112878度到85.05112878度。
指定單位的參數 unit 必須是以下單位的其中一個:
-
m 表示單位爲米。
-
km 表示單位爲千米。
-
mi 表示單位爲英里。
-
ft 表示單位爲英尺。
關於GEORADIUS的參數
通過
georadius
就可以完成 附近的人功能withcoord:帶上座標
withdist:帶上距離,單位與半徑單位相同
COUNT n : 只顯示前n個(按距離遞增排序)
----------------georadius--------------------- 127.0.0.1:6379> GEORADIUS china:city 120 30 500 km withcoord withdist # 查詢經緯度(120,30)座標500km半徑內的成員 1) 1) "hangzhou" 2) "29.4151" 3) 1) "120.20000249147415" 2) "30.199999888333501" 2) 1) "shanghai" 2) "205.3611" 3) 1) "121.40000134706497" 2) "31.400000253193539" ------------geohash--------------------------- 127.0.0.1:6379> geohash china:city yichang shanghai # 獲取成員經緯座標的geohash表示 1) "wmrjwbr5250" 2) "wtw6ds0y300"
Hyperloglog(基數統計)
Redis HyperLogLog 是用來做基數統計的算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、並且是很小的。
花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基數。
因爲 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。
其底層使用string數據類型
什麼是基數?
數據集中不重複的元素的個數。
應用場景:
網頁的訪問量(UV):一個用戶多次訪問,也只能算作一個人。
傳統實現,存儲用戶的id,然後每次進行比較。當用戶變多之後這種方式及其浪費空間,而我們的目的只是計數,Hyperloglog就能幫助我們利用最小的空間完成。
----------PFADD--PFCOUNT--------------------- 127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素 (integer) 1 127.0.0.1:6379> type myelemx # hyperloglog底層使用String string 127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基數 (integer) 11 127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s (integer) 1 127.0.0.1:6379> PFCOUNT myelemy (integer) 11 ----------------PFMERGE----------------------- 127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合併myelemx和myelemy 成爲myelemz OK 127.0.0.1:6379> PFCOUNT myelemz # 估算基數 (integer) 17
如果允許容錯,那麼一定可以使用Hyperloglog !
如果不允許容錯,就使用set或者自己的數據類型即可 !
算法原理:HyperLogLog 算法的原理講解以及 Redis 是如何應用它的 - 指尖下的幽靈 - 博客園 (cnblogs.com)
BitMaps(位圖)
使用位存儲,信息狀態只有 0 和 1
Bitmap是一串連續的2進制數字(0或1),每一位所在的位置爲偏移(offset),在bitmap上可執行AND,OR,XOR,NOT以及其它位操作。
應用場景
簽到統計、狀態統計
命令 | 描述 |
---|---|
setbit key offset value |
爲指定key的offset位設置值 |
getbit key offset |
獲取offset位的值 |
bitcount key [start end] |
統計字符串被設置爲1的bit數,也可以指定統計範圍按字節 |
bitop operration destkey key[key..] |
對一個或多個保存二進制位的字符串 key 進行位元操作,並將結果保存到 destkey 上。 |
BITPOS key bit [start] [end] |
返回字符串裏面第一個被設置爲1或者0的bit位。start和end只能按字節,不能按位 |
------------setbit--getbit-------------- 127.0.0.1:6379> setbit sign 0 1 # 設置sign的第0位爲 1 (integer) 0 127.0.0.1:6379> setbit sign 2 1 # 設置sign的第2位爲 1 不設置默認 是0 (integer) 0 127.0.0.1:6379> setbit sign 3 1 (integer) 0 127.0.0.1:6379> setbit sign 5 1 (integer) 0 127.0.0.1:6379> type sign string 127.0.0.1:6379> getbit sign 2 # 獲取第2位的數值 (integer) 1 127.0.0.1:6379> getbit sign 3 (integer) 1 127.0.0.1:6379> getbit sign 4 # 未設置默認是0 (integer) 0 -----------bitcount---------------------------- 127.0.0.1:6379> BITCOUNT sign # 統計sign中爲1的位數 (integer) 4
bitmaps的底層
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9PlszjhS-1597890996519)(D:\我\MyBlog\狂神說 Redis.assets\image-20200803234336175.png)]
這樣設置以後你能get到的值是:\xA2\x80,所以bitmaps是一串從左到右的二進制串
那麼如果redis想要實現原子性該怎麼處理
加鎖
watch or setnx
試一下另一個線程修改money但是值不變,看看watch是不是隻是解決了cas但是沒解決aba問題?
答:沒有解決,修改會後仍可以執行事務。
五、事務
Redis的單條命令是保證原子性的,但是redis事務不能保證原子性
Redis事務本質:一組命令的集合。
----------------- 隊列 set set set 執行 -------------------
事務中每條命令都會被序列化,執行過程中按順序執行,不允許其他命令進行干擾。
- 一次性
- 順序性
- 排他性
- Redis事務沒有隔離級別的概念
- Redis單條命令是保證原子性的,但是事務不保證原子性!
Redis事務操作過程
- 開啓事務(
multi
) - 命令入隊
- 執行事務(
exec
)
所以事務中的命令在加入時都沒有被執行,直到提交時纔會開始執行(Exec)一次性完成。
127.0.0.1:6379> multi # 開啓事務 OK 127.0.0.1:6379> set k1 v1 # 命令入隊 QUEUED 127.0.0.1:6379> set k2 v2 # .. QUEUED 127.0.0.1:6379> get k1 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> keys * QUEUED 127.0.0.1:6379> exec # 事務執行 1) OK 2) OK 3) "v1" 4) OK 5) 1) "k3" 2) "k2" 3) "k1"
取消事務(discurd
)
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> DISCARD # 放棄事務 OK 127.0.0.1:6379> EXEC (error) ERR EXEC without MULTI # 當前未開啓事務 127.0.0.1:6379> get k1 # 被放棄事務中命令並未執行 (nil)
事務錯誤
代碼語法錯誤(編譯時異常)所有的命令都不執行
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> error k1 # 這是一條語法錯誤命令 (error) ERR unknown command `error`, with args beginning with: `k1`, # 會報錯但是不影響後續命令入隊 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors. # 執行報錯 127.0.0.1:6379> get k1 (nil) # 其他命令並沒有被執行
代碼邏輯錯誤 (運行時異常) **其他命令可以正常執行 ** >>> 所以不保證事務原子性
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> INCR k1 # 這條命令邏輯錯誤(對字符串進行增量) QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) (error) ERR value is not an integer or out of range # 運行時報錯 4) "v2" # 其他命令正常執行 # 雖然中間有一條命令報錯了,但是後面的指令依舊正常執行成功了。 # 所以說Redis單條指令保證原子性,但是Redis事務不能保證原子性。
監控
悲觀鎖:
- 很悲觀,認爲什麼時候都會出現問題,無論做什麼都會加鎖
樂觀鎖:
- 很樂觀,認爲什麼時候都不會出現問題,所以不會上鎖!更新數據的時候去判斷一下,在此期間是否有人修改過這個數據
- 獲取version
- 更新的時候比較version
使用watch key
監控指定數據,相當於樂觀鎖加鎖。
正常執行
127.0.0.1:6379> set money 100 # 設置餘額:100 OK 127.0.0.1:6379> set use 0 # 支出使用:0 OK 127.0.0.1:6379> watch money # 監視money (上鎖) OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY use 20 QUEUED 127.0.0.1:6379> exec # 監視值沒有被中途修改,事務正常執行 1) (integer) 80 2) (integer) 20
測試多線程修改值,使用watch可以當做redis的樂觀鎖操作(相當於getversion)
我們啓動另外一個客戶端模擬插隊線程。
線程1:
127.0.0.1:6379> watch money # money上鎖 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY use 20 QUEUED 127.0.0.1:6379> # 此時事務並沒有執行
模擬線程插隊,線程2:
127.0.0.1:6379> INCRBY money 500 # 修改了線程一中監視的money
(integer) 600
回到線程1,執行事務:
127.0.0.1:6379> EXEC # 執行之前,另一個線程修改了我們的值,這個時候就會導致事務執行失敗
(nil) # 沒有結果,說明事務執行失敗
127.0.0.1:6379> get money # 線程2 修改生效
"600"
127.0.0.1:6379> get use # 線程1事務執行失敗,數值沒有被修改
"0"
解鎖獲取最新值,然後再加鎖進行事務。
unwatch
進行解鎖。
注意:每次提交執行exec後都會自動釋放鎖,不管是否成功
六、Jedis
使用Java來操作Redis,Jedis是Redis官方推薦使用的Java連接redis的客戶端。
-
導入依賴
<!--導入jredis的包--> <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.70</version> </dependency>
-
編碼測試
-
連接數據庫
-
修改redis的配置文件
vim /usr/local/bin/myconfig/redis.conf
- 1
-
將只綁定本地註釋
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4IRUFJ95-1597890996520)(狂神說 Redis.assets/image-20200813161921480.png)]
-
保護模式改爲 no
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oKjIVapw-1597890996521)(狂神說 Redis.assets/image-20200813161939847.png)]
-
允許後臺運行
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-c2IMvpZL-1597890996522)(狂神說 Redis.assets/image-20200813161954567.png)]
-
-
-
開放端口6379
firewall-cmd --zone=public --add-port=6379/tcp --permanet
- 1
重啓防火牆服務
systemctl restart firewalld.service
- 1
-
阿里雲服務器控制檯配置安全組
-
重啓redis-server
[root@AlibabaECS bin]# redis-server myconfig/redis.conf
- 1
-
操作命令
TestPing.java
public class TestPing { public static void main(String[] args) { Jedis jedis = new Jedis("192.168.xx.xxx", 6379); String response = jedis.ping(); System.out.println(response); // PONG } }
-
斷開連接
-
事務
public class TestTX { public static void main(String[] args) { Jedis jedis = new Jedis("39.99.xxx.xx", 6379); JSONObject jsonObject = new JSONObject(); jsonObject.put("hello", "world"); jsonObject.put("name", "kuangshen"); // 開啓事務 Transaction multi = jedis.multi(); String result = jsonObject.toJSONString(); // jedis.watch(result) try { multi.set("user1", result); multi.set("user2", result); // 執行事務 multi.exec(); }catch (Exception e){ // 放棄事務 multi.discard(); } finally { // 關閉連接 System.out.println(jedis.get("user1")); System.out.println(jedis.get("user2")); jedis.close(); } } }
七、SpringBoot整合
- 導入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
springboot 2.x後 ,原來使用的 Jedis 被 lettuce 替換。
jedis:採用的直連,多個線程操作的話,是不安全的。如果要避免不安全,使用jedis pool連接池!更像BIO模式
lettuce:採用netty,實例可以在多個線程中共享,不存在線程不安全的情況!可以減少線程數據了,更像NIO模式
我們在學習SpringBoot自動配置的原理時,整合一個組件並進行配置一定會有一個自動配置類xxxAutoConfiguration,並且在spring.factories中也一定能找到這個類的完全限定名。Redis也不例外。
那麼就一定還存在一個RedisProperties類
之前我們說SpringBoot2.x後默認使用Lettuce來替換Jedis,現在我們就能來驗證了。
先看Jedis:
@ConditionalOnClass註解中有兩個類是默認不存在的,所以Jedis是無法生效的
然後再看Lettuce:
完美生效。
現在我們回到RedisAutoConfiguratio
只有兩個簡單的Bean
- RedisTemplate
- StringRedisTemplate
當看到xxTemplate時可以對比RestTemplat、SqlSessionTemplate,通過使用這些Template來間接操作組件。那麼這倆也不會例外。分別用於操作Redis和Redis中的String數據類型。
在RedisTemplate上也有一個條件註解,說明我們是可以對其進行定製化的
說完這些,我們需要知道如何編寫配置文件然後連接Redis,就需要閱讀RedisProperties
這是一些基本的配置屬性。
還有一些連接池相關的配置。注意使用時一定使用Lettuce的連接池。
-
編寫配置文件
# 配置redis spring.redis.host=39.99.xxx.xx spring.redis.port=6379
-
使用RedisTemplate
@SpringBootTest class Redis02SpringbootApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { // redisTemplate 操作不同的數據類型,api和我們的指令是一樣的 // opsForValue 操作字符串 類似String // opsForList 操作List 類似List // opsForHah // 除了基本的操作,我們常用的方法都可以直接通過redisTemplate操作,比如事務和基本的CRUD // 獲取連接對象 //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); //connection.flushDb(); //connection.flushAll(); redisTemplate.opsForValue().set("mykey","kuangshen"); System.out.println(redisTemplate.opsForValue().get("mykey")); } }
測試結果
此時我們回到Redis查看數據時候,驚奇發現全是亂碼,可是程序中可以正常輸出:
這時候就關係到存儲對象的序列化問題,在網絡中傳輸的對象也是一樣需要序列化,否者就全是亂碼。
我們轉到看那個默認的RedisTemplate內部什麼樣子:
在最開始就能看到幾個關於序列化的參數。
默認的序列化器是採用JDK序列化器
而默認的RedisTemplate中的所有序列化器都是使用這個序列化器:
後續我們定製RedisTemplate就可以對其進行修改。
RedisSerializer
提供了多種序列化方案:
-
直接調用RedisSerializer的靜態方法來返回序列化器,然後set
-
自己new 相應的實現類,然後set
定製RedisTemplate的模板:
我們創建一個Bean加入容器,就會觸發RedisTemplate上的條件註解使默認的RedisTemplate失效。
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { // 將template 泛型設置爲 <String, Object> RedisTemplate<String, Object> template = new RedisTemplate(); // 連接工廠,不必修改 template.setConnectionFactory(redisConnectionFactory); /* * 序列化設置 */ // key、hash的key 採用 String序列化方式 template.setKeySerializer(RedisSerializer.string()); template.setHashKeySerializer(RedisSerializer.string()); // value、hash的value 採用 Jackson 序列化方式 template.setValueSerializer(RedisSerializer.json()); template.setHashValueSerializer(RedisSerializer.json()); template.afterPropertiesSet(); return template; } }
這樣一來,只要實體類進行了序列化,我們存什麼都不會有亂碼的擔憂了。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oc8kJP08-1597890996523)(狂神說 Redis.assets/image-20200817175638086.png)]
八、自定義Redis工具類
使用RedisTemplate需要頻繁調用.opForxxx
然後才能進行對應的操作,這樣使用起來代碼效率低下,工作中一般不會這樣使用,而是將這些常用的公共API抽取出來封裝成爲一個工具類,然後直接使用工具類來間接操作Redis,不但效率高並且易用。
工具類參考博客:
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html
https://www.cnblogs.com/zhzhlong/p/11434284.html
九、Redis.conf
容量單位不區分大小寫,G和GB有區別
可以使用 include 組合多個配置問題
網絡配置
日誌輸出級別
日誌輸出文件
持久化規則
由於Redis是基於內存的數據庫,需要將數據由內存持久化到文件中
持久化方式:
- RDB
- AOF
RDB文件相關
主從複製
Security模塊中進行密碼設置
客戶端連接相關
maxclients 10000 最大客戶端數量
maxmemory <bytes> 最大內存限制
maxmemory-policy noeviction # 內存達到限制值的處理策略
- 1
- 2
- 3
redis 中的默認的過期策略是 volatile-lru 。
設置方式
config set maxmemory-policy volatile-lru
- 1
maxmemory-policy 六種方式
**1、volatile-lru:**只對設置了過期時間的key進行LRU(默認值)
2、allkeys-lru : 刪除lru算法的key
**3、volatile-random:**隨機刪除即將過期key
**4、allkeys-random:**隨機刪除
5、volatile-ttl : 刪除即將過期的
6、noeviction : 永不過期,返回錯誤
AOF相關部分
十、持久化—RDB
RDB:Redis Databases
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-C0mm1D4A-1597890996524)(狂神說 Redis.assets/image-20200818122236614.png)]
什麼是RDB
在指定時間間隔後,將內存中的數據集快照寫入數據庫 ;在恢復時候,直接讀取快照文件,進行數據的恢復 ;
默認情況下, Redis 將數據庫快照保存在名字爲 dump.rdb的二進制文件中。文件名可以在配置文件中進行自定義。
工作原理
在進行 RDB
的時候,redis
的主線程是不會做 io
操作的,主線程會 fork
一個子線程來完成該操作;
- Redis 調用forks。同時擁有父進程和子進程。
- 子進程將數據集寫入到一個臨時 RDB 文件中。
- 當子進程完成對新 RDB 文件的寫入時,Redis 用新 RDB 文件替換原來的 RDB 文件,並刪除舊的 RDB 文件。
這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益(因爲是使用子進程進行寫操作,而父進程依然可以接收來自客戶端的請求。)
觸發機制
- save的規則滿足的情況下,會自動觸發rdb原則
- 執行flushall命令,也會觸發我們的rdb原則
- 退出redis,也會自動產生rdb文件
save
使用 save
命令,會立刻對當前內存中的數據進行持久化 ,但是會阻塞,也就是不接受其他操作了;
由於
save
命令是同步命令,會佔用Redis的主進程。若Redis數據非常多時,save
命令執行速度會非常慢,阻塞所有客戶端的請求。
flushall命令
flushall
命令也會觸發持久化 ;
觸發持久化規則
滿足配置條件中的觸發條件 ;
可以通過配置文件對 Redis 進行設置, 讓它在“ N 秒內數據集至少有 M 個改動”這一條件被滿足時, 自動進行數據集保存操作。
bgsave
bgsave
是異步進行,進行持久化的時候,redis
還可以將繼續響應客戶端請求 ;
bgsave和save對比
命令 | save | bgsave |
---|---|---|
IO類型 | 同步 | 異步 |
阻塞? | 是 | 是(阻塞發生在fock(),通常非常快) |
複雜度 | O(n) | O(n) |
優點 | 不會消耗額外的內存 | 不阻塞客戶端命令 |
缺點 | 阻塞客戶端命令 | 需要fock子進程,消耗內存 |
優缺點
優點:
- 適合大規模的數據恢復
- 對數據的完整性要求不高
缺點:
- 需要一定的時間間隔進行操作,如果redis意外宕機了,這個最後一次修改的數據就沒有了。
- fork進程的時候,會佔用一定的內容空間。
十一、持久化AOF
Append Only File
將我們所有的命令都記錄下來,history,恢復的時候就把這個文件全部再執行一遍
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Z8wr9lBW-1597890996525)(狂神說 Redis.assets/image-20200818123711375.png)]
以日誌的形式來記錄每個寫的操作,將Redis執行過的所有指令記錄下來(讀操作不記錄),只許追加文件但不可以改寫文件,redis啓動之初會讀取該文件重新構建數據,換言之,redis重啓的話就根據日誌文件的內容將寫指令從前到後執行一次以完成數據的恢復工作。
什麼是AOF
快照功能(RDB)並不是非常耐久(durable): 如果 Redis 因爲某些原因而造成故障停機, 那麼服務器將丟失最近寫入、以及未保存到快照中的那些數據。 從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方式: AOF 持久化。
如果要使用AOF,需要修改配置文件:
appendonly no yes
則表示啓用AOF
默認是不開啓的,我們需要手動配置,然後重啓redis,就可以生效了!
如果這個aof文件有錯位,這時候redis是啓動不起來的,我需要修改這個aof文件
redis給我們提供了一個工具redis-check-aof --fix
優點和缺點
appendonly yes # 默認是不開啓aof模式的,默認是使用rdb方式持久化的,在大部分的情況下,rdb完全夠用
appendfilename "appendonly.aof"
# appendfsync always # 每次修改都會sync 消耗性能
appendfsync everysec # 每秒執行一次 sync 可能會丟失這一秒的數據
# appendfsync no # 不執行 sync ,這時候操作系統自己同步數據,速度最快
優點
- 每一次修改都會同步,文件的完整性會更加好
- 沒秒同步一次,可能會丟失一秒的數據
- 從不同步,效率最高
缺點
- 相對於數據文件來說,aof遠遠大於rdb,修復速度比rdb慢!
- Aof運行效率也要比rdb慢,所以我們redis默認的配置就是rdb持久化
-
-
十二、RDB和AOP選擇
RDB 和 AOF 對比
RDB | AOF | |
---|---|---|
啓動優先級 | 低 | 高 |
體積 | 小 | 大 |
恢復速度 | 快 | 慢 |
數據安全性 | 丟數據 | 根據策略決定 |
如何選擇使用哪種持久化方式?
一般來說, 如果想達到足以媲美 PostgreSQL 的數據安全性, 你應該同時使用兩種持久化功能。
如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失, 那麼你可以只使用 RDB 持久化。
有很多用戶都只使用 AOF 持久化, 但並不推薦這種方式: 因爲定時生成 RDB 快照(snapshot)非常便於進行數據庫備份, 並且 RDB 恢復數據集的速度也要比 AOF 恢復的速度要快。
十三、Redis發佈與訂閱
Redis 發佈訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IBT2pjCa-1597890996526)(狂神說 Redis.assets/image-20200818162849693.png)]
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關係:
當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被髮送給訂閱它的三個客戶端:
命令
命令 | 描述 |
---|---|
PSUBSCRIBE pattern [pattern..] |
訂閱一個或多個符合給定模式的頻道。 |
PUNSUBSCRIBE pattern [pattern..] |
退訂一個或多個符合給定模式的頻道。 |
PUBSUB subcommand [argument[argument]] |
查看訂閱與發佈系統狀態。 |
PUBLISH channel message |
向指定頻道發佈消息 |
SUBSCRIBE channel [channel..] |
訂閱給定的一個或多個頻道。 |
SUBSCRIBE channel [channel..] |
退訂一個或多個頻道 |
示例
------------訂閱端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 訂閱sakura頻道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 訂閱成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到來自sakura頻道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到來自sakura頻道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"
--------------消息發佈端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 發佈消息到sakura頻道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 發佈消息
(integer) 1
-----------------查看活躍的頻道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"
原理
每個 Redis 服務器進程都維持着一個表示服務器狀態的 redis.h/redisServer 結構, 結構的 pubsub_channels 屬性是一個字典, 這個字典就用於保存訂閱頻道的信息,其中,字典的鍵爲正在被訂閱的頻道, 而字典的值則是一個鏈表, 鏈表中保存了所有訂閱這個頻道的客戶端。
客戶端訂閱,就被鏈接到對應頻道的鏈表的尾部,退訂則就是將客戶端節點從鏈表中移除。
缺點
- 如果一個客戶端訂閱了頻道,但自己讀取消息的速度卻不夠快的話,那麼不斷積壓的消息會使redis輸出緩衝區的體積變得越來越大,這可能使得redis本身的速度變慢,甚至直接崩潰。
- 這和數據傳輸可靠性有關,如果在訂閱方斷線,那麼他將會丟失所有在短線期間發佈者發佈的消息。
應用
- 消息訂閱:公衆號訂閱,微博關注等等(起始更多是使用消息隊列來進行實現)
- 多人在線聊天室。
稍微複雜的場景,我們就會使用消息中間件MQ處理。
十四、Redis主從複製
概念
主從複製,是指將一臺Redis服務器的數據,複製到其他的Redis服務器。前者稱爲主節點(Master/Leader),後者稱爲從節點(Slave/Follower), 數據的複製是單向的!只能由主節點複製到從節點(主節點以寫爲主、從節點以讀爲主)。
默認情況下,每臺Redis服務器都是主節點,一個主節點可以有0個或者多個從節點,但每個從節點只能由一個主節點。
作用
- 數據冗餘:主從複製實現了數據的熱備份,是持久化之外的一種數據冗餘的方式。
- 故障恢復:當主節點故障時,從節點可以暫時替代主節點提供服務,是一種服務冗餘的方式
- 負載均衡:在主從複製的基礎上,配合讀寫分離,由主節點進行寫操作,從節點進行讀操作,分擔服務器的負載;尤其是在多讀少寫的場景下,通過多個從節點分擔負載,提高併發量。
- 高可用基石:主從複製還是哨兵和集羣能夠實施的基礎。
爲什麼使用集羣
- 單臺服務器難以負載大量的請求
- 單臺服務器故障率高,系統崩壞概率大
- 單臺服務器內存容量有限。
環境配置
我們在講解配置文件的時候,注意到有一個replication
模塊 (見Redis.conf中第8條)
查看當前庫的信息:info replication
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 從機數量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
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
既然需要啓動多個服務,就需要多個配置文件。每個配置文件對應修改以下信息:
- 端口號
- pid文件名
- 日誌文件名
- rdb文件名
啓動單機多服務集羣:
一主二從配置
==默認情況下,每臺Redis服務器都是主節點;==我們一般情況下只用配置從機就好了!
認老大!一主(79)二從(80,81)
使用SLAVEOF host port
就可以爲從機配置主機了。
然後主機上也能看到從機的狀態:
我們這裏是使用命令搭建,是暫時的,==真實開發中應該在從機的配置文件中進行配置,==這樣的話是永久的。
使用規則
-
從機只能讀,不能寫,主機可讀可寫但是多用於寫。
127.0.0.1:6381> set name sakura # 從機6381寫入失敗 (error) READONLY You can't write against a read only replica. 127.0.0.1:6380> set name sakura # 從機6380寫入失敗 (error) READONLY You can't write against a read only replica. 127.0.0.1:6379> set name sakura OK 127.0.0.1:6379> get name "sakura"
當主機斷電宕機後,默認情況下從機的角色不會發生變化 ,集羣中只是失去了寫操作,當主機恢復以後,又會連接上從機恢復原狀。
-
當從機斷電宕機後,若不是使用配置文件配置的從機,再次啓動後作爲主機是無法獲取之前主機的數據的,若此時重新配置稱爲從機,又可以獲取到主機的所有數據。這裏就要提到一個同步原理。
-
第二條中提到,默認情況下,主機故障後,不會出現新的主機,有兩種方式可以產生新的主機:
- 從機手動執行命令
slaveof no one
,這樣執行以後從機會獨立出來成爲一個主機 - 使用哨兵模式(自動選舉)
- 從機手動執行命令
如果沒有老大了,這個時候能不能選擇出來一個老大呢?手動!
如果主機斷開了連接,我們可以使用SLAVEOF no one
讓自己變成主機!其他的節點就可以手動連接到最新的主節點(手動)!如果這個時候老大修復了,那麼久重新連接!
十五、哨兵模式
更多信息參考博客:https://www.jianshu.com/p/06ab9daf921d
**主從切換技術的方法是:當主服務器宕機後,需要手動把一臺從服務器切換爲主服務器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。*這不是一種推薦的方式,更多時候,我們優先考慮*哨兵模式。
單機單個哨兵
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2ENYVAPp-1597890996527)(狂神說 Redis.assets/image-20200818233231154.png)]
哨兵的作用:
- 通過發送命令,讓Redis服務器返回監控其運行狀態,包括主服務器和從服務器。
- 當哨兵監測到master宕機,會自動將slave切換成master,然後通過發佈訂閱模式通知其他的從服務器,修改配置文件,讓它們切換主機。
多哨兵模式
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Ga1RyfVc-1597890996528)(狂神說 Redis.assets/image-20200818233316478.png)]
哨兵的核心配置
sentinel monitor mymaster 127.0.0.1 6379 1
- 數字1表示 :當一個哨兵主觀認爲主機斷開,就可以客觀認爲主機故障,然後開始選舉新的主機。(最低通過的票數)
測試
redis-sentinel xxx/sentinel.conf
成功啓動哨兵模式
此時哨兵監視着我們的主機6379,當我們斷開主機後:
哨兵模式優缺點
優點:
- 哨兵集羣,基於主從複製模式,所有主從複製的優點,它都有
- 主從可以切換,故障可以轉移,系統的可用性更好
- 哨兵模式是主從模式的升級,手動到自動,更加健壯
缺點:
- Redis不好在線擴容,集羣容量一旦達到上限,在線擴容就十分麻煩
- 實現哨兵模式的配置其實是很麻煩的,裏面有很多配置項
哨兵模式的全部配置
完整的哨兵模式配置文件 sentinel.conf
# Example sentinel.conf # 哨兵sentinel實例運行的端口 默認26379 port 26379 # 哨兵sentinel的工作目錄 dir /tmp # 哨兵sentinel監控的redis主節點的 ip port # master-name 可以自己命名的主節點名字 只能由字母A-z、數字0-9 、這三個字符".-_"組成。 # quorum 當這些quorum個數sentinel哨兵認爲master主節點失聯 那麼這時 客觀上認爲主節點失聯了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 1 # 當在Redis實例中開啓了requirepass foobared 授權密碼 這樣所有連接Redis實例的客戶端都要提供密碼 # 設置哨兵sentinel 連接主從的密碼 注意必須爲主從設置一樣的驗證密碼 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定多少毫秒之後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認爲主節點下線 默認30秒 # sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 30000 # 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步, 這個數字越小,完成failover所需的時間就越長, 但是如果這個數字越大,就意味着越 多的slave因爲replication而不可用。 可以通過將這個值設爲 1 來保證每次只有一個slave 處於不能處理命令請求的狀態。 # sentinel parallel-syncs <master-name> <numslaves> sentinel parallel-syncs mymaster 1 # 故障轉移的超時時間 failover-timeout 可以用在以下這些方面: #1. 同一個sentinel對同一個master兩次failover之間的間隔時間。 #2. 當一個slave從一個錯誤的master那裏同步數據開始計算時間。直到slave被糾正爲向正確的master那裏同步數據時。 #3.當想要取消一個正在進行的failover所需要的時間。 #4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置爲指向master,但是就不按parallel-syncs所配置的規則來了 # 默認三分鐘 # sentinel failover-timeout <master-name> <milliseconds> sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION #配置當某一事件發生時所需要執行的腳本,可以通過腳本來通知管理員,例如當系統運行不正常時發郵件通知相關人員。 #對於腳本的運行結果有以下規則: #若腳本執行後返回1,那麼該腳本稍後將會被再次執行,重複次數目前默認爲10 #若腳本執行後返回2,或者比2更高的一個返回值,腳本將不會重複執行。 #如果腳本在執行過程中由於收到系統中斷信號被終止了,則同返回值爲1時的行爲相同。 #一個腳本的最大執行時間爲60s,如果超過這個時間,腳本將會被一個SIGKILL信號終止,之後重新執行。 #通知型腳本:當sentinel有任何警告級別的事件發生時(比如說redis實例的主觀失效和客觀失效等等),將會去調用這個腳本, #這時這個腳本應該通過郵件,SMS等方式去通知系統管理員關於系統不正常運行的信息。調用該腳本時,將傳給腳本兩個參數, #一個是事件的類型, #一個是事件的描述。 #如果sentinel.conf配置文件中配置了這個腳本路徑,那麼必須保證這個腳本存在於這個路徑,並且是可執行的,否則sentinel無法正常啓動成功。 #通知腳本 # sentinel notification-script <master-name> <script-path> sentinel notification-script mymaster /var/redis/notify.sh # 客戶端重新配置主節點參數腳本 # 當一個master由於failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關於master地址已經發生改變的信息。 # 以下參數將會在調用腳本時傳給腳本: # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> # 目前<state>總是“failover”, # <role>是“leader”或者“observer”中的一個。 # 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的 # 這個腳本應該是通用的,能被多次調用,不是針對性的。 # sentinel client-reconfig-script <master-name> <script-path> sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis哨兵模式詳解
十六、緩存穿透與雪崩
緩存穿透(查不到)
概念
在默認情況下,用戶請求數據時,會先在緩存(Redis)中查找,若沒找到即緩存未命中,再在數據庫中進行查找,數量少可能問題不大,可是一旦大量的請求數據(例如秒殺場景)緩存都沒有命中的話,就會全部轉移到數據庫上,造成數據庫極大的壓力,就有可能導致數據庫崩潰。網絡安全中也有人惡意使用這種手段進行攻擊被稱爲洪水攻擊。
解決方案
布隆過濾器
對所有可能查詢的參數以Hash的形式存儲,以便快速確定是否存在這個值,在控制層先進行攔截校驗,校驗不通過直接打回,減輕了存儲系統的壓力。
緩存空對象
一次請求若在緩存和數據庫中都沒找到,就在緩存中方一個空對象用於處理後續這個請求。
這樣做有一個缺陷:存儲空對象也需要空間,大量的空對象會耗費一定的空間,存儲效率並不高。解決這個缺陷的方式就是設置較短過期時間
即使對空值設置了過期時間,還是會存在緩存層和存儲層的數據會有一段時間窗口的不一致,這對於需要保持一致性的業務會有影響。
緩存擊穿(量太大,緩存過期)
概念
相較於緩存穿透,緩存擊穿的目的性更強,一個存在的key,在緩存過期的一刻,同時有大量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量大、壓力驟增。這就是緩存被擊穿,只是針對其中某個key的緩存不可用而導致擊穿,但是其他的key依然可以使用緩存響應。
比如熱搜排行上,一個熱點新聞被同時大量訪問就可能導致緩存擊穿。
解決方案
-
設置熱點數據永不過期
這樣就不會出現熱點數據過期的情況,但是當Redis內存空間滿的時候也會清理部分數據,而且此種方案會佔用空間,一旦熱點數據多了起來,就會佔用部分空間。
-
加互斥鎖(分佈式鎖)
在訪問key之前,採用SETNX(set if not exists)來設置另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key。保證同時刻只有一個線程訪問。這樣對鎖的要求就十分高。
緩存雪崩
概念
大量的key設置了相同的過期時間,導致在緩存在同一時刻全部失效,造成瞬時DB請求量大、壓力驟增,引起雪崩。
解決方案
-
redis高可用
這個思想的含義是,既然redis有可能掛掉,那我多增設幾臺redis,這樣一臺掛掉之後其他的還可以繼續工作,其實就是搭建的集羣
-
限流降級
這個解決方案的思想是,在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。
-
數據預熱
數據加熱的含義就是在正式部署之前,我先把可能的數據先預先訪問一遍,這樣部分可能大量訪問的數據就會加載到緩存中。在即將發生大併發訪問前手動觸發加載緩存不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。