1. 程式人生 > 實用技巧 >使用QUOTA(磁碟配額)來限制使用者空間

使用QUOTA(磁碟配額)來限制使用者空間

Nosql概述

1、單機MySQL的年代!

1、 資料量如果太大,一個機器就放不下了!

2、資料的索引(B + Tree) 超過300萬就一定要建立索引

3、訪問量(讀寫混合),一個伺服器承受不了~

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

網站80%都是讀操作,每次都要去查詢資料庫的話就十分麻煩!希望減輕伺服器的壓力,可以使用快取來保證效率!

發展過程:優化資料結構和索引 ---> 檔案快取(IO) ---> Memecached(當時最熱門的技術!)

3、分庫分表 + 水平拆分 + MySQL叢集

技術和業務在發展的同時,對人的要求也越來越高了!

本質:資料庫(讀,寫)

早些年MyISAM:表鎖,十分影響效率!高併發下就會出現嚴重的鎖問題。

轉戰Innodb:行鎖

慢慢就開始使用分庫分表來解決寫的壓力!MySQL在那個年代推出了表分割槽!這個並沒有多少公司使用!

MySQL的叢集,很好滿足了那個年代的需求。

4、如今最近的年代

技術爆炸:

  • 2010 -- 2020 十年時間,世界發生了翻天覆地的變化;(定位,也是一種資料,音樂,熱榜!)
  • MySQL等關係型資料庫就不夠用了!資料量很多,變化很快!
  • MySQL有的使用它來儲存一些比較大的檔案,部落格,圖片!資料表很大,效率就低了!如果有一種資料庫專門處理這種資料,MySQL壓力就變得十分小(研究如何處理這些問題!)大資料的IO壓力下,表幾乎沒法更改!

為什麼要用NoSQL!

使用者的個人資訊,社交網路,地理位置。使用者自己產生的資料,使用者日誌等等爆發式增長!

這個時候就需要使用nosql資料庫了,可以很好的處理以上的情況!

什麼是NoSQL

NoSQL

關係型資料庫:表格,行,列

NoSQL = Not Only SQL (不僅僅是SQL)

泛指:非關係型資料庫,隨著web2.0網際網路的誕生!傳統的關係型資料庫很難對付web2.0時代!尤其是超大規模的高併發的社群!暴露出來很多難以克服的問題,NoSQL在當今大資料環境下發展的十分迅速,Redis是發展最快的,而且是我們當下必須掌握的一個技術!

很多的資料型別使用者的個人資訊,社交網路,地理位置。這些資料型別的儲存不需要一個固定的格式!不需要多餘的操作就可以橫向擴充套件! Map<String,Object> 使用鍵值對來控制!

NoSQL特點

解耦!

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

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

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

4、傳動的 RDMS 和 NoSQL

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

NoSQL的四大分類

KV鍵值對

  • 新浪:Redis
  • 美團:Redis + Tair
  • 阿里、百度:Redis + memcache

文件型資料庫(bson格式 和 json一樣)

  • MongoDB(一般必須要掌握)
    • MongoDB是一個基於分散式儲存的資料庫, C++編寫,主要用來處理大量的文件!
    • MongoDB是一個介於關係型資料庫和非關係型資料中中間的產品!MongoDB是非關係型資料庫中功能最豐富,最像關係型資料庫的!
  • ConthDB

列儲存資料庫

  • HBase
  • 分散式檔案系統

圖形關係資料庫

Redis入門

概述

Redis是什麼?

  • Redis(Remote Dictionary Server),即遠端字典服務!
    • 是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。
    • Redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了 master-slave(主從)同步。
    • 免費和開源!是當下最熱門的NoSQL技術之一!也被稱之為結構化資料庫!

Redis能幹嘛?

1、記憶體儲存、持久化,記憶體中是斷電即失,所以說持久化很重要(RDB、AOF)

2、效率高,可以用於快取記憶體

3、釋出訂閱系統

4、地圖資訊分析

5、計時器、計數器(瀏覽量!)

6、.....

Linux環境中安裝

1、官網下載redis-5.0.5.tar.gz壓縮包

2、安裝gcc和gc-c++

  • yum install gcc
  • yum install gc-c++

3、將redis-5.0.5.tar.gz上傳到/opt資料夾下

4、執行 tar -zxvf redis-5.0.5.tar.gz 進行解壓

5、解壓之後,進入解壓之後的資料夾redis-5.0.5,執行make,進行編譯

6、執行make install 進行編譯安裝

7、安裝之後,安裝目錄為 /usr/local/bin

  • benchmark:效能測試工具,可以在自己電腦上執行,看電腦效能如何
  • check-aof:修復有問題的AOF檔案
  • check-dump:修復有問題的dump.rdb檔案
  • sentilnel:Redis叢集使用
  • redis-server:Redis伺服器啟動命令
  • redis-cli:客戶端,操作入口

8、前臺啟動

  • /usr/local/bin 資料夾下可以直接執行 redis-server 進行啟動 埠號:6379

9、後臺啟動(推薦)

  • 備份redis.conf

    • 拷貝一份 redi.conf 到其他目錄

    • cp /opt/redis-5.0.5/redis.conf /myredis
      
  • 後臺啟動設定 daemonize no 改成 yes

    • 修改69行 ,將bind 127.0.0.1註釋掉
    • 修改88行,protected-mode yes改為 no
    • 修改redis.conf(136行) 檔案將裡面的 daemonize no 改成 yes,讓服務在後臺啟動
  • Redis 啟動

    • redis-server /myredis/redis.conf
      
      ps -ef|grep redis-server
      
  • 用客戶端訪問:redis-cli -p 6379

  • 測試驗證:ping

  • Redis關閉

    • 單例項關閉redis伺服器:
      • redis-cli shutdown
    • 也可以進入客戶端關閉伺服器
      • shutdown
    • 多例項關閉,指定埠關閉:redis-cli -p 6379 shutdown

測試效能

redis-benchmark 是一個壓力測試工具!

官方自帶的效能測試工具!

redis效能測試工具引數:

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

檢視分析

基礎的知識

Redis預設有16個數據庫

預設使用的是第0個

可以使用select 進行切換資料庫

127.0.0.1:6379> select 3  # 切換資料庫
OK
127.0.0.1:6379[3]> dbsize  # 檢視DB大小!
(integer) 0
127.0.0.1:6379[3]> 

127.0.0.1:6379[3]> keys *  #檢視資料庫所有的 key
1) "name"
127.0.0.1:6379[3]> 

清空當前資料庫 flushdb

127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]> 

清除所有資料庫的內容 flushall

127.0.0.1:6379> set name kingtl
OK
127.0.0.1:6379> get name
"kingtl"
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]> flushall
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> 

Redis 是單執行緒的!

  • Redis是很快的,官方表示,Redis是基於記憶體操作,CPU不是Redis效能瓶頸,Redis的瓶頸是根據及其的記憶體和網路頻寬。既然可以使用單執行緒來實現,所以就使用單執行緒了!
  • Redis是C語言寫的,官方提供的資料為 100000+ 的QPS,這個不比同樣是使用key-value的Memecache差!

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

1、誤區1:高效能的伺服器一定是多執行緒的?

2、誤區2:多執行緒(CPU上下文會切換!)一定比單執行緒效率高!

CPU>記憶體>硬碟

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

五大資料型別

官網文件

全段翻譯:

Redis 是一個開源(BSD許可)的,記憶體中的資料結構儲存系統,它可以用作資料庫快取訊息中介軟體。 它支援多種型別的資料結構,如 字串(strings)雜湊(hashes)列表(lists)集合(sets)有序集合(sorted sets) 與範圍查詢, bitmapshyperloglogs地理空間(geospatial) 索引半徑查詢。 Redis 內建了 複製(replication)LUA指令碼(Lua scripting)LRU驅動事件(LRU eviction)事務(transactions) 和不同級別的 磁碟持久化(persistence), 並通過 Redis哨兵(Sentinel)和自動 分割槽(Cluster)提供高可用性(high availability)。

Redis-Key

127.0.0.1:6379> keys *    # 檢視所有的 key
1) "age"
2) "name"
127.0.0.1:6379> exists name   # 判斷當前 key 是否存在
(integer) 1
127.0.0.1:6379> move name 1  # 移動當前key  到指定的庫
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> clear
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name kingtl
OK
127.0.0.1:6379> clear
127.0.0.1:6379> expire name 10  # 設定key 的過期時間,單位是:秒
(integer) 1
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> ttl name  # 檢視當前key 的剩餘時間
(integer) 3
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name   # 檢視當前 key的型別
string
127.0.0.1:6379> type age
string

String(字串)

##############################################################################################
127.0.0.1:6379> exists key1  # 判斷某個key  是否存在
(integer) 1
127.0.0.1:6379> append key1 "hello" # 追加字串,如果當前key不存在,就相當於setKey
(integer) 7
127.0.0.1:6379> get key1   #獲得值
"v1hello"
127.0.0.1:6379> append key1 world
(integer) 12
127.0.0.1:6379> get key1
"v1helloworld"
127.0.0.1:6379> strlen key1 #獲得字串的長度!
(integer) 12
127.0.0.1:6379> append key1 ,kingtl
(integer) 19
127.0.0.1:6379> get key1
"v1helloworld,kingtl"
127.0.0.1:6379> append k2 zhansan
(integer) 7
127.0.0.1:6379> keys *
1) "age"
2) "name"
3) "key1"
4) "k2"
127.0.0.1:6379> 

##############################################################################################
# i++ 
#步長: i+=
127.0.0.1:6379> set views 0   #  初始瀏覽量0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增1   瀏覽量變為1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #自減1  瀏覽量減1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incrby views 10   #可以設定步長,指定增量
(integer) 10
127.0.0.1:6379> decrby views 5   #可以設定步長,指定減量
(integer) 5
127.0.0.1:6379> 

##############################################################################################
字串範圍 range
127.0.0.1:6379> set key1 hello,kingtl   #設定 key1 的值
OK
127.0.0.1:6379> get key1
"hello,kingtl"
127.0.0.1:6379> 
127.0.0.1:6379> getrange key1 0 3   # 擷取字串  [0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1  # 獲取全部的字串  和 get key是一樣的
"hello,kingtl"
127.0.0.1:6379>  

# 替換!
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"  
127.0.0.1:6379> setrange key2 1 xx   #替換指定位置開始的字串!
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
127.0.0.1:6379> 

##############################################################################################
#setex (set with expire)  #設定過期時間
#setnx (set if not exist)  # 不存在再設定(在分散式鎖中是常常使用的)
127.0.0.1:6379> setex key3 30 hello  # 設定 key3 的值為 hello,30秒後過期
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> setnx mykey reids   # 如果mykey 不存在,建立mykey 
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey mongodb  #如果mykey 存在,建立失敗!
(integer) 0
127.0.0.1:6379> get mykey
"reids"
127.0.0.1:6379> 
##############################################################################################
#mset 
#mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3   #同時設定多個值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k1"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3  # 同時獲取多個值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4  # msetnx 是一個原子性的操作,要麼一起成功,要麼一起失敗!
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> 


# 物件
set user:1 {name:zhangsan,age:3}  #設定一個user:1 物件值為json字串來儲存一個物件。

# 這裡的key是一個巧妙的設計: user:{id}:{filed},如此設計在redis中是完全OK的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

##############################################################################################

getset # 先get然後再set
127.0.0.1:6379> getset db redis #如果不存在,返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb # 如果存在值,獲取原來的值,並設定新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> 

資料結構是相同的!

String類似的使用場景:value除了是字串還可以是數字!

  • 計數器
  • 統計多單位的數量 uid:{id}:follow 0
  • 粉絲數
  • 物件快取儲存!

List

基本的資料型別,列表。

  • 在redis裡面,可以把list玩成,棧、佇列、阻塞佇列!
  • 所有的list命令都是用 l開頭的,Redis不區分大小寫命令
##############################################################################################
127.0.0.1:6379> lpush list one  #將一個值或者多個值,插入到列表的頭部 (左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1   # 獲取list中的值
1) "three"
2) "two"
3) "one" 
127.0.0.1:6379> lrange list 0 1   # 通過區間獲取具體的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list right  # 將一個值或者多個值,插入到列表尾部 (右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> 

##############################################################################################
#lpop
#rpop
127.0.0.1:6379> lrange list 0 -1  
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list    # 移除list的第一個元素
"three"
127.0.0.1:6379> rpop list   # 移除list的最後一個元素
"right"
##############################################################################################
# lindex
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0  # 通過下標獲得list 中某一個值
"two"
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> 
##############################################################################################
#llen
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> llen list  #返回列表的長度
(integer) 3
##############################################################################################
移除指定的值!
#lrem
127.0.0.1:6379> lpush list three
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one   #移除list集合中指定個數的value,精確匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> 
##############################################################################################
#trim   修剪。
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello2
(integer) 3
127.0.0.1:6379> rpush mylist hello3
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2  # 通過下標擷取指定的長度,這個list已經被改變了,截取了只剩下擷取的元素!
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
##############################################################################################
#rpoplpush  #移除列表的最後一個元素,並且移動到新的列表中
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello2
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist #移除列表的最後一個元素,並且移動到新的列表中
"hello2"
127.0.0.1:6379> lrange mylist 0 -1   #檢視原來的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1  #檢視目標列表中的值
1) "hello2"
127.0.0.1:6379> 
##############################################################################################
# lset 將列表中指定下標的值替換為另外一個值,更新操作
127.0.0.1:6379> exists list  # 判斷這個列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item  # 如果不存在列表我們就會去更新報錯
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list  0 0
1) "value1"
127.0.0.1:6379> lset list 0 item # 如果存在,更新當前下標的值
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other   #如果不存在,就會報錯
(error) ERR index out of range
127.0.0.1:6379> 
##############################################################################################
#linsert   將某個具體的value插入到列表中某個元素的前面或者後面
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after "world" new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
127.0.0.1:6379> 

##############################################################################################

小結:

  • 實際上是一個連結串列,before Node after ,left,right 都可以插入值
  • 如果key不存在,建立新的連結串列
  • 如果key存在,新增內容
  • 若果移除了所有的值,空連結串列,也代表不存在!
  • 在兩邊插入或者改動值,效率最高!中間元素,相對來說,效率會低一點!

訊息排隊!訊息佇列 (Lpush Rpop) , 棧 (Lpush Lpop)

Set

set中的值是不能重複的。

##############################################################################################
127.0.0.1:6379> sadd myset hello    # set集合中新增元素
(integer) 1
127.0.0.1:6379> sadd myset kingtl
(integer) 1
127.0.0.1:6379> sadd myset lovekingtl
(integer) 1
127.0.0.1:6379> smembers myset   # 檢視指定set的所有值
1) "kingtl"
2) "lovekingtl"
3) "hello"
127.0.0.1:6379> 
127.0.0.1:6379> sismember myset hello   #判斷某一個值是不是在set集合中!
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
##############################################################################################
127.0.0.1:6379> scard myset  #獲取Set集合中的內容元素個數
(integer) 3

##############################################################################################
#rem
127.0.0.1:6379> srem myset hello  #移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> smembers myset
1) "kingtl"
2) "lovekingtl"
127.0.0.1:6379> 
##############################################################################################
#set 無需不重複集合。抽隨機

127.0.0.1:6379> SMEMBERS myset
1) "loverkingtl2"
2) "kingtl"
3) "lovekingtl"
127.0.0.1:6379> SRANDMEMBER myset  #隨機抽選出一個元素
"kingtl"
127.0.0.1:6379> SRANDMEMBER myset
"lovekingtl"
127.0.0.1:6379> SRANDMEMBER myset
"lovekingtl"
127.0.0.1:6379> SRANDMEMBER myset
"kingtl"
127.0.0.1:6379> SRANDMEMBER myset
"loverkingtl2"
127.0.0.1:6379> SRANDMEMBER myset 2 #隨機抽選出指定個數的元素
1) "loverkingtl2"
2) "kingtl"
127.0.0.1:6379> 
##############################################################################################
#刪除指定的key,隨機刪除一個Key!

127.0.0.1:6379> SMEMBERS myset
1) "loverkingtl2"
2) "kingtl"
3) "lovekingtl"
127.0.0.1:6379> spop myset  #隨機刪除一些set集合中的元素!
"kingtl"
127.0.0.1:6379> spop myset 
"lovekingtl"
127.0.0.1:6379> smembers myset
1) "loverkingtl2"
127.0.0.1:6379> 
##############################################################################################
#將一個指定的值,移動到另外一個set集合!
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset kingtl
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 kingtl  #將一個指定的值,移動到另外一個set集合!
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kingtl"
2) "set2"
127.0.0.1:6379> 
##############################################################################################
微博,B站,共同關注! (交集)
數字集合類:
 - 差集
 - 交集
 - 並集
 
127.0.0.1:6379> SDIFF key1 key2    #差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2  #交集,共同好友可以這樣實現!
1) "c"
127.0.0.1:6379> SUNION key1 key2   #並集
1) "e"
2) "b"
3) "c"
4) "a"
5) "d"

##############################################################################################

微博,A使用者將所有關注的人放到一個set集合中!將他的粉絲也放在一個集合中,

共同關注,共同愛好,二度好友,推薦好友(六度分割理論)

Hash(雜湊)

Map集合,key-map!這個值時一個map集合!本質和String型別沒有太大區別,還是一個簡單的 key-value !

set myhash field kingtl

##############################################################################################
127.0.0.1:6379> hset myhash field1 kingtl  #set一個具體 key-value
(integer) 1
127.0.0.1:6379> hget myhash field1  #獲取一個欄位值
"kingtl"
127.0.0.1:6379> hmset myhash field1 hello field2 world #set 多個 key-value
OK
127.0.0.1:6379> hmget myhash field1 field2   #獲取多個欄位值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash   #獲取全部的資料
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1  #刪除hash指定的key欄位!對應的value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
127.0.0.1:6379> 

##############################################################################################
#hlen

127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash  #獲取hash表的欄位數量
(integer) 2
127.0.0.1:6379> 

##############################################################################################

127.0.0.1:6379> HEXISTS myhash field1  #判斷hash中指定欄位是否存在!
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
##############################################################################################
#只獲得所有的 field
#只獲得所有的 值
127.0.0.1:6379> HKEYS myhash  #只獲得所有的 field
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash  #只獲得所有的 值
1) "world"
2) "hello"
##############################################################################################
# incr
# decr

127.0.0.1:6379> hset myhash field3 5   #指定一個初始值
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1  #指定增魯昂
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello #如果不存在 ,則可以設定
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world   #如果存在,則不能設定
(integer) 0

##############################################################################################

hash變更的資料 user name age ,尤其是使用者資訊之類的,進場變動的資訊!hash更適合於物件的儲存!String更加適合字串儲存!

Zset(有序集合)

在set的基礎上,增加了一個值,set k1 v1 zset k1 score1 v1

##############################################################################################
127.0.0.1:6379> zadd myset 1 one  #新增一個值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three   # 新增多個值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> 
##############################################################################################
排序如何實現
127.0.0.1:6379> zadd salary 2500 xiaohong   #新增三個使用者
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 kingtl
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf    # 顯示全部使用者  從小到大排序
1) "kingtl"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1   # 顯示全部使用者  從大到小排序
1) "zhangsan"
2) "kingtl"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores   # 顯示全部的使用者並且附帶成績
1) "kingtl"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores   #顯示工資小於2500員工的 升序排列
1) "kingtl"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> 
##############################################################################################
移除rem中的元素
127.0.0.1:6379> ZRANGE salary 0 -1   
1) "kingtl"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong   # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> ZRANGE salart 0 -1
(empty list or set)
127.0.0.1:6379> ZRANGE salary 0 -1
1) "kingtl"
2) "zhangsan"
127.0.0.1:6379> ZCARD salary    # 獲取有序集合中的個數
(integer) 2
##############################################################################################
127.0.0.1:6379> ZADD myset 1 hello
(integer) 1
127.0.0.1:6379> ZADD myset 2 world 3 kingtl
(integer) 2
127.0.0.1:6379> zcount myset 1 3  #獲取指定區間的成員數量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

##############################################################################################

案例思路: set 排序

  • 儲存班級成績表,工資表排序!
  • 普通訊息 1, 重要訊息 2 ,帶權重進行判斷!
  • 排行榜應用實現,去Top N測試!

三種特殊資料型別

geospatial 地理位置

朋友的定位,附近的人,打車距離計算?

Redis的Geo 在Redis3.2版本就推出了! 這個功能可以推算地理位置的資訊,兩地之間的距離,方圓幾裡的人

可以查詢一些測試資料:http://www.jsons.cn/lngcode/

getadd

#getadd 新增地理位置
# 規則:兩級無法新增,我們一般會下載城市資料,直接通過java一次性匯入!
# 引數:key 值(緯度、經度、名稱)
#有效的經度介於 -180 度至 180 度之間。
#有效的緯度介於 -85.05112878 度至 85.05112878 度之間。
127.0.0.1:6379> GEOADD china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> GEOADD china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> GEOADD china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2

getpos

獲得當前定位: 一定是一個座標值!

127.0.0.1:6379> GEOPOS china:city beijing chongqing 獲取指定的城市的經度和緯度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"
127.0.0.1:6379> 

geodist

兩個人之間的距離

單位:

  • m 表示單位 米
  • km 表示單位 千米
  • mi 表示單位 英里
  • ft 表示單位 英尺
127.0.0.1:6379> GEODIST china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> GEODIST china:city beijing shanghai km    #檢視北京到上海的直線距離
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqing
"1464070.8051"
127.0.0.1:6379> GEODIST china:city beijing chongqing km   #檢視北京到重慶的直線距離
"1464.0708"

georadius 以給定的經緯度為中心,找出某一半徑內的元素

附近的人?(獲得所有附近的人的地址,定位!) 通過半徑來查詢!

獲得指定數量的人!200

所有的資料都應該錄入: china:city,才會讓結果更加清晰

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km   #以100 30 這個經緯度為中心,尋找方圓1000KM內的城市
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 1500 km
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist   #顯示到中心位置的距離
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord   #顯示他人的定位資訊
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"  
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1   # 篩選出指定的結果!
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"

georadiusbymember

#找出位於指定元素周圍的其他元素!
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km  
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 4000 km
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"

geohash 返回一個或多個位置元素的geohash 表示

該命令將返回11個字元的geohash字串

#將二維的經緯度 轉換為  一維的字串,如果兩個字串越接近,那麼則距離越近
127.0.0.1:6379> GEOHASH china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

GEO 底層實現原理 其實就是 Zset !可以使用Zset命令操作geo

127.0.0.1:6379> ZRANGE china:city 0 -1   #檢視地圖中全部元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing   # 移除指定元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"

Hyperloglog

什麼是基數?

A{1,3,5,7,8,9,7}

B{1,3,5,7,8}

基數(不重複的元素) = 5 ,可以接受誤差!

簡介

Redis 2.8.9 版本就恆信了 Hyperloglog資料結構!

Redis sHyperloglog 基數統計的演算法!

優點:佔用的記憶體是固定的,2^64 不同的元素的技術,只需要耗費 12KB記憶體! 如果從記憶體角度來看的話,Hyperloglog 首選!

  • 網頁的 UV(一個人訪問一個網站多次,但是還是算作一個人!)
    • 傳統的方式:set儲存使用者的id,然後就可以統計set中元素數量作為標準判斷!(這個方式如果儲存大量的使用者id,就會比較麻煩!目的是為了計數,而不是儲存使用者id)

0.81% 錯誤率! 統計UI任務,可以忽略不記的!

測試使用

127.0.0.1:6379> PFADD mykey a b c d e f g h i j   # 建立第一組元素  mykey
(integer) 1
127.0.0.1:6379> PFCOUNT mykey       # 統計 mykey 中元素的基數數量
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x c v b   # 建立第二組元素  mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 7
127.0.0.1:6379> PFMERGE  mykey3 mykey mykey2   # 合併兩組 mykey mykey2 => mykey3  並集
OK
127.0.0.1:6379> PFCOUNT mycount
(integer) 0
127.0.0.1:6379> PFCOUNT mykey3   #看並集的數量
(integer) 13

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

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

Bitmap

位儲存

統計使用者資訊,活躍,不活躍! 登入、未登入! 打卡,365打卡! 兩個狀態的,都可以使用 Bitmap!

Bitmap 點陣圖,資料結構!都是操作二進位制位來進行記錄,就只有0 和 1兩個狀態!

365 天 = 365 bit 1位元組 = 8 bit

使用bitmap 來記錄 週一到週日的 開啟!

周天:0 週一: 1 週二:2 .....

檢視某一天是否打卡:

127.0.0.1:6379> GETBIT sign 3
(integer) 1
127.0.0.1:6379> GETBIT sign 6
(integer) 0

統計操作:打卡的天數

127.0.0.1:6379> bitcount sign   #統計這周的打卡記錄,就可以看到是否有全勤
(integer) 4

事務

事務

原子性:要麼同時成功,要麼同時失敗!

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

Redis事務沒有隔離級別的概念!

所有的命令在事務中,並沒有直接被執行!只有發起執行命令的時候才會執行! Exec

Redis 事務本質:一組命令的集合!一個事務中的所有命令都會被序列化,在事務執行過程中,會按照順序執行!

一次性、順序性、排他性!執行一系列的命令!

Redis的事務:

  • 開啟事務(multiI )
  • 命令入隊()
  • 執行事務(exec)

正常執行事務!

127.0.0.1:6379> multi   #開啟事務
OK
127.0.0.1:6379> set k1 v1    #命令入隊
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec   #執行事務
1) OK
2) OK
3) "v2"
4) OK

放棄事務!

127.0.0.1:6379> multi   # 開啟事務
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD    # 取消事務
OK
127.0.0.1:6379> get key4   #  事務佇列中的命令 都不會被執行
(nil)

編譯型異常(程式碼有問題!有錯誤!),事務中的所有的命令都不會被執行!

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1 
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3   #錯誤的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4 
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec  #執行事務報錯!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5   # 所有的命令都不會被執行
(nil)

執行時異常(1/0),如果事務佇列中存在語法型錯誤,那麼執行命令的時候,其他命令是可以正常執行的,錯誤命令丟擲異常

127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> INCR k1   #  執行的時候失敗
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range  #雖然第一條命令報錯!但是依舊正常執行成功了!
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

監控 ! Watch (面試常問)

悲觀鎖

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

樂觀鎖

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

Redis 監控測試

正常執行成功!

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> WATCH money   #監視 money 物件 
OK
127.0.0.1:6379> multi   #事務正常結束,資料期間沒有發生變動,這個時候就正常執行成功!
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

測試多執行緒修改值,使用watch 可以當做redis 的樂觀鎖操作!

127.0.0.1:6379> WATCH money   # 監視 money 
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec   # 執行之前,另外一個執行緒修改了值,這個時候就會導致事務失敗
(nil)

如果修改失敗,獲取最新的值就好:

127.0.0.1:6379> UNWATCH   # 如果發現事務執行失敗,就先解鎖
OK
127.0.0.1:6379> WATCH money    # 獲取最新的值,再次監視,相當於mysql裡面的 select version
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec    # 比對監視的值是否發生了變化,如果沒有變化,那麼久可以執行成功!如果變化了,就執行失敗
1) (integer) 990
2) (integer) 30

Jedis

使用Java來操作Redis

什麼是Jedis?

  • 官方推薦的java連線開發工具!使用java操作Redis的中介軟體!如果要使用java操作Redis,那麼一定要對Jedis十分熟悉!

測試

1、 匯入對應的依賴

<!--    匯入jedis的包-->
    <dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>

<!--        fastjson-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>

    </dependencies>

2、編碼測試

  • 連線資料庫
  • 操作命令
  • 斷開連線
package com.kingtl;

import redis.clients.jedis.Jedis;

public class TestPing {

    public static void main(String[] args) {
        //1、new Jedis() 物件即可
        Jedis jedis = new Jedis("47.107.250.181",6379);
        // Jedis 所有方法就是 Redis裡面的命令
        System.out.println(jedis.ping());
    }

}

輸出:

常用的API

String

List

Set

Hash

Zset

所有的API,都是redis裡面的命令

事務

package com.kingtl;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("47.107.250.181",6379);

        jedis.flushDB();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","kingtl");

        //開啟事務
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        //jedis.watch(result);

        try {
            multi.set("user1",result);
            multi.set("user2",result);
            int i = 1/0;  //程式碼會丟擲異常,執行失敗!

            multi.exec();   //執行事務
        } catch (Exception e) {
            multi.discard(); // 放棄事務
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();  //關閉連線
        }


    }
}

SpringBoot整合

SpringBoot操作資料:

SpringData 也是和 SpringBoot齊名的專案!

說明:在SpringBoot2.x之後,原來使用 Jedis 被替換成了 lettuce

Jedis:採用的是直連,多個執行緒操作的話,是不安全的,如果要想避免不安全,使用 redis pool 連線池! 更像 BIO 模式

lettuce:採用netty,例項可以在多個執行緒進行共享,不存線上程不安全的情況!減少執行緒數量! 更像 NIO模式

原始碼分析:

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}
)   //可以自己定義一個redisTemplate來替換這個預設的!
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    // 預設的RedisTemplate 沒有過多的設定, redis 物件都是需要序列化的! 
    //兩個泛型都是 Object , Object 的型別,我們後面使用需要強制轉換!<String,Object>
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean   //由於 String 是 redis中最常用的型別,所以單獨提出來了 一個 Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

整合測試

1、 匯入依賴

<!--        操作redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置連線

#配置redis
spring.redis.host=47.107.250.181
spring.redis.port=6379

3、測試

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        //redisTemplate   操作不同的資料型別,api和redis指令是一樣的
        // opsForValue  操作字串 類似String
        //opsForList  操作List 類似 List
        //opsForSet  操作set
        //opsForHash
        //opsForGeo
        //opsForZSet
        //opsForHyperLogLog


        //除了基本的操作。常用的方法可以直接通過redisTemplate操作。比如事務、基本的CRUD


        //獲取redis的連線物件
        //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //connection.flushDb();
        //connection.flushAll();


        redisTemplate.opsForValue().set("mykey","kingtl");
        System.out.println(redisTemplate.opsForValue().get("mykey"));

    }

}

編寫一個 自己的RedisTempalte

package com.kingtl.redis02springboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class RedisConfig {

    //寫好的固定模板
    //自己定義了  一個 RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException{
        //為了開發方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //序列化配置
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectJackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key 也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value序列化方式採用jackson
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
       //hash的value序列化方式採用jackson
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }


}

Redis.conf詳解

啟動的時候,就通過配置檔案來啟動!

單位

1、配置檔案 unit單位 對大小寫不敏感!

包含

網路

bind 127.0.0.1 #繫結的ip
protected-mode no  #保護模式 
port 6379   #埠設定 

通用 GENERAL

daemonize yes #以守護程序的方式執行,預設是 no

pidfile /var/run/redis_6379.pid  #如果以後臺方式執行,需要指定一個 pid 檔案!

#日誌
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)   生產環境使用
# warning (only very important / critical messages are logged)
loglevel notice

logfile ""  #日誌的檔案位置名

databases 16  #資料庫的數量,預設是 16 個數據庫

always-show-logo yes  #是否總是顯示logo

快照 SNAPSHOTTING

持久化,在規定的時間內,執行了多少次操作,則會持久化到檔案 .rdb .aof

redis是記憶體資料庫,如果沒有持久化,那麼資料斷電即失

#如果900秒內,如果至少有 1 個key 進行了修改,就進行持久化操作
save 900 1   

#如果300秒內,如果至少有 10 個key 進行了修改,就進行持久化操作
save 300 10

#如果60秒內,如果至少有 10000 個key 進行了修改,就進行持久化操作
save 60 10000



stop-writes-on-bgsave-error yes  #持久化如果出錯,是否還需要繼續工作!

rdbcompression yes  # 是否壓縮 rdb 檔案,需要消耗一些 cpu 資源! 

rdbchecksum yes  # 儲存 rdb 檔案的時候,進行錯誤的檢查校驗!

dir ./    # rdb 檔案儲存的目錄

REPLICATION 複製

  • 主從複製

SECURITY 安全

可以在這裡設定 redis 的密碼,預設是沒有密碼!

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass  # 獲取 redis 的密碼
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456"   #設定redis 的密碼
OK
127.0.0.1:6379> config get requirepass   # 發現所有的命令  沒有許可權執行了
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth "123456"   # 使用密碼進行登入!
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

限制CLIENTS

maxclients 10000  #設定能連線上 redis 的最大客戶端數量

maxmemory <bytes> # redis 配置最大的記憶體容量

maxmemory-policy noeviction # 記憶體達到上限之後的處理策略
    maxmemory-policy 六種方式
    1、volatile-lru:只對設定了過期時間的key進行LRU(預設值) 
    2、allkeys-lru : 刪除lru演算法的key   
    3、volatile-random:隨機刪除即將過期key   
    4、allkeys-random:隨機刪除   
    5、volatile-ttl : 刪除即將過期的   
    6、noeviction : 永不過期,返回錯誤

APPEND ONLY MODE模式 aof配置

appendonly no   # 預設是不開啟aof模式的,預設是使用rdb方式持久化的,在大部分情況下,rdb完全夠用了!

appendfilename "appendonly.aof"  # 持久化的檔案的名字   

# appendfsync always  # 每次修改都會 sync ,消耗效能
appendfsync everysec   # 每秒執行一次  sync,可能會丟失這1S的資料!
# appendfsync no   # 不執行  sync,這個時候,作業系統自己同步資料,速度是最快!

Redis持久化

面試和工作,持久化都是重點!

Redis是記憶體資料庫,如果不講記憶體中的資料庫狀態儲存到磁碟,那麼一旦伺服器程序退出,伺服器中的資料庫狀態也會消失。所以Redis提供了 持久化功能!

RDB(Redis DataBase)

什麼是RDB

在主從複製中, rdb 就是備用的!從機上面!

在指定的時間內將記憶體中的資料集快照寫入磁碟,也就是行話將的 Snapshot快照,它恢復時是將快照檔案 直接讀到記憶體裡。

Redis會單獨建立(fork) 一個子程序來進行持久化,會先將資料寫入到一個臨時檔案中,待持久化過程結束了,再用這個臨時檔案替換上次持久化好的檔案。整個過程中,主程序是不進行任何IO操作的。這就確保了極高的效能。如果需要進行大規模資料的恢復,且對於資料恢復的完整性不是非常敏感,那麼RDB方式要比AOF方式更加的高效。RDB的缺點是最後一次持久化後的資料可能丟失。預設的就是RDB,一般情況下不需要修改這個配置!

在生產環境會將這個檔案進行備份!

RDB儲存的檔案是 dump.rdb 都是在配置檔案快照中進行配置!

測試 60秒內 修改5次 key 就觸發 rdb 操作

觸發機制

1、save的規則滿足的情況下,會自動觸發rdb規則

2、執行 flushall 命令,也會觸發 rdb 規則!

3、退出redis,也會產生 rdb 檔案

備份就會自動生成一個 dump.rdb 檔案

如何恢復 rdb 檔案!

1、只需要將rdb檔案放在 redis 啟動目錄下,redis啟動的時候會自動檢查 dump.rdb,恢復其中的資料

2、檢視需要存放的位置

127.0.0.1:6379> config get dir
1) "dir" 
2) "/root"  #如果在這個目錄下 存在 dump.rdb 檔案,啟動就會自動恢復其中的資料

幾乎預設的配置就夠用了

優點:

1、 適合大規模的資料恢復!

2、對資料完整性不高!

缺點:

1、 需要一定的時間間隔進行操作!如果redis意外宕機了,最後一次修改的資料就沒有了!

2、fork程序的時候,會佔用一定的內容空間!

AOF(Append Only File)

將執行的所有命令都記錄下來,恢復的時候,就把這個檔案再執行一遍!

是什麼?

以日誌的形式來記錄每個寫操作,將Redis執行過的所有指令記錄下來(讀操作不記錄),只許住家檔案不可以改寫檔案,Redis啟動之初會讀取該檔案重新構建資料,換言之,redis重啟的話就根據日誌檔案的內容將寫指令從前到後執行一次以完成資料的恢復工作。

AOF儲存的是 appendonly.aof 檔案

append

預設時候不開啟的。需要手動進行配置!只需要將 appendonly 改為 yes 就開啟了 aof !

重啟,redis 就生效了!

如果aof 檔案有錯誤,那麼 redis 是啟動不了的。需要修復這個 aof 檔案!redis 提供了一個工具 redis-check-aof --fix

如果檔案正常,重啟就可以恢復了!

重寫規則說明

aof 預設就是檔案的無限追加,檔案會越來越大!

如果aof 檔案大於 64m,就會fork一個新的程序來將我們的檔案進行重寫!

優點和缺點

appendonly no   # 預設是不開啟aof模式的,預設是使用rdb方式持久化的,在大部分情況下,rdb完全夠用了!

appendfilename "appendonly.aof"  # 持久化的檔案的名字   

# appendfsync always  # 每次修改都會 sync ,消耗效能
appendfsync everysec   # 每秒執行一次  sync,可能會丟失這1S的資料!
# appendfsync no   # 不執行  sync,這個時候,作業系統自己同步資料,速度是最快!


優點:

1、每一次修改都同步,檔案完整性會更好!

2、每秒同步一次,可能會丟失一秒的資料!

3、從不同步,效率最高

缺點:

1、 相對資料檔案來說,aof遠遠大於 rdb,修復的速度也比 rdb 慢!

2、aof 執行效率 也要比 rdb慢,所以redis預設的配置就是 rdb 持久化!

擴充套件:

1、RDB持久化方式能夠在指定的時間間隔內對你的資料進行快照儲存

2、AOF持久化方式記錄每次對伺服器寫的操作,當伺服器重啟的時候會重新執行這些命令來恢復原始的資料,AOF命令以Redis協議追加儲存每次寫的操作到檔案末尾,Redis還能對AOF檔案進行後臺重寫,使得AOF檔案的體積不至於過大。

3、只做快取,如果你只希望你的資料在伺服器執行的時候存在,也可以不使用任何持久化

4、同時開啟兩種持久化方式

  • 在這種情況下,當redis重啟的時候會優先載入AOF檔案來恢復原始的資料,因為在通常情況下AOF檔案儲存的資料集要比RDB檔案儲存的資料集要完整。
  • RDB的資料不實時,同時使用兩者伺服器重啟也只會找AOF檔案,那要不要只使用AOF呢?作者建議不要,因為RDB更適合用於備份資料庫(AOF在不斷地變化不好備份),快速重啟,而且不會有AOF可能潛在的Bug,留著作為一個萬一的手段。、

5、效能建議

  • 因為RDB檔案只用作後備用途,建議只在Slave上持久化RDB檔案,而且只要15分鐘備份一次就夠了,值保留 save 900 1 這條規則。
  • 如果Enable AOF,好處是在最惡劣的情況下也只會丟失不超過兩秒資料,啟動指令碼較簡單值load自己的AOF檔案就可以了,代價以是帶來了持續的IO,二是AOF rewrite 的最後將 rewrite 過程中產生的新資料寫到新檔案造成的阻塞幾乎是不可避免的。只要硬碟許可,應該儘量減少AOF rewrite的頻率,AOF重寫的基礎大小預設64M太小了,可以設到5G以上,預設超過原大小100%大小重寫可以改到適當的數值。
  • 如果不Enable AOF,僅靠Master-Slave Repllcation 實現高可用性也可以,能聲調一大筆IO,也幾點少了 rewrite 時帶來的系統波動。代價是 如果 Master/Slave 同時倒掉,會丟失十幾分鐘的資料,啟動指令碼也要比較兩個 Master/Slave 中的RDB檔案,載入較新的那個,微博就是這種架構。

Redis釋出訂閱

Redis釋出訂閱(pub/sub)是一種訊息通訊模式:傳送者(pub)傳送訊息,訂閱者(sub)接收訊息。微博、微信、關注系統!

Redis客戶端可以訂閱任意數量的頻道。

訂閱/釋出訊息圖:

第一個:訊息傳送者

第二個:頻道

第三個:訊息訂閱者!

命令

這些命令被廣泛用於構建即使通訊應用,比如網路聊天室(chatroom)和實時廣播、實時提醒等。

測試

訂閱端:

127.0.0.1:6379> SUBSCRIBE kingtl    # 訂閱一個頻道 kingtl
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kingtl"
3) (integer) 1

#等待讀取推送的資訊

1) "message"    # 訊息
2) "kingtl"     # 哪個頻道的訊息
3) "hello,kingtl"  # 訊息的具體內容

1) "message"
2) "kingtl"
3) "hello,redis"

傳送端:

127.0.0.1:6379> PUBLISH kingtl "hello,kingtl"   # 釋出者 釋出訊息到頻道!
(integer) 1
127.0.0.1:6379> PUBLISH kingtl "hello,redis"    # 釋出者 釋出訊息到頻道!
(integer) 1

原理

  • Redis是使用C 實現的,通過分析 Redis 原始碼裡的 pubsub.c 檔案,瞭解釋出和訂閱機制的底層實現,藉此加深對 Redis 的理解。

  • Redis 通過 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令實現釋出和訂閱功能。

  • 通過 SUBSCRIBE 命令訂閱某頻道後,redis-server 裡維護了一個字典,字典的鍵就是一個個 頻道!而字典的值則是一個連結串列,連結串列中儲存了所有訂閱這個 channel 的客戶端。SUBSCRIBE 命令的關鍵,就是講客戶端新增到給定 channel 的訂閱連結串列中。

  • 通過 PuBLISH 命令想訂閱者傳送訊息,redis-server 會使用給定的頻道作為鍵,在它所維護的 channel 字典中查詢記錄了訂閱這個頻道的所有客戶端的連結串列,遍歷這個連結串列,將訊息釋出給所有訂閱者。

  • Pub/Sub 從字面上理解就是 釋出(Publish) 與訂閱(Subscribe) ,在Redis中,你可以設定對某一個key值進行訊息釋出及訊息訂閱,當一個key值進行了訊息釋出後,所有訂閱它的客戶端都會受到相應的訊息。這一功能最明顯的用法就是用作實時訊息系統,比如普通的即時聊天,群聊等功能。

使用場景

1、實時訊息系統!

2、實時聊天!(頻道當做聊天室,將資訊回顯給所有人!)

3、訂閱、關注系統都是可以的!

稍微複雜的場景,就會使用 訊息中介軟體

Redis主從複製

概念

主從複製:是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。前者稱為主節點(master/leader),後者稱為從節點(slave/follower);資料的複製是單項的,只能由主節點到從節點。Master以寫為主,Slave 以讀為主。

預設情況下,每臺Redis伺服器都是主節點;

且一個主節點可以有多個從節點(或者沒有從節點),但一個從節點只能有一個主節點。

主從複製的作用:

1、資料冗餘:主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘方式。

2、故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗餘。

3、複雜均衡:在主從複製的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連線主節點,讀Redis資料時應用連線從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的併發量。

4、高可用(叢集)基石:出了上述作用以外,主從複製還是 哨兵叢集 能夠實施的基礎,因此說主從複製是Redis高可用的基礎。

一般來說,要將Redis運用於工程專案中,只使用一臺Redis是萬萬不能的(宕機,一主二從),原因如下:

1、從 結構 上,單個Redis伺服器會發生 單點故障,並且一臺伺服器需要處理所有的請求負載,壓力較大;

2、從 容量 上,單個Redis伺服器記憶體容量有限,就算一臺Redis伺服器記憶體容量為 256G,也不能將所有記憶體用作 Redis 儲存記憶體,一般來說,單臺Redis最大使用記憶體不應該超過20G。

電商網站上的商品,一般都是一次上傳,無數次瀏覽的,說專業點也就是“多讀少寫”。

對於這種場景,我們可以使用如下架構:

主從複製! 讀寫分離!80%的情況下都是在進行讀操作!減緩伺服器壓力!架構中經常使用! 一主二從!

只要在公司中,主從複製就是必須要使用的,因為在真實的專案中不可能單機使用 Redis !

環境配置

只配置從庫,不用配置主庫

127.0.0.1:6379> info replication    # 檢視當前庫的資訊
# Replication
role:master      # 角色  master
connected_slaves:0    # 沒有從機
master_replid:55bc3bb13244688151e477aecf5c7f3e2b56fac3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

複製3個配置檔案,然後修改對應的資訊

1、埠

2、pid名字

3、log檔名字

4、dump.rdb 名字

修改完畢之後,啟動3個redis伺服器,可以通過程序資訊檢視!

一主二從

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

# 在從機中配置
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379    # slaveof host ip 認主機
OK
127.0.0.1:6380> info replication
# Replication
role:slave   # 當前角色   從機
master_host:127.0.0.1  # 主機的資訊
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:ebf8b7253354b8f2267d5de86bd0e945b29a9776
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
127.0.0.1:6380> 


# 在主機中檢視
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1    # 多了從機的配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=56,lag=0   # 多了從機的配置
master_replid:ebf8b7253354b8f2267d5de86bd0e945b29a9776
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:56
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:56
127.0.0.1:6379> 

如果兩個從機都配置完了,在主機檢視就是有兩個從機的

注意

真實的從主配置應該在配置檔案中配置,這樣的話是永久的。 這裡使用的命令配置,是臨時的。

細節

主機可以寫,從機不能寫 只能讀!主機中的所有資訊和資料,都會自動被從機儲存!

主機寫:

從機只能讀取內容:

測試:主機斷開連線,從機依舊連線到主機的,但是沒有寫操作,這個時候,主機如果恢復了,從機依舊可以直接獲取到主機寫的資訊!

如果是使用命令列,來配置的主從,這個時候如果重啟了,就會變回主機!只要變為從機,立馬就會從主機中獲取值!

複製原理

Slave 啟動成功連線到 master 後會傳送一個 Sync同步 命令

Master 接到命令,啟動後臺的存檔程序,同時收集所有接收到的用於修改資料集命令,在後臺程序執行完畢之後,master將傳送整個資料檔案到slave,並完成一次完全同步。

全量複製:而slave服務在接收到資料庫檔案資料後,將其存檔並載入到記憶體中。

增量複製:Master 繼續將新的所有收集到的修改命令依次傳給slave,完成同步。

但是隻要是重新連線master,一次完全同步(全量複製)將被自動執行。主機中的資料一定可以在從機中看到。

層層鏈路

上一個 M 連線下一個 S !也可以完成主從複製!

如果主機宕機了,從機中選擇出一個作為主機!手動!哨兵模式沒出來之前!

如果主機斷開了連線, 我們可以使用 SLAVEOF no one讓自己程式設計主機! 其他的節點就可以手動連線到最新的這個主節點(手動)! 如果主機這個時候恢復了,那就只能重新連線!

127.0.0.1:6381> SLAVEOF no one
OK
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_replid:5de4bf9d435aee276d1dda6fe23efc5bd032f5a8
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

哨兵模式

(自動選舉主機的模式)

概述

主從切換技術的方法是:當主伺服器宕機後,需要手動把一臺從伺服器切換為主伺服器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。這不是一種推薦的方式,更多時候,我們優先考慮哨兵模式。Redis從2.8開始正式提供了 Sentinel(哨兵)架構來解決這個問題。

謀朝篡位的自動版,能夠後臺監控主機是否故障,如果故障了根據投票數自動將從庫轉換為主庫。

哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的程序,作為程序,它會獨立執行。其原理是 哨兵通過傳送命令,等待Redis伺服器響應,從而監控執行的多個Redis例項。

這裡的哨兵有兩個作用

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

然而一個哨兵程序對Redis伺服器進行監控,可能會出現問題,為此,我們可以使用多個哨兵進行監控。各個哨兵之間還會進行監控,這樣就形成了多哨兵模式。

假設主伺服器宕機,哨兵1線檢測到這個結果,系統並不會馬上進行 failover 過程,僅僅是哨兵1主管的認為主伺服器不可用,這個現象稱為 主觀下線。當後面的哨兵也檢測到主伺服器不可用,並且數量達到一定值時,那麼哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行 failover [故障轉移]操作。切換成功後,就會通過釋出訂閱模式,讓各個哨兵把自己監控的從伺服器實現切換主機,這個過程稱為 客觀下線

測試

1、配置哨兵配置檔案

# sentinel monitor 被監控的名稱 host port 1   數字1,代表主機掛了,讓slave投票,票數最多最多的就會稱為主機
sentinel monitor myredis 127.0.0.1 6379 1 

2、啟動哨兵

[root@KingTL myredis]# redis-sentinel /myredis/sentinel.conf 
22933:X 03 Aug 2020 18:36:47.800 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
22933:X 03 Aug 2020 18:36:47.800 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=22933, just started
22933:X 03 Aug 2020 18:36:47.800 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 5.0.5 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 22933
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

22933:X 03 Aug 2020 18:36:47.802 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
22933:X 03 Aug 2020 18:36:47.815 # Sentinel ID is 86c0d61e90caf14717b0f476b6237412b2dafa3b
22933:X 03 Aug 2020 18:36:47.815 # +monitor master myredis 127.0.0.1 6379 quorum 1
22933:X 03 Aug 2020 18:36:47.817 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
22933:X 03 Aug 2020 18:36:47.819 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

如果 Master 節點斷開了,這個時候就會從從機中隨機選擇一個伺服器!(這裡面有一個投票演算法!)

哨兵日誌:

如果主機此時恢復了,只能歸併到新的主機下,當做從機,這就是哨兵模式的規則!

哨兵模式

優點:

1、哨兵叢集,基於主從複製模式,所有的主從配置優點,它都有!

2、主從可以切換,故障可以轉移,系統的可用性就會更好!

3、哨兵模式就是主從模式的升級,手動到自動,更加健壯!

缺點:

1、Redis不好線上擴容,叢集容量一旦到達上限,線上擴容就十分麻煩!

2、實現哨兵模式的配置其實是很麻煩的,裡面有很多選擇!

哨兵模式的全部配置

# Example sentinel.conf

#哨兵 sentinel 例項執行的埠  預設 26379
prot 26379

# 哨兵 Sentinel 的工作目錄
div /tmp

# 哨兵 sentinel 監控的redis主節點的 ip  port 
# master-name  可以自己命名的主節點名字,只能有字母A-Z、數字0-9、這三個字元“.-_”組成。
# quorum 配置多少個 sentinel 哨兵統一認為 master 主節點失聯,那麼這時客觀上認為主節點失聯了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 當在Redis例項中開啟了 requirepass foobared 授權密碼 這樣所有連線Redis例項的客戶端都要提供密碼
# 設定哨兵 Sentinel 連線主從的密碼  注意必須為主從設定一樣的驗證密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

#指定多少毫秒之後 主節點沒有應答哨兵 sentinel 此時 哨兵主管上認為主節點下線 預設30秒
sentinel down-after-millisecondes mymaster 30000

# 這個配置項指定了在發生 failover 主備切換時最多可以有多少個 slaver 同時對新的 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 被糾正為想正確的 mater 那裡同步資料時。
#3、當想要取消一個正在進行的 failover 所需要的時間。
#4、當進行 failover 時,配置所有 slaves 執行新的 master 所需要的最大時間。不過,即使過了這個超時,slaves 依然會被正確配置為指向master,但是就不按 parallel-syncs 所配置的規則來了
# 預設三分鐘
# sentinel faulover-timeout <master-name> <millisenconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTTION

# 配置當某一事件發生時所需要執行的指令碼,可以通過指令碼來通知管理員,例如當系統執行不正常時發郵件通知相關人員。
# 對於指令碼的執行結果有以下規則:
# 若指令碼執行後返回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快取的使用,極大的提升了應用程式的效能和效率,特別是資料查詢方面。但同時,它也帶來了一些問題。其中,最要害的問題,就是資料的一致性問題,從嚴格意義上講,這個問題無解。如果對資料一致性要求很高,那麼就不能使用快取。

另外的一些典型問題就是,快取穿透、快取雪崩和快取擊穿。目前,業界也都有比較流行的解決方案。

快取穿透(查不到)

概念

快取穿透的概念很簡單,使用者想要查詢一個數據,發現redis記憶體資料庫沒有,也就是快取沒有命中,與是向持久層資料庫查詢。發現也沒有,與是本次查詢失敗。當用戶很多的時候,快取都沒有命中(秒殺!!!場景),與是都去請求了持久層資料庫。這回給持久層資料庫造成很大壓力,這時候就相當於出現了快取穿透。

解決方案

布隆過濾器

布隆過濾器是一種資料結構,對所有可能查詢的引數以 hash 形式儲存,在控制層先進行校驗,不符合則丟棄,從而避免了對底層儲存系統的查詢壓力;

快取空物件

當儲存層不命中後,即使返回的空物件也會將其快取起來,同時會設定一個過期時間,之後再訪問這個資料將會從快取中獲取,保護了後端資料來源;

但是這種方法存在兩個問題:

1、如果空值能夠被快取起來,這就以為著快取需要更多的空間儲存更多的鍵,因為這當中可能會有很多的空值的鍵;

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

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

概述

這裡需要注意和快取穿透的區別,快取擊穿,是指一個key非常熱點,在不停的扛著大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破快取,直接請求資料庫,就想在一個屏障上鑿開了一個洞。

當某個key在過期的瞬間,有大量的請求併發訪問,這類資料一般是熱點資料,由於快取過期,會同時訪問資料庫來查詢最新資料,並且回寫快取,會導致資料庫瞬間壓力過大。

解決方案

設定熱點資料永不過期

從快取曾當面上來看,沒有設定過期時間,所有不會出現熱點key過期後產生的問題。

加互斥鎖

分散式鎖:使用分散式鎖,保證對於每個key同時只有一個執行緒去查詢後端服務,其他執行緒沒有活兒分散式鎖的許可權,因此只需要等待即可。這種方式將高併發的壓力轉移到了分散式鎖,因此對分散式鎖的考驗很大。

快取雪崩

概念

快取雪崩,是指在某一個時間段,快取集中過期失效。Redis宕機!

產生雪崩的原因之一,比如在寫文字的時候,馬上就要到雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了快取,假設快取一個小時。那麼到了凌晨一點鐘的時候,這批商品的快取就都過期了。而對這批商品的訪問查詢,都落到了資料庫上,對於資料庫而言,就會產生週期性的壓力波峰。於是所有的請求都會達到儲存層,儲存層的呼叫量會暴增,造成儲存層也會掛掉的情況。

其實集中過期,倒不是非常致命,比較致命的快取雪崩,是快取伺服器某個節點宕機或斷網。因為自然形成的快取雪崩,一定是在某個時間段集中建立快取,這個時候,資料庫也是可以頂住壓力的。無非就是對資料庫產生週期性的壓力而已。而快取服務節點的宕機。對資料庫伺服器造成的壓力是不可預知的。很有可能瞬間就把資料庫壓垮。

解決方案

redis高可用

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

限流降級

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

資料預熱

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