1. 程式人生 > 其它 >Redis技術總結

Redis技術總結

Redis技術總結

1,Nosql概述

為什麼要使用nosql

大資料時代(一般的資料庫無法進行分析處理),hadoop(2006)、

1.1,資料庫發展史

1,單機mysql版

90年代,網站訪問量很少,單個數據庫就夠了!更多使用的是靜態網頁

這種情況下整個網站的瓶頸:

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

2,資料的索引(B+Tree)300萬就

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

當出現上面的情況,就必須要升級(晉級)!

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

​ 網站80%的情況都是讀資料,使用資料庫去查詢肯定很不方便,直接使用快取來加快查詢速度

發展過程:優化資料結構和索引->檔案快取(IO)->Memchached

3,分庫分表+水平拆分+Mysql叢集

資料庫的本質:讀、寫

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

Innodb:行鎖

後續慢慢使用分庫分表來解決寫的壓力!MySql在那個時候推出了表分割槽(但是,沒有多少人使用)

Mysql叢集很好的滿足了那個年代的所有需求

4,如今的現在

mysql等關係型資料庫不夠用了!各種各樣的資料需求(視訊、圖片、位置、閱覽量、使用者日誌等)

這個時候需要使用nosql

1.2,什麼是NoSQL

​ 關係型資料庫:表格、行、列(POI)

​ not only SQL(不僅僅是SQL),泛指非關係型資料庫,隨著web2.0網際網路誕生,尤其是超大規模的高併發社群.NoSQL在當前大資料時代發展十分迅速,redis是發展最快的一種!

​ 很多型別的資料,社交網路、地理位置等!儲存不需要一個固定的格式,不需要多餘的操作即可橫向擴充套件!例如Map<String,Object>使用鍵值對來控制!

NoSQL特點

解耦!

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

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

3,資料型別多樣!(不需要事先設計資料庫!隨取隨用!如果是資料量十分大的表,無法設計了)

4,傳統RDBMS和NoSQL的區別

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

瞭解:3V+3高

大資料時代的3V:主要是描述問題(海量volume、多樣variety、實時velocity)

大資料時代的3高:主要是對程式的要求(高併發、高可擴{隨時水平拆分、擴充套件}、高效能)

實際實踐中是NoSQL+RDBMS一起使用

1.3,阿里巴巴演進史

沒有什麼問題是加一層解決不了的!

1,商品的基本資訊
	名稱、價格、商家資訊(使用MySQL即可)
2,商品的描述、評論(文字比較多)
	文件型資料庫中,MongoDB
3,圖片
	分散式檔案系統FastDFS
	- 淘寶的 TFS
	- GOOGLE的 GFS
	- hadoop的 HDFS
	- 阿里雲的 OSS
4,商品的關鍵字(搜尋)
	- 搜尋引擎 solr elasticsearch
	- ISearch:多隆
5,熱門的波段資訊
	- 記憶體資料庫
	- redis、taur、memachached
6,商品的交易,外部的支付介面
	- 三方應用

一個簡單的網頁背後-技術一定不是大家所想的那麼簡單!

大型網際網路應用問題!

  • 資料型別太多
  • 資料來源繁多,經常重構
  • 資料需要改造,大面積改造

1.4,NoSQL的四大分類

①KV鍵值對

  • 新浪:redis
  • 美團:redis + tair
  • 阿里、百度:redis+memecached

②文件資料庫

(bson格式binary和json格式一樣)

  • MongoDB(一般必須掌握)

    • 基於分散式檔案儲存的資料庫,C++編寫,主要用於處理大量的文件
    • 介於關係型資料庫和非關係資料庫中間的產品!交集
  • CountDB

③列儲存資料庫

  • HBase
  • 分散式檔案系統

④圖形關係資料庫

不是儲存圖形的,儲存的是關係,例如:朋友圈社交網路,廣告推薦

Neo4j,InfoGrid:

4種大類的粗略對比

2,redis入門

2.1,概述

是什麼?

​ redis (remote dictionary server),即遠端字典服務,是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。從2010年3月15日起,Redis的開發工作由VMware主持。從2013年5月開始,Redis的開發由Pivotal贊助。

能幹什麼?

1,記憶體儲存、持久化,記憶體中是斷電即丟,所以持久化很重要(rdb、aof)

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

3,釋出訂閱系統

4,地圖資訊分析

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

6,.......

特性

1,多樣的資料型別

2,持久化

3,叢集

4,事務

....

2.2,安裝redis

2.2.1,docker容器安裝

安裝最新的redis映象(注意:redis映象好像自己不帶配置檔案的,需要額外自己下載)

docker search redis
docker pull redis

# dockerhub官網直接執行樣例
docker run -itd --name ac-redis -p 6379:6379 redis

# 我自己使用redis.conf配置檔案掛載配置啟動的命令
docker run -itd \
-p 6379:6379 \
-v /opt/docker/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-v /opt/docker/redis/data:/data \
--name ac-redis \
redis \
redis-server /usr/local/etc/redis/redis.conf

注意事項!redis.conf檔案中有一個bind 配置項,該配置表示只接受繫結該ip的請求來源,當前我們使用docker啟動的話,可以直接註釋掉並且設定redis的密碼配置requirepass 。否則,我們只能在docker映象內訪問redis服務,外部(其他主機)無法連線成功!

2.3,測試效能

​ redis-benchmark是官方自帶的一個壓力測試工具!redis-benchmark命令引數。

​ 簡單測試:

# 測試:100並非連線,100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

王婆賣瓜自賣自誇,redis自己測試自己有多強!

2.4,基礎知識

redis預設有16個數據庫(0-15),預設使用第0個庫

127.0.0.1:6379> select 3 # 選擇資料庫
OK
127.0.0.1:6379[3]> DBSIZE # 檢視當前庫下資料情況
0
127.0.0.1:6379[3]> set hello world
OK
127.0.0.1:6379[3]> DBSIZE
1
127.0.0.1:6379[3]> keys * # 檢視所有key
1) "hello"
127.0.0.1:6379> flushall # 清空所有資料庫
OK

redis使用6379埠的原因-merz廣告女的故事(6宮格鍵盤)

mysql使用3306埠-女兒的名字

redis是單執行緒的!

​ redis是基於記憶體操作,cpu不是redis效能瓶頸,redis的瓶頸是根據機器的記憶體和網路頻寬,既可以使用單執行緒來實現,就使用單執行緒!

​ redis是C語言寫的,官方提供的資料為100000+的QPS,不比memecache(k-v的)差!

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

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

cpu>記憶體>硬碟的速度比較

​ 核心:redis是將所有資料全部放在記憶體中,所以使用單執行緒去操作效率最高,多執行緒(cpu上下文切換-耗時1500nm-2000nm之間),對於記憶體系統來說,如果沒有上下文切換效率就是最高的!多次記憶體讀寫都是在同一個執行緒上,這個就是最佳方案!(詳情見redis 單執行緒的理解

2.5,使用場景

序號 場景 描述 關聯技術
1 快取 利用redis的高併發儲存熱點資料 redis為什麼這麼快?
2 資料共享 session共享 將session儲存redis
3 分散式鎖 單節點的set lock 1 nx ex
多節點的redLock
其實本質還是資料共享的功能
4 分散式ID 利用incr k1命令的原子性 ,獲取分散式ID 分散式ID的解決方案
5 計數器 還是利用incr 命令來進行數值統計
例如:請求數、呼叫次數等
incr命令,原子性
6 限流 本質還是計數器的拓展
利用計數器統計訪問次數,然後限制訪問
限流與熔斷
7 位統計 本質就是bitmap資料型別的特性 bitmap資料結構特性
8 購物車 本質就是redis的hashMap資料結構的特性
可以儲存物件資料
hashMap資料結構儲存物件
9 使用者訊息時間線
timeline
本質就是redis的list雙向連結串列資料結構特性
可以記錄訊息的先後順序
雙向連結串列資料結構,FIFO、LIFO
10 訊息佇列 redis自己有一個訊息佇列的功能
不過現在基本上都沒有人使用
11 抽獎 redis set中的一個隨機數功能
12 點贊、簽到、打卡 redis set資料結構的記錄與統計功能
13 商品標籤 redis set資料結構的記錄與統計功能
14 商品篩選、互相關注 redis set資料結構的交併集功能
15 地理位置功能 redis geography資料結構 地理位置資料結構
16 排行榜 redis zset資料結構 跳錶,類似於B+數
17 網站的閱歷次數估算 hyperloglog資料結構 底層伯努利概率分佈

2.6,注意事項

序號 可能出現的問題 描述
1 資料一致性問題 redis叢集是一個弱一致性的快取
想要實現強一致性,就需要使用類似qurom查詢N/2+1個節點進行確認資料
例如:redLock
2 大key問題 由於資料操作是單執行緒,所有的資料操作都是需要排隊處理的
所以,遇見大key時,會有等待導致的併發降低
3 資料預熱問題 我們使用快取,如果不進行資料預熱
系統初始化後的一段時間,基本上所有的請求都會打到DB
4 快取穿透 請求客觀不存在的資料
如果不做響應的處理(布隆過濾器、存null值等),那麼每次請求都會打到DB
5 快取擊穿 某個快取過期後,併發請求直接都打到DB中
使用分散式鎖解決
6 雪崩 大量快取同一時刻過期,導致大量DB查詢
解決方案:①隨機過期時間;②redis高可用(多節點備用);③限流降級(分散式鎖);④資料預熱

3,五大資料型別

​ Redis 是一個開源(BSD 許可)的記憶體資料結構儲存,用作資料庫、快取和訊息代理。Redis 提供資料結構,例如字串(String)、雜湊(hashes)、列表(list)、集合(sets)、具有範圍查詢的排序集合(sorted sets)、點陣圖(bitmaps)、超日誌(hyperloglogs)、地理空間索引(geospatial indexes)和流(streams)。Redis 具有內建複製、Lua 指令碼(Lua scripting)、LRU 驅逐(LRU eviction)、事務(transactions)和不同級別的磁碟永續性(different levels of on-disk persistence),並通過哨兵(Redis Sentinel) 和叢集(Redis Cluster) 自動分割槽提供高可用性(high available)。

注意:redis不區分大小寫命令

3.1,String

90%的程式設計師只會使用字串型別!(自增可以用於我自己的fileserver瀏覽量統計)

命令

append(追加字串,如果不存在則set)、strlen(檢視長度)、keys *(檢視所有的key)、

incr(自增1)、decr(自減1)、incrby(按照步長增加)、decrby(按照步長減少)、

getrange(字串擷取)、setrange(字串替換)、

setex(設定過期時間)、setnx(如果不存在才設定-用於分散式鎖)、

mset(設定多個值)、mget(批量獲取多個值)、

msetnx(批量設定多個只有不存在才設定的值,msetnx保證原子性!)、msetex(不存在該命令!)

getset(先獲取值然後設定值)

點選檢視命令執行情況

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> flashdb
(error) ERR unknown command `flashdb`, with args beginning with: 
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
1) "hello"
127.0.0.1:6379> append hello -wanyu
(integer) 11
127.0.0.1:6379> get hello
"world-wanyu"
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> append k1 -vvv
(integer) 6
127.0.0.1:6379> get k1
"v1-vvv"
127.0.0.1:6379> strlen k1
(integer) 6
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> incrby views 10
(integer) 12
127.0.0.1:6379> decrby views 10
(integer) 2
127.0.0.1:6379> decrby views 10
(integer) -8
127.0.0.1:6379> getrange k1 0 -1
"v1-vvv"
127.0.0.1:6379> getrange k1 0 2
"v1-"
127.0.0.1:6379> set k2 abcdefg
OK
127.0.0.1:6379> setrange k2 3 xxx
(integer) 7
127.0.0.1:6379> get k2
"abcxxxg"
127.0.0.1:6379> setex k3 10 v3
OK
127.0.0.1:6379> ttl k3
(integer) 7
127.0.0.1:6379> ttl k3
(integer) 6
127.0.0.1:6379> ttl k3
(integer) 5
127.0.0.1:6379> ttl k3
(integer) 4
127.0.0.1:6379> ttl k3
(integer) 4
127.0.0.1:6379> ttl k3
(integer) 3
127.0.0.1:6379> ttl k3
(integer) 3
127.0.0.1:6379> ttl k3
(integer) 2
127.0.0.1:6379> ttl k3
(integer) -2
127.0.0.1:6379> ttl k3
(integer) -2
127.0.0.1:6379> setnx k4 v4
(integer) 1
127.0.0.1:6379> setnx k4 v4
(integer) 0
127.0.0.1:6379> setnx k4 v444
(integer) 0
127.0.0.1:6379> get k4
"v4"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> mset k1 v1 k2 v2
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
127.0.0.1:6379> get v2
(nil)
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
127.0.0.1:6379> msetnx k1 v1 k4 v4
(integer) 0
127.0.0.1:6379> msetnx k3 v3 k4 v4
(integer) 1
127.0.0.1:6379> msetex k5 v5 k6 v6
127.0.0.1:6379> mset user:1:name wanyu user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "wanyu"
2) "18"
127.0.0.1:6379> mset view:1 1 view:2 10
OK
127.0.0.1:6379> get view:1 view:2
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> mget view:1 view:2
1) "1"
2) "10"
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379>

特殊用法:

# 批量設定使用者資訊
127.0.0.1:6379> mset user:1:name wanyu user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "wanyu"
2) "18"

# 批量設定閱覽量
127.0.0.1:6379> mset view:1 1 view:2 10
OK
127.0.0.1:6379> mget view:1 view:2
1) "1"
2) "10"

# getset語句
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"

string類似使用場景:

  • 計數器
  • 統計多單位的數量 set uid:4564456:follow 0,然後使用incr key統計!
  • 粉絲數
  • 物件快取儲存

3.2,List

基本的資料型別(雙端無環連結串列),列表可以當做棧(heap,單端、先入後出)、佇列(queue、單向進入,反向輸出、先入先出)、阻塞佇列(兩端都可以取阻塞佇列)!

命令

lpush(將一個值插入列表頭部,左)、rpush(將一個數據插入列表尾部,右)、

lpop(彈出左邊的值)、rpop(彈出右邊的值)、lrange(檢視list資料)、

lindex(通過下標獲取某個佇列index下標的值,用於生產者和消費者模型)、

llen(檢視list的長度)、lrem(移除指定個數的值)、ltrim(擷取保留指定的長度)、

rpoplpush(移除列表最右邊的元素,新增到新列表的最左邊)、lset(給指定的下標設值-更新操作)、

linsert(將某個元素插入到某個已有元素的之前或之後)

點選檢視命令執行情況

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> lpush l1 1
(integer) 1
127.0.0.1:6379> lpush l1 2
(integer) 2
127.0.0.1:6379> lpush l1 3
(integer) 3
127.0.0.1:6379> keys
(error) ERR wrong number of arguments for 'keys' command
127.0.0.1:6379> keys *
1) "l1"
127.0.0.1:6379> lrange l1 1 3
1) "2"
2) "1"
127.0.0.1:6379> lrange l1 1 2
1) "2"
2) "1"
127.0.0.1:6379> lrange l1 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> rpush 10
(error) ERR wrong number of arguments for 'rpush' command
127.0.0.1:6379> rpush l1 10
(integer) 4
127.0.0.1:6379> lrange 0 -1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange l1 0 -1
1) "3"
2) "2"
3) "1"
4) "10"
127.0.0.1:6379> lpop l1
"3"
127.0.0.1:6379> rpop l1
"10"
127.0.0.1:6379> lindex l1 1
"1"
127.0.0.1:6379> lindex l1 2
(nil)
127.0.0.1:6379> lindex l1 0
"2"
127.0.0.1:6379> llen l1
(integer) 2
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> lpush l1 0
(integer) 1
127.0.0.1:6379> lpush l1 1
(integer) 2
127.0.0.1:6379> lpush l1 2
(integer) 3
127.0.0.1:6379> lpush l1 3
(integer) 4
127.0.0.1:6379> lpush l1 4
(integer) 5
127.0.0.1:6379> lpush l1 5
(integer) 6
127.0.0.1:6379> lpush l1 6
(integer) 7
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> llen l1
(integer) 7
127.0.0.1:6379> lrange -1 0
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange 0 -1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange l1 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "1"
7) "0"
127.0.0.1:6379> lrem l1 1 1
(integer) 1
127.0.0.1:6379> lrem l1 1 1
(integer) 0
127.0.0.1:6379> lrange l1 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "0"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys
(error) ERR wrong number of arguments for 'keys' command
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> rpush l1 1
(integer) 1
127.0.0.1:6379> rpush l1 2
(integer) 2
127.0.0.1:6379> rpush l1 3
(integer) 3
127.0.0.1:6379> rpush l1 4\
(integer) 4
127.0.0.1:6379> rpush l1 5
(integer) 5
127.0.0.1:6379> lrange l1 0 -1
1) "1"
2) "2"
3) "3"
4) "4\\"
5) "5"
127.0.0.1:6379> ltrim l1 4 5
OK
127.0.0.1:6379> lrange l1 0 -1
1) "5"
127.0.0.1:6379> flushdb 
OK
127.0.0.1:6379> rpush l1 1
(integer) 1
127.0.0.1:6379> rpush l1 2
(integer) 2
127.0.0.1:6379> rpush l1 3
(integer) 3
127.0.0.1:6379> rpush l1 4
(integer) 4
127.0.0.1:6379> lrange l1 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> ltrim l1 1 2
OK
127.0.0.1:6379> lrange l1 0 -1
1) "2"
2) "3"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> rpush l1 1
(integer) 1
127.0.0.1:6379> rpush l1 2
(integer) 2
127.0.0.1:6379> rpush l1 3
(integer) 3
127.0.0.1:6379> rpoplpush l1 l2
"3"
127.0.0.1:6379> lrange l1 0 -1
1) "1"
2) "2"
127.0.0.1:6379> lrange l2 0 -1
1) "3"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> exists l1
(integer) 0
127.0.0.1:6379> lset l1 0 0
(error) ERR no such key
127.0.0.1:6379> lpush l1 1
(integer) 1
127.0.0.1:6379> exists l1
(integer) 1
127.0.0.1:6379> lset l1 0 0
OK
127.0.0.1:6379> lrange l1 0 -1
1) "0"
127.0.0.1:6379> lset l1 1 0
(error) ERR index out of range
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> rpush l1 1
(integer) 1
127.0.0.1:6379> rpush l1 2
(integer) 2
127.0.0.1:6379> rpush l1 3
(integer) 3
127.0.0.1:6379> rpush l1 4
(integer) 4
127.0.0.1:6379> rpush l1 5
(integer) 5
127.0.0.1:6379> linsert l1 before 3 33
(integer) 6
127.0.0.1:6379> lrange l1 0 -1
1) "1"
2) "2"
3) "33"
4) "3"
5) "4"
6) "5"
127.0.0.1:6379> linsert l1 after 3 333
(integer) 7
127.0.0.1:6379> lrange l1 0 -1
1) "1"
2) "2"
3) "33"
4) "3"
5) "333"
6) "4"
7) "5"
127.0.0.1:6379> 

小結

  • 其本質是一個連結串列,before node after,left 、right都可以插入值
  • 如果key不存在,建立新的連結串列
  • 如果key存在,新增內容
  • 如果移除了所有制,空連結串列,也可以代表不存在!
  • 在兩邊插入或者改動值,效率最高!中間元素,效率相對較低
  • 使用:訊息佇列(lpush、rpop)、棧(lpush、lpop)

3.3,Set

set的值是不能重複的,set是無序不重複集合

命令

sadd(新加元素)、smembers(檢視所有元素)、sismember(檢視是否存在)、

scard(獲取集合中元素個數)、srem(移除集合中的某個元素)、

srandmember(隨機獲取指定個數的元素)、spop(隨機移除一個元素)、

smove(將某個集合中的元素移除,並新增到另一個集合)、

sdiff(差集!檢視多個集合不同的元素)、sinter(交集!檢視多個集合相同的元素)、

sunion(並集!獲取多個集合合併後的結果)

點選檢視命令執行情況

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> 
127.0.0.1:6379> sadd s1 1
(integer) 1
127.0.0.1:6379> sadd s1 2
(integer) 1
127.0.0.1:6379> sadd s1 1
(integer) 0
127.0.0.1:6379> smembers s1
1) "1"
2) "2"
127.0.0.1:6379> sismember s1 3
(integer) 0
127.0.0.1:6379> sismember s1 2
(integer) 1
127.0.0.1:6379> sismember s2 2
(integer) 0
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> sismember k1 v1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> sadd k1 1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> scard s1
(integer) 2
127.0.0.1:6379> srem s1 1
(integer) 1
127.0.0.1:6379> srem s1 1
(integer) 0
127.0.0.1:6379> srandmember s1
"2"
127.0.0.1:6379> srandmember s1
"2"
127.0.0.1:6379> srandmember s1
"2"
127.0.0.1:6379> sadd s1 1
(integer) 1
127.0.0.1:6379> sadd s1 3
(integer) 1
127.0.0.1:6379> sadd s1 4
(integer) 1
127.0.0.1:6379> sadd s1 5
(integer) 1
127.0.0.1:6379> sadd s1 6
(integer) 1
127.0.0.1:6379> sadd s1 7
(integer) 1
127.0.0.1:6379> sadd s1 8
(integer) 1
127.0.0.1:6379> sadd s1 9
(integer) 1
127.0.0.1:6379> sadd s1 0
(integer) 1
127.0.0.1:6379> scard s1
(integer) 10
127.0.0.1:6379> srandmember s1
"3"
127.0.0.1:6379> srandmember s1 3
1) "6"
2) "9"
3) "1"
127.0.0.1:6379> srandmember s1 3
1) "3"
2) "0"
3) "8"
127.0.0.1:6379> srandmember s1 3
1) "5"
2) "3"
3) "9"
127.0.0.1:6379> srandmember s1 3
1) "7"
2) "4"
3) "8"
127.0.0.1:6379> srandmember s1 3
1) "3"
2) "9"
3) "4"
127.0.0.1:6379> srandmember s1 11
 1) "0"
 2) "1"
 3) "2"
 4) "3"
 5) "4"
 6) "5"
 7) "6"
 8) "7"
 9) "8"
10) "9"
127.0.0.1:6379> srandmember s1 15
 1) "0"
 2) "1"
 3) "2"
 4) "3"
 5) "4"
 6) "5"
 7) "6"
 8) "7"
 9) "8"
10) "9"
127.0.0.1:6379> smove s1 s2 9
(integer) 1
127.0.0.1:6379> smembers s1
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"
7) "6"
8) "7"
9) "8"
127.0.0.1:6379> smembers s2
1) "9"
127.0.0.1:6379> smembers s1
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"
7) "6"
8) "7"
9) "8"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> sadd s1 1
(integer) 1
127.0.0.1:6379> sadd s1 2
(integer) 1
127.0.0.1:6379> sadd s1 3
(integer) 1
127.0.0.1:6379> sadd s1 4
(integer) 1
127.0.0.1:6379> sadd s2 3
(integer) 1
127.0.0.1:6379> sadd s2 4
(integer) 1
127.0.0.1:6379> sadd s2 5
(integer) 1
127.0.0.1:6379> sadd s2 6
(integer) 1
127.0.0.1:6379> sdiff s1 s2
1) "1"
2) "2"
127.0.0.1:6379> sinter s1 s2
1) "3"
2) "4"
127.0.0.1:6379> 
127.0.0.1:6379> sunion s1 s2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:6379> sadd s3 4 6
(integer) 2
127.0.0.1:6379> sadd s3 4 6 7 8 
(integer) 2
127.0.0.1:6379> sadd s3 4 6 7 8 9 9 9
(integer) 1
127.0.0.1:6379> sdiff s1 s2 s3
1) "1"
2) "2"
127.0.0.1:6379> sunion s1 s2 s3
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
127.0.0.1:6379>

樣例:微博將A使用者的所有關注的人放在一個集合中,將他的粉絲也放在另外一個集合中,可以檢視共同關注的人!共同關注、共同愛好、二度好友、推薦好友!(六度分割理論)

3.4,Hash

Map集合,key-value鍵值對!

命令

hset(新增元素)、hget(獲取元素)、hdel(刪除某個filed)

hmset(批量更新元素)、hmget(批量獲取元素)、

hgetall(獲取所有資料)、hlen(獲取hash的長度)、

hexists(檢視集合中某個key是否存在)、

hkeys(獲取所有key)、hvals(獲取所有value)、

hincrby(按照步長自增指定個數,可以負值)、hsetnx(如果沒有則設定)

點選檢視命令執行情況

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> hset h1 f1 v1
(integer) 1
127.0.0.1:6379> hset h1 f2 v2
(integer) 1
127.0.0.1:6379> hget h1 f1
"v1"
127.0.0.1:6379> hget h1 f2
"v2"
127.0.0.1:6379> hget h1 f3
(nil)
127.0.0.1:6379> hmset h1 f3 v3 f4 v4 f5 v5
OK
127.0.0.1:6379> hmget h1 f2 f3
1) "v2"
2) "v3"
127.0.0.1:6379> get h1
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> hget h1 f1
"v1"
127.0.0.1:6379> hdel h1 f1
(integer) 1
127.0.0.1:6379> hget h1 f1
(nil)
127.0.0.1:6379> hgetall h1
1) "f2"
2) "v2"
3) "f3"
4) "v3"
5) "f4"
6) "v4"
7) "f5"
8) "v5"
127.0.0.1:6379> hlen h1
(integer) 4
127.0.0.1:6379> hexists h1 f1
(integer) 0
127.0.0.1:6379> hexists h1 f2
(integer) 1
127.0.0.1:6379> hkeys h1
1) "f2"
2) "f3"
3) "f4"
4) "f5"
127.0.0.1:6379> hvals h1
1) "v2"
2) "v3"
3) "v4"
4) "v5"
127.0.0.1:6379> hset h2 f1 1
(integer) 1
127.0.0.1:6379> hincrby h2 f1 2
(integer) 3
127.0.0.1:6379> decrby h2 f1 5
(error) ERR wrong number of arguments for 'decrby' command
127.0.0.1:6379> hdecrby h2 f1 5
(error) ERR unknown command `hdecrby`, with args beginning with: `h2`, `f1`, `5`, 
127.0.0.1:6379> hsetnx h3 f1 v1
(integer) 1
127.0.0.1:6379> hsetnx h3 f1 v1
(integer) 0
127.0.0.1:6379>

應用

hash變更的資料,尤其是使用者資訊類的,更加適合物件的儲存

3.5,Zset

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

底層是壓縮列表和跳錶實現

命令

zadd(新增元素)、zrange(遍歷所有)、zrevrange(反向遍歷)、

zrangebyscore(獲取篩選後的遞增排序結果)、

zrevrangebyscore(獲取篩選後的遞減排序結果)、zrem(移除元素)、

zcard(獲取個數)、zcount(計數)、其中符號(是開區間

點選檢視命令執行情況

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> zadd z1 1 1 2 2 3 3
(integer) 3
127.0.0.1:6379> zadd z2 100 wanyu
(integer) 1
127.0.0.1:6379> zadd z2 60 zhangsan
(integer) 1
127.0.0.1:6379> zadd z2 70 lisi
(integer) 1
127.0.0.1:6379> zadd z2 20 wangwu
(integer) 1
127.0.0.1:6379> zrangebyscore z2 
(error) ERR wrong number of arguments for 'zrangebyscore' command
127.0.0.1:6379> zrangebyscore z2 -inf +inf
1) "wangwu"
2) "zhangsan"
3) "lisi"
4) "wanyu"
127.0.0.1:6379> zrangebyscore z2 +inf -inf
(empty array)
127.0.0.1:6379> zrangebyscore z2 -inf +inf withscores 
1) "wangwu"
2) "20"
3) "zhangsan"
4) "60"
5) "lisi"
6) "70"
7) "wanyu"
8) "100"
127.0.0.1:6379> zrangebyscore z2 -inf 70 withscores
1) "wangwu"
2) "20"
3) "zhangsan"
4) "60"
5) "lisi"
6) "70"
127.0.0.1:6379> zrevrange z2
(error) ERR wrong number of arguments for 'zrevrange' command
127.0.0.1:6379> zrevrange z2 0 -1
1) "wanyu"
2) "lisi"
3) "zhangsan"
4) "wangwu"
127.0.0.1:6379> zrange z2 0 -1
1) "wangwu"
2) "zhangsan"
3) "lisi"
4) "wanyu"
127.0.0.1:6379> zrangebyscore z2 -inf 70 withscores
1) "wangwu"
2) "20"
3) "zhangsan"
4) "60"
5) "lisi"
6) "70"
127.0.0.1:6379> zrangebyscore z2 -inf (70 withscores
1) "wangwu"
2) "20"
3) "zhangsan"
4) "60"
127.0.0.1:6379> zrevrangebyscore z2 +inf -inf withscores
1) "wanyu"
2) "100"
3) "lisi"
4) "70"
5) "zhangsan"
6) "60"
7) "wangwu"
8) "20"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> zrem lisi
(error) ERR wrong number of arguments for 'zrem' command
127.0.0.1:6379> zrem z2  lisi
(integer) 1
127.0.0.1:6379> zcard z2
(integer) 3
127.0.0.1:6379> zcount z2 40 70
(integer) 1
127.0.0.1:6379> zcount z2 40 (70
(integer) 1
127.0.0.1:6379> zcount z2 40 60
(integer) 1
127.0.0.1:6379> zcount z2 40 (60
(integer) 0
127.0.0.1:6379> 

應用

set排序、班級成績、工資,訊息的優先順序,排行榜應用!

拓展

壓縮列表

跳錶

可以把它理解為一個丐版的B+樹

查詢、刪除、插入

時間複雜度O(logN),空間複雜度O(N)

相較於紅黑樹或者二叉樹優點:

  1. 範圍查詢(直接找到2個節點然後直接返回中間資料)!
  2. 實現更加簡單

4,三種特殊資料型別

4.1,Geospatial

地理位置型別,朋友定位、附件的人、打車距離,redis3.2版本已經推出了

命令

geoadd(新增地理位置)、geodist(檢視兩地距離)、

geopos(獲取某個地點的經緯度)、georadius(檢視方圓特定距離內的城市)、

georadiusbymember(檢視方圓特定距離的元素成員)、geohash(將二維的座標轉為一維的,降維)、

點選檢視命令執行情況

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> geoadd china:city 116.408 39.904 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 120.165 30.319 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.279 30.573 wuhan
(integer) 1
127.0.0.1:6379> geodist china:city wuhan hangzhou
"565044.5982"
127.0.0.1:6379> geodist china:city wuhan hangzhou km
"565.0446"
127.0.0.1:6379> geodist china:city wuhan hangzhou kmg
(error) ERR unsupported unit provided. please use m, km, ft, mi
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> geopos china:city hangzhou
1) 1) "120.16499966382980347"
   2) "30.31899997732214302"
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "wuhan"
2) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord
1) 1) "wuhan"
   2) 1) "114.27899926900863647"
      2) "30.57299931525717795"
2) 1) "hangzhou"
   2) 1) "120.16499966382980347"
      2) "30.31899997732214302"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist
1) 1) "wuhan"
   2) "415.8636"
   3) 1) "114.27899926900863647"
      2) "30.57299931525717795"
2) 1) "hangzhou"
   2) "977.8811"
   3) 1) "120.16499966382980347"
      2) "30.31899997732214302"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist withhash
1) 1) "wuhan"
   2) "415.8636"
   3) (integer) 4052121270844835
   4) 1) "114.27899926900863647"
      2) "30.57299931525717795"
2) 1) "hangzhou"
   2) "977.8811"
   3) (integer) 4054135069633163
   4) 1) "120.16499966382980347"
      2) "30.31899997732214302"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist withhash count
(error) ERR syntax error
127.0.0.1:6379> georadius china:city 110 30 1000 km count 
(error) ERR syntax error
127.0.0.1:6379> georadius china:city 110 30 1000 km count 
(error) ERR syntax error
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 2000 km
1) "wuhan"
2) "hangzhou"
3) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 1500 km
1) "wuhan"
2) "hangzhou"
3) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 1300 km
1) "wuhan"
2) "hangzhou"
3) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 1200 km
1) "beijing"
2) "wuhan"
3) "hangzhou"
127.0.0.1:6379> georadiusbymember china:city beijing 1100 km
1) "beijing"
2) "wuhan"
127.0.0.1:6379> geohash china:city
(empty array)
127.0.0.1:6379> geohash china:city beijing
1) "wx4g0bm9xh0"
127.0.0.1:6379> geohash china:city beijing hangzhou
1) "wx4g0bm9xh0"
2) "wtmkqrmkzr0"
127.0.0.1:6379> zrange china:city 0 -1
1) "wuhan"
2) "hangzhou"
3) "beijing"
127.0.0.1:6379> zrangebyscore china:city 0 -1 withscores
(empty array)
127.0.0.1:6379> zrangebyscore china:city -inf  +inf withscores
1) "wuhan"
2) "4052121270844835"
3) "hangzhou"
4) "4054135069633163"
5) "beijing"
6) "4069885369376452"

geo的底層原理其實就是Zset,我們可以使用Zset命令來操作geo,可以通過zrange檢視geo的資料

4.2,hyperloglog

什麼是基數?

A{1,3,5,7,8,9},B{1,3,5,7,8},基數(不重複元素的個數)

redis 2.8.9版本推出更新hyperloglog資料結構!

網頁的UV(一人訪問一個網站多次,但是還是算作一個人)!

傳統的使用set進行儲存,但是會消耗大量的記憶體空間!

優點:2^64不同的元素統計,只需要耗費12kb的記憶體!(但是有0.81%的錯誤率)

命令

pfadd(新增元素)、pfmerge(合併到新物件)、pfcount(統計資料)

點選檢視命令執行情況

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> pfadd hy1 a b c d e f
(integer) 1
127.0.0.1:6379> pfadd hy2 e f g h i j k 
(integer) 1
127.0.0.1:6379> pfmerge hy3 hy1 hy2
OK
127.0.0.1:6379> pfcount hy3
(integer) 11
127.0.0.1:6379> pfcount hy1
(integer) 6
127.0.0.1:6379> pfcount hy2
(integer) 7
127.0.0.1:6379> pfadd hy4 a a a b
(integer) 1
127.0.0.1:6379> pfcount hy4
(integer) 2
127.0.0.1:6379> 

如果允許容錯,就直接使用hyperloglog;否則使用set即可!

4.3,bitmaps

位儲存

統計疫情感染人數: 0 0 0 0 1 0 1

統計使用者資訊:活躍、不活躍!登入、未登入!打卡,365打卡(只有2個狀態)

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

命令

setbit(設定元素)、getbit(獲取元素)、bitcount(獲取統計結果)

點選檢視命令執行情況

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> setbit b1 0 0
(integer) 0
127.0.0.1:6379> setbit b1 4 0
(integer) 0
127.0.0.1:6379> setbit b1 4 0
(integer) 0
127.0.0.1:6379> getbit b1
(error) ERR wrong number of arguments for 'getbit' command
127.0.0.1:6379> getbit b1 0
(integer) 0
127.0.0.1:6379> getbit b1 1
(integer) 0
127.0.0.1:6379> getbit b1 2
(integer) 0
127.0.0.1:6379> getbit b1 100
(integer) 0
127.0.0.1:6379> setbit b1 100 1
(integer) 0
127.0.0.1:6379> setbit b1 100 1
(integer) 1
127.0.0.1:6379> setbit b1 100 1
(integer) 1
127.0.0.1:6379> setbit b1 100 1
(integer) 1
127.0.0.1:6379> getbit b1 100 1
(error) ERR wrong number of arguments for 'getbit' command
127.0.0.1:6379> getbit b1 100
(integer) 1
127.0.0.1:6379> bitcount b1
(integer) 1

5,事務

一個事務中的所有命令會被序列化!會按照順序執行!redis事務沒有隔離的概念!

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

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

回顧事務:ACID原則-Atomicity原子性、consistency一致性、isolation隔離性、durability永續性。

  • Atomicity(原子性):一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
  • Consistency(一致性):在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續資料庫可以自發性地完成預定的工作。
  • Isolation(隔離性):資料庫允許多個併發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致資料的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。
  • Durability(永續性):事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。

redis事務:

  • 開啟事務(multi)
  • 命令入隊(普通redis命令)
  • 執行事務(exec)
  • 放棄事務(discard)
  • 監視某個key(watch)
  • 取消監控(unwatch)

5.1,命令

正常執行事務

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 1
QUEUED
127.0.0.1:6379(TX)> set k2 2
QUEUED
127.0.0.1:6379(TX)> set k3 3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK

放棄事務

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 1
QUEUED
127.0.0.1:6379(TX)> set k2 1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k2
"2"

編譯型異常(命令不對),所有的命令都不會執行!

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> getset k3 v3
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 "v1"
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"

5.2,監控(watch)

悲觀鎖:

  • 很悲觀,小心翼翼,認為什麼時候都會出問題(效率很低,volatile、synchronized)

樂觀鎖:

  • 很樂觀,大大咧咧,認為什麼時候都不會出問題。更新資料的時候去判斷一下,在此期間是否有人修改過資料(version)

正常執行命令

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	 			# 監視期間資料沒有發生變動
OK
127.0.0.1:6379> multi					# 事務正常結束,期間沒有資料發生變動
OK
127.0.0.1:6379(TX)> decrby money 20		# 
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>

異常執行結果!可以當做redis樂觀鎖操作!

執行緒1開啟事務後,執行緒2修改money值,導致執行緒1exec失敗!

解決辦法unwatch,如果執行失敗就重新監控執行!

127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi 
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 90
2) (integer) 30
3) "90"

watch和unwatch只在單個命令視窗有效!已經實際測試過!

6,Jedis

本質就是redis的命令執行器所有的命令都沒有變化,redis官方推薦的java連線工具!使用java操作redis的中介軟體!

6.1,匯入對應的依賴

        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
		<!-- 2022.03.03 當前最新的依賴 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.1.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

6.2,編碼測試

  • 連線資料庫
  • 操作命令
  • 斷開連線
@Lazy
@Log4j2
@Service(value = "jedisImpl")
public class JedisImpl implements InitializingBean {

    @Value("${redis.ip:127.0.0.1}")
    private String redisIp;

    @Value("${redis.port:6379}")
    private Integer redisPort;

    @Value("${redis.auth:123456}")
    private String redisAuth;

    Jedis jedis;

    @Override
    public void afterPropertiesSet() {
        jedis = new Jedis(redisIp, redisPort);
        log.info(jedis.auth(redisAuth));
        log.info(jedis.ping());
        
        // redis事務!
        jedis.watch("k1");
        Transaction multi = jedis.multi();
        try {
            multi.watch("k1");
            multi.set("k1","v1");
            multi.set("k2","v2");
            int i = 1/0;
            log.info(multi.get("k1"));
            log.info(JSONObject.toJSONString(multi.exec()));
        }catch (Exception e){
            multi.discard();
            multi.unwatch();
            log.info("jedis 事務失敗! ");
        }
    }
}

常用的api:五大基本型別、3大特殊型別

7,springBoot整合

Spring-data,在springBoot2.x之後,原來使用的jedis被替換為lettuce?

jedis:採用直連,多個執行緒直連的話是不安全的,如果想要避免不安全的,需要使用jedis pool!BIO

lettuce:採用netty,例項可以在讀喲個執行緒中共享,不存線上程安全問題!可以減少執行緒安全問題,更想NIO模式!高階Redis客戶端,用於執行緒安全同步,非同步和響應使用,支援叢集,Sentinel,管道和編碼器。目前springboot預設使用的客戶端(RedisTemplate)。

依賴包:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

如何查詢redis相關配置

原始碼分析(RedisTemplate模板類):

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate") // 我們可以自己定義一個模板類
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 預設沒有過多的設定
        // 兩個泛型都是object型別
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean // 最常使用的類
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		return new StringRedisTemplate(redisConnectionFactory);
	}
}

測試類:

@SpringBootTest
class RedisStudy001ApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        System.out.println("hello");
        redisTemplate.opsForValue().set("name","wanyu");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }
}

結果(被序列化):

127.0.0.1:6379[1]> keys *
1) "\xac\xed\x00\x05t\x00\x04name"

自定義RedisTemplate

@Configuration
public class RedisConfig {

    // 自定義配置類
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);

        // jackson序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objecMapper = new ObjectMapper();
        objecMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objecMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objecMapper);

        // String序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key採用String序列化
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
}

8,配置解析

redis-6.2.6版本的redis.conf

redis配置檔案詳解

如果使用docker執行redis,則直接進入redis.conf的外部掛載卷檔案檢視即可

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

  • 單位
# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

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

  • 配置包含(INCLUDES)
################################## INCLUDES ###################################

# Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

就好比Nginx、spring的配置包含

  • 模組(MODULES)

Redis模組化基本介紹

  • 網路(NETWORK)
# bing 繫結監聽iP
# bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses
# bind 127.0.0.1 ::1              # listens on loopback IPv4 and IPv6
# bind * -::*                     # like the default, all available interfaces
bind * -::*   # 繫結監聽iP,可以監聽所有ip
protected-mode yes	# 保護模式
port 6379 # 埠設定
tcp-backlog 511		#
timeout 0

redis tcp-backlog配置

redis.conf詳解之tcp-backlog

Redis 優化之 tcp-backlog

  • 通用配置
daemonize no #是否以守護程序執行,docker容器化沒有必要設定該值,docker有自己的配置
pidfile /var/redis/run/redis_6379.pid # 如果上文配置了yes才需要寫入pid到該檔案

loglevel notice # 日誌級別,notice適量日誌資訊,使用於生產環境
logfile /var/redis/log/redis_6379.log # 日誌目錄
databases 16 # 資料庫個數預設16個,0-15db
always-show-logo no # 是否顯示啟動log
  • 快照(SNAPSHOTTING)

持久化檔案,在規定的時間與規則下進行持久化備份!(.rdb檔案)

# 3600秒內有1個key修改就持久化一次
save 3600 1
# 300秒內 有100個key修改就持久化一次
save 300 100
# 60秒內 有10000個key修改就持久化一次
save 60 10000
stop-writes-on-bgsave-error yes # 持久化出錯後,是否繼續工作
rdbcompression yes # 是否壓縮cpu的資源
rdbchecksum yes # 儲存rdb檔案的時候,進行錯誤校驗
rdb-del-sync-files no #rdb檔案是否刪除同步鎖
dbfilename dump.rdb # rdb檔名稱
dir ./ # 儲存路徑
  • 主從複製(REPLICATION)

  • (KEYS TRACKING)

  • 安全(SECURITY)

requirepass xxxx # 設定密碼

127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "xxxx"
  • 客戶端(CLIENTS)
maxclients 10000 # 最大連線數
maxmemory <bytes> # 記憶體限制
maxmemory-policy noeviction  # 記憶體達到上限後的處理策略

1、volatile-lru:只對設定了過期時間的key進行LRU(預設值) 
2、allkeys-lru : 刪除lru演算法的key   
3、volatile-random:隨機刪除即將過期key   
4、allkeys-random:隨機刪除   
5、volatile-ttl : 刪除即將過期的   
6、noeviction : 永不過期,返回錯誤
  • 延遲釋放(LAZY FREEING)
lazyfree-lazy-eviction:針對redis記憶體使用達到maxmeory,並設定有淘汰策略時;在被動淘汰鍵時,是否採用lazy free機制;
因為此場景開啟lazy free, 可能使用淘汰鍵的記憶體釋放不及時,導致redis記憶體超用,超過maxmemory的限制。此場景使用時,請結合業務測試。
lazyfree-lazy-expire --todo 驗證這類操作 同步到從庫的是DEL還是UNLINK:針對設定有TTL的鍵,達到過期後,被redis清理刪除時是否採用lazy free機制;此場景建議開啟,因TTL本身是自適應調整的速度。
lazyfree-lazy-server-del:針對有些指令在處理已存在的鍵時,會帶有一個隱式的DEL鍵的操作。如rename命令,當目標鍵已存在,redis會先刪除目標鍵,如果這些目標鍵是一個big key,那就會引入阻塞刪除的效能問題。 此引數設定就是解決這類問題,建議可開啟。
slave-lazy-flush:針對slave進行全量資料同步,slave在載入master的RDB檔案前,會執行flushall來清理自己的資料場景,
引數設定決定是否採用異常flush機制。如果記憶體變動不大,建議可開啟。可減少全量同步耗時,從而減少主庫因輸出緩衝區爆漲引起的記憶體使用增長。

redis重度使用患者應該都遇到過使用 DEL 命令刪除體積較大的鍵, 又或者在使用 FLUSHDB 和 FLUSHALL 刪除包含大量鍵的資料庫時,造成redis阻塞的情況;另外redis在清理過期資料和淘汰記憶體超限的資料時,如果碰巧撞到了大體積的鍵也會造成伺服器阻塞。(前臺執行緒邏輯刪除、後臺執行緒真實刪除)

為了解決以上問題, redis 4.0 引入了lazyfree的機制,它可以將刪除鍵或資料庫的操作放在後臺執行緒裡執行, 從而儘可能地避免伺服器阻塞。

redis 4.0新增配置項lazy freeing

#define LAZYFREE_THRESHOLD 64
// 首先定義了啟用後臺刪除的閾值,物件中的元素大於該閾值時才真正丟給後臺執行緒去刪除,如果物件中包含的元素太少就沒有必要丟給後臺執行緒,因為執行緒同步也要一定的消耗。
int dbAsyncDelete(redisDb *db, robj *key) {
    if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
    //清除待刪除key的過期時間
 
    dictEntry *de = dictUnlink(db->dict,key->ptr);
    //dictUnlink返回資料庫字典中包含key的條目指標,並從資料庫字典中摘除該條目(並不會釋放資源)
    if (de) {
        robj *val = dictGetVal(de);
        size_t free_effort = lazyfreeGetFreeEffort(val);
        //lazyfreeGetFreeEffort來獲取val物件所包含的元素個數
 
        if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
            atomicIncr(lazyfree_objects,1);
            //原子操作給lazyfree_objects加1,以備info命令檢視有多少物件待後臺執行緒刪除
            bioCreateBackgroundJob(BIO_LAZY_FREE ,val,NULL,NULL);
            //此時真正把物件val丟到後臺執行緒的任務佇列中
            dictSetVal(db->dict,de,NULL);
            //把條目裡的val指標設定為NULL,防止刪除資料庫字典條目時重複刪除val物件
        }
    }
 
    if (de) {
        dictFreeUnlinkedEntry(db->dict,de);
        //刪除資料庫字典條目,釋放資源
        return 1;
    } else {
        return 0;
    }
}

以上便是非同步刪除的邏輯,首先會清除過期時間,然後呼叫dictUnlink把要刪除的物件從資料庫字典摘除,再判斷下物件的大小(太小就沒必要後臺刪除),如果足夠大就丟給後臺執行緒,最後清理下資料庫字典的條目資訊。

由以上的邏輯可以看出,當unlink一個體積較大的鍵時,實際的刪除是交給後臺執行緒完成的,所以並不會阻塞redis

  • aof配置(APPEND ONLY MODE)
appendonly no 	# 預設不開啟,預設使用rdb模式已經夠了
appendfilename "appendonly.aof"
# appendfsync always
appendfsync everysec		# 每秒執行一次
# appendfsync no

9,持久化

redis是記憶體資料庫(RAM,隨機存取儲存器),一點掉電或者重啟都會消失,只有進行持久化操作才能恢復!

9.1,rdb(redis database)

類似於阿里雲ECS的的快照功能,它有自己的定時、閾值策略。生產環境需要進行備份

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

redis會單獨建立(fork)一個子程序來進行持久化,先將資料寫入一個臨時檔案,待持久化過程結束,再用這個臨時檔案替代上次的持久化檔案!整個過程,主程序是不進行IO操作的,這就保證了極高的效能。如果需要進行大規模的資料恢復,且對於資料的完整性不是特別敏感,那麼使用rdb比使用aof方式更加高效。rdb的缺點是最後一次持久化後的資料可能會丟失!(時間差!)

# 配置
# 3600秒內有1個key修改就持久化一次
save 3600 1
# 300秒內 有100個key修改就持久化一次
save 300 100
# 60秒內 有10000個key修改就持久化一次
save 60 10000
stop-writes-on-bgsave-error yes # 持久化出錯後,是否繼續工作
rdbcompression yes # 是否壓縮cpu的資源
rdbchecksum yes # 儲存rdb檔案的時候,進行錯誤校驗
dbfilename dump.rdb # rdb檔名稱
dir ./ # 儲存路徑
  • 通過.rdb檔案恢復資料

1,只需要將rdb檔案放在我們redis的啟動目錄就可以,redis啟動的時候會自動檢查.rdb檔案

2,config get dir檢視放置的位置(幾乎它自己的配置就夠用了)

> config get dir	#通過該命令查詢放置的位置,如果使用docker資料卷掛載就直接放心外部相應的掛載點即可
dir
/data

優點:

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

2,對資料的完整性不高!

缺點:

1,需要一定的時間間隔程序操作!如果redis意外宕機,這個最後一次修改的資料就沒有了(2次快照間隔的時間點,即到未到的快照節點)

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

9.2,aof(append only file)

將所有命令記錄下來(history),恢復的時候把所有檔案全部執行一遍!

以日誌的形式記錄每一個寫操作,將redis執行過的所有命令記錄,只許追加檔案但不可以改寫檔案,redis啟動之初會讀取該檔案重新構建資料,redis重啟會更加日誌檔案將寫命令從前到後執行一次以完成資料的恢復!

配置

appendonly no 	# 預設不開啟,預設使用rdb模式已經夠了
appendfilename "appendonly.aof"
# appendfsync always
appendfsync everysec		# 每秒執行一次
# appendfsync no
no-appendfsync-on-rewrite no # 是否進行重新

# 重寫規則!
auto-aof-rewrite-percentage 100	# aof就是無限預設追加
auto-aof-rewrite-min-size 64mb	# 如果檔案大於64mb,fork一個新的程序來將我們的檔案重寫

隨著命令不斷寫入AOF,檔案會越來越大==redis如何控制AOF大小,為了解決這個問題,redis引入了AOF重寫機制壓縮檔案。檔案能縮小的原因是:

1.舊檔案中的無效命令不會保留,如del key1,sort

2.多條合併成一條,如lplush list a,lplush list b轉換為lplush a b,也可以合併重複項。

  • 如果出現手動篡改的值導致恢復失敗

解決辦法:

  1. 因為我這邊使用的docker啟動redis,redis容器啟動不了,我這邊使用不了裡面的redis-check-aof工具,所以我這邊先把這個壞掉的appendonly.aof改名為appendonly.aof-bak!
  2. 然後正確無資料恢復的情況下啟動redis容器,之後進入redis容器內部,/usr/local/bin目錄下使用redis-check-aof工具修復我的appendonly.aof-bak檔案
  3. 最後,修改appendonly.aof-bak檔案為appendonly.aof,如果檔案修復好了就可以重啟容器即可完成!!

優點:

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

缺點:

  1. 相對於資料檔案來說,aof遠遠大於rdb,恢復的速度也比rdb慢
  2. aof的執行效率比rdb慢,所以我們一般都是使用redis預設的rdb即可

擴充套件:

  1. rdb持久化能夠在指定時間和策略下對資料進行快照儲存
  2. aof持久化方式每次對伺服器寫的操作進行記錄,redis重啟時通過執行命令來恢復資料,aof每次追加儲存寫的操作到檔案末尾,redis還能對aof檔案進行後臺重寫,使得aof體積不至於過大!
  3. 如果只做快取,就沒有必要開啟持久化
  4. 同時開啟2種持久化,優先載入aof檔案原因是aof資料更加完整!要不要只使用aof來進行持久化呢?不建議因為rdb更適合進行備份資料庫(aof在不斷變化不好備份),快速重啟,而且aof可能會有一些bug,留作備用
  5. 效能建議:rdb一般只用作備份建議只在slave上進行持久化rdb檔案,而且只需要15分鐘備份一次即可只保留save 900 1;如果開啟aof,好處是最惡劣的情況資料丟失不會超過2秒,啟動指令碼簡單隻load自己的aof檔案即可,代價是帶來持續的io,並且aof rewrite過程中將新資料寫入新檔案造成的阻塞幾乎是必須的。只要硬碟許可,應該儘量減少aof rewrite的評論,兩個重寫的配置往大調節;如果不開啟aof,僅靠master-slave repllcation實現該可用,能夠省掉一大筆io,也減少了rewrite帶來的系統性能波動。代價是如果master/slave都同時壞掉(斷電),會丟失幾十分鐘的資料,啟動指令碼也要比較兩個master、slave的rdb檔案,載入那個較新(或者最大),微博就是這種架構!

10,釋出訂閱

redis實現訊息佇列&釋出/訂閱模式使用

redis釋出訂閱(pub/sub)是一種訊息通訊模式:釋出者(pub)傳送訊息,訂閱者(sub)接收訊息!

redis客戶端可以訂閱任意數量的頻道!

被廣泛應用於即時通訊應用,比如網路聊天室(chartroom)和實時廣播、實時提醒等。微信、微博、關注系統

訂閱/釋出訊息圖:

channel1有3個訂閱者(訊息接收者)

當有訊息傳送者釋出訊息時,就會自動給這個channel傳送訊息

命令

  • publish(訊息釋出)、psubscribe(訂閱頻道)
  • punsubscribe(退訂頻道)

原理

通過subscribe命令訂閱某個頻道後,redis-server會維護一個字典,字典裡面就是一個個頻道,而字典的值則是一個連結串列,連結串列中儲存了所有訂閱這個頻道的客戶端。subscribe命令的核心是將客戶端新增給特定channel的訂閱連結串列中。

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

使用場景

  1. 實時訊息系統
  2. 實時聊天
  3. 訂閱關注系統

稍微複雜的邏輯就會使用訊息中介軟體來實現(rabbitmq、rocketmq、activemq)

11,主從複製

11.1,概念

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

預設情況下,每臺redis伺服器都是主節點;且一個主節點可以由多個從節點(或者沒有從節點)。但一個從節點只能由一個主節點。

主從複製的作用:

  1. 資料冗餘:主從複製實現的資料的熱備份,是持久化之外的一種資料冗餘方式(備份機!備胎)
  2. 故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;服務冗餘(備胎)
  3. 負載均衡:在主從複製的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫redis資料時應用連結到主節點,讀取資料時連結到從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高redis伺服器的併發量!
  4. 高可用(叢集)基石:除了上述作用以為,主從複製還是哨兵和叢集能夠與實施的基礎,因此說主從複製是redis高可用的基礎。

生產環境中要將redis運用於工程專案中,只使用1臺redis伺服器保障性不夠:

  1. 從結構上,單個redis伺服器會發生單點故障,並且只有一臺伺服器需要處理所有的請求負載,壓力較大
  2. 從容量上,單個redis伺服器記憶體容量有限,就算一臺redis的記憶體容量為256G,也不能將所有記憶體作為redis儲存記憶體,一般來講,單臺redis使用最大記憶體不應該超過20G

電商網站上面的商品,一般都是一次上傳,無數次瀏覽,就是專業點的“多讀少寫”的資料!對於這種場景,我們可以使用如下架構:

主從複製,讀寫分離!80%情況下都是讀的操作!減緩伺服器的壓力,構架中經常使用!1主2從,後面還有哨兵模式會自己選舉!

只要在公司中,主從複製是必用的

11.2,配置

只修改從庫,不用配置主庫!

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:70860adb239f32bc6521d8eaace9959e5cbefb40
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個配置,修改對應的資訊(對應docker映象好像只用修改對外暴露的埠即可)

  1. pid名稱(docker沒有必要修改)
  2. log日誌檔案
  3. dump.rdb檔案
# 6380從機
docker run -it \
-p 6380:6380 \
-v /opt/docker/redis/conf/redis6380.conf:/usr/local/etc/redis/redis.conf \
-v /opt/docker/redis/data:/data \
--name ac-redis6380 \
redis \
redis-server /usr/local/etc/redis/redis.conf

# 6381從機
docker run -itd \
-p 6381:6381 \
-v /opt/docker/redis/conf/redis6381.conf:/usr/local/etc/redis/redis.conf \
-v /opt/docker/redis/data:/data \
--name ac-redis6381 \
redis \
redis-server /usr/local/etc/redis/redis.conf 

11.3,一主二從

預設三臺都是主節點,我們只用配置從機配置即可

命令

slaveof(配置從機中的主節點,也可以直接在配置中修改)

info replication(檢視當前redis的主從角色資訊)

slaveof no one(取消主從設定)

# 6380、6380從節點
slaveof ip 埠
# 注意如果主節點有設定密碼,需要在配置中的masterauth配置一下,否則從節點連結不上

> info replication
# Replication
role:slave
master_host:47.98.35.29
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_read_repl_offset:14
slave_repl_offset:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:9d65bf62f55d047a65a39288e0d1f28eef640eb9
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

# 主節點
> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=47.98.35.29,port=6380,state=online,offset=84,lag=0
slave1:ip=47.98.35.29,port=6381,state=online,offset=84,lag=0
master_failover_state:no-failover
master_replid:9d65bf62f55d047a65a39288e0d1f28eef640eb9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84

細節

主機可以讀寫,從機只能讀(好像可以配置)!

主機斷開連線,從機依然連線到主機,但是沒有寫操作,這時主機恢復,從機依舊可以直接獲取主機寫的資料!如果從機使用的命令設定自己的主節點資訊,重啟後需要重新設定從機的角色!

只要設定了從機角色,資料立馬會從主機中寫入進從機!

複雜原來

slave啟動成功後連線到master後悔傳送一條sync同步命令

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

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

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

只要是重新連線master,一次全量同步就會自動執行!

redis 主從複製搭建,全量複製和部分複製

全量複製流程
	如果從伺服器以前沒有複製過任何主伺服器,或者之前執行過SLAVEOF no one命令,那麼從伺服器在開始一次新的複製時將向主伺服器傳送PSYNC ? -1命令,主動請求主伺服器進行完整重同步(因為這時不可能執行部分重同步);
相反地,如果從伺服器已經複製過某個主伺服器,那麼從伺服器在開始一次新的複製時將向主伺服器傳送PSYNC <runid> <offset>命令:其中runid是上一次複製的主伺服器的執行ID,而offset則是從伺服器當前的複製偏移量,接收到這個命令的主伺服器會通過這兩個引數來判斷應該對從伺服器執行哪種同步操作,如何判斷已經在介紹runid時進行詳細說明。
根據情況,接收到PSYNC命令的主伺服器會向從伺服器返回以下三種回覆的其中一種:

	如果主伺服器返回+FULLRESYNC <runid> <offset>回覆,那麼表示主伺服器將與從伺服器執行完整重同步操作:其中runid是這個主伺服器的執行ID,從伺服器會將這個ID儲存起來,在下一次傳送PSYNC命令時使用;而offset則是主伺服器當前的複製偏移量,從伺服器會將這個值作為自己的初始化偏移量;
	如果主伺服器返回+CONTINUE回覆,那麼表示主伺服器將與從伺服器執行部分同步操作,從伺服器只要等著主伺服器將自己缺少的那部分資料傳送過來就可以了;
	如果主伺服器返回-ERR回覆,那麼表示主伺服器的版本低於Redis 2.8,它識別不了PSYNC命令,從伺服器將向主伺服器傳送SYNC命令,並與主伺服器執行完整同步操作。
   
   由此可見psync也有不足之處,當從庫重啟以後runid發生變化,也就意味者從庫還是會進行全量複製,而在實際的生產中進行從庫的維護很多時候會進行重啟,而正是有由於全量同步需要主庫執行快照,以及資料傳輸會帶不小的影響。因此在4.0版本,psync命令做了改進,以下說明。

11.4,層層鏈路

上一個M連結下一個S,下一個S又連結一個S

如果老大沒有了,那能不能選擇一個新master出來?---哨兵模式!

12,哨兵模式

redis主從複製下哨兵模式

12.1,概念

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

能夠監控主機是否故障,如果發生故障根據投票數自動將從庫轉為master庫。

redis提供sentinel的命令,是一個獨立的程序。其原理是哨兵通過傳送命令,等待redis伺服器響應,從而監控執行的多個redis例項。

這裡的哨兵有2個作用

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

然而一個哨兵程序對redis伺服器進行監控也有弊端,如果該哨兵程序崩了?為此我們可以使用多個哨兵進行監控,各個哨兵之間還會進行監控,這樣就形成了多哨兵模式,如下圖所示:

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

12.2,測試

我們現在的狀態是一主二從

  1. 配置哨兵配置檔案sentinel.conf檔案
# 被監控的名稱 host port 後面的數字1是,代表主機掛了,slave投票看讓誰接替成為主機,票數多的成為主機
sentinel monitor ac-redis 127.0.0.1 6379 1
sentinel auth-pass ac-redis 密碼

哨兵日誌

如果主機宕機後,之後又恢復了,它也只能自動轉為從機!

優點:

  1. 哨兵模式,基於主從複製模式,所有的主從配置節點,他全有
  2. 主從可以切換,故障可以轉移failover,系統的可用性更好
  3. 哨兵模式就是主從模式的升級版本,手動到自動,更加健壯

缺點:

  1. redis不好線上擴容,叢集容量一旦達到上限,線上擴容很麻煩
  2. 實現哨兵模式的配置很麻煩,裡面的選擇很多

哨兵模式的全部配置

port 26379	# sentinel 執行埠
dir /tmp	# 工作目錄
sentinel monitor <master-name> <ip> <port> <quorum>	# quorum配置多少個哨兵統一任務主節點失聯?那麼就認為客觀失聯
sentinel auth-pass <mysater-name> <password>
sentinel down-after-milliseconds <mysater-name> <milliseconds> # 主觀上認為主節點下線
sentinel parallel-syncs <mysater-name> <numslaves>	#發生failover時,同時有多少個從節點對新的master進行資料同步
sentinel failover-timeout <mysater-name> <milliseconds> 
# 編寫sh指令碼進行擴充套件操作
sentinel notification-script <mysater-name> <script-path>

13,docker搭建叢集

13.1,部署

基於Docker的Redis叢集搭建

docker搭建redis叢集

# 建立網絡卡
docker network create redis-cluster --subnet 172.38.0.0/16

# 批量建立配置檔案
for port in $(seq 1 6); \
do \
mkdir -p /opt/docker/redis/cluster/node-${port}/conf
mkdir -p /opt/docker/redis/cluster/node-${port}/data
touch /opt/docker/redis/cluster/node-${port}/conf/redis.conf
cat <<EOF>> /opt/docker/redis/cluster/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
masterauth passwd123 
requirepass passwd123 
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly no
EOF
done

# 批量啟動
for port in $(seq 1 6); \
do \
docker run -d -p 637${port}:6379 -p 1637${port}:16379 \
--name redis-${port} \
-v /opt/docker/redis/cluster/node-${port}/data:/data \
-v /opt/docker/redis/cluster/node-${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
--net redis-cluster --ip 172.38.0.1${port} \
redis redis-server /usr/local/etc/redis/redis.conf
done

# 進入任意一個容器配置叢集
[root@iZbp1jbs6mikemed8a6mooZ:~]# docker exec -it redis-1 /bin/bash
root@b518b77d2f00:/data# ls
dump.rdb  nodes.conf
root@b518b77d2f00:/data# redis-cli -a passwd123 --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.38.0.15:6379 to 127.38.0.11:6379
Adding replica 127.38.0.16:6379 to 127.38.0.12:6379
Adding replica 127.38.0.14:6379 to 127.38.0.13:6379
M: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.14:6379
   replicates 782f6a0992ca7ef58438797aad9482e8e8ad054e
S: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.15:6379
   replicates 782f6a0992ca7ef58438797aad9482e8e8ad054e
S: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.16:6379
   replicates 782f6a0992ca7ef58438797aad9482e8e8ad054e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join

>>> Performing Cluster Check (using node 127.38.0.11:6379)
M: 782f6a0992ca7ef58438797aad9482e8e8ad054e 127.38.0.11:6379
   slots:[0-16383] (16384 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
root@b518b77d2f00:/data# 

# 進入查詢資訊
root@b518b77d2f00:/data# redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:1
cluster_current_epoch:1
cluster_my_epoch:1
cluster_stats_messages_pong_sent:1
cluster_stats_messages_meet_sent:1
cluster_stats_messages_sent:2
cluster_stats_messages_pong_received:1
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:2
127.0.0.1:6379> 
127.0.0.1:6379> get k2
-> Redirected to slot [449] located at 172.38.0.11:6379
"v2"

# 在其他機器上面處理資料,會自動跳轉!!!(牛皮)
172.38.0.11:6379> get k3
"v3"
172.38.0.11:6379> set k4 v4
-> Redirected to slot [8455] located at 172.38.0.12:6379
OK
172.38.0.12:6379> set k5 v5
-> Redirected to slot [12582] located at 172.38.0.13:6379
OK

# 強行關閉之前的node1,在其他節點上檢視
172.38.0.13:6379> cluster nodes
50b403f66e12068200d3c92e9a2c13537d85a3ed 172.38.0.12:6379@16379 master - 0 1646446787860 2 connected 5461-10922
a45fade1226aac38c4d4da1e1da6d5b7b08b38a6 172.38.0.14:6379@16379 slave a4ed284eb6675c3da8b3fb94105068bc87d999fc 0 1646446787000 3 connected
a4ed284eb6675c3da8b3fb94105068bc87d999fc 172.38.0.13:6379@16379 myself,master - 0 1646446786000 3 connected 10923-16383
9cabfe0fe2b9a0ce9ba1ebc5db5e4913c4718c7f 172.38.0.11:6379@16379 master,fail - 1646446780530 1646446778016 1 connected
563c7bc98645a09f3456bab96257f29255f4cba0 172.38.0.15:6379@16379 master - 0 1646446787559 7 connected 0-5460
bc042914377512247baabad9963eaf9fc59e59de 172.38.0.16:6379@16379 slave 50b403f66e12068200d3c92e9a2c13537d85a3ed 0 1646446786856 2 connected

# 如果最後想要刪除所有redis叢集容器,並且刪除目錄 可以執行如下指令碼
for port in $(seq 1 6); \
do \
docker rm -f redis-${port}
rm -rf /opt/docker/redis/cluster/node-${port}
done

叢集優點:

  1. Redis 叢集的分片特徵在於將鍵空間分拆了16384個槽位,每一個節點負責其中一些槽位(資料分片)。
  2. Redis提供一定程度的可用性,可以在某個節點宕機或者不可達的情況下繼續處理命令.
  3. Redis 叢集中不存在中心(central)節點或者代理(proxy)節點, 叢集的其中一個主要設計目標是達到線性可擴充套件性(linear scalability)。

特點:

  1. 所有的節點相互連線;
  2. 叢集訊息通訊通過叢集匯流排通訊,,叢集匯流排埠大小為客戶端服務埠+10000,這個10000是固定值;
  3. 節點與節點之間通過二進位制協議進行通訊;
  4. 客戶端和叢集節點之間通訊和通常一樣,通過文字協議進行;
  5. 叢集節點不會代理查詢;

13.2,投票機制

  1. 故障節點主觀下線
  2. 故障節點客觀下線
  3. Sentinel叢集選舉Leader
  4. Sentinel Leader決定新主節點

本質就是raft演算法

13.3,動態擴容和刪減節點

新增後執行如下命令進行重新分配槽

刪除之前,先把歷史的資料槽轉移到其他主節點

../redis-trib.rb reshard 192.168.230.129:6379

14,快取穿透、擊穿和雪崩

redis快取的使用,極大的提升了應用的效能和效率,特別是查詢資料效能(redis-benchmark)。高可用的關鍵!

14.1,快取穿透

概念

使用者想要查詢某個資料,但是快取沒有命中,於是向持久層(sql)查詢。發現也沒有,於是本次查詢失敗。當很多這樣的快取沒有命中(例如秒殺場景)的請求過來時,會給持久層造成很大的壓力。

解決方案

1,布隆過濾器(Bloom Filter)

本質上布隆過濾器是一種資料結構,比較巧妙的概率型資料結構(probabilistic data structure),特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”

相比於傳統的 List、Set、Map 等資料結構,它更高效、佔用空間更少,但是缺點是其返回的結果是概率性的,而不是確切的。

布隆過濾器我的理解:

  • 初始化一個bitmap空間(大小為n)
  • 將已有的所有key使用多個hash函式進行計算,並將結果index=hashCode%n,在上述bitmap空間對應的index標記為1(這樣就粗略的記錄了相關key值是否可能存在的資訊,當然也有可能多個不同key值,他們的index是一樣的~這個就是誤差,如果想要減少誤差就需要,把bitmap的值擴容)
  • 來了一條key查詢
    • 如果按照上述步驟計算index,且在bitmap空間中數值為1,那麼說明:這個key有可能存在(除非是該key與我之前記錄的key的index一樣導致的誤差),後續我在查詢redis庫
    • 如果按照上述步驟計算index,且在bitmap空間中數值為1,那麼說明:這個key我這邊根本沒有記錄,絕對不可能存在,就無需查詢redis庫了

redis4.0之後就自己實現了布隆過濾器,使用樣例見github連結

# 127.0.0.1:6379> BF.ADD newFilter foo
(integer) 1
# 127.0.0.1:6379> BF.EXISTS newFilter foo
(integer) 1
# 127.0.0.1:6379> BF.EXISTS newFilter bar
(integer) 0

2,快取空物件

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

但是快取空物件會存在問題:

  1. 如果空值能夠被快取起來,這就意味著快取需要更多的空間儲存更多的鍵,因為這當中可能會有很多的空值的鍵;
  2. 即使對空值設定了過期時間,還是會存在快取層和儲存層的資料會有一段時間視窗不一致,這對於保持一致性的業務會有影響!

14.2,快取擊穿

概念

這裡需要注意和穿透的區別,快取擊穿是指一個key非常熱點,在不停的進行大併發,大併發集中對著這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就會擊穿快取層,直接請求到儲存層

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

解決方案

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

從快取層面來看,沒有設定過期時間,所以不會出現熱點key過期的問題(但是耗費資源)

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

14.3,快取雪崩

概念

在某個時刻,快取集中過期失效,redis宕機!

產生的原因之一,比如雙十一寫入資料的時候,定點瞬間寫入一批訂單資訊(過期時間可能一樣),到期時這一批的訂單全部同時過期。而對這一批訂單進行查詢的時候就全部會穿透快取層-直擊儲存層。對於資料庫而言就會有周期性壓力波峰,嚴重的時候會把資料庫搞崩!

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

例子:雙十一,停掉一些服務(保證主要服務可用)

解決方案

  1. redis高可用,既然redis可以掛掉,那麼就多設定幾臺redis伺服器,這樣一臺掛掉,其他的可以繼續工作,本質就是搭建叢集(異地多活!)
  2. 限流降級(springCloud必須學習!),在快取失效後,通過加鎖或者佇列來控制讀取資料庫寫入快取的執行緒數量。比如某個key只允許一個執行緒查詢資料和寫入資料,其他執行緒等待!
  3. 資料預熱,在正式部署之前,先把可能的資料預先訪問一遍(假設這些資料是熱點資料),這樣部分可能大量訪問的資料就會載入到快取中。在即將發生的大併發訪問前,手動觸發載入快取不同的key,設定不同的過期時間,讓快取失效的時間儘量均勻一些!

15,效能調優

關於redis效能問題分析和優化

主要可從記憶體、命令處理數、延遲時間、記憶體碎片率、回收key這幾個方面來進行效能調優!

可以通過info命令查詢相關資料資訊(server ,clients ,memory ,persistence ,stats ,replication ,cpu ,commandstats ,cluster ,keyspace)

15.1,記憶體

127.0.0.1:6379> info memory
# Memory
used_memory:2682624
used_memory_human:2.56M
used_memory_rss:7704576
used_memory_rss_human:7.35M
used_memory_peak:2763288
used_memory_peak_human:2.64M
used_memory_peak_perc:97.08%
used_memory_overhead:2558112
used_memory_startup:1468416
used_memory_dataset:124512
used_memory_dataset_perc:10.25%
allocator_allocated:2744432
allocator_active:3035136
allocator_resident:5451776
total_system_memory:7976460288
total_system_memory_human:7.43G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.11
allocator_frag_bytes:290704
allocator_rss_ratio:1.80
allocator_rss_bytes:2416640
rss_overhead_ratio:1.41
rss_overhead_bytes:2252800
mem_fragmentation_ratio:2.92
mem_fragmentation_bytes:5062976
mem_not_counted_for_evict:0
mem_replication_backlog:1048576
mem_clients_slaves:20512
mem_clients_normal:20496
mem_aof_buffer:0
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0
lazyfreed_objects:0

記憶體使用率是Redis服務最關鍵的一部分。如果Redis例項的記憶體使用率超過可用最大記憶體 (used_memory > 可用最大記憶體),那麼作業系統開始進行記憶體與swap空間交換,把記憶體中舊的或不再使用的內容寫入硬碟上(硬碟上的這塊空間叫Swap分割槽),以便留出新的實體記憶體給新頁或活動頁(page)使用。

如果Redis程序上發生記憶體交換,那麼Redis和依賴Redis上資料的應用會受到嚴重的效能影響。 通過檢視used_memory指標可知道Redis正在使用的記憶體情況,如果used_memory>可用最大記憶體,那就說明Redis例項正在進行記憶體交換或者已經記憶體交換完畢。

  • 假如快取資料小於4GB,就使用32位的Redis例項。因為32位例項上的指標大小隻有64位的一半,它的記憶體空間佔用空間會更少些。 這有一個壞處就是,假設實體記憶體超過4GB,那麼32位例項能使用的記憶體仍然會被限制在4GB以下。 要是例項同時也共享給其他一些應用使用的話,那可能需要更高效的64位Redis例項,這種情況下切換到32位是不可取的。 不管使用哪種方式,Redis的dump檔案在32位和64位之間是互相相容的, 因此倘若有減少佔用記憶體空間的需求,可以嘗試先使用32位,後面再切換到64位上。
  • 儘可能的使用Hash資料結構。因為Redis在儲存小於100個欄位的Hash結構上,其儲存效率是非常高的。所以在不需要集合(set)操作或list的push/pop操作的時候,儘可能的使用Hash結構。比如,在一個web應用程式中,需要儲存一個物件表示使用者資訊,使用單個key表示一個使用者,其每個屬性儲存在Hash的欄位裡,這樣要比給每個屬性單獨設定一個key-value要高效的多。 通常情況下倘若有資料使用string結構,用多個key儲存時,那麼應該轉換成單key多欄位的Hash結構。 如上述例子中介紹的Hash結構應包含,單個物件的屬性或者單個使用者各種各樣的資料。Hash結構的操作命令是HSET(key, fields, value)和HGET(key, field),使用它可以儲存或從Hash中取出指定的欄位。
  • 設定key的過期時間。一個減少記憶體使用率的簡單方法就是,每當儲存物件時確保設定key的過期時間。倘若key在明確的時間週期內使用或者舊key不大可能被使用時,就可以用Redis過期時間命令(expire,expireat, pexpire, pexpireat)去設定過期時間,這樣Redis會在key過期時自動刪除key。 假如你知道每秒鐘有多少個新key-value被建立,那可以調整key的存活時間,並指定閥值去限制Redis使用的最大記憶體。
  • 回收key。在Redis配置檔案中(一般叫Redis.conf),通過設定“maxmemory”屬性的值可以限制Redis最大使用的記憶體,修改後重啟例項生效。 也可以使用客戶端命令config set maxmemory 去修改值,這個命令是立即生效的,但會在重啟後會失效,需要使用config rewrite命令去重新整理配置檔案。 若是啟用了Redis快照功能,應該設定“maxmemory”值為系統可使用記憶體的45%,因為快照時需要一倍的記憶體來複制整個資料集,也就是說如果當前已使用45%,在快照期間會變成95%(45%+45%+5%),其中5%是預留給其他的開銷。 如果沒開啟快照功能,maxmemory最高能設定為系統可用記憶體的95%。

15.2,命令處理數

127.0.0.1:6379> info stats
# Stats
total_connections_received:16
total_commands_processed:4833
instantaneous_ops_per_sec:1
total_net_input_bytes:238021
total_net_output_bytes:131153
instantaneous_input_kbps:0.04
instantaneous_output_kbps:0.01
rejected_connections:0
sync_full:1
sync_partial_ok:0
sync_partial_err:1
expired_keys:0
expired_stale_perc:0.00
expired_time_cap_reached_count:0
expire_cycle_cpu_milliseconds:97
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:348
total_forks:2
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0
tracking_total_keys:0
tracking_total_items:0
tracking_total_prefixes:0
unexpected_error_replies:0
total_error_replies:11
dump_payload_sanitizations:0
total_reads_processed:4830
total_writes_processed:561
io_threaded_reads_processed:0
io_threaded_writes_processed:0

在Redis例項中,跟蹤命令處理總數是解決響應延遲問題最關鍵的部分,因為Redis是個單執行緒模型,客戶端過來的命令是按照順序執行的。比較常見的延遲是頻寬,通過千兆網絡卡的延遲大約有200μs。倘若明顯看到命令的響應時間變慢,延遲高於200μs,那可能是Redis命令佇列裡等待處理的命令數量比較多。 如上所述,延遲時間增加導致響應時間變慢可能是由於一個或多個慢命令引起的,這時可以看到每秒命令處理數在明顯下降,甚至於後面的命令完全被阻塞,導致Redis效能降低。要分析解決這個效能問題,需要跟蹤命令處理數的數量和延遲時間。

比如可以寫個指令碼,定期記錄total_commands_processed的值。當客戶端明顯發現響應時間過慢時,可以通過記錄的total_commands_processed歷史資料值來判斷命理處理總數是上升趨勢還是下降趨勢,以便排查問題。

通過與記錄的歷史資料比較得知,命令處理總數確實是處於上升或下降狀態,那麼可能是有2個原因引起的:

  1. 命令佇列裡的命令數量過多,後面命令一直在等待中
  2. 幾個慢命令阻塞Redis

解決辦法:

  • 使用多引數命令:若是客戶端在很短的時間內傳送大量的命令過來,會發現響應時間明顯變慢,這由於後面命令一直在等待佇列中前面大量命令執行完畢。有個方法可以改善延遲問題,就是通過單命令多引數的形式取代多命令單引數的形式。舉例來說,迴圈使用LSET命令去新增1000個元素到list結構中,是效能比較差的一種方式,更好的做法是在客戶端建立一個1000元素的列表,用單個命令LPUSH或RPUSH,通過多引數構造形式一次性把1000個元素髮送的Redis服務上。下面是Redis的一些操作命令,有單個引數命令和支援多個引數的命令,通過這些命令可儘量減少使用多命令的次數。
  • 管道命令:另一個減少多命令的方法是使用管道(pipeline),把幾個命令合併一起執行,從而減少因網路開銷引起的延遲問題。因為10個命令單獨傳送到服務端會引起10次網路延遲開銷,使用管道會一次性把執行結果返回,僅需要一次網路延遲開銷。Redis本身支援管道命令,大多數客戶端也支援,倘若當前例項延遲很明顯,那麼使用管道去降低延遲是非常有效的。
  • 避免操作大集合的慢命令:如果命令處理頻率過低導致延遲時間增加,這可能是因為使用了高時間複雜度的命令操作導致,這意味著每個命令從集合中獲取資料的時間增大。 所以減少使用高時間複雜的命令,能顯著的提高的Redis的效能。

15.3,延遲時間

Redis的延遲資料是無法從info資訊中獲取的。可以用 Redis-cli工具加 --latency引數執行,如:

Redis之所以這麼流行的主要原因之一就是低延遲特性帶來的高效能,所以說解決延遲問題是提高Redis效能最直接的辦法。拿1G頻寬來說,若是延遲時間遠高於200μs,那明顯是出現了效能問題。 雖然在伺服器上會有一些慢的IO操作,但Redis是單核接受所有客戶端的請求,所有請求是按良好的順序排隊執行。因此若是一個客戶端發過來的命令是個慢操作,那麼其他所有請求必須等待它完成後才能繼續執行。

解決辦法:

  1. 使用slowlog查出引發延遲的慢命令:Redis中的slowlog命令可以讓我們快速定位到那些超出指定執行時間的慢命令,預設情況下命令若是執行時間超過10ms就會被記錄到日誌。slowlog只會記錄其命令執行的時間,不包含io往返操作,也不記錄單由網路延遲引起的響應慢。通常1gb頻寬的網路延遲,預期在200μs左右,倘若一個命令僅執行時間就超過10ms,那比網路延遲慢了近50倍。 想要檢視所有執行時間比較慢的命令,可以通過使用Redis-cli工具,輸入slowlog get命令檢視,返回結果的第三個欄位以微妙位單位顯示命令的執行時間。假如只需要檢視最後10個慢命令,輸入slowlog get 10即可;還可以通過設定config set slowlog-log-slower-than 5000 在日誌中記錄慢命令資訊
  2. 監控客戶端的連線:因為Redis是單執行緒模型(只能使用單核),來處理所有客戶端的請求, 但由於客戶端連線數的增長,處理請求的執行緒資源開始降低分配給單個客戶端連線的處理時間,這時每個客戶端需要花費更多的時間去等待Redis共享服務的響應。這種情況下監控客戶端連線數是非常重要的,因為客戶端建立連線數的數量可能超出預期的數量,也可能是客戶端端沒有有效的釋放連線。在Redis-cli工具中輸入info clients可以檢視到當前例項的所有客戶端連線資訊。Redis預設允許客戶端連線的最大數量是10000。若是看到連線數超過5000以上,那可能會影響Redis的效能。倘若一些或大部分客戶端傳送大量的命令過來,這個數字會低的多。
  3. 限制客戶端連線數:自Redis2.6以後,允許使用者在配置檔案(Redis.conf)maxclients屬性上修改客戶端連線的最大數,也可以通過在Redis-cli工具上輸入config set maxclients 去設定最大連線數。根據連線數負載的情況,這個數字應該設定為預期連線數峰值的110到150之間,若是連線數超出這個數字後,Redis會拒絕並立刻關閉新來的連線。通過設定最大連線數來限制非預期數量的連線數增長,是非常重要的。另外,新連線嘗試失敗會返回一個錯誤訊息,這可以讓客戶端知道,Redis此時有非預期數量的連線數,以便執行對應的處理措施。 上述二種做法對控制連線數的數量和持續保持Redis的效能最優是非常重要的,
  4. 加強記憶體管理:較少的記憶體會引起Redis延遲時間增加。如果Redis佔用記憶體超出系統可用記憶體,作業系統會把Redis程序的一部分資料,從實體記憶體交換到硬碟上,記憶體交換會明顯的增加延遲時間。關於怎麼監控和減少記憶體使用,可檢視used_memory介紹章節。
  5. 效能資料指標:分析解決Redis效能問題,通常需要把延遲時間的資料變化與其他效能指標的變化相關聯起來。命令處理總數下降的發生可能是由慢命令阻塞了整個系統,但如果命令處理總數的增加,同時記憶體使用率也增加,那麼就可能是由於記憶體交換引起的效能問題。對於這種效能指標相關聯的分析,需要從歷史資料上來觀察到資料指標的重要變化,此外還可以觀察到單個性能指標相關聯的所有其他效能指標資訊。這些資料可以在Redis上收集,週期性的呼叫內容為Redis info的指令碼,然後分析輸出的資訊,記錄到日誌檔案中。當延遲發生變化時,用日誌檔案配合其他資料指標,把資料串聯起來排查定位問題。

15.4,記憶體碎片

info資訊中的mem_fragmentation_ratio給出了記憶體碎片率的資料指標,它是由操系統分配的記憶體除以Redis分配的記憶體得出(mem_fragmentation_ratio = used_memory_rss / used_memory):

127.0.0.1:6379> info memory
# Memory
used_memory:2707680
used_memory_human:2.58M
used_memory_rss:7372800
used_memory_rss_human:7.03M
used_memory_peak:2767696
used_memory_peak_human:2.64M
used_memory_peak_perc:97.83%
used_memory_overhead:2592088
used_memory_startup:1481872
used_memory_dataset:115592
used_memory_dataset_perc:9.43%
allocator_allocated:2752632
allocator_active:3096576
allocator_resident:5496832
total_system_memory:7976460288
total_system_memory_human:7.43G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.12
allocator_frag_bytes:343944
allocator_rss_ratio:1.78
allocator_rss_bytes:2400256
rss_overhead_ratio:1.34
rss_overhead_bytes:1875968
mem_fragmentation_ratio:2.76
mem_fragmentation_bytes:4706136
mem_not_counted_for_evict:0
mem_replication_backlog:1048576
mem_clients_slaves:0
mem_clients_normal:61496
mem_aof_buffer:0
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0
lazyfreed_objects:0

倘若記憶體碎片率mem_fragmentation_ratio 超過了1.5,那可能是作業系統或Redis例項中記憶體管理變差的表現。下面有3種方法解決記憶體管理變差的問題,並提高Redis效能:

  1. 重啟Redis伺服器:如果記憶體碎片率超過1.5,重啟Redis伺服器可以讓額外產生的記憶體碎片失效並重新作為新記憶體來使用,使作業系統恢復高效的記憶體管理。額外碎片的產生是由於Redis釋放了記憶體塊,但記憶體分配器並沒有返回記憶體給作業系統,這個記憶體分配器是在編譯時指定的,可以是libc、jemalloc或者tcmalloc。 通過比較used_memory_peak, used_memory_rss和used_memory_metrics的資料指標值可以檢查額外記憶體碎片的佔用。從名字上可以看出,used_memory_peak是過去Redis記憶體使用的峰值,而不是當前使用記憶體的值。如果used_memory_peak和used_memory_rss的值大致上相等,而且二者明顯超過了used_memory值,這說明額外的記憶體碎片正在產生。 在Redis-cli工具上輸入info memory可以檢視上面三個指標的資訊。在重啟伺服器之前,需要在Redis-cli工具上輸入shutdown save命令,意思是強制讓Redis資料庫執行儲存操作並關閉Redis服務,這樣做能保證在執行Redis關閉時不丟失任何資料。 在重啟後,Redis會從硬碟上載入持久化的檔案,以確保資料集持續可用。
  2. 限制記憶體交換: 如果記憶體碎片率低於1,Redis例項可能會把部分資料交換到硬碟上。記憶體交換會嚴重影響Redis的效能,所以應該增加可用實體記憶體或減少實Redis記憶體佔用。 可檢視used_memory章節的優化建議。
  3. 修改記憶體分配器:Redis支援glibc’s malloc、jemalloc11、tcmalloc幾種不同的記憶體分配器,每個分配器在記憶體分配和碎片上都有不同的實現。不建議普通管理員修改Redis預設記憶體分配器,因為這需要完全理解這幾種記憶體分配器的差異,也要重新編譯Redis。這個方法更多的是讓其瞭解Redis記憶體分配器所做的工作,當然也是改善記憶體碎片問題的一種辦法

15.5,回收key

info資訊中的evicted_keys欄位顯示的是,因為maxmemory限制導致key被回收刪除的數量。回收key的情況只會發生在設定maxmemory值後,不設定會發生記憶體交換。 當Redis由於記憶體壓力需要回收一個key時,Redis首先考慮的不是回收最舊的資料,而是在最近最少使用的key或即將過期的key中隨機選擇一個key,從資料集中刪除。

這可以在配置檔案中設定maxmemory-policy值為“volatile-lru”或“volatile-ttl”,來確定Redis是使用lru策略還是過期時間策略。 倘若所有的key都有明確的過期時間,那過期時間回收策略是比較合適的。若是沒有設定key的過期時間或者說沒有足夠的過期key,那設定lru策略是比較合理的,這可以回收key而不用考慮其過期狀態。

  1. 增加記憶體限制:倘若開啟快照功能,maxmemory需要設定成實體記憶體的45%,這幾乎不會有引發記憶體交換的危險。若是沒有開啟快照功能,設定系統可用記憶體的95%是比較合理的,具體參考前面的快照和maxmemory限制章節。如果maxmemory的設定是低於45%或95%(視持久化策略),通過增加maxmemory的值能讓Redis在記憶體中儲存更多的key,這能顯著減少回收key的數量。 若是maxmemory已經設定為推薦的閥值後,增加maxmemory限制不但無法提升效能,反而會引發記憶體交換,導致延遲增加、效能降低。 maxmemory的值可以在Redis-cli工具上輸入config set maxmemory命令來設定。需要注意的是,這個設定是立即生效的,但重啟後丟失,需要永久化儲存的話,再輸入config rewrite命令會把記憶體中的新配置重新整理到配置檔案中。
  2. 對例項進行分片:分片是把資料分割成合適大小,分別存放在不同的Redis例項上,每一個例項都包含整個資料集的一部分。通過分片可以把很多伺服器聯合起來儲存資料,相當於增加總的實體記憶體,使其在沒有記憶體交換和回收key的策略下也能儲存更多的key。假如有一個非常大的資料集,maxmemory已經設定,實際記憶體使用也已經超過了推薦設定的閥值,那通過資料分片能明顯減少key的回收,從而提高Redis的效能。 分片的實現有很多種方法,下面是Redis實現分片的幾種常見方式:
  • a. Hash分片:一個比較簡單的方法實現,通過Hash函式計算出key的Hash值,然後值所在範圍對應特定的Redis例項。
  • b. 代理分片:客戶端把請求傳送到代理上,代理通過分片配置表選擇對應的Redis例項。 如Twitter的Twemproxy,豌豆莢的codis。
  • c. 一致性Hash分片
  • d. 虛擬桶分片

15.6,redis大key情景

某讀書會Redis 大Key引發的線上事故分析總結

如何在Redis中查詢大key

安裝使用大key工具rdb_bigkeys,親測可行!

隨著使用者量積累300w,查詢的這些資訊的QPS從1100驟降至200,多方查詢後發現是大key(超512kb)的資料導致。

問題分析:

  1. redis記憶體浪費嚴重,頻繁LRU清楚(io操作),導致快取穿透
  2. 叢集傾斜問題,由於各種key的耗費記憶體不一樣,導致不同資料槽裡面的資料量不一致,提取慢
  3. 單執行緒提取慢,指令佇列擠壓嚴重(redis6.0後提供多執行緒支援,6.0版本以前嚴格來說也是多執行緒,不過執行使用者命令的請求時是單執行緒模型,沒有多資料線的程競爭關係,效率依然很高,還有一些執行緒來執行後臺任務,例如unlink刪除大key,rdb持久化等)
./rdb_bigkeys --bytes 0 --file bigkeys.csv --sep 0 --sorted --threads 4 /opt/docker/redis/data/dump6379.rdb

優化策略:

  1. 對於大key資料進行裁剪(將無意義資訊刪除)
  2. 業務優化,減少set長度(實際業務場景中,沒有必要保留全量的資料,只需要前100就已經綽綽有餘)
  3. 開啟redis客戶端快取(相當於額外加了一層快取,如果已經有相關資料,就不用去查詢redis,套娃!!)
  4. 定時掃描發現大key,手動清理(rdb_bigkeys工具手動查詢rdb持久化檔案中是否有大key資料,也可以使用redis-cli提供的bigkeys引數掃描大key)

15.7,壓縮列表

壓縮列表是Redis為了節約記憶體而開發的,由一系列特殊編碼的連續記憶體塊組成的順序型資料結構。一個壓縮列表可以包含任意多個節點,每個節點可以儲存一個位元組陣列或者一個整數值。

15.7.1,壓縮列表結構

1.壓縮列表結構:

引數說明:
zlbytes:記錄整個壓縮列表佔用的記憶體位元組數。
zltail:記錄壓縮列表表尾節點距離壓縮列表起始地址有多少位元組。
zllen:記錄了壓縮列表包含的節點數量。
entryN:壓縮列表的節點,節點長度由節點儲存的內容決定。
zlend:特殊值0xFF(十進位制255),用於標記壓縮列表的末端。

  1. 壓縮列表節點結構:

引數說明:
previous_entry_length:記錄壓縮列表中前一個節點的長度。previous_entry_length屬性的長度可以是1位元組或者5位元組:如果前一節點的長度小於 254 位元組,那麼previous_entry_length屬性的長度為1位元組,前一節點的長度就儲存在這一個位元組裡面。如果前一節點的長度大於等於254位元組,那麼previous_entry_length屬性的長度為5位元組,其中屬性的第一位元組會被設定為0xFE(十進位制值 254),而之後的四個位元組則用於儲存前一節點的長度。因為節點的previous_entry_length屬性記錄了前一個節點的長度,所以程式可以通過指標運算,根據當前節點的起始地址來計算出前一個節點的起始地址,縮列表的從表尾向表頭遍歷操作就是使用這一原理實現的。
encoding:記錄節點的contents屬性所儲存資料的型別以及長度。分兩種情況:(1)一位元組、兩位元組或者五位元組長,值的最高位為00 、01或者10的是位元組陣列編碼,這種編碼表示節點的content屬性儲存著位元組陣列,陣列的長度由編碼除去最高兩位之後的其他位記錄;(2)一位元組長,值的最高位以11開頭的是整數編碼,這種編碼表示節點的content屬性儲存著整數值,整數值的型別和長度由編碼除去最高兩位之後的其他位記錄。
contents:儲存節點的值,可以是一個位元組陣列或整數,型別和長度由節點的'encoding'屬性決定。

16,分散式

16.1,分散式鎖

16.1.1,普通版本

加鎖:

    @Override
    public boolean getLock(String key, long timeSeconds) {
        return redisTemplate.opsForValue().setIfAbsent(key, "lock", timeSeconds, TimeUnit.SECONDS);
    }

    @Override
    public boolean getLock(String key) {
        return redisTemplate.opsForValue().setIfAbsent(key, "lock", 300, TimeUnit.SECONDS);
    }

解鎖:

    /** 釋放鎖lua指令碼 */
    private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

	@Override
    public boolean releaseLock(String key) {
        Object lockValue = redisTemplate.opsForValue().get(key);
        if(lockValue == null){
            return true;
        }

        String value = lockValue.toString();
        Object[] objects = new Object[]{value};
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT,Long.class);

        Long result =  (Long)redisTemplate.execute(redisScript, Collections.singletonList(key),objects);
        if(result==1L){
            return true;
        }else {
            return false;
        }
    }

事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節點上,即使Redis通過sentinel保證高可用,如果這個master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:

  1. 在Redis的master節點上拿到了鎖;
  2. 但是這個加鎖的key還沒有同步到slave節點;
  3. master故障,發生故障轉移,slave節點升級為master節點;
  4. 導致鎖丟失。

16.1.2,RedLock

antirez提出的redlock演算法大概是這樣的:

在Redis的分散式環境中,我們假設有N個Redis master。這些節點完全互相獨立,不存在主從複製或者其他叢集協調機制。我們確保將在N個例項上使用與在Redis單例項下相同方法獲取和釋放鎖。現在我們假設有5個Redis master節點,同時我們需要在5臺伺服器上面執行這些Redis例項,這樣保證他們不會同時都宕掉。

原理:

  • 首先生成多個redis叢集的Rlock,並將其構造程RedLock
  • 依次迴圈對三個叢集進行加鎖,加鎖方式和redission一致
  • 如果迴圈加鎖的過程中加鎖失敗,那麼需要判斷加鎖失敗的次數是否超出了最大值(要多數成功)
  • 加鎖的過程中需要判斷是否加鎖超時
  • 若失敗,向所有節點請求解鎖

優點:

  • redis在專案中很常見
  • 容易取得可靠性和效能的平衡

缺點:

  • RedLock演算法需要多套redis例項,資源耗費

實現:

maven依賴:

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.3.2</version>
</dependency>

加鎖

Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.0.1:5378")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.0.1:5379")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.0.1:5380")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);

String resourceName = "REDLOCK_KEY";

RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
// 向3個redis例項嘗試加鎖
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;
try {
    // isLock = redLock.tryLock();
    // 500ms拿不到鎖, 就認為獲取鎖失敗。10000ms即10s是鎖失效時間。
    isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
    System.out.println("isLock = "+isLock);
    if (isLock) {
        //TODO if get lock success, do something;
    }
} catch (Exception e) {
} finally {
    // 無論如何, 最後都要解鎖
    redLock.unlock();
}

原始碼(加鎖):

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);
    // 獲取鎖時需要在redis例項上執行的lua命令
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              // 首先分散式鎖的KEY不能存在,如果確實不存在,那麼執行hset命令(hset REDLOCK_KEY uuid+threadId 1),並通過pexpire設定失效時間(也是鎖的租約時間)
              "if (redis.call('exists', KEYS[1]) == 0) then " +
                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              // 如果分散式鎖的KEY已經存在,並且value也匹配,表示是當前執行緒持有的鎖,那麼重入次數加1,並且設定失效時間
              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              // 獲取分散式鎖的KEY的失效時間毫秒數
              "return redis.call('pttl', KEYS[1]);",
              // 這三個引數分別對應KEYS[1],ARGV[1]和ARGV[2]
                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

原始碼(解鎖):

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    // 釋放鎖時需要在redis例項上執行的lua命令
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            // 如果分散式鎖KEY不存在,那麼向channel釋出一條訊息
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; " +
            "end;" +
            // 如果分散式鎖存在,但是value不匹配,表示鎖已經被佔用,那麼直接返回
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; " +
            // 如果就是當前執行緒佔有分散式鎖,那麼將重入次數減1
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
            // 重入次數減1後的值如果大於0,表示分散式鎖有重入過,那麼只設置失效時間,還不能刪除
            "if (counter > 0) then " +
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "else " +
                // 重入次數減1後的值如果為0,表示分散式鎖只獲取過1次,那麼刪除這個KEY,併發布解鎖訊息
                "redis.call('del', KEYS[1]); " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; "+
            "end; " +
            "return nil;",
            // 這5個引數分別對應KEYS[1],KEYS[2],ARGV[1],ARGV[2]和ARGV[3]
            Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}

16.2,儲存與獲取

16.2.1,儲存

首先,在redis的每一個節點上,都有這麼兩個東西,一個是插槽(slot)可以理解為是一個可以儲存兩個數值的一個變數這個變數的取值範圍是:0-16383。還有一個就是cluster我個人把這個cluster理解為是一個叢集管理的外掛。當你往Redis Cluster中加入一個Key時,redis會根據crc16的演算法得出一個結果,然後把結果對 16384 求餘數,計算這個key應該分佈到哪個hash slot中。

說明:

1.一個hash slot中會有很多key和value。你可以理解成表的分割槽。使用單節點時的redis時只有一個表,所有的key都放在這個表裡;2.改用Redis Cluster以後會自動為你生成16384個分割槽表,你insert資料時會根據上面的簡單演算法來決定你的key應該存在哪個分割槽,每個分割槽裡有很多key。)這樣每個 key 都會對應一個編號在 0-16383 之間的雜湊槽,通過這個值,去找到對應的插槽所對應的節點,然後直接自動跳轉到這個對應的節點上進行存取操作。

2.一個hash slot可以存多少資料:摘自Redis官網的Data type章節,意思是記憶體允許的情況下,可以存超過40億資料

3.redis cluster雜湊槽數量能改變嗎?

不能,因為程式碼演算法寫死了,固定是2的14次方這個數字上

16.2.2,獲取

當client向redis cluster中的任意一個節點發送與資料庫key有關的命令時,

接收命令的節點會計算出要處理的key屬於哪個雜湊槽(hash slot),

並且先檢查這個hash slot是否屬於自己(管轄):

  • 如果key所在的槽正好屬於自己(管轄),節點會直接執行這個key相關命令。

  • 如果key所在的槽不屬於自己(管轄),那麼節點會給client返回一個MOVED錯誤,

  • 指引client轉向負責對應槽的節點,並客戶端需要再次傳送想要執行的和key相關的命令。

總結:針對redis叢集如果快取的業務資料沒那麼重要redis可以不做備份,例如有3個節點的叢集,3個全都是主節點master,如果資料比較重要那麼就要對這3個節點都增加一個備份節點slave,判斷一個節點是否掛掉是通過投票來決定的,投票過程是叢集中所有master參與,如果半數以上master節點與master節點通訊超時(cluster-node-timeout),認為當前master節點掛掉.

如果redis叢集沒有備份,那麼當master掛掉之後那麼這個節點上的資料因為沒有salve備份和替換有可能會丟失,所以一般叢集都會有備份的,一般是3主3從的配置,開始3個主節點是確定好的,當有一個主節點宕機,並且它有多個從節點那麼這多個從節點就開始競爭選舉master(為什麼設定為多主多從)

參考連結

1,【狂神說Java】Redis最新超詳細版教程通俗易懂

2,Redis英文官網

3,Redis中文翻譯官網

4,Redis-github

5,redis 單執行緒的理解

6,為什麼Redis 單執行緒卻能支撐高併發?

7,棧heap和堆stack的區別複習

8,Redis設計與實現-連結串列list

9,HyperLogLog 演算法的原理講解以及 Redis 是如何應用它的(待學習)

10,Java面試常考的 BIO,NIO,AIO 總結

11,@Import註解的作用

12,Redlock:Redis分散式鎖最牛逼的實現

13,詳細解析Redis中的布隆過濾器及其應用

14,大資料分析常用去重演算法分析『HyperLogLog 篇』

15,Redis資料結構之壓縮列表

待完成

zset原理

hyperloglog原理

redis多執行緒

redis多路複用網路epoll

叢集動態擴容縮容~2022.03.25 刪除前,先遷移資料槽;新增後,遷移資料槽

投票演算法~2022.03.25 Raft演算法

布隆過濾器~2022.03.25 與jrg午休交流知道其原理!