天天看點

redis學習筆記

一、Nosql概述

為什麼使用Nosql

1、單機Mysql時代
redis學習筆記

90年代,一個網站的通路量一般不會太大,單個資料庫完全夠用。随着使用者增多,網站出現以下問題

  1. 資料量增加到一定程度,單機資料庫就放不下了
  2. 資料的索引(B+ Tree),一個機器記憶體也存放不下
  3. 通路量變大後(讀寫混合),一台伺服器承受不住。
2、Memcached(緩存) + Mysql + 垂直拆分(讀寫分離)

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

redis學習筆記

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

  1. 優化資料庫的資料結構和索引(難度大)
  2. 檔案緩存,通過IO流擷取比每次都通路資料庫效率略高,但是流量爆炸式增長時候,IO流也承受不了
  3. MemCache,當時最熱門的技術,通過在資料庫和資料庫通路層之間加上一層緩存,第一次通路時查詢資料庫,将結果儲存到緩存,後續的查詢先檢查緩存,若有直接拿去使用,效率顯著提升。
3、分庫分表 + 水準拆分 + Mysql叢集
redis學習筆記
4、如今最近的年代

如今資訊量井噴式增長,各種各樣的資料出現(使用者定位資料,圖檔資料等),大資料的背景下關系型資料庫(RDBMS)無法滿足大量資料要求。Nosql資料庫就能輕松解決這些問題。

目前一個基本的網際網路項目
redis學習筆記
為什麼要用NoSQL ?

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

這時候我們就需要使用NoSQL資料庫的,Nosql可以很好的處理以上的情況!

什麼是Nosql

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

Not Only Structured Query Language

關系型資料庫:列+行,同一個表下資料的結構是一樣的。

非關系型資料庫:資料存儲沒有固定的格式,并且可以進行橫向擴充。

NoSQL泛指非關系型資料庫,随着web2.0網際網路的誕生,傳統的關系型資料庫很難對付web2.0時代!尤其是超大規模的高并發的社群,暴露出來很多難以克服的問題,NoSQL在當今大資料環境下發展的十分迅速,Redis是發展最快的。

Nosql特點

  1. 友善擴充(資料之間沒有關系,很好擴充!)
  2. 大資料量高性能(Redis一秒可以寫8萬次,讀11萬次,NoSQL的緩存記錄級,是一種細粒度的緩存,性能會比較高!)
  3. 資料類型是多樣型的!(不需要事先設計資料庫,随取随用)
  4. 傳統的 RDBMS 和 NoSQL
    傳統的 RDBMS(關系型資料庫)
    - 結構化組織
    - SQL
    - 資料和關系都存在單獨的表中 row col
    - 操作,資料定義語言
    - 嚴格的一緻性
    - 基礎的事務
    - ...
               
    Nosql
    - 不僅僅是資料
    - 沒有固定的查詢語言
    - 鍵值對存儲,列存儲,文檔存儲,圖形資料庫(社交關系)
    - 最終一緻性
    - CAP定理和BASE
    - 高性能,高可用,高擴充
    - ...
               
了解:3V + 3高

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

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

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

  1. 高并發
  2. 高可擴
  3. 高性能

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

阿裡巴巴演進分析

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

redis學習筆記
redis學習筆記
# 商品資訊
- 一般存放在關系型資料庫:Mysql,阿裡巴巴使用的Mysql都是經過内部改動的。

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

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

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

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

# 商品交易,外部支付接口
- 第三方應用
           

Nosql的四大分類

KV鍵值對
  • 新浪:Redis
  • 美團:Redis + Tair
  • 阿裡、百度:Redis + Memcache
文檔型資料庫(bson資料格式):
  • MongoDB(掌握)
    • 基于分布式檔案存儲的資料庫。C++編寫,用于處理大量文檔。
    • MongoDB是RDBMS和NoSQL的中間産品。MongoDB是非關系型資料庫中功能最豐富的,NoSQL中最像關系型資料庫的資料庫。
  • ConthDB
列存儲資料庫
  • HBase(大資料必學)
  • 分布式檔案系統
圖關系資料庫

用于廣告推薦,社交網絡

  • Neo4j、InfoGrid
分類 Examples舉例 典型應用場景 資料模型 優點 缺點
鍵值對(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容緩存,主要用于處理大量資料的高通路負載,也用于一些日志系統等等。 Key 指向 Value 的鍵值對,通常用hash table來實作 查找速度快 資料無結構化,通常隻被當作字元串或者二進制資料
Cassandra, HBase, Riak 分布式的檔案系統 以列簇式存儲,将同一列資料存在一起 查找速度快,可擴充性強,更容易進行分布式擴充 功能相對局限
文檔型資料庫 CouchDB, MongoDb Web應用(與Key-Value類似,Value是結構化的,不同的是資料庫能夠了解Value的内容) Key-Value對應的鍵值對,Value為結構化資料 資料結構要求不嚴格,表結構可變,不需要像關系型資料庫一樣需要預先定義表結構 查詢性能不高,而且缺乏統一的查詢文法。
圖形(Graph)資料庫 Neo4J, InfoGrid, Infinite Graph 社交網絡,推薦系統等。專注于建構關系圖譜 圖結構 利用圖結構相關算法。比如最短路徑尋址,N度關系查找等 很多時候需要對整個圖做計算才能得出需要的資訊,而且這種結構不太好做分布式的叢集

二、Redis入門

概述

Redis是什麼?

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

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

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

Redis能該幹什麼?
  1. 記憶體存儲、持久化,記憶體是斷電即失的,是以需要持久化(RDB、AOF)
  2. 高效率、用于高速緩沖
  3. 釋出訂閱系統
  4. 地圖資訊分析
  5. 計時器、計數器(eg:浏覽量)
  6. 。。。
特性
  1. 多樣的資料類型
  2. 持久化
  3. 叢集
  4. 事務

環境搭建

官網:https://redis.io/

推薦使用Linux伺服器學習。

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

Windows安裝

https://github.com/dmajkic/redis

  1. 解壓安裝包
    redis學習筆記
  2. 開啟redis-server.exe
  3. 啟動redis-cli.exe測試
    redis學習筆記

Linux安裝

  1. 下載下傳安裝包!

    redis-5.0.8.tar.gz

  2. 解壓Redis的安裝包!程式一般放在

    /opt

    目錄下
    redis學習筆記
  3. 基本環境安裝
    yum install gcc-c++
    # 然後進入redis目錄下執行
    make
    # 然後執行
    make install
               
redis學習筆記
  1. redis預設安裝路徑

    /usr/local/bin

    redis學習筆記
  2. 将redis的配置檔案複制到 程式安裝目錄

    /usr/local/bin/kconfig

    redis學習筆記
  3. redis預設不是背景啟動的,需要修改配置檔案!
    redis學習筆記
  4. 通過制定的配置檔案啟動redis服務
    redis學習筆記
  5. 使用redis-cli連接配接指定的端口号測試,Redis的預設端口6379
    redis學習筆記
  6. 檢視redis程序是否開啟
    redis學習筆記
  7. 關閉Redis服務

    shutdown

    redis學習筆記
  8. 再次檢視程序是否存在
  9. 後面我們會使用單機多Redis啟動叢集測試

測試性能

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

redis學習筆記

簡單測試:

# 測試:100個并發連接配接 100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
12
           
redis學習筆記

基礎知識

redis預設有16個資料庫
redis學習筆記

預設使用的第0個;

16個資料庫為:DB 0~DB 15

預設使用DB 0 ,可以使用

select n

切換到DB n,

dbsize

可以檢視目前資料庫的大小,與key數量相關。

127.0.0.1:6379> config get databases # 指令行檢視資料庫數量databases
1) "databases"
2) "16"

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

# 不同資料庫之間 資料是不能互通的,并且dbsize 是根據庫中key的個數。
127.0.0.1:6379> set name sakura 
OK
127.0.0.1:6379> SELECT 8
OK
127.0.0.1:6379[8]> get name # db8中并不能擷取db0中的鍵值對。
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 0
OK
127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"
127.0.0.1:6379> DBSIZE # size和key個數相關
(integer) 5
           

keys *

:檢視目前資料庫中所有的key。

flushdb

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

flushall

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

Redis是單線程的,Redis是基于記憶體操作的。

是以Redis的性能瓶頸不是CPU,而是機器記憶體和網絡帶寬。

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

Redis為什麼單線程還這麼快?
  • 誤區1:高性能的伺服器一定是多線程的?
  • 誤區2:多線程(CPU上下文會切換!)一定比單線程效率高!

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

三、五大資料類型

Redis是一個開源(BSD許可),記憶體存儲的資料結構伺服器,可用作資料庫,高速緩存和消息隊列代理。它支援字元串、哈希表、清單、集合、有序集合,位圖,hyperloglogs等資料類型。内置複制、Lua腳本、LRU收回、事務以及不同級别磁盤持久化功能,同時通過Redis Sentinel提供高可用,通過Redis Cluster提供自動分區。

Redis-key

在redis中無論什麼資料類型,在資料庫中都是以key-value形式儲存,通過進行對Redis-key的操作,來完成對資料庫中資料的操作。

下面學習的指令:

  • exists key

    :判斷鍵是否存在
  • del key

    :删除鍵值對
  • move key db

    :将鍵值對移動到指定資料庫
  • expire key second

    :設定鍵值對的過期時間
  • type key

    :檢視value的資料類型
127.0.0.1:6379> keys * # 檢視目前資料庫所有key
(empty list or set)
127.0.0.1:6379> set name qinjiang # set key
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> move age 1 # 将鍵值對移動到指定資料庫
(integer) 1
127.0.0.1:6379> EXISTS age # 判斷鍵是否存在
(integer) 0 # 不存在
127.0.0.1:6379> EXISTS name
(integer) 1 # 存在
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> del age # 删除鍵值對
(integer) 1 # 删除個數


127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> EXPIRE age 15 # 設定鍵值對的過期時間

(integer) 1 # 設定成功 開始計數
127.0.0.1:6379> ttl age # 檢視key的過期剩餘時間
(integer) 13
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key過期,-1表示key未設定過期時間

127.0.0.1:6379> get age # 過期的key 會被自動delete
(nil)
127.0.0.1:6379> keys *
1) "name"

127.0.0.1:6379> type name # 檢視value的資料類型
string
           

關于

TTL

指令

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

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

關于重命名

RENAME

RENAMENX

  • RENAME key newkey

    修改 key 的名稱
  • RENAMENX key newkey

    僅當 newkey 不存在時,将 key 改名為 newkey 。

更多指令學習:https://www.redis.net.cn/order/

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-wBVZtGVm-1597890996517)(狂神說 Redis.assets/image-20200813114228439.png)]

String(字元串)

普通的set、get直接略過。

描述 示例

APPEND key value

向指定的key的value後追加字元串 127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> append msg " world" (integer) 11 127.0.0.1:6379> get msg “hello world”

DECR/INCR key

将指定key的value數值進行+1/-1(僅對于數字) 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> incr age (integer) 21 127.0.0.1:6379> decr age (integer) 20

INCRBY/DECRBY key n

按指定的步長對數值進行加減 127.0.0.1:6379> INCRBY age 5 (integer) 25 127.0.0.1:6379> DECRBY age 10 (integer) 15

INCRBYFLOAT key n

為數值加上浮點型數值 127.0.0.1:6379> INCRBYFLOAT age 5.2 “20.2”

STRLEN key

擷取key儲存值的字元串長度 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> STRLEN msg (integer) 11

GETRANGE key start end

按起止位置擷取字元串(閉區間,起止位置都取) 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 3 9 “lo worl”

SETRANGE key offset value

用指定的value 替換key中 offset開始的值 127.0.0.1:6379> SETRANGE msg 2 hello (integer) 7 127.0.0.1:6379> get msg “tehello”

GETSET key value

将給定 key 的值設為 value ,并傳回 key 的舊值(old value)。 127.0.0.1:6379> GETSET msg test “hello world”

SETNX key value

僅當key不存在時進行set 127.0.0.1:6379> SETNX msg test (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1

SETEX key seconds value

set 鍵值對并設定過期時間 127.0.0.1:6379> setex name 10 root OK 127.0.0.1:6379> get name (nil)

MSET key1 value1 [key2 value2..]

批量set鍵值對 127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 OK

MSETNX key1 value1 [key2 value2..]

批量設定鍵值對,僅當參數中所有的key都不存在時執行 127.0.0.1:6379> MSETNX k1 v1 k4 v4 (integer) 0

MGET key1 [key2..]

批量擷取多個key儲存的值 127.0.0.1:6379> MGET k1 k2 k3 1) “v1” 2) “v2” 3) “v3”

PSETEX key milliseconds value

和 SETEX 指令相似,但它以毫秒為機關設定 key 的生存時間,

getset key value

如果不存在值,則傳回nil,如果存在值,擷取原來的值,并設定新的值

String類似的使用場景:value除了是字元串還可以是數字,用途舉例:

  • 計數器
  • 統計多機關的數量:uid:123666:follow 0
  • 粉絲數
  • 對象存儲緩存

List(清單)

Redis清單是簡單的字元串清單,按照插入順序排序。你可以添加一個元素到清單的頭部(左邊)或者尾部(右邊)

一個清單最多可以包含 232 - 1 個元素 (4294967295, 每個清單超過40億個元素)。

首先我們清單,可以經過規則定義将其變為隊列、棧、雙端隊列等

redis學習筆記

正如圖Redis中List是可以進行雙端操作的,是以指令也就分為了LXXX和RLLL兩類,有時候L也表示List例如LLEN

LPUSH/RPUSH key value1[value2..]

從左邊/右邊向清單中PUSH值(一個或者多個)。

LRANGE key start end

擷取list 起止元素(索引從左往右 遞增)

LPUSHX/RPUSHX key value

向已存在的列名中push值(一個或者多個)
`LINSERT key BEFORE AFTER pivot value`

LLEN key

檢視清單長度

LINDEX key index

通過索引擷取清單元素

LSET key index value

通過索引為元素設值

LPOP/RPOP key

從最左邊/最右邊移除值 并傳回

RPOPLPUSH source destination

将清單的尾部(右)最後一個值彈出,并傳回,然後加到另一個清單的頭部

LTRIM key start end

通過下标截取指定範圍内的清單

LREM key count value

List中是允許value重複的

count > 0

:從頭部開始搜尋 然後删除指定的value 至多删除count個

count < 0

:從尾部開始搜尋…

count = 0

:删除清單中所有的指定value。

BLPOP/BRPOP key1[key2] timout

移出并擷取清單的第一個/最後一個元素, 如果清單沒有元素會阻塞清單直到等待逾時或發現可彈出元素為止。

BRPOPLPUSH source destination timeout

RPOPLPUSH

功能相同,如果清單沒有元素會阻塞清單直到等待逾時或發現可彈出元素為止。
---------------------------LPUSH---RPUSH---LRANGE--------------------------------

127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1}
(integer) 1
127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1}
(integer) 2
127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3}
(integer) 3
127.0.0.1:6379> get mylist # 普通的get是無法擷取list值的
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 擷取起止位置範圍内的元素
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 2
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 1
1) "k2"
2) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1 # 擷取全部元素
1) "k2"
2) "k1"
3) "k3"

---------------------------LPUSHX---RPUSHX-----------------------------------

127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失敗
(integer) 0
127.0.0.1:6379> LPUSHX list v1 v2  
(integer) 0
127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左邊 PUSH k4 k5
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k1"
5) "k3"

---------------------------LINSERT--LLEN--LINDEX--LSET----------------------------

127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素後 插入ins_key1
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "ins_key1"
5) "k1"
6) "k3"
127.0.0.1:6379> LLEN mylist # 檢視mylist的長度
(integer) 6
127.0.0.1:6379> LINDEX mylist 3 # 擷取下标為3的元素
"ins_key1"
127.0.0.1:6379> LINDEX mylist 0
"k5"
127.0.0.1:6379> LSET mylist 3 k6 # 将下标3的元素 set值為k6
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k6"
5) "k1"
6) "k3"

---------------------------LPOP--RPOP--------------------------

127.0.0.1:6379> LPOP mylist # 左側(頭部)彈出
"k5"
127.0.0.1:6379> RPOP mylist # 右側(尾部)彈出
"k3"

---------------------------RPOPLPUSH--------------------------

127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"
4) "k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 将mylist的最後一個值(k1)彈出,加入到newlist的頭部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1
1) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"

---------------------------LTRIM--------------------------

127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"

# 初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2
---------------------------LREM--------------------------

127.0.0.1:6379> LREM mylist 3 k2 # 從頭部開始搜尋 至多删除3個 k2
(integer) 3
# 删除後:mylist: k2,k2,k2,k4,k2,k2,k2,k2

127.0.0.1:6379> LREM mylist -2 k2 #從尾部開始搜尋 至多删除2個 k2
(integer) 2
# 删除後:mylist: k2,k2,k2,k4,k2,k2


---------------------------BLPOP--BRPOP--------------------------

mylist: k2,k2,k2,k4,k2,k2
newlist: k1

127.0.0.1:6379> BLPOP newlist mylist 30 # 從newlist中彈出第一個值,mylist作為候選
1) "newlist" # 彈出
2) "k1"
127.0.0.1:6379> BLPOP newlist mylist 30
1) "mylist" # 由于newlist空了 從mylist中彈出
2) "k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 逾時了

127.0.0.1:6379> BLPOP newlist 30 # 我們連接配接另一個用戶端向newlist中push了test, 阻塞被解決。
1) "newlist"
2) "test"
(12.54s)
           
小結
  • list實際上是一個連結清單,before Node after , left, right 都可以插入值
  • 如果key不存在,則建立新的連結清單
  • 如果key存在,新增内容
  • 如果移除了所有值,空連結清單,也代表不存在
  • 在兩邊插入或者改動值,效率最高!修改中間元素,效率相對較低

應用:

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

Set(集合)

Redis的Set是string類型的無序集合。集合成員是唯一的,這就意味着集合中不能出現重複的資料。

Redis 中 集合是通過哈希表實作的,是以添加,删除,查找的複雜度都是O(1)。

集合中最大的成員數為 232 - 1 (4294967295, 每個集合可存儲40多億個成員)。

SADD key member1[member2..]

向集合中無序增加一個/多個成員

SCARD key

擷取集合的成員數

SMEMBERS key

傳回集合中所有的成員

SISMEMBER key member

查詢member元素是否是集合的成員,結果是無序的

SRANDMEMBER key [count]

随機傳回集合中count個成員,count預設值為1

SPOP key [count]

随機移除并傳回集合中count個成員,count預設值為1

SMOVE source destination member

将source集合的成員member移動到destination集合

SREM key member1[member2..]

移除集合中一個/多個成員

SDIFF key1[key2..]

傳回所有集合的差集 key1- key2 - …

SDIFFSTORE destination key1[key2..]

在SDIFF的基礎上,将結果儲存到集合中(覆寫)。不能儲存到其他類型key噢!

SINTER key1 [key2..]

傳回所有集合的交集

SINTERSTORE destination key1[key2..]

在SINTER的基礎上,存儲結果到集合中。覆寫

SUNION key1 [key2..]

傳回所有集合的并集

SUNIONSTORE destination key1 [key2..]

在SUNION的基礎上,存儲結果到及和張。覆寫

SSCAN KEY [MATCH pattern] [COUNT count]

在大量資料環境下,使用此指令周遊集合中元素,每次周遊部分
---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------

127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成員 m1~m4
(integer) 4
127.0.0.1:6379> SCARD myset # 擷取集合的成員數目
(integer) 4
127.0.0.1:6379> smembers myset # 擷取集合中所有成員
1) "m4"
2) "m3"
3) "m2"
4) "m1"
127.0.0.1:6379> SISMEMBER myset m5 # 查詢m5是否是myset的成員
(integer) 0 # 不是,傳回0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1 # 是,傳回1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1

---------------------SRANDMEMBER--SPOP----------------------------------

127.0.0.1:6379> SRANDMEMBER myset 3 # 随機傳回3個成員
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> SRANDMEMBER myset # 随機傳回1個成員
"m3"
127.0.0.1:6379> SPOP myset 2 # 随機移除并傳回2個成員
1) "m1"
2) "m4"
# 将set還原到{m1,m2,m3,m4}

---------------------SMOVE--SREM----------------------------------------

127.0.0.1:6379> SMOVE myset newset m3 # 将myset中m3成員移動到newset集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "m4"
2) "m2"
3) "m1"
127.0.0.1:6379> SMEMBERS newset
1) "m3"
127.0.0.1:6379> SREM newset m3 # 從newset中移除m3元素
(integer) 1
127.0.0.1:6379> SMEMBERS newset
(empty list or set)

# 下面開始是多集合操作,多集合操作中若隻有一個參數預設和自身進行運算
# setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}

-----------------------------SDIFF------------------------------------

127.0.0.1:6379> SDIFF setx sety setz # 等價于setx-sety-setz
1) "m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
1) "m4"
2) "m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
1) "m5"


-------------------------SINTER---------------------------------------
# 共同關注(交集)

127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
1) "m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
1) "m2"
2) "m6"

-------------------------SUNION---------------------------------------

127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集
1) "m4"
2) "m6"
3) "m3"
4) "m2"
5) "m1"
6) "m5"
127.0.0.1:6379> SUNION setx sety # setx sety 并集
1) "m4"
2) "m6"
3) "m2"
4) "m1"
5) "m5"
           

Hash(哈希)

Redis hash 是一個string類型的field和value的映射表,hash特别适合用于存儲對象。

Set就是一種簡化的Hash,隻變動key,而value使用預設值填充。可以将一個Hash表作為一個對象進行存儲,表中存放對象的資訊。

HSET key field value

将哈希表 key 中的字段 field 的值設為 value 。重複設定同一個field會覆寫,傳回0

HMSET key field1 value1 [field2 value2..]

同時将多個 field-value (域-值)對設定到哈希表 key 中。

HSETNX key field value

隻有在字段 field 不存在時,設定哈希表字段的值。

HEXISTS key field

檢視哈希表 key 中,指定的字段是否存在。

HGET key field value

擷取存儲在哈希表中指定字段的值

HMGET key field1 [field2..]

擷取所有給定字段的值

HGETALL key

擷取在哈希表key 的所有字段和值

HKEYS key

擷取哈希表key中所有的字段

HLEN key

擷取哈希表中字段的數量

HVALS key

擷取哈希表中所有值

HDEL key field1 [field2..]

删除哈希表key中一個/多個field字段

HINCRBY key field n

為哈希表 key 中的指定字段的整數值加上增量n,并傳回增量後結果 一樣隻适用于整數型字段

HINCRBYFLOAT key field n

為哈希表 key 中的指定字段的浮點數值加上增量 n。

HSCAN key cursor [MATCH pattern] [COUNT count]

疊代哈希表中的鍵值對。
------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura # 将studentx哈希表作為一個對象,設定name為sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc # 重複設定field進行覆寫,并傳回0
(integer) 0
127.0.0.1:6379> HSET studentx age 20 # 設定studentx的age為20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 設定sex為1,tel為15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 設定已存在的field
(integer) 0 # 失敗
127.0.0.1:6379> HSETNX studentx email [email protected]
(integer) 1 # 成功

----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在

-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 擷取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 擷取studentx中name、age、tel字段的value
1) "gyc"
2) "20"
3) "15623667886"
127.0.0.1:6379> HGETALL studentx # 擷取studentx中所有的field及其value
 1) "name"
 2) "gyc"
 3) "age"
 4) "20"
 5) "sex"
 6) "1"
 7) "tel"
 8) "15623667886"
 9) "email"
10) "[email protected]"


--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 檢視studentx中所有的field
1) "name"
2) "age"
3) "sex"
4) "tel"
5) "email"
127.0.0.1:6379> HLEN studentx # 檢視studentx中的字段數量
(integer) 5
127.0.0.1:6379> HVALS studentx # 檢視studentx中所有的value
1) "gyc"
2) "20"
3) "1"
4) "15623667886"
5) "[email protected]"

-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "email"

-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段數值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整數字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"
           

Hash變更的資料user name age,尤其是使用者資訊之類的,經常變動的資訊!Hash更适合于對象的存儲,Sring更加适合字元串存儲!

Zset(有序集合)

不同的是每個元素都會關聯一個double類型的分數(score)。redis正是通過分數來為集合中的成員進行從小到大的排序。

score相同:按字典順序排序

有序集合的成員是唯一的,但分數(score)卻可以重複。

ZADD key score member1 [score2 member2]

向有序集合添加一個或多個成員,或者更新已存在成員的分數

ZCARD key

擷取有序集合的成員數

ZCOUNT key min max

計算在有序集合中指定區間score的成員數

ZINCRBY key n member

有序集合中對指定成員的分數加上增量 n

ZSCORE key member

傳回有序集中,成員的分數值

ZRANK key member

傳回有序集合中指定成員的索引

ZRANGE key start end

通過索引區間傳回有序集合成指定區間内的成員

ZRANGEBYLEX key min max

通過字典區間傳回有序集合的成員

ZRANGEBYSCORE key min max

通過分數傳回有序集合指定區間内的成員-inf 和 +inf分别表示最小最大值,隻支援開區間()

ZLEXCOUNT key min max

在有序集合中計算指定字典區間内成員數量

ZREM key member1 [member2..]

移除有序集合中一個/多個成員

ZREMRANGEBYLEX key min max

移除有序集合中給定的字典區間的所有成員

ZREMRANGEBYRANK key start stop

移除有序集合中給定的排名區間的所有成員

ZREMRANGEBYSCORE key min max

移除有序集合中給定的分數區間的所有成員

ZREVRANGE key start end

傳回有序集中指定區間内的成員,通過索引,分數從高到底

ZREVRANGEBYSCORRE key max min

傳回有序集中指定分數區間内的成員,分數從高到低排序

ZREVRANGEBYLEX key max min

傳回有序集中指定字典區間内的成員,按字典順序倒序

ZREVRANK key member

傳回有序集合中指定成員的排名,有序內建員按分數值遞減(從大到小)排序

ZINTERSTORE destination numkeys key1 [key2 ..]

計算給定的一個或多個有序集的交集并将結果集存儲在新的有序集合 key 中,numkeys:表示參與運算的集合數,将score相加作為結果的score

ZUNIONSTORE destination numkeys key1 [key2..]

計算給定的一個或多個有序集的交集并将結果集存儲在新的有序集合 key 中

ZSCAN key cursor [MATCH pattern\] [COUNT count]

疊代有序集合中的元素(包括元素成員和元素分值)
-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中添加成員m1 score=1 以及成員m2 score=2..
(integer) 2
127.0.0.1:6379> ZCARD myzset # 擷取有序集合的成員數
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1 # 擷取score在 [0,1]區間的成員數量
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2

----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 将成員m2的score +5
"7"
127.0.0.1:6379> ZSCORE myzset m1 # 擷取成員m1的score
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"

--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 擷取成員m1的索引,索引按照score排序,score相同索引值按字典順序順序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 擷取索引在 0~1的成員
1) "m1"
2) "m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 擷取全部成員
1) "m1"
2) "m3"
3) "m2"

#testset=>{abc,add,amaze,apple,back,java,redis} score均為0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 傳回所有成員
1) "abc"
2) "add"
3) "amaze"
4) "apple"
5) "back"
6) "java"
7) "redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分頁 按索引顯示查詢結果的 0,1,2條記錄
1) "abc"
2) "add"
3) "amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 顯示 3,4,5條記錄
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 顯示 (-,apple] 區間内的成員
1) "abc"
2) "add"
3) "amaze"
4) "apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 顯示 [apple,java]字典區間的成員
1) "apple"
2) "back"
3) "java"

-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 傳回score在 [1,10]之間的的成員
1) "m1"
2) "m3"
3) "m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
1) "m1"
2) "m3"

--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3

------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成員abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典區間[apple,java]中的所有成員
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成員
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成員
(integer) 2


# testset=> {abc,add,apple,amaze,back,java,redis} score均為0
# myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score遞減排序,然後按索引,傳回結果的 0~3
1) "m9"
2) "m7"
3) "m4"
4) "m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 傳回排序結果的 索引的2~4
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score遞減順序 傳回集合中分數在[2,6]之間的成員
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 傳回集合中(add,java]字典區間的成員
1) "java"
2) "back"
3) "apple"
4) "amaze"

-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score遞減順序,傳回成員m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4


# mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小紅、小剛的數學成績
# enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小紅、小剛的英語成績
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 将mathscore enscore進行合并 結果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合并後的score是之前集合中所有score的和
1) "xm"
2) "160"
3) "xg"
4) "177"
5) "xh"
6) "188"

127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取兩個集合的成員score最小值作為結果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
1) "xm"
2) "70"
3) "xg"
4) "87"
5) "xh"
6) "93"
           

應用案例:

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

四、三種特殊資料類型

Geospatial(地理位置)

使用經緯度定位地理坐标并用一個有序集合zset儲存,是以zset指令也可以使用

geoadd key longitud(經度) latitude(緯度) member [..]

将具體經緯度的坐标存入一個有序集合

geopos key member [member..]

擷取集合中的一個/多個成員坐标

geodist key member1 member2 [unit]

傳回兩個給定位置之間的距離。預設以米作為機關。
`georadius key longitude latitude radius m km

GEORADIUSBYMEMBER key member radius...

功能與GEORADIUS相同,隻是中心位置不是具體的經緯度,而是使用結合中已有的成員作為中心點。

geohash key member1 [member2..]

傳回一個或多個位置元素的Geohash表示。使用Geohash位置52點整數編碼。

有效經緯度

  • 有效的經度從-180度到180度。
  • 有效的緯度從-85.05112878度到85.05112878度。

指定機關的參數 unit 必須是以下機關的其中一個:

  • m 表示機關為米。
  • km 表示機關為千米。
  • mi 表示機關為英裡。
  • ft 表示機關為英尺。

關于GEORADIUS的參數

通過

georadius

就可以完成 附近的人功能

withcoord:帶上坐标

withdist:帶上距離,機關與半徑機關相同

COUNT n : 隻顯示前n個(按距離遞增排序)

----------------georadius---------------------
127.0.0.1:6379> GEORADIUS china:city 120 30 500 km withcoord withdist # 查詢經緯度(120,30)坐标500km半徑内的成員
1) 1) "hangzhou"
   2) "29.4151"
   3) 1) "120.20000249147415"
      2) "30.199999888333501"
2) 1) "shanghai"
   2) "205.3611"
   3) 1) "121.40000134706497"
      2) "31.400000253193539"
     
------------geohash---------------------------
127.0.0.1:6379> geohash china:city yichang shanghai # 擷取成員經緯坐标的geohash表示
1) "wmrjwbr5250"
2) "wtw6ds0y300"
           

Hyperloglog(基數統計)

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

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

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

其底層使用string資料類型

什麼是基數?

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

應用場景:

網頁的通路量(UV):一個使用者多次通路,也隻能算作一個人。

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

PFADD key element1 [elememt2..]

添加指定元素到 HyperLogLog 中

PFCOUNT key [key]

傳回給定 HyperLogLog 的基數估算值。

PFMERGE destkey sourcekey [sourcekey..]

将多個 HyperLogLog 合并為一個 HyperLogLog
----------PFADD--PFCOUNT---------------------
127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素
(integer) 1
127.0.0.1:6379> type myelemx # hyperloglog底層使用String
string
127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基數
(integer) 11
127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s
(integer) 1
127.0.0.1:6379> PFCOUNT myelemy
(integer) 11

----------------PFMERGE-----------------------
127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合并myelemx和myelemy 成為myelemz
OK
127.0.0.1:6379> PFCOUNT myelemz # 估算基數
(integer) 17
           

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

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

BitMaps(位圖)

使用位存儲,資訊狀态隻有 0 和 1

Bitmap是一串連續的2進制數字(0或1),每一位所在的位置為偏移(offset),在bitmap上可執行AND,OR,XOR,NOT以及其它位操作。

應用場景

簽到統計、狀态統計

setbit key offset value

為指定key的offset位設定值

getbit key offset

擷取offset位的值

bitcount key [start end]

統計字元串被設定為1的bit數,也可以指定統計範圍按位元組

bitop operration destkey key[key..]

對一個或多個儲存二進制位的字元串 key 進行位元操作,并将結果儲存到 destkey 上。

BITPOS key bit [start] [end]

傳回字元串裡面第一個被設定為1或者0的bit位。start和end隻能按位元組,不能按位
------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 設定sign的第0位為 1 
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 設定sign的第2位為 1  不設定預設 是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string

127.0.0.1:6379> getbit sign 2 # 擷取第2位的數值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未設定預設是0
(integer) 0

-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 統計sign中為1的位數
(integer) 4
           

bitmaps是一串從左到右的二進制串

五、事務

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

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

----------------- 隊列 set set set 執行 -------------------

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

  • 一次性
  • 順序性
  • 排他性
  1. Redis事務沒有隔離級别的概念
  2. Redis單條指令是保證原子性的,但是事務不保證原子性!

Redis事務操作過程

  • 開啟事務(

    multi

  • 指令入隊
  • 執行事務(

    exec

是以事務中的指令在加入時都沒有被執行,直到送出時才會開始執行(Exec)一次性完成。

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

取消事務(

discurd

)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD # 放棄事務
OK
127.0.0.1:6379> EXEC 
(error) ERR EXEC without MULTI # 目前未開啟事務
127.0.0.1:6379> get k1 # 被放棄事務中指令并未執行
(nil)
           

事務錯誤

代碼文法錯誤(編譯時異常)所有的指令都不執行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> error k1 # 這是一條文法錯誤指令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 會報錯但是不影響後續指令入隊 
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 執行報錯
127.0.0.1:6379> get k1 
(nil) # 其他指令并沒有被執行
           
代碼邏輯錯誤 (運作時異常) **其他指令可以正常執行 ** >>> 是以不保證事務原子性
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> INCR k1 # 這條指令邏輯錯誤(對字元串進行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 運作時報錯
4) "v2" # 其他指令正常執行

# 雖然中間有一條指令報錯了,但是後面的指令依舊正常執行成功了。
# 是以說Redis單條指令保證原子性,但是Redis事務不能保證原子性。
           

監控

悲觀鎖:

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

樂觀鎖:

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

使用

watch key

監控指定資料,相當于樂觀鎖加鎖。

正常執行
127.0.0.1:6379> set money 100 # 設定餘額:100
OK
127.0.0.1:6379> set use 0 # 支出使用:0
OK
127.0.0.1:6379> watch money # 監視money (上鎖)
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> exec # 監視值沒有被中途修改,事務正常執行
1) (integer) 80
2) (integer) 20
           
測試多線程修改值,使用watch可以當做redis的樂觀鎖操作(相當于getversion)

我們啟動另外一個用戶端模拟插隊線程。

線程1:

127.0.0.1:6379> watch money # money上鎖
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> 	# 此時事務并沒有執行
           

模拟線程插隊,線程2:

127.0.0.1:6379> INCRBY money 500 # 修改了線程一中監視的money
(integer) 600
           

回到線程1,執行事務:

127.0.0.1:6379> EXEC # 執行之前,另一個線程修改了我們的值,這個時候就會導緻事務執行失敗
(nil) # 沒有結果,說明事務執行失敗

127.0.0.1:6379> get money # 線程2 修改生效
"600"
127.0.0.1:6379> get use # 線程1事務執行失敗,數值沒有被修改
"0"
           
解鎖擷取最新值,然後再加鎖進行事務。

unwatch

進行解鎖。

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

六、Jedis

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

  1. 導入依賴
    <!--導入jredis的包-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
    <!--fastjson-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.70</version>
    </dependency>
               
  2. 編碼測試
    • 連接配接資料庫
      1. 修改redis的配置檔案
        vim /usr/local/bin/myconfig/redis.conf
                   
        1. 将隻綁定本地注釋

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

        2. 保護模式改為 no

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

        3. 允許背景運作

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

  3. 開放端口6379
    firewall-cmd --zone=public --add-port=6379/tcp --permanet
               
    重新開機防火牆服務
    systemctl restart firewalld.service
               
    1. 阿裡雲伺服器控制台配置安全組
    2. 重新開機redis-server
      [root@AlibabaECS bin]# redis-server myconfig/redis.conf 
      
                 
  • 操作指令

    TestPing.java

    public class TestPing {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.xx.xxx", 6379);
            String response = jedis.ping();
            System.out.println(response); // PONG
        }
    }
               
  • 斷開連接配接
  1. public class TestTX {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("39.99.xxx.xx", 6379);
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("hello", "world");
            jsonObject.put("name", "kuangshen");
            // 開啟事務
            Transaction multi = jedis.multi();
            String result = jsonObject.toJSONString();
            // jedis.watch(result)
            try {
                multi.set("user1", result);
                multi.set("user2", result);
                // 執行事務
                multi.exec();
            }catch (Exception e){
                // 放棄事務
                multi.discard();
            } finally {
                // 關閉連接配接
                System.out.println(jedis.get("user1"));
                System.out.println(jedis.get("user2"));
                jedis.close();
            }
        }
    }
               

七、SpringBoot整合

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

springboot 2.x後 ,原來使用的 Jedis 被 lettuce 替換。

jedis:采用的直連,多個線程操作的話,是不安全的。如果要避免不安全,使用jedis pool連接配接池!更像BIO模式

lettuce:采用netty,執行個體可以在多個線程中共享,不存線上程不安全的情況!可以減少線程資料了,更像NIO模式

我們在學習SpringBoot自動配置的原理時,整合一個元件并進行配置一定會有一個自動配置類xxxAutoConfiguration,并且在spring.factories中也一定能找到這個類的完全限定名。Redis也不例外。

redis學習筆記

那麼就一定還存在一個RedisProperties類

redis學習筆記

之前我們說SpringBoot2.x後預設使用Lettuce來替換Jedis,現在我們就能來驗證了。

先看Jedis:

redis學習筆記

@ConditionalOnClass注解中有兩個類是預設不存在的,是以Jedis是無法生效的

然後再看Lettuce:

redis學習筆記

完美生效。

現在我們回到RedisAutoConfiguratio

redis學習筆記

隻有兩個簡單的Bean

  • RedisTemplate
  • StringRedisTemplate

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

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

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

redis學習筆記

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

redis學習筆記

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

redis學習筆記
  1. 編寫配置檔案
    # 配置redis
    spring.redis.host=39.99.xxx.xx
    spring.redis.port=6379
               
  2. 使用RedisTemplate
    @SpringBootTest
    class Redis02SpringbootApplicationTests {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void contextLoads() {
    
            // redisTemplate 操作不同的資料類型,api和我們的指令是一樣的
            // opsForValue 操作字元串 類似String
            // opsForList 操作List 類似List
            // opsForHah
    
            // 除了基本的操作,我們常用的方法都可以直接通過redisTemplate操作,比如事務和基本的CRUD
    
            // 擷取連接配接對象
            //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            //connection.flushDb();
            //connection.flushAll();
    
            redisTemplate.opsForValue().set("mykey","kuangshen");
            System.out.println(redisTemplate.opsForValue().get("mykey"));
        }
    }
               
  3. 測試結果

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

    redis學習筆記

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

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

    redis學習筆記

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

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

    redis學習筆記
    而預設的RedisTemplate中的所有序列化器都是使用這個序列化器:
    redis學習筆記
    後續我們定制RedisTemplate就可以對其進行修改。

    RedisSerializer

    提供了多種序列化方案:
    • 直接調用RedisSerializer的靜态方法來傳回序列化器,然後set
      redis學習筆記
    • 自己new 相應的實作類,然後set
      redis學習筆記
  4. 定制RedisTemplate的模闆:

    我們建立一個Bean加入容器,就會觸發RedisTemplate上的條件注解使預設的RedisTemplate失效。

    @Configuration
    public class RedisConfig {
    
       @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            // 将template 泛型設定為 <String, Object>
            RedisTemplate<String, Object> template = new RedisTemplate();
            // 連接配接工廠,不必修改
            template.setConnectionFactory(redisConnectionFactory);
            /*
             * 序列化設定
             */
            // key、hash的key 采用 String序列化方式
            template.setKeySerializer(RedisSerializer.string());
            template.setHashKeySerializer(RedisSerializer.string());
            // value、hash的value 采用 Jackson 序列化方式
            template.setValueSerializer(RedisSerializer.json());
            template.setHashValueSerializer(RedisSerializer.json());
            template.afterPropertiesSet();
            
            return template;
        }
    }
               

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

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-oc8kJP08-1597890996523)(狂神說 Redis.assets/image-20200817175638086.png)]

八、自定義Redis工具類

使用RedisTemplate需要頻繁調用

.opForxxx

然後才能進行對應的操作,這樣使用起來代碼效率低下,工作中一般不會這樣使用,而是将這些常用的公共API抽取出來封裝成為一個工具類,然後直接使用工具類來間接操作Redis,不但效率高并且易用。

工具類參考部落格:

https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

https://www.cnblogs.com/zhzhlong/p/11434284.html

九、Redis.conf

容量機關不區分大小寫,G和GB有差別
redis學習筆記
可以使用 include 組合多個配置問題
redis學習筆記
網絡配置
redis學習筆記
日志輸出級别
redis學習筆記
日志輸出檔案
redis學習筆記
持久化規則

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

持久化方式:

  • RDB
  • AOF
redis學習筆記
RDB檔案相關
redis學習筆記
redis學習筆記
主從複制
redis學習筆記
Security子產品中進行密碼設定
redis學習筆記
用戶端連接配接相關
maxclients 10000  最大用戶端數量
maxmemory <bytes> 最大記憶體限制
maxmemory-policy noeviction # 記憶體達到限制值的處理政策
           

redis 中的預設的過期政策是 volatile-lru 。

設定方式

config set maxmemory-policy volatile-lru 
           

maxmemory-policy 六種方式

1、volatile-lru:隻對設定了過期時間的key進行LRU(預設值)

2、allkeys-lru : 删除lru算法的key

3、volatile-random:随機删除即将過期key

4、allkeys-random:随機删除

5、volatile-ttl : 删除即将過期的

6、noeviction : 永不過期,傳回錯誤

AOF相關部分
redis學習筆記
redis學習筆記

十、持久化—RDB

RDB:Redis Databases

redis學習筆記

什麼是RDB

在指定時間間隔後,将記憶體中的資料集快照寫入資料庫 ;在恢複時候,直接讀取快照檔案,進行資料的恢複 ;

redis學習筆記

預設情況下, Redis 将資料庫快照儲存在名字為 dump.rdb的二進制檔案中。檔案名可以在配置檔案中進行自定義。

工作原理

在進行

RDB

的時候,

redis

的主線程是不會做

io

操作的,主線程會

fork

一個子線程來完成該操作;

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

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

redis學習筆記

觸發機制

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

save

save

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

由于

save

指令是同步指令,會占用Redis的主程序。若Redis資料非常多時,

save

指令執行速度會非常慢,阻塞所有用戶端的請求。
redis學習筆記

flushall指令

flushall

指令也會觸發持久化 ;

觸發持久化規則

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

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

bgsave

bgsave

是異步進行,進行持久化的時候,

redis

還可以将繼續響應用戶端請求 ;

redis學習筆記

bgsave和save對比

IO類型 同步 異步
阻塞? 是(阻塞發生在fock(),通常非常快)
複雜度 O(n)
不會消耗額外的記憶體 不阻塞用戶端指令
阻塞用戶端指令 需要fock子程序,消耗記憶體

優缺點

優點:

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

缺點:

  1. 需要一定的時間間隔進行操作,如果redis意外當機了,這個最後一次修改的資料就沒有了。
  2. fork程序的時候,會占用一定的内容空間。

十一、持久化AOF

Append Only File

将我們所有的指令都記錄下來,history,恢複的時候就把這個檔案全部再執行一遍

redis學習筆記
以日志的形式來記錄每個寫的操作,将Redis執行過的所有指令記錄下來(讀操作不記錄),隻許追加檔案但不可以改寫檔案,redis啟動之初會讀取該檔案重新建構資料,換言之,redis重新開機的話就根據日志檔案的内容将寫指令從前到後執行一次以完成資料的恢複工作。

什麼是AOF

快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因為某些原因而造成故障停機, 那麼伺服器将丢失最近寫入、以及未儲存到快照中的那些資料。 從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方式: AOF 持久化。

如果要使用AOF,需要修改配置檔案:

redis學習筆記

appendonly no yes

則表示啟用AOF

預設是不開啟的,我們需要手動配置,然後重新開機redis,就可以生效了!

如果這個aof檔案有錯位,這時候redis是啟動不起來的,我需要修改這個aof檔案

redis給我們提供了一個工具

redis-check-aof --fix

優點和缺點
appendonly yes  # 預設是不開啟aof模式的,預設是使用rdb方式持久化的,在大部分的情況下,rdb完全夠用
appendfilename "appendonly.aof"

# appendfsync always # 每次修改都會sync 消耗性能
appendfsync everysec # 每秒執行一次 sync 可能會丢失這一秒的資料
# appendfsync no # 不執行 sync ,這時候作業系統自己同步資料,速度最快
           
  1. 每一次修改都會同步,檔案的完整性會更加好
  2. 沒秒同步一次,可能會丢失一秒的資料
  3. 從不同步,效率最高
  1. 相對于資料檔案來說,aof遠遠大于rdb,修複速度比rdb慢!
  2. Aof運作效率也要比rdb慢,是以我們redis預設的配置就是rdb持久化

十二、RDB和AOP選擇

RDB 和 AOF 對比

啟動優先級
體積
恢複速度
資料安全性 丢資料 根據政策決定

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

一般來說, 如果想達到足以媲美 PostgreSQL 的資料安全性, 你應該同時使用兩種持久化功能。

如果你非常關心你的資料, 但仍然可以承受數分鐘以内的資料丢失, 那麼你可以隻使用 RDB 持久化。

有很多使用者都隻使用 AOF 持久化, 但并不推薦這種方式: 因為定時生成 RDB 快照(snapshot)非常便于進行資料庫備份, 并且 RDB 恢複資料集的速度也要比 AOF 恢複的速度要快。

十三、Redis釋出與訂閱

Redis 釋出訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-IBT2pjCa-1597890996526)(狂神說 Redis.assets/image-20200818162849693.png)]

下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個用戶端 —— client2 、 client5 和 client1 之間的關系:

redis學習筆記

當有新消息通過 PUBLISH 指令發送給頻道 channel1 時, 這個消息就會被發送給訂閱它的三個用戶端:

redis學習筆記

PSUBSCRIBE pattern [pattern..]

訂閱一個或多個符合給定模式的頻道。

PUNSUBSCRIBE pattern [pattern..]

退訂一個或多個符合給定模式的頻道。

PUBSUB subcommand [argument[argument]]

檢視訂閱與釋出系統狀态。

PUBLISH channel message

向指定頻道釋出消息

SUBSCRIBE channel [channel..]

訂閱給定的一個或多個頻道。

SUBSCRIBE channel [channel..]

退訂一個或多個頻道

------------訂閱端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 訂閱sakura頻道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 訂閱成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到來自sakura頻道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到來自sakura頻道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息釋出端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 釋出消息到sakura頻道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 釋出消息
(integer) 1

-----------------檢視活躍的頻道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"
           

原理

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

redis學習筆記

用戶端訂閱,就被連結到對應頻道的連結清單的尾部,退訂則就是将用戶端節點從連結清單中移除。

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

應用

  1. 消息訂閱:公衆号訂閱,微網誌關注等等(起始更多是使用消息隊列來進行實作)
  2. 多人線上聊天室。

稍微複雜的場景,我們就會使用消息中間件MQ處理。

十四、Redis主從複制

概念

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

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

作用

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

為什麼使用叢集

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

環境配置

隻配置從庫 不配置主庫

我們在講解配置檔案的時候,注意到有一個

replication

子產品 (見Redis.conf中第8條)

檢視目前庫的資訊:

info replication

127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 從機數量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
           

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

  • 端口号
  • pid檔案名
  • 日志檔案名
  • rdb檔案名

啟動單機多服務叢集:

redis學習筆記

一主二從配置

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

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

SLAVEOF host port

就可以為從機配置主機了。

redis學習筆記

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

redis學習筆記

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

redis學習筆記

使用規則

  1. 從機隻能讀,不能寫,主機可讀可寫但是多用于寫。
    127.0.0.1:6381> set name sakura # 從機6381寫入失敗
    (error) READONLY You can't write against a read only replica.
    
    127.0.0.1:6380> set name sakura # 從機6380寫入失敗
    (error) READONLY You can't write against a read only replica.
    
    127.0.0.1:6379> set name sakura
    OK
    127.0.0.1:6379> get name
    "sakura"
               
  2. 當主機斷電當機後,預設情況下從機的角色不會發生變化 ,叢集中隻是失去了寫操作,當主機恢複以後,又會連接配接上從機恢複原狀。
  3. 當從機斷電當機後,若不是使用配置檔案配置的從機,再次啟動後作為主機是無法擷取之前主機的資料的,若此時重新配置稱為從機,又可以擷取到主機的所有資料。這裡就要提到一個同步原理。
  4. 第二條中提到,預設情況下,主機故障後,不會出現新的主機,有兩種方式可以産生新的主機:
    • 從機手動執行指令

      slaveof no one

      ,這樣執行以後從機會獨立出來成為一個主機
    • 使用哨兵模式(自動選舉)
如果沒有老大了,這個時候能不能選擇出來一個老大呢?手動!

如果主機斷開了連接配接,我們可以使用

SLAVEOF no one

讓自己變成主機!其他的節點就可以手動連接配接到最新的主節點(手動)!如果這個時候老大修複了,那麼久重新連接配接!

十五、哨兵模式

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

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

哨兵模式是一種特殊的模式,首先Redis提供了哨兵的指令,哨兵是一個獨立的程序,作為程序,它會獨

立運作。其原理是哨兵通過發送指令,等待Redis伺服器響應,進而監控運作的多個Redi執行個體。

單機單個哨兵

redis學習筆記

哨兵的作用:

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

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

多哨兵模式

redis學習筆記

哨兵的核心配置 sentinel.conf

sentinel monitor mymaster 127.0.0.1 6379 1
           
  • 數字1表示 :當一個哨兵主觀認為主機斷開,就可以客觀認為主機故障,然後開始選舉新的主機。
測試 啟動哨兵
redis-sentinel xxx/sentinel.conf
           

成功啟動哨兵模式

redis學習筆記

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

redis學習筆記
哨兵模式優缺點
  1. 哨兵叢集,基于主從複制模式,所有主從複制的優點,它都有
  2. 主從可以切換,故障可以轉移,系統的可用性更好
  3. 哨兵模式是主從模式的更新,手動到自動,更加健壯
  1. Redis不好線上擴容,叢集容量一旦達到上限,線上擴容就十分麻煩
  2. 實作哨兵模式的配置其實是很麻煩的,裡面有很多配置項
哨兵模式的全部配置

完整的哨兵模式配置檔案 sentinel.conf

# Example sentinel.conf
 
# 哨兵sentinel執行個體運作的端口 預設26379
port 26379
 
# 哨兵sentinel的工作目錄
dir /tmp
 
# 哨兵sentinel監控的redis主節點的 ip port 
# master-name  可以自己命名的主節點名字 隻能由字母A-z、數字0-9 、這三個字元".-_"組成。
# quorum 當這些quorum個數sentinel哨兵認為master主節點失聯 那麼這時 客觀上認為主節點失聯了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 當在Redis執行個體中開啟了requirepass foobared 授權密碼 這樣所有連接配接Redis執行個體的用戶端都要提供密碼
# 設定哨兵sentinel 連接配接主從的密碼 注意必須為主從設定一樣的驗證密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認為主節點下線 預設30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,
這個數字越小,完成failover所需的時間就越長,
但是如果這個數字越大,就意味着越 多的slave因為replication而不可用。
可以通過将這個值設為 1 來保證每次隻有一個slave 處于不能處理指令請求的狀态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
 
 
 
# 故障轉移的逾時時間 failover-timeout 可以用在以下這些方面: 
#1. 同一個sentinel對同一個master兩次failover之間的間隔時間。
#2. 當一個slave從一個錯誤的master那裡同步資料開始計算時間。直到slave被糾正為向正确的master那裡同步資料時。
#3.當想要取消一個正在進行的failover所需要的時間。  
#4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個逾時,slaves依然會被正确配置為指向master,但是就不按parallel-syncs所配置的規則來了
# 預設三分鐘
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置當某一事件發生時所需要執行的腳本,可以通過腳本來通知管理者,例如當系統運作不正常時發郵件通知相關人員。
#對于腳本的運作結果有以下規則:
#若腳本執行後傳回1,那麼該腳本稍後将會被再次執行,重複次數目前預設為10
#若腳本執行後傳回2,或者比2更高的一個傳回值,腳本将不會重複執行。
#如果腳本在執行過程中由于收到系統中斷信号被終止了,則同傳回值為1時的行為相同。
#一個腳本的最大執行時間為60s,如果超過這個時間,腳本将會被一個SIGKILL信号終止,之後重新執行。
 
#通知型腳本:當sentinel有任何警告級别的事件發生時(比如說redis執行個體的主觀失效和客觀失效等等),将會去調用這個腳本,
#這時這個腳本應該通過郵件,SMS等方式去通知系統管理者關于系統不正常運作的資訊。調用該腳本時,将傳給腳本兩個參數,
#一個是事件的類型,
#一個是事件的描述。
#如果sentinel.conf配置檔案中配置了這個腳本路徑,那麼必須保證這個腳本存在于這個路徑,并且是可執行的,否則sentinel無法正常啟動成功。
#通知腳本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 用戶端重新配置主節點參數腳本
# 當一個master由于failover而發生改變時,這個腳本将會被調用,通知相關的用戶端關于master位址已經發生改變的資訊。
# 以下參數将會在調用腳本時傳給腳本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>總是“failover”,
# <role>是“leader”或者“observer”中的一個。 
# 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的
# 這個腳本應該是通用的,能被多次調用,不是針對性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

           

十六、緩存穿透與雪崩

緩存穿透(查不到)

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

解決方案

布隆過濾器

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

redis學習筆記

緩存空對象

一次請求若在緩存和資料庫中都沒找到,就在緩存中方一個空對象用于處理後續這個請求。

redis學習筆記

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

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

緩存擊穿(量太大,緩存過期)

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

比如熱搜排行上,一個熱點新聞被同時大量通路就可能導緻緩存擊穿。

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

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

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

    在通路key之前,采用SETNX(set if not exists)來設定另一個短期key來鎖住目前key的通路,通路結束再删除該短期key。保證同時刻隻有一個線程通路。這樣對鎖的要求就十分高。

緩存雪崩

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

redis學習筆記
  • redis高可用

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

  • 限流降級

    這個解決方案的思想是,在緩存失效後,通過加鎖或者隊列來控制讀資料庫寫緩存的線程數量。比如對某個key隻允許一個線程查詢資料和寫緩存,其他線程等待。

  • 資料預熱

    資料加熱的含義就是在正式部署之前,我先把可能的資料先預先通路一遍,這樣部分可能大量通路的資料就會加載到緩存中。在即将發生大并發通路前手動觸發加載緩存不同的key,設定不同的過期時間,讓緩存失效的時間點盡量均勻。

參考:https://blog.csdn.net/weixin_43873227/article/details/106107270