1. 程式人生 > 程式設計 >五分鐘帶你瞭解Java的介面資料校驗

五分鐘帶你瞭解Java的介面資料校驗

–更加詳細筆記參見百度雲(狂神筆記/redis.pdf)–

一、Nosql概述

為什麼使用Nosql

1、單機Mysql時代

在這裡插入圖片描述

90年代,一個網站的訪問量一般不會太大,單個數據庫完全夠用。隨著使用者增多,網站出現以下問題

  1. 資料量增加到一定程度,單機資料庫就放不下了
  2. 資料的索引(B+ Tree),一個機器記憶體也存放不下
  3. 訪問量變大後(讀寫混合),一臺伺服器承受不住。

2、Memcached(快取) + Mysql + 垂直拆分(讀寫分離)

網站80%的情況都是在讀,每次都要去查詢資料庫的話就十分的麻煩!所以說我們希望減輕資料庫的壓力,我們可以使用快取來保證效率!

在這裡插入圖片描述

優化過程經歷了以下幾個過程:

  1. 優化資料庫的資料結構和索引(難度大)
  2. 檔案快取,通過IO流獲取比每次都訪問資料庫效率略高,但是流量爆炸式增長時候,IO流也承受不了
  3. 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特點

  1. 方便擴充套件(資料之間沒有關係,很好擴充套件!)

  2. 大資料量高效能(Redis一秒可以寫8萬次,讀11萬次,NoSQL的快取記錄級,是一種細粒度的快取,效能會比較高!)

  3. 資料型別是多樣型的!(不需要事先設計資料庫,隨取隨用)

  4. 傳統的 RDBMS 和 NoSQL

    傳統的 RDBMS(關係型資料庫)
    - 結構化組織
    - SQL
    - 資料和關係都存在單獨的表中 row col
    - 操作,資料定義語言
    - 嚴格的一致性
    - 基礎的事務
    - ...
    12345678
    
    Nosql
    - 不僅僅是資料
    - 沒有固定的查詢語言
    - 鍵值對儲存,列儲存,文件儲存,圖形資料庫(社交關係)
    - 最終一致性
    - CAP定理和BASE
    - 高效能,高可用,高擴充套件
    - ...
    12345678
    

瞭解:3V + 3高

大資料時代的3V :主要是描述問題

  1. 海量Velume
  2. 多樣Variety
  3. 實時Velocity

大資料時代的3高 : 主要是對程式的要求

  1. 高併發
  2. 高可擴
  3. 高效能

真正在公司中的實踐:NoSQL + RDBMS 一起使用才是最強的。

阿里巴巴演進分析

推薦閱讀:阿里雲的這群瘋子https://yq.aliyun.com/articles/653511

1

在這裡插入圖片描述

# 商品資訊
- 一般存放在關係型資料庫:Mysql,阿里巴巴使用的Mysql都是經過內部改動的。

# 商品描述、評論(文字居多)
- 文件型資料庫:MongoDB

# 圖片
- 分散式檔案系統 FastDFS
- 淘寶:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里雲: oss

# 商品關鍵字 用於搜尋
- 搜尋引擎:solr,elasticsearch
- 阿里:Isearch 多隆

# 商品熱門的波段資訊
- 記憶體資料庫:Redis,Memcache

# 商品交易,外部支付介面
- 第三方應用
12345678910111213141516171819202122

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, MongoDbWeb應用(與Key-Value類似,Value是結構化的,不同的是資料庫能夠了解Value的內容)Key-Value對應的鍵值對,Value為結構化資料資料結構要求不嚴格,表結構可變,不需要像關係型資料庫一樣需要預先定義表結構查詢效能不高,而且缺乏統一的查詢語法。
圖形(Graph)資料庫Neo4J, InfoGrid, Infinite Graph社交網路,推薦系統等。專注於構建關係圖譜圖結構利用圖結構相關演算法。比如最短路徑定址,N度關係查詢等很多時候需要對整個圖做計算才能得出需要的資訊,而且這種結構不太好做分散式的叢集

二、Redis入門

概述

Redis是什麼?

Redis(Remote Dictionary Server ),即遠端字典服務。

是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。

與memcached一樣,為了保證效率,資料都是快取在記憶體中。區別的是redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。

Redis能該幹什麼?

  1. 記憶體儲存、持久化,記憶體是斷電即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用於高速緩衝
  3. 釋出訂閱系統
  4. 地圖資訊分析
  5. 計時器、計數器(eg:瀏覽量)
  6. 。。。

特性

  1. 多樣的資料型別

  2. 持久化

  3. 叢集

  4. 事務

環境搭建

官網:https://redis.io/

推薦使用Linux伺服器學習。

windows版本的Redis已經停更很久了…

Windows安裝

https://github.com/dmajkic/redis

  1. 解壓安裝包
    在這裡插入圖片描述
  2. 開啟redis-server.exe
  3. 啟動redis-cli.exe測試在這裡插入圖片描述

Linux安裝

  1. 下載安裝包!redis-5.0.8.tar.gz

  2. 解壓Redis的安裝包!程式一般放在 /opt 目錄下

    在這裡插入圖片描述

  3. 基本環境安裝

    yum install gcc-c++
    # 然後進入redis目錄下執行
    make
    # 然後執行
    make install
    12345
    

在這裡插入圖片描述

  1. redis預設安裝路徑 /usr/local/bin在這裡插入圖片描述

  2. 將redis的配置檔案複製到 程式安裝目錄 /usr/local/bin/kconfig

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-hxvGQ47d-1597890996509)(狂神說 Redis.assets/image-20200813114000868.png)]

  3. redis預設不是後臺啟動的,需要修改配置檔案!

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-dDdKTUgd-1597890996510)(狂神說 Redis.assets/image-20200813114019063.png)]

  4. 通過制定的配置檔案啟動redis服務

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-jOypL57Z-1597890996511)(狂神說 Redis.assets/image-20200813114030597.png)]

  5. 使用redis-cli連線指定的埠號測試,Redis的預設埠6379

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-LnDaISQ4-1597890996512)(狂神說 Redis.assets/image-20200813114045299.png)]

  6. 檢視redis程序是否開啟

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9PhN1jC1-1597890996513)(狂神說 Redis.assets/image-20200813114103769.png)]

  7. 關閉Redis服務 shutdown

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Y54EuOYm-1597890996514)(狂神說 Redis.assets/image-20200813114116691.png)]

  8. 再次檢視程序是否存在

  9. 後面我們會使用單機多Redis啟動叢集測試

測試效能

**redis-benchmark:**Redis官方提供的效能測試工具,引數選項如下:

img

簡單測試:

# 測試:100個併發連線 100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
12
123

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-plMshjFg-1597890996515)(狂神說 Redis.assets/image-20200813114143365.png)]

基礎知識

redis預設有16個數據庫

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-v2S3n3Si-1597890996516)(狂神說 Redis.assets/image-20200813114158322.png)]

預設使用的第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
12345678910111213141516171819202122232425262728

keys * :檢視當前資料庫中所有的key。

flushdb:清空當前資料庫中的鍵值對。

flushall:清空所有資料庫的鍵值對。

Redis是單執行緒的,Redis是基於記憶體操作的。

所以Redis的效能瓶頸不是CPU,而是機器記憶體和網路頻寬。

那麼為什麼Redis的速度如此快呢,效能這麼高呢?QPS達到10W+

Redis為什麼單執行緒還這麼快?

  • 誤區1:高效能的伺服器一定是多執行緒的?
  • 誤區2:多執行緒(CPU上下文會切換!)一定比單執行緒效率高!

核心:Redis是將所有的資料放在記憶體中的,所以說使用單執行緒去操作效率就是最高的,多執行緒(CPU上下文會切換:耗時的操作!),對於記憶體系統來說,如果沒有上下文切換效率就是最高的,多次讀寫都是在一個CPU上的,在記憶體儲存資料情況下,單執行緒就是最佳的方案。

三、五大資料型別

Redis是一個開源(BSD許可),記憶體儲存的資料結構伺服器,可用作資料庫快取記憶體訊息佇列代理。它支援、、、、,,等資料型別。內建複製、、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
1234567891011121314151617181920212223242526272829303132333435363738394041424344

關於TTL命令

Redis的key,通過TTL命令返回key的過期時間,一般來說有3種:

  1. 當前key沒有設定過期時間,所以會返回-1.
  2. 當前key有設定過期時間,而且key已經過期,所以會返回-2.
  3. 當前key有設定過期時間,且key還沒有過期,故會返回key的正常剩餘時間.

關於重新命名RENAMERENAMENX

  • 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不存在時進行set127.0.0.1:6379> SETNX msg test (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1
SETEX key seconds valueset 鍵值對並設定過期時間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億個元素)。

首先我們列表,可以經過規則定義將其變為佇列、棧、雙端佇列等

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-VPvbIltc-1597890996518)(狂神說 Redis.assets/image-20200813114255459.png)]

正如圖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 valueList中是允許value重複的 count > 0:從頭部開始搜尋 然後刪除指定的value 至多刪除count個 count < 0:從尾部開始搜尋… count = 0:刪除列表中所有的指定value。
BLPOP/BRPOP key1[key2] timout移出並獲取列表的第一個/最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。
BRPOPLPUSH source destination timeoutRPOPLPUSH功能相同,如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。
---------------------------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)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129

小結

  • 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"
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384

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"
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071

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"
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136

應用案例:

  • 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"
123456789101112131415

Hyperloglog(基數統計)

Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、並且是很小的。

花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基數。

因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。

其底層使用string資料型別

什麼是基數?

資料集中不重複的元素的個數。

應用場景:

網頁的訪問量(UV):一個使用者多次訪問,也只能算作一個人。

傳統實現,儲存使用者的id,然後每次進行比較。當用戶變多之後這種方式及其浪費空間,而我們的目的只是計數,Hyperloglog就能幫助我們利用最小的空間完成。

命令描述
PFADD key element1 [elememt2..]新增指定元素到 HyperLogLog 中
PFCOUNT key [key]返回給定 HyperLogLog 的基數估算值。
PFMERGE destkey sourcekey [sourcekey..]將多個 HyperLogLog 合併為一個 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
1234567891011121314151617

如果允許容錯,那麼一定可以使用Hyperloglog !

如果不允許容錯,就使用set或者自己的資料型別即可 !

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
12345678910111213141516171819202122

bitmaps的底層

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9PlszjhS-1597890996519)(D:\我\MyBlog\狂神說 Redis.assets\image-20200803234336175.png)]

這樣設定以後你能get到的值是:\xA2\x80,所以bitmaps是一串從左到右的二進位制串

五、事務

Redis的單條命令是保證原子性的,但是redis事務不能保證原子性

Redis事務本質:一組命令的集合。

----------------- 佇列 set set set 執行 -------------------

事務中每條命令都會被序列化,執行過程中按順序執行,不允許其他命令進行干擾。

  • 一次性
  • 順序性
  • 排他性

  1. Redis事務沒有隔離級別的概念
  2. 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"
1234567891011121314151617181920

取消事務(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)
123456789101112

事務錯誤

程式碼語法錯誤(編譯時異常)所有的命令都不執行

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) # 其他命令並沒有被執行
1234567891011121314

程式碼邏輯錯誤 (執行時異常) **其他命令可以正常執行 ** >>> 所以不保證事務原子性

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事務不能保證原子性。
123456789101112131415161718

監控

悲觀鎖:

  • 很悲觀,認為什麼時候都會出現問題,無論做什麼都會加鎖

樂觀鎖:

  • 很樂觀,認為什麼時候都不會出現問題,所以不會上鎖!更新資料的時候去判斷一下,在此期間是否有人修改過這個資料
  • 獲取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
123456789101112131415

測試多執行緒修改值,使用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> 	# 此時事務並沒有執行
123456789

模擬執行緒插隊,執行緒2:

127.0.0.1:6379> INCRBY money 500 # 修改了執行緒一中監視的money
(integer) 600
12

回到執行緒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"
1234567

解鎖獲取最新值,然後再加鎖進行事務。

unwatch進行解鎖。

注意:每次提交執行exec後都會自動釋放鎖,不管是否成功

六、Jedis

使用Java來操作Redis,Jedis是Redis官方推薦使用的Java連線redis的客戶端。

  1. 匯入依賴

    <!--匯入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>
    123456789101112
    
  2. 編碼測試

    • 連線資料庫

      1. 修改redis的配置檔案

        vim /usr/local/bin/myconfig/redis.conf
        1
        
        1. 將只繫結本地註釋

          [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-4IRUFJ95-1597890996520)(狂神說 Redis.assets/image-20200813161921480.png)]

        2. 保護模式改為 no

          [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oKjIVapw-1597890996521)(狂神說 Redis.assets/image-20200813161939847.png)]

        3. 允許後臺執行

          [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-c2IMvpZL-1597890996522)(狂神說 Redis.assets/image-20200813161954567.png)]

  3. 開放埠6379

    firewall-cmd --zone=public --add-port=6379/tcp --permanet
    
    12
    

    重啟防火牆服務

    systemctl restart firewalld.service
    
    12
    
    1. 阿里雲伺服器控制檯配置安全組

    2. 重啟redis-server

      [root@AlibabaECS bin]# redis-server myconfig/redis.conf 
      
      12
      
  • 操作命令

    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
        }
    }
    1234567
    
  • 斷開連線

  1. 事務

    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();
            }
        }
    }
    123456789101112131415161718192021222324252627
    

七、SpringBoot整合

  1. 匯入依賴
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1234

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

img

只有兩個簡單的Bean

  • RedisTemplate
  • StringRedisTemplate

當看到xxTemplate時可以對比RestTemplat、SqlSessionTemplate,通過使用這些Template來間接操作元件。那麼這倆也不會例外。分別用於操作Redis和Redis中的String資料型別。

在RedisTemplate上也有一個條件註解,說明我們是可以對其進行定製化的

說完這些,我們需要知道如何編寫配置檔案然後連線Redis,就需要閱讀RedisProperties

在這裡插入圖片描述

這是一些基本的配置屬性。

在這裡插入圖片描述

還有一些連線池相關的配置。注意使用時一定使用Lettuce的連線池。

在這裡插入圖片描述

  1. 編寫配置檔案

    # 配置redis
    spring.redis.host=39.99.xxx.xx
    spring.redis.port=6379
    123
    1234
    
  2. 使用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"));
        }
    }
    12345678910111213141516171819202122232425
    
  3. 測試結果

    此時我們回到Redis檢視資料時候,驚奇發現全是亂碼,可是程式中可以正常輸出:

    在這裡插入圖片描述

    這時候就關係到儲存物件的序列化問題,在網路中傳輸的物件也是一樣需要序列化,否者就全是亂碼。

    我們轉到看那個預設的RedisTemplate內部什麼樣子:

    在這裡插入圖片描述

    在最開始就能看到幾個關於序列化的引數。

    預設的序列化器是採用JDK序列化器

    在這裡插入圖片描述

    而預設的RedisTemplate中的所有序列化器都是使用這個序列化器:

    在這裡插入圖片描述

    後續我們定製RedisTemplate就可以對其進行修改。

    RedisSerializer提供了多種序列化方案:

    • 直接呼叫RedisSerializer的靜態方法來返回序列化器,然後set

      在這裡插入圖片描述

    • 自己new 相應的實現類,然後set

      在這裡插入圖片描述

  4. 定製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;
        }
    }
    1234567891011121314151617181920212223
    

    這樣一來,只要實體類進行了序列化,我們存什麼都不會有亂碼的擔憂了。

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

img

可以使用 include 組合多個配置問題

在這裡插入圖片描述

網路配置

在這裡插入圖片描述

日誌輸出級別

img

日誌輸出檔案

在這裡插入圖片描述

持久化規則

由於Redis是基於記憶體的資料庫,需要將資料由記憶體持久化到檔案中

持久化方式:

  • RDB
  • AOF

在這裡插入圖片描述

RDB檔案相關

在這裡插入圖片描述

在這裡插入圖片描述

主從複製

在這裡插入圖片描述

Security模組中進行密碼設定

在這裡插入圖片描述

客戶端連線相關

maxclients 10000  最大客戶端數量
maxmemory <bytes> 最大記憶體限制
maxmemory-policy noeviction # 記憶體達到限制值的處理策略
123

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 一個子執行緒來完成該操作;

  1. Redis 呼叫forks。同時擁有父程序和子程序。
  2. 子程序將資料集寫入到一個臨時 RDB 檔案中。
  3. 當子程序完成對新 RDB 檔案的寫入時,Redis 用新 RDB 檔案替換原來的 RDB 檔案,並刪除舊的 RDB 檔案。

這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益(因為是使用子程序進行寫操作,而父程序依然可以接收來自客戶端的請求。)

在這裡插入圖片描述

觸發機制


  1. save的規則滿足的情況下,會自動觸發rdb原則
  2. 執行flushall命令,也會觸發我們的rdb原則
  3. 退出redis,也會自動產生rdb檔案

save

使用 save 命令,會立刻對當前記憶體中的資料進行持久化 ,但是會阻塞,也就是不接受其他操作了;

由於 save 命令是同步命令,會佔用Redis的主程序。若Redis資料非常多時,save命令執行速度會非常慢,阻塞所有客戶端的請求。

在這裡插入圖片描述

flushall命令

flushall 命令也會觸發持久化 ;

觸發持久化規則

滿足配置條件中的觸發條件 ;

可以通過配置檔案對 Redis 進行設定, 讓它在“ N 秒內資料集至少有 M 個改動”這一條件被滿足時, 自動進行資料集儲存操作。

在這裡插入圖片描述

在這裡插入圖片描述

bgsave

bgsave 是非同步進行,進行持久化的時候,redis 還可以將繼續響應客戶端請求 ;

在這裡插入圖片描述

bgsave和save對比

命令savebgsave
IO型別同步非同步
阻塞?是(阻塞發生在fock(),通常非常快)
複雜度O(n)O(n)
優點不會消耗額外的記憶體不阻塞客戶端命令
缺點阻塞客戶端命令需要fock子程序,消耗記憶體

優缺點

優點:

  1. 適合大規模的資料恢復
  2. 對資料的完整性要求不高

缺點:

  1. 需要一定的時間間隔進行操作,如果redis意外宕機了,這個最後一次修改的資料就沒有了。
  2. 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 ,這時候作業系統自己同步資料,速度最快
123456

優點

  1. 每一次修改都會同步,檔案的完整性會更加好
  2. 沒秒同步一次,可能會丟失一秒的資料
  3. 從不同步,效率最高

缺點

  1. 相對於資料檔案來說,aof遠遠大於rdb,修復速度比rdb慢!
  2. Aof執行效率也要比rdb慢,所以我們redis預設的配置就是rdb持久化

十二、RDB和AOP選擇

RDB 和 AOF 對比

RDBAOF
啟動優先順序
體積
恢復速度
資料安全性丟資料根據策略決定

如何選擇使用哪種持久化方式?

一般來說, 如果想達到足以媲美 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"
12345678910111213141516171819202122

原理

每個 Redis 伺服器程序都維持著一個表示伺服器狀態的 redis.h/redisServer 結構, 結構的 pubsub_channels 屬性是一個字典, 這個字典就用於儲存訂閱頻道的資訊,其中,字典的鍵為正在被訂閱的頻道, 而字典的值則是一個連結串列, 連結串列中儲存了所有訂閱這個頻道的客戶端。

在這裡插入圖片描述

客戶端訂閱,就被連結到對應頻道的連結串列的尾部,退訂則就是將客戶端節點從連結串列中移除。

缺點

  1. 如果一個客戶端訂閱了頻道,但自己讀取訊息的速度卻不夠快的話,那麼不斷積壓的訊息會使redis輸出緩衝區的體積變得越來越大,這可能使得redis本身的速度變慢,甚至直接崩潰。
  2. 這和資料傳輸可靠性有關,如果在訂閱方斷線,那麼他將會丟失所有在短線期間釋出者釋出的訊息。

應用

  1. 訊息訂閱:公眾號訂閱,微博關注等等(起始更多是使用訊息佇列來進行實現)
  2. 多人線上聊天室。

稍微複雜的場景,我們就會使用訊息中介軟體MQ處理。

十四、Redis主從複製

概念

主從複製,是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。前者稱為主節點(Master/Leader),後者稱為從節點(Slave/Follower), 資料的複製是單向的!只能由主節點複製到從節點(主節點以寫為主、從節點以讀為主)。

預設情況下,每臺Redis伺服器都是主節點,一個主節點可以有0個或者多個從節點,但每個從節點只能由一個主節點。

作用

  1. 資料冗餘:主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘的方式。
  2. 故障恢復:當主節點故障時,從節點可以暫時替代主節點提供服務,是一種服務冗餘的方式
  3. 負載均衡:在主從複製的基礎上,配合讀寫分離,由主節點進行寫操作,從節點進行讀操作,分擔伺服器的負載;尤其是在多讀少寫的場景下,通過多個從節點分擔負載,提高併發量。
  4. 高可用基石:主從複製還是哨兵和叢集能夠實施的基礎。

為什麼使用叢集

  1. 單臺伺服器難以負載大量的請求
  2. 單臺伺服器故障率高,系統崩壞概率大
  3. 單臺伺服器記憶體容量有限。

環境配置

我們在講解配置檔案的時候,注意到有一個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
123456789101112

既然需要啟動多個服務,就需要多個配置檔案。每個配置檔案對應修改以下資訊:

  • 埠號
  • pid檔名
  • 日誌檔名
  • rdb檔名

啟動單機多服務叢集:

在這裡插入圖片描述

一主二從配置

==預設情況下,每臺Redis伺服器都是主節點;==我們一般情況下只用配置從機就好了!

認老大!一主(79)二從(80,81)

使用SLAVEOF host port就可以為從機配置主機了。

在這裡插入圖片描述

然後主機上也能看到從機的狀態:

在這裡插入圖片描述

我們這裡是使用命令搭建,是暫時的,==真實開發中應該在從機的配置檔案中進行配置,==這樣的話是永久的。

在這裡插入圖片描述

使用規則

  1. 從機只能讀,不能寫,主機可讀可寫但是多用於寫。

     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"
    12345678910
    
  2. 當主機斷電宕機後,預設情況下從機的角色不會發生變化 ,叢集中只是失去了寫操作,當主機恢復以後,又會連線上從機恢復原狀。

  3. 當從機斷電宕機後,若不是使用配置檔案配置的從機,再次啟動後作為主機是無法獲取之前主機的資料的,若此時重新配置稱為從機,又可以獲取到主機的所有資料。這裡就要提到一個同步原理。

  4. 第二條中提到,預設情況下,主機故障後,不會出現新的主機,有兩種方式可以產生新的主機:

    • 從機手動執行命令slaveof no one,這樣執行以後從機會獨立出來成為一個主機
    • 使用哨兵模式(自動選舉)

如果沒有老大了,這個時候能不能選擇出來一個老大呢?手動!

如果主機斷開了連線,我們可以使用SLAVEOF no one讓自己變成主機!其他的節點就可以手動連線到最新的主節點(手動)!如果這個時候老大修復了,那麼久重新連線!

十五、哨兵模式

更多資訊參考部落格:https://www.jianshu.com/p/06ab9daf921d

**主從切換技術的方法是:當主伺服器宕機後,需要手動把一臺從伺服器切換為主伺服器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。*這不是一種推薦的方式,更多時候,我們優先考慮*哨兵模式

單機單個哨兵

img

哨兵的作用:

  • 通過傳送命令,讓Redis伺服器返回監控其執行狀態,包括主伺服器和從伺服器。
  • 當哨兵監測到master宕機,會自動將slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機。

多哨兵模式

img

哨兵的核心配置

sentinel monitor mymaster 127.0.0.1 6379
1
  • 數字1表示 :當一個哨兵主觀認為主機斷開,就可以客觀認為主機故障,然後開始選舉新的主機。

測試

redis-sentinel xxx/sentinel.conf
1

成功啟動哨兵模式

在這裡插入圖片描述

此時哨兵監視著我們的主機6379,當我們斷開主機後:

在這裡插入圖片描述

哨兵模式優缺點

優點:

  1. 哨兵叢集,基於主從複製模式,所有主從複製的優點,它都有
  2. 主從可以切換,故障可以轉移,系統的可用性更好
  3. 哨兵模式是主從模式的升級,手動到自動,更加健壯

缺點:

  1. Redis不好線上擴容,叢集容量一旦達到上限,線上擴容就十分麻煩
  2. 實現哨兵模式的配置其實是很麻煩的,裡面有很多配置項

哨兵模式的全部配置

完整的哨兵模式配置檔案 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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071

十六、快取穿透與雪崩

快取穿透(查不到)

概念

在預設情況下,使用者請求資料時,會先在快取(Redis)中查詢,若沒找到即快取未命中,再在資料庫中進行查詢,數量少可能問題不大,可是一旦大量的請求資料(例如秒殺場景)快取都沒有命中的話,就會全部轉移到資料庫上,造成資料庫極大的壓力,就有可能導致資料庫崩潰。網路安全中也有人惡意使用這種手段進行攻擊被稱為洪水攻擊。

解決方案

布隆過濾器

對所有可能查詢的引數以Hash的形式儲存,以便快速確定是否存在這個值,在控制層先進行攔截校驗,校驗不通過直接打回,減輕了儲存系統的壓力。

在這裡插入圖片描述

快取空物件

一次請求若在快取和資料庫中都沒找到,就在快取中方一個空物件用於處理後續這個請求。

在這裡插入圖片描述

這樣做有一個缺陷:儲存空物件也需要空間,大量的空物件會耗費一定的空間,儲存效率並不高。解決這個缺陷的方式就是設定較短過期時間

即使對空值設定了過期時間,還是會存在快取層和儲存層的資料會有一段時間視窗的不一致,這對於需要保持一致性的業務會有影響。

快取擊穿(量太大,快取過期)

概念

相較於快取穿透,快取擊穿的目的性更強,一個存在的key,在快取過期的一刻,同時有大量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量大、壓力驟增。這就是快取被擊穿,只是針對其中某個key的快取不可用而導致擊穿,但是其他的key依然可以使用快取響應。

比如熱搜排行上,一個熱點新聞被同時大量訪問就可能導致快取擊穿。

解決方案

  1. 設定熱點資料永不過期

    這樣就不會出現熱點資料過期的情況,但是當Redis記憶體空間滿的時候也會清理部分資料,而且此種方案會佔用空間,一旦熱點資料多了起來,就會佔用部分空間。

  2. 加互斥鎖(分散式鎖)

    在訪問key之前,採用SETNX(set if not exists)來設定另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key。保證同時刻只有一個執行緒訪問。這樣對鎖的要求就十分高。

快取雪崩

概念

大量的key設定了相同的過期時間,導致在快取在同一時刻全部失效,造成瞬時DB請求量大、壓力驟增,引起雪崩。

在這裡插入圖片描述

解決方案

  • redis高可用

    這個思想的含義是,既然redis有可能掛掉,那我多增設幾臺redis,這樣一臺掛掉之後其他的還可以繼續工作,其實就是搭建的叢集

  • 限流降級

    這個解決方案的思想是,在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。

  • 資料預熱

    資料加熱的含義就是在正式部署之前,我先把可能的資料先預先訪問一遍,這樣部分可能大量訪問的資料就會載入到快取中。在即將發生大併發訪問前手動觸發載入快取不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻。