1. 程式人生 > 其它 >【力扣 087】23. 合併K個升序連結串列

【力扣 087】23. 合併K個升序連結串列

概述

redis是什麼

Redis(Remote Dictionary Server ),即遠端字典服務,是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。

redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。

免費和開源! 當下最熱門的NoSQL技術!也被稱為結構化資料庫

Redis能幹什麼

  1. 記憶體儲存 持久化。記憶體是斷電即失的,所以持久化很重要(rdb aof)

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

  3. 釋出訂閱系統

  4. 地圖資訊分析

  5. 計數器 計時器(瀏覽量)

  6. ......

特性

  1. 多樣的資料型別

  2. 持久化

  3. 叢集

  4. 事務

  5. ......

學習中需要用到的東西

  1. 公眾號 狂神說

  2. 官網 Redis

  3. 中文網 CRUG網站 (redis.cn)

  4. github

  5. windows版本在Github下載,不推薦。

Redis推薦在Linux伺服器上搭建

 

Linux安裝

下載軟體包

https://download.redis.io/releases/redis-5.0.14.tar.gz

在ec2上使用curl命令下載

curl -o redis-5.0.14.tar.gz https://download.redis.io/releases/redis-5.0.14.tar.gz

解壓安裝包

[root@ip-172-31-89-155 opt]# cd /software/
[root@ip-172-31-89-155 software]# ls
redis-5.0.14.tar.gz
[root@ip-172-31-89-155 software]# tar -zxvf redis-5.0.14.tar.gz -C /opt/

[root@ip-172-31-89-155 software]# cd /opt
[root@ip-172-31-89-155 opt]# ls
redis-5.0.14
[root@ip-172-31-89-155 opt]# cd /
[root@ip-172-31-89-155 /]# ln -s /opt/redis-5.0.14/ redis
[root@ip-172-31-89-155 /]# ls -l redis
lrwxrwxrwx. 1 root root 18 Oct 27 06:57 redis -> /opt/redis-5.0.14/

安裝redis

#yum install gcc-c++ make
#gcc -v 檢查版本
#make


[root@ip-172-31-89-155 redis]# make
cd src && make all
make[1]: Entering directory '/opt/redis-5.0.14/src'
  CC Makefile.dep

Hint: It's a good idea to run 'make test' ;)

make[1]: Leaving directory '/opt/redis-5.0.14/src'


[root@ip-172-31-89-155 redis]# make install
cd src && make install
make[1]: Entering directory '/opt/redis-5.0.14/src'

Hint: It's a good idea to run 'make test' ;)

  INSTALL install
  INSTALL install
  INSTALL install
  INSTALL install
  INSTALL install
make[1]: Leaving directory '/opt/redis-5.0.14/src'
[root@ip-172-31-89-155 redis]#

redis預設的安裝目錄/usr/local/bin

[root@ip-172-31-89-155 bin]# pwd
/usr/local/bin

[root@ip-172-31-89-155 bin]# ls
redis-benchmark redis-check-rdb redis-sentinel
redis-check-aof redis-cli       redis-server

拷貝redis配置檔案

[root@ip-172-31-89-155 bin]# mkdir /redis_config
[root@ip-172-31-89-155 bin]# cp /opt/redis-5.0.14/redis.conf /redis_config/
[root@ip-172-31-89-155 bin]# cd /redis_config/
[root@ip-172-31-89-155 redis_config]# ls
redis.conf

啟動redis

修改redis.conf

daemonize no --> yes 後臺執行

啟動redis

cd /usr/local/bin

通過指定的配置檔案啟動
# ./redis-server /redis_config/redis.conf
15247:C 27 Oct 2022 07:22:15.575 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
15247:C 27 Oct 2022 07:22:15.575 # Redis version=5.0.14, bits=64, commit=00000000, modified=0, pid=15247, just started
15247:C 27 Oct 2022 07:22:15.575 # Configuration loaded

連線測試

連線redis server
# ./redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name goldtree
OK
127.0.0.1:6379> get name
"goldtree"
127.0.0.1:6379>
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379>


redis連線成功

檢視redis程序

# ps -ef|grep redis
root       15248       1  0 07:22 ?        00:00:03 ./redis-server 127.0.0.1:6379
root       15260    1434  0 07:25 pts/0    00:00:00 ./redis-cli -p 6379
root       15450   15428  0 08:10 pts/1    00:00:00 grep --color=auto redis

關閉redis服務

# ./redis-cli -p 6379
127.0.0.1:6379> shutdown
not connected>
# ps -ef|grep redis
root       15260    1434  0 07:25 pts/0    00:00:00 ./redis-cli -p 6379
root       15469   15428  0 08:13 pts/1    00:00:00 grep --color=auto redis

redis-benchmark效能測試

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

官方自帶的效能測試工具

redis-benchmark命令引數

Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>]

-h <hostname>     Server hostname (default 127.0.0.1)
-p <port>         Server port (default 6379)
-s <socket>       Server socket (overrides host and port)
-a <password>     Password for Redis Auth
-c <clients>       Number of parallel connections (default 50)
-n <requests>     Total number of requests (default 100000)
-d <size>         Data size of SET/GET value in bytes (default 3)
--dbnum <db>       SELECT the specified db number (default 0)
-k <boolean>       1=keep alive 0=reconnect (default 1)
-r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD
Using this option the benchmark will expand the string __rand_int__
inside an argument with a 12 digits number in the specified range
from 0 to keyspacelen-1. The substitution changes every time a command
is executed. Default tests use this to hit random keys in the
specified range.
-P <numreq>       Pipeline <numreq> requests. Default 1 (no pipeline).
-e                 If server replies with errors, show them on stdout.
                  (no more than 1 error per second is displayed)
-q                 Quiet. Just show query/sec values
--csv             Output in CSV format
-l                 Loop. Run the tests forever
-t <tests>         Only run the comma separated list of tests. The test
                  names are the same as the ones produced as output.
-I                 Idle mode. Just open N idle connections and wait.

Examples:

Run the benchmark with the default configuration against 127.0.0.1:6379:
  $ redis-benchmark

Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:
  $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20

Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:
  $ redis-benchmark -t set -n 1000000 -r 100000000

Benchmark 127.0.0.1:6379 for a few commands producing CSV output:
  $ redis-benchmark -t ping,set,get -n 100000 --csv

Benchmark a specific command line:
  $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0

Fill a list with 10000 random elements:
  $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__

On user specified command lines __rand_int__ is replaced with a random integer
with a range of values selected by the -r option.

基礎知識

redis預設有16個數據庫

在redis.conf中

預設使用第0個數據庫

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

切換到3號資料庫

[root@ip-172-31-89-155 bin]# ./redis-cli -p 6379
127.0.0.1:6379> select 3 #切換到3號資料庫
OK
127.0.0.1:6379[3]>
127.0.0.1:6379[3]> dbsize #檢視DB大小
(integer) 0
127.0.0.1:6379[3]>


127.0.0.1:6379[3]> set name pangjuzi
OK
127.0.0.1:6379[3]> get name
"pangjuzi"
127.0.0.1:6379[3]> dbsize
(integer) 1

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

檢視資料庫中所有的key

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

清空DB有兩種方式

#清空當前DB
127.0.0.1:6379[3]> flushdb
OK

#清空所有DB
127.0.0.1:6379[3]> flushall
OK

redis是單執行緒的

redis是很快的,官方表示,redis是基於記憶體操作的,CPU不是redis效能瓶頸,redis的瓶頸是機器的記憶體和網路頻寬,因此使用單執行緒實現。

redis是C語言寫的,官方提供的資料為100000+的QPS,完全不比memcache差!

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

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

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

CPU的速度 > 記憶體 > 硬碟速度

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

redis五大資料型別

官方文件

Redis is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

全文翻譯

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

 

redis-key

############################################################
127.0.0.1:6379> flushall #清空資料庫
OK
127.0.0.1:6379> keys * #檢視所有的key
(empty list or set)
127.0.0.1:6379> set name goldtree
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379>
############################################################
127.0.0.1:6379> EXISTS name #key name是否存在
(integer) 1
127.0.0.1:6379> EXISTS gender
(integer) 0
############################################################
127.0.0.1:6379> move name 1 #把key name 移動到1號資料庫
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
############################################################
127.0.0.1:6379[1]> set name pangjuzi
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> get name
"pangjuzi"
127.0.0.1:6379[1]> expire name 10 #給name設定10秒過期時間
(integer) 1
127.0.0.1:6379[1]> ttl name #檢視剩餘過期時間
(integer) 6
127.0.0.1:6379[1]> ttl name
(integer) 2
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> get name
(nil)
############################################################
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> type name #檢視key的資料型別
string
127.0.0.1:6379> type age
string


String(字串)

127.0.0.1:6379> set key1 v1 
OK
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> append key1 "hello" #在key1鍵中追加hello
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 #獲取key1中值的長度
(integer) 7
127.0.0.1:6379> append key1 ",goldtree"
(integer) 16
127.0.0.1:6379> get key1
"v1hello,goldtree"
127.0.0.1:6379> strlen key1
(integer) 16
127.0.0.1:6379> append name goldtree #若key不存在,就相當於set name goldtree
(integer) 8
127.0.0.1:6379> keys *
1) "key1"
2) "name"
#################################################################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #原子操作加1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #整數原子減1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> incrby views 10 #以步長為10自增長
(integer) 9
127.0.0.1:6379> incrby views 10
(integer) 19
127.0.0.1:6379> get views
"19"
127.0.0.1:6379> decrby views 5 #以步長為5自減少
(integer) 14
127.0.0.1:6379> get views
"14"

######################################################################
#字串範圍 range
127.0.0.1:6379> set key1 hello,goldtree
OK
127.0.0.1:6379> get key1
"hello,goldtree"
127.0.0.1:6379> GETRANGE key1 0 4 #獲取前5個字元 [0,4]
"hello"
127.0.0.1:6379> GETRANGE key1 0 -1 #獲取所有字串
"hello,goldtree"

#替換
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 nn #替換指定位置開始的字串
(integer) 7
127.0.0.1:6379> get key2
"anndefg"

#################################################################
# setex 設定key對應字串value,並且設定key在給定的seconds時間之後超時過期。
# setnx key不存在就是設定value,key存在就什麼都不做(分散式鎖中常用)

127.0.0.1:6379> SETEX key3 60 dajiahao
OK
127.0.0.1:6379> ttl key3
(integer) 56
127.0.0.1:6379> ttl key3
(integer) 53
127.0.0.1:6379> keys *
1) "key3"
2) "key2"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> keys *
1) "key2"
2) "key1"


127.0.0.1:6379> keys *
1) "key2"
2) "key1"
127.0.0.1:6379> get key2
"anndefg"
127.0.0.1:6379> SETNX key2 aaaaa
(integer) 0
127.0.0.1:6379> get key2
"anndefg"
127.0.0.1:6379> SETNX key3 aaaaa
(integer) 1
127.0.0.1:6379> get key3
"aaaaa"

####################################################################
#mset
對應給定的keys到他們相應的values上。MSET會用新的value替換已經存在的value,就像普通的SET命令一樣
127.0.0.1:6379> MSET key1 hello key2 world
OK
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379> get key2
"world"

#msetnx
對應給定的keys到他們相應的values上。只要有一個key已經存在,MSETNX一個操作都不會執行
127.0.0.1:6379> MSETNX key1 hello key2 therer
(integer) 1
127.0.0.1:6379> MSETNX key2 there key3 world
(integer) 0
127.0.0.1:6379> MGET key1 key2 key3
1) "hello"
2) "therer"
3) (nil)

#mget 返回所有指定的key的value。對於每個不對應string或者不存在的key,都返回特殊值nil

#物件
127.0.0.1:6379> set user:1 {name:goldtree,age:3} #設定一個user:1物件 值為json字串

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

##########################################################################
#getset 自動將key對應到value並且返回原來key對應的value

127.0.0.1:6379> INCR mycounter
(integer) 1
127.0.0.1:6379> GETSET mycounter 0
"1"
127.0.0.1:6379> get mycounter
"0"

String型別的使用場景:value除了是字串還可以是數字

  • 計數器

  • 統計多單位的數量

  • 粉絲數

  • 物件快取儲存

List

基本資料型別,列表

在redis裡面,redis可以把list玩成堆疊,佇列,阻塞佇列

在列表中插入資料

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> LPUSH list one #從左邊在list中放入one,向列表頭部新增值
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 #讀取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> keys *
1) "list"

127.0.0.1:6379> RPUSH list four #從右邊在list中放入four,向列表尾部新增值
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"

移除列表中的資料

#LPOP   移除並且返回 key 對應的 list 的第一個元素

127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> LPOP list
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "four"

#RPOP 移除並返回存於 key 的 list 的最後一個元素
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> RPOP list
"four"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"

通過index獲得值

127.0.0.1:6379> LPUSH list one two three four five
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> LINDEX list 3
"two"

獲得list得長度

127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"

127.0.0.1:6379> LLEN list
(integer) 5

移除list中的值

# LREM  key count value
# count > 0: 從頭往尾移除值為 value 的元素。
# count < 0: 從尾往頭移除值為 value 的元素。
# count = 0: 移除所有值為 value 的元素。

127.0.0.1:6379> Lpush list one one two two one one two two
(integer) 8

127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "two"
3) "one"
4) "one"
5) "two"
6) "two"
7) "one"
8) "one"
127.0.0.1:6379> LREM list 3 one
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "two"
3) "two"
4) "two"
5) "one"
127.0.0.1:6379> LREM list 5 two
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
127.0.0.1:6379> LREM list 5 two
(integer) 0
127.0.0.1:6379> LRANGE list 0 -1
1) "one"

修剪(trim)一個已存在的 list

127.0.0.1:6379> RPUSH list one two three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> LTRIM list 1 -1
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "three"

RPOPLPUSH

原子性地返回並移除儲存在 source 的列表的最後一個元素(列表尾部元素), 並把該元素放入儲存在 destination 的列表的第一個元素位置(列表頭部)

# RPOPLPUSH source destination
127.0.0.1:6379> RPUSH list one two three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> RPOPLPUSH list otherlist
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "three"

LPUSHX RPUSHX LSET

#LPUSHX key value
#只有當 key 已經存在並且存著一個 list 的時候,在這個 key 下面的 list 的頭部插入 value。 與 LPUSH 相反,當 key 不存在的時候不會進行任何操作

#RPUSHX key value
#將值 value 插入到列表 key 的表尾, 當且僅當 key 存在並且是一個列表。 和 RPUSH 命令相反, 當 key 不存在時,RPUSHX 命令什麼也不做

#LSET key index value
#設定 index 位置的list元素的值為 value

127.0.0.1:6379> RPUSH list Hello
(integer) 1
127.0.0.1:6379> RPUSHX list World
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "Hello"
2) "World"
127.0.0.1:6379> RPUSHX otherlist World
(integer) 0
127.0.0.1:6379> LRANGE otherlist 0 -1
(empty list or set)

127.0.0.1:6379> lset list 0 goldtree
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "goldtree"
2) "World"
127.0.0.1:6379> lset otherlist 0 goldtree
(error) ERR no such key


LINSERT

#LINSERT key BEFORE|AFTER pivot value
#把 value 插入存於 key 的列表中在基準值 pivot 的前面或後面。
#當 key 不存在時,這個list會被看作是空list,任何操作都不會發生。
#當 key 存在,但儲存的不是一個list的時候,會返回error
127.0.0.1:6379> RPUSH list Hello World
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "Hello"
2) "World"
127.0.0.1:6379> LINSERT list before Hello we
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "we"
2) "Hello"
3) "World"
127.0.0.1:6379> LINSERT list after Hello the
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "we"
2) "Hello"
3) "the"
4) "World"

小結

  • list實際是一個連結串列,before node after ,left,right都可以插入值

  • 如果key不存在,建立新的連結串列

  • 如果key存在,新增內容

  • 如果移除了所有值,空連結串列,也代表不存在

  • 在兩邊插入或者改動值,效率最高。中間元素,相對來說效率會低一點

  • 訊息佇列,lpush rpop

  • 棧 lpush lpop

Set (集合)

Set中的值是不能重複的,是無序的

SADD key member [member ...]

#新增一個或多個指定的member元素到集合的 key中.指定的一個或者多個元素member 如果已經在集合key中存在則忽略.如果集合key 不存在,則新建集合key,並新增member元素到集合key中
127.0.0.1:6379> SADD myset "Hello World" #""號被看作一個member
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "Hello World"
127.0.0.1:6379> SADD myset goldtree
(integer) 1
127.0.0.1:6379> SADD myset World
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "Hello World"
2) "World"
3) "goldtree"
127.0.0.1:6379> SADD myset World #已存在的member不會重複新增
(integer) 0
127.0.0.1:6379> SADD myset xiao wan zi
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "xiao"
2) "World"
3) "goldtree"
4) "zi"
5) "Hello World"
6) "wan"

SISMEMBER key member

#返回成員 member 是否是儲存的集合 key的成員
127.0.0.1:6379> SISMEMBER myset Hello
(integer) 0
127.0.0.1:6379> SISMEMBER myset World
(integer) 1

SCARD key

#返回集合儲存的key的基數 (集合元素的數量).
127.0.0.1:6379> SCARD myset
(integer) 6

SREM key member [member ...]

#在key集合中移除指定的元素. 如果指定的元素不是key集合中的元素則忽略 如果key集合不存在則被視為一個空的集合,該命令返回0
127.0.0.1:6379> SADD myset one
(integer) 1
127.0.0.1:6379> SADD myset two
(integer) 1
127.0.0.1:6379> SADD myset three
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "two"
2) "three"
3) "one"
127.0.0.1:6379> SREM myset three
(integer) 1
127.0.0.1:6379> SREM myset four
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "two"
2) "one"

SRANDMEMBER key [count]

#僅提供key引數,那麼隨機返回key集合中的一個元素
#Redis 2.6開始,可以接受 count 引數
#如果count是整數且小於元素的個數,返回含有 count 個不同的元素的陣列,如果count是個整數且大於集合中元素的個數時,僅返回整個集合的所有元素
#當count是負數,則會返回一個包含count的絕對值的個數元素的陣列,如果count的絕對值大於元素的個數,則返回的結果集裡會出現一個元素出現多次的情況
127.0.0.1:6379> SADD myset one two three
(integer) 3
127.0.0.1:6379> SRANDMEMBER myset
"two"
127.0.0.1:6379> SRANDMEMBER myset
"one"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "one"
2) "three"
127.0.0.1:6379> SRANDMEMBER myset 5
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> SRANDMEMBER myset -2
1) "two"
2) "three"
127.0.0.1:6379> SRANDMEMBER myset -5
1) "two"
2) "one"
3) "two"
4) "three"
5) "one"

SPOP key [count]

#從儲存在key的集合中移除並返回一個或多個隨機元素
127.0.0.1:6379> SADD myset one two three
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "two"
2) "three"
3) "one"
127.0.0.1:6379> SPOP myset #隨機彈出一個member
"two"
127.0.0.1:6379> SMEMBERS myset
1) "three"
2) "one"
127.0.0.1:6379> SPOP myset -1 #count不能是負數
(error) ERR index out of range
127.0.0.1:6379> SPOP myset 2 #count大於member數量,彈出所有member
1) "one"
2) "three"
127.0.0.1:6379> SMEMBERS myset
(empty list or set)

SMOVE source destination member

#將member從source集合移動到destination集合中
127.0.0.1:6379> SADD myset one two
(integer) 2
127.0.0.1:6379> SADD myotherset three
(integer) 1
127.0.0.1:6379> SMOVE myset myotherset two
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "one"
127.0.0.1:6379> SMEMBERS myotherset
1) "two"
2) "three"

SINTER key [key ...] SINTERSTORE destination key [key ...]

#返回指定所有的集合的成員的交集 共同關注
127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> SADD key2 c d
(integer) 2
127.0.0.1:6379> SADD key3 f g h
(integer) 3
127.0.0.1:6379> SINTER key1 key2 key3
(empty list or set)
127.0.0.1:6379> SINTER key1 key2
1) "c"

SDIFF key [key ...] SDIFFSTORE destination key [key ...]

#返回一個集合與給定集合的差集的元素
127.0.0.1:6379> SDIFF key1 key2
1) "b"
2) "a"
127.0.0.1:6379> SDIFF key2 key1
1) "d"

SUNION key [key ...] SUNIONSTORE destination key [key ...]

#返回給定的多個集合的並集中的所有成員
127.0.0.1:6379> SUNION key1 key2
1) "b"
2) "c"
3) "d"
4) "a"

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

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

Hash

Map集合

HSET key field value

#設定 key 指定的雜湊集中指定欄位的值。
#如果 key 指定的雜湊集不存在,會建立一個新的雜湊集並與 key 關聯。
#如果欄位在雜湊集中存在,它將被重寫
127.0.0.1:6379> HSET myhash field1 "Hello"
(integer) 1
127.0.0.1:6379> HGET myhash field1
"Hello"

HMSET key field value [field value ...]

#設定 key 指定的雜湊集中指定欄位的值。該命令將重寫所有在雜湊集中存在的欄位。如果 key 指定的雜湊集不存在,會建立一個新的雜湊集並與 key 關聯
127.0.0.1:6379> HMSET myhash field1 Hello field2 World
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"

HMGET key field [field ...]

#返回 key 指定的雜湊集中指定欄位的值
127.0.0.1:6379> HMGET myhash field1 field2 nofield
1) "Hello"
2) "World"
3) (nil)

HGETALL key

#返回 key 指定的雜湊集中所有的欄位和值
127.0.0.1:6379> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"

HDEL key field [field ...]

#從 key 指定的雜湊集中移除指定的域
127.0.0.1:6379> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "World"

HLEN key

#返回 key 指定的雜湊集包含的欄位的數量
127.0.0.1:6379> HSET myhash field1 Hello
(integer) 1
127.0.0.1:6379> HSET myhash field2 World
(integer) 1
127.0.0.1:6379> HLEN myhash
(integer) 2

HSTRLEN key field

#返回hash指定field的value的字串長度,如果hash或者field不存在,返回0
127.0.0.1:6379> HSTRLEN myhash field1
(integer) 5

HEXISTS key field

#返回hash裡面field是否存在
127.0.0.1:6379> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HEXISTS myhash field1
(integer) 1
127.0.0.1:6379> HEXISTS myhash field10
(integer) 0

HKEYS key 和 HVALS key

#HKEYS key    返回 key 指定的雜湊集中所有欄位的名字
#HVALS key 返回 key 指定的雜湊集中所有欄位的值
127.0.0.1:6379> HKEYS myhash
1) "field1"
2) "field2"
127.0.0.1:6379> HVALS myhash
1) "Hello"
2) "World"

HINCRBY key field increment

#增加 key 指定的雜湊集中指定欄位的數值
#HINCRBY 支援的值的範圍限定在 64位 有符號整數
127.0.0.1:6379> HSET myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HGETALL myhash
1) "field"
2) "6"
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HGETALL myhash
1) "field"
2) "5"
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
127.0.0.1:6379> HGETALL myhash
1) "field"
2) "-5"

HINCRBYFLOAT key field increment

#為指定key的hash的field欄位值執行float型別的increment加
127.0.0.1:6379> HSET myhash field 10.50
(integer) 1
127.0.0.1:6379> HINCRBYFLOAT myhash field 0.1
"10.6"
127.0.0.1:6379> HSET myhash field 5.0e3
(integer) 0
127.0.0.1:6379> HGETALL myhash
1) "field"
2) "5.0e3"
127.0.0.1:6379> HINCRBYFLOAT myhash field 2.0e2
"5200"
127.0.0.1:6379> HGETALL myhash
1) "field"
2) "5200"

HSETNX key field value

#只在 key 指定的雜湊集中不存在指定的欄位時,設定欄位的值
127.0.0.1:6379> HSETNX myhash field hello
(integer) 1
127.0.0.1:6379> HSETNX myhash field world
(integer) 0
127.0.0.1:6379> HGET myhash field
"hello"

小結

  • hash儲存變更的資料,例如使用者資訊的儲存

sorted set(有序集合)

ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

#將所有指定成員新增到鍵為key有序集合(sorted set)裡面
#如果指定新增的成員已經是有序集合裡面的成員,則會更新改成員的分數(scrore)並更新到正確的排序位置
XX: 僅僅更新存在的成員,不新增新成員。
NX: 不更新存在的成員。只新增新成員。
CH: 修改返回值為發生變化的成員總數
INCR: 當ZADD指定這個選項時,成員的操作就等同ZINCRBY命令,對成員的分數進行遞增操作

127.0.0.1:6379> ZADD myzset 1 one
(integer) 1
127.0.0.1:6379> ZADD myzset 1 uno
(integer) 1
127.0.0.1:6379> ZADD myzset 2 three 3 two
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "one"
2) "uno"
3) "three"
4) "two"
127.0.0.1:6379> ZRANGE myzset 0 -1 withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "three"
6) "2"
7) "two"
8) "3"

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

#返回key的有序集合中的分數在min和max之間的所有元素
#ZRANGEBYSCORE zset (1 5 返回所有符合條件1 < score <= 5的成員
127.0.0.1:6379> ZADD salary 5000 xiaowanzi 3000 mama 250 baba
(integer) 3
127.0.0.1:6379> ZRANGE salary 0 -1
1) "baba"
2) "mama"
3) "xiaowanzi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf inf
1) "baba"
2) "mama"
3) "xiaowanzi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf inf withscores
1) "baba"
2) "250"
3) "mama"
4) "3000"
5) "xiaowanzi"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 3000 withscores
1) "baba"
2) "250"
3) "mama"
4) "3000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf (3000 withscores
1) "baba"
2) "250"

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

#ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf
1) "xiaowanzi"
2) "mama"
3) "baba"

127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores
1) "xiaowanzi"
2) "5000"
3) "mama"
4) "3000"
5) "baba"
6) "250"

ZREM key member [member ...]

127.0.0.1:6379> ZREM salary mama
(integer) 1
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores
1) "xiaowanzi"
2) "5000"
3) "baba"
4) "250"

ZCARD key

#返回key的有序集元素個數
127.0.0.1:6379> ZCARD salary
(integer) 2

ZCOUNT key min max

#返回有序集key中,score值在min和max之間(預設包括score值等於min或max)的成員個數
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZRANGE myzset -1 0
(empty list or set)
127.0.0.1:6379> ZCOUNT myzset -inf inf
(integer) 3
127.0.0.1:6379> ZCOUNT myzset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myzset (1 3
(integer) 2

 

 

三種特殊資料型別

 

geospatial 地理位置

定位 附近的人 打車距離計算

Redis的Geo 在Redis3.2版本推出,可以推算兩地之間的距離

可以查詢一些測試資料 城市經緯度查詢-國內城市經度緯度線上查詢工具 (jsons.cn)

只有6個命令

GEOADD key longitude latitude member [longitude latitude member ...]

GEODIST key member1 member2 [unit] 返回兩個給定位置之間的距離

  • m 表示單位為米。

  • km 表示單位為千米。

  • mi 表示單位為英里。

  • ft 表示單位為英尺

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] #以給定的經緯度為中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素

GEOPOS key member [member ...] #GEOPOS key member [member ...]

GEOHASH key member [member ...]

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

127.0.0.1:6379> GEOADD mygeo 116.405285 39.904989 beijing 121.472644 31.231706 shanghai 113.280637 23.125178 guangzhou 114.085947 22.547 zhenzhen
(integer) 4

127.0.0.1:6379> GEODIST mygeo beijing shanghai km
"1067.5980"

127.0.0.1:6379> GEORADIUS mygeo 120 30 200 km withdist
1) 1) "shanghai"
2) "196.5640"

127.0.0.1:6379> GEORADIUS mygeo 120 30 1500 km withdist
1) 1) "zhenzhen"
2) "1016.9532"
2) 1) "guangzhou"
2) "1015.1519"
3) 1) "shanghai"
2) "196.5640"
4) 1) "beijing"
2) "1149.1099"

127.0.0.1:6379> GEOPOS mygeo beijing
1) 1) "116.40528291463851929"
2) "39.9049884229125027"

127.0.0.1:6379> GEOhash mygeo beijing
1) "wx4g0b7xrt0"
127.0.0.1:6379> GEOhash mygeo beijing shanghai
1) "wx4g0b7xrt0"
2) "wtw3sjt9vg0"

127.0.0.1:6379> GEORADIUSBYMEMBER mygeo shanghai 500 km withdist
1) 1) "shanghai"
2) "0.0000"
127.0.0.1:6379> GEORADIUSBYMEMBER mygeo shanghai 1500 km withdist
1) 1) "zhenzhen"
2) "1211.6366"
2) 1) "guangzhou"
2) "1211.5290"
3) 1) "shanghai"
2) "0.0000"
4) 1) "beijing"
2) "1067.5980"

GEO的底層是zset


127.0.0.1:6379> ZRANGE mygeo 0 -1
1) "zhenzhen"
2) "guangzhou"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> zrem mygeo shanghai
(integer) 1
127.0.0.1:6379> ZRANGE mygeo 0 -1
1) "zhenzhen"
2) "guangzhou"
3) "beijing"
127.0.0.1:6379>

hyperloglog

什麼是基數

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

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

基數(不重複的元素)= 5

簡介

redis2.8.9版本就更新了Hyperloglog資料結構

Redis Hyperloglgo做基數統計的演算法

優點:佔用的記憶體是固定的,2^64不同的元素的技術,只需要12KB記憶體

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

傳統的方式,set儲存使用者的id,然後就可以統計set中的元素的數量。如果儲存大量的使用者id會消耗大量的儲存空間。我們的目的是計數,不是儲存使用者id。

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

測試使用

127.0.0.1:6379> PFADD hll a a a b c d e f g g g j k
(integer) 1
127.0.0.1:6379> PFCOUNT hll
(integer) 9
127.0.0.1:6379> PFADD hll2 h w i o du sj ww a
(integer) 1

127.0.0.1:6379> PFCOUNT hll hll2
(integer) 16

127.0.0.1:6379> PFMERGE hll3 hll hll2
OK
127.0.0.1:6379> PFCOUNT hll3
(integer) 16

 

bitmaps

位儲存

統計人數 : 0 0 1 0 0 0 0 1

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

測試 記錄週一到週日的打卡狀態

127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 6 1
(integer) 0
127.0.0.1:6379> GETBIT sign 1
(integer) 1
127.0.0.1:6379> GETBIT sign 2
(integer) 0
127.0.0.1:6379> BITCOUNT sign #統計打卡天數
(integer) 4

Redis的基本事務

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

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

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

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

Redis單條命令有原子性,要麼同時成功,要麼同時失敗,原子性!但是Redis事務不保證原子性

Redis的事務:

  • 開啟事務(MULTI)

  • 命令入隊()

  • 執行事務(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> EXEC #執行事務
1) OK
2) OK
3) "v1"
4) OK

放棄事務

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD #放棄事務
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k4
(nil)

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

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

 

 

執行時異常(1/0),如果事務佇列存在語法性錯誤,在執行時,正確的命令會被執行。錯誤命令丟擲異常

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

監控!Watch

悲觀鎖

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

樂觀鎖

  • 很樂觀,認為什麼時候都不會出現問題,所以不會上鎖。更新資料時判斷,在此期間是否有人修改過資料。

  • 獲取version

  • 更新的時候比較version

Redis監視測試

正常執行成功

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

模擬money在事務執行時發生變化

#先在client1
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED

#這時在client2
127.0.0.1:6379> set money 1000
OK

#然後在client1
127.0.0.1:6379> EXEC #由於money發生變動,事務沒有執行
(nil)

#使用watch可以當作redis的樂觀鎖操作!

Jedis

我們要使用java來操作

什麼是Jedis 是Redis官方推薦的java連線卡法工具。使用java操作Redis的中介軟體。如果使用java操作redis,那麼要定要對jedis十分的熟悉

intellij idea

介紹

IDEA 全稱 IntelliJ IDEA,是java程式語言整合開發環境。IntelliJ在業界被公認為最好的Java開發工具,尤其在智慧程式碼助手、程式碼自動提示、重構JavaEE支援、各類版本工具(gitsvn等)、JUnitCVS整合、程式碼分析、 創新的GUI設計等方面的功能可以說是超常的。IDEA是JetBrains公司的產品,這家公司總部位於捷克共和國的首都布拉格,開發人員以嚴謹著稱的東歐程式設計師為主。它的旗艦版還支援HTMLCSSPHPMySQLPython等。免費版只支援Java,Kotlin等少數語言。

下載

IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrains

測試

  1. 建立project 可以建立以空專案

  2. 建立module

匯入jedis包

登入Maven Repository: Search/Browse/Explore (mvnrepository.com)

搜尋jedis

下載這個

 

 

複製貼上到module裡

匯入fastjson

最後結果

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

編碼測試

  • 連線資料庫

  • 操作命令

  • 斷開連線

測試程式碼

package com.goldtree;
import redis.clients.jedis.Jedis;

public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("44.201.159.141",6379);
System.out.println(jedis.ping());
}
}

測試結果

"C:\Program Files\Java\jdk1.8.0_202\bin\java.exe" "-javaagent:C:\Program 
PONG

注意事項

編輯redis.conf

#註釋這行
#bind 127.0.0.1

#修改這行
protected-mode yes-->no

 

常用的API

package com.goldtree;

import redis.clients.jedis.Jedis;

import java.util.Set;

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

System.out.println("清空資料: "+jedis.flushDB());
System.out.println("判斷username鍵是否存在: "+jedis.exists("username"));
System.out.println("新增<'username','goldtree'>的鍵值對: " +jedis.set("username","goldtree"));
System.out.println("新增<'password','12345678'>的鍵值對: "+jedis.set("password","12345678"));
System.out.print("系統中所有的鍵如下: ");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("刪除鍵password: "+jedis.del("password"));
System.out.println("判斷鍵password是否存在 " +jedis.exists("password"));
System.out.println("檢視鍵username儲存值的型別: "+jedis.type("username"));
System.out.println("隨機返回key空間的一個: "+jedis.randomKey());
System.out.println("重新命名key:"+jedis.rename("username","name"));
System.out.println("取出改後的name "+jedis.get("name"));
System.out.println("按索引查詢: "+jedis.select(0));
System.out.println("刪除當前選擇資料庫中所有的keys: "+jedis.flushDB());
System.out.println("返回當前資料庫中key的數目: "+jedis.dbSize());
System.out.println("刪除所有資料庫的keys: "+jedis.flushAll());


}
}

重溫事務

package com.goldtree;

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

public class MULTI {
public static void main(String[] args) {
Jedis jedis = new Jedis("54.152.87.57",6300);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","goldtree");
// 開啟事務
jedis.flushDB();
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();

try {
multi.set("user1", result);
multi.set("user2", result);
int a =1/0; //故意出錯,事務失敗
multi.exec();
}catch (Exception e){
multi.discard(); //放棄事務
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); //關閉連線
}



}
}

Redis 持久化

 

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

RDB(Redis DataBase)

RDB是Redis用來進行持久化的一種方式,是把當前記憶體中的資料集快照寫入磁碟,也就是 Snapshot 快照(資料庫中所有鍵值對資料)。恢復時是將快照檔案直接讀到記憶體裡。

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

RDB儲存的檔案是dump.rdb

RDB 有兩種觸發方式,分別是自動觸發和手動觸發。

 

預設快照配置

# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

save 900 1
save 300 10
save 60 10000

# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes

# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes

# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes

# The filename where to dump the DB
dbfilename dump.rdb

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./

觸發機制

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

  2. 執行flushall命令,也會產生rdb檔案

  3. Redis shutdown時,也會產生rdb檔案

如何恢復rdb檔案

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

2 檢視需要存放的位置

127.0.0.1:6300> CONFIG GET dir
1) "dir"
2) "/usr/local/bin"

優點:

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

  2. 如果資料的完整性要求不高

缺點:

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

  2. fork程序的時候會佔用記憶體空間

 

 

AOF(Append Only File)

將所以命令記錄下來,類似history,恢復的時候把這個檔案全部執行一遍。

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

AOF預設時關閉的。只需要將appendonly改為yes就可以啟動AOF

如果aof檔案有錯誤,redis時啟動不了的,需要修復這個檔案。

redis提供一個工具redis-check-aof --fix

這個工具會把aof檔案中錯誤刪除。會丟資料

優點和缺點

優點

1 每次修改都同步,檔案的完整會更好

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

3 從不同步,效率最高。

缺點

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

2 aof執行效率比rdb慢

 

擴充套件

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

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

3 只做快取,可以不使用任何持久化

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

  • 當redis重啟時,會優先載入aof檔案恢復原始的資料,因為在通常情況下aof檔案儲存的資料集要比rdf檔案儲存的資料完整

  • rdb的資料不實時,同時使用兩者時,伺服器重啟也只會找aof檔案,那要不要只使用aof呢?建議不要,因為rdb更適合用於備份資料庫(aof在不斷變化不好備份),快速重啟,而且不會有aof可能潛在的bug。

5 效能建議

  • 因為rdb檔案只用做後備用途,建議只在slave上持久化rdb檔案,而且只要15分鐘一次就夠了。只保留save 900 1這條規則

  • 如果enable aof,好處時在最惡劣情況下也只會丟失不超過2秒的資料,啟動指令碼較簡單隻load自己的aof檔案就可以了,代價一是帶來了持續的IO,二是aof rewrite的最後將rewrite過程中產生的新資料寫到新檔案造成的阻塞幾乎是不可避免的。只要硬碟許可,應該儘量減少aof rewrite的頻率,aof重寫的基礎大小預設是64mb,可以設定5GB以上,預設超過原大小100%大小重寫也可以改到適當的數值。

  • 如果不enable aof,僅靠Master-slave replication實現高可用也可以,能節省一大筆IO,也減少了rewrite帶來的系統波動,代價是如果Master-Slave同時宕機,會丟失十幾分鐘的資料,啟動指令碼也要比較兩個Master/Slave中的RDB檔案,載入較新的那個。微博就是這種架構。

Redis釋出訂閱

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

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

訂閱/釋出訊息圖

第一個:訊息傳送者,第二個:頻道,第三個:訊息訂閱者

下圖展示了頻道channel,以及訂閱這個頻道的三個客戶端之間的關係

當有新形象通過PUPBLISH命令傳送給頻道channel時,這個訊息就會被髮送給訂閱它的三個客戶端

命令

這些命令被廣泛用於構建即時通訊應用,比如網路聊天室和實時廣播等

  1. PSUBSCRIBE pattern [pattern ...] 訂閱一個或多個給定模式的頻道

  2. PUBSUB subcommand [argument [argument ...]] 檢視訂閱與釋出系統狀態

  3. PUBLISH channel message 把資訊傳送到指定的頻道

  4. PUNSUBSCRIBE [pattern [pattern ...]] 退訂所有給定模式的頻道

  5. SUBSCRIBE channel [channel ...] 訂閱給定的一個或多個頻道的資訊

  6. UNSUBSCRIBE channel [channel ...] 退訂給定的頻道

     

測試

#訂閱一個叫goldtree的頻道
127.0.0.1:6300> SUBSCRIBE goldtree
Reading messages... (press Ctrl-C to quit) #等待讀取資訊
1) "subscribe"
2) "goldtree"
3) (integer) 1

#再連線一個client,做為傳送端傳送訊息
127.0.0.1:6300> PUBLISH goldtree "hello little one"
(integer) 1
127.0.0.1:6300>

#回到訂閱的客戶端
127.0.0.1:6300> SUBSCRIBE goldtree
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "goldtree"
3) (integer) 1
1) "message"
2) "goldtree"
3) "hello little one"

使用場景

  1. 實時資訊系統

  2. 實時聊天

  3. 訂閱關注系統

稍微複雜的場景使用資訊中介軟體MQ

Redis主從複製

1)使用非同步複製。 2)一個主伺服器可以有多個從伺服器。 3)從伺服器也可以有自己的從伺服器。 4)複製功能不會阻塞主伺服器。 5)可以通過複製功能來讓主伺服器免於執行持久化操作,由從伺服器去執行持久化操作即可。

 

詳細介紹

1)Redis 使用非同步複製。從 Redis2.8開始,從伺服器會以每秒一次的頻率向主伺服器報告複製流(replication stream)的處理進度。

2)一個主伺服器可以有多個從伺服器。

3)不僅主伺服器可以有從伺服器,從伺服器也可以有自己的從伺服器,多個從伺服器之間可以構成一個圖狀結構。

4)複製功能不會阻塞主伺服器:即使有一個或多個從伺服器正在進行初次同步, 主伺服器也可以繼續處理命令請求。

5)複製功能也不會阻塞從伺服器:只要在 redis.conf 檔案中進行了相應的設定, 即使從伺服器正在進行初次同步, 伺服器也可以使用舊版本的資料集來處理命令查詢。

6)在從伺服器刪除舊版本資料集並載入新版本資料集的那段時間內,連線請求會被阻塞。

7)還可以配置從伺服器,讓它在與主伺服器之間的連線斷開時,向客戶端傳送一個錯誤。

8)複製功能可以單純地用於資料冗餘(data redundancy),也可以通過讓多個從伺服器處理只讀命令請求來提升擴充套件性(scalability): 比如說,繁重的SORT命令可以交給附屬節點去執行。

9)可以通過複製功能來讓主伺服器免於執行持久化操作:只要關閉主伺服器的持久化功能,然後由從伺服器去執行持久化操作即可。

Redis主從複製

主庫關閉持久化的危險性

1.當配置Redis複製功能時,強烈建議開啟主伺服器的持久化功能。 否則的話,由於延遲等問題,部署的服務應該要避免自動拉起。

2.為了幫助理解主伺服器關閉持久化時自動拉起的危險性,參考一下以下會導致主從伺服器資料全部丟失的例子:

1)假設節點A為主伺服器,並且關閉了持久化。並且節點B和節點C從節點A複製資料

2)節點A崩潰,然後由自動拉起服務重啟了節點A. 由於節點A的持久化被關閉了,所以重啟之後沒有任何資料

3)節點B和節點C將從節點A複製資料,但是A的資料是空的,於是就把自身儲存的資料副本刪除。

結論:

1)在關閉主伺服器上的持久化,並同時開啟自動拉起程序的情況下,即便使用Sentinel來實現Redis的高可用性,也是非常危險的。因為主伺服器可能拉起得非常快,以至於Sentinel在配置的心跳時間間隔內沒有檢測到主伺服器已被重啟,然後還是會執行上面的資料丟失的流程。

2)無論何時,資料安全都是極其重要的,所以應該禁止主伺服器關閉持久化的同時自動拉起。

redis主從複製原理

1)從伺服器向主伺服器傳送 SYNC 命令。

2)接到 SYNC 命令的主伺服器會呼叫BGSAVE 命令,建立一個 RDB 檔案,並使用緩衝區記錄接下來執行的所有寫命令。

3)當主伺服器執行完 BGSAVE 命令時,它會向從伺服器傳送 RDB 檔案,而從伺服器則會接收並載入這個檔案。

4)主伺服器將緩衝區儲存的所有寫命令傳送給從伺服器執行。

主從複製的作用包括

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

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

  3. 負載均衡:在主從複製基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務,分擔伺服器負載;尤其在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的併發量

  4. 高可用基石:除了上述作用外,主從複製還是哨兵模式和叢集能夠實現的基礎,因此說主從複製是高可用的基礎

一般來說,要將Redis運用於工程專案中,只是用一臺伺服器是萬萬不能的,原因如下:

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

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

  3. 電商網站上的商品,一般都是一次上傳,無數次瀏覽的。一般一主二從

  4. 生產環境主從複製必須要使用

測試環境配置

節點名 IP 角色
node1 10.0.0.200 master
node2 10.0.0.31 slave
node3 10.0.0.32 slave

只配置從庫。不用配置主庫!

檢視節點複製資訊

[root@redhat86m bin]# redis-cli -p 6300
127.0.0.1:6300> info replication
# Replication
role:master     #角色
connected_slaves:0 #沒有從機
master_replid:d660f33e10d562c72b815c7345552222c64a9512
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
[root@redhat86 ~]# redis-cli -p 6300
127.0.0.1:6300> info replication
# Replication
role:master
connected_slaves:0
master_replid:ac850f048ffd04d3ea7128a12899ea7fc23f87ed
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
[root@redhat86m ~]# redis-cli -p 6300
127.0.0.1:6300> info replication
# Replication
role:master
connected_slaves:0
master_replid:90ac1d51cbcfb9892e116efb04249631b853d280
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

目前三臺機器都是master

要配置一主二從的配置

一般只配置從機

#在10.0.0.31執行
127.0.0.1:6300> SLAVEOF 10.0.0.200 6300
OK
#在看一下replication資訊
127.0.0.1:6300> info replication
# Replication
role:slave     #角色變為了slave
master_host:10.0.0.200  #識別到了master
master_port:6300
master_link_status:up

#在10.0.0.32執行
127.0.0.1:6300> SLAVEOF 10.0.0.200 6300
OK
#在看一下replication資訊
127.0.0.1:6300> info replication
# Replication
role:slave     #角色變為了slave
master_host:10.0.0.200  #識別到了master
master_port:6300
master_link_status:up

#在master 10.0.0.200執行
127.0.0.1:6300> info replication
# Replication
role:master
connected_slaves:2         #識別到兩個從機
slave0:ip=10.0.0.31,port=6300,state=online,offset=168,lag=1
slave1:ip=10.0.0.32,port=6300,state=online,offset=168,lag=1

SLAVEOF 10.0.0.200 6300命令是臨時生效,如果要永久生效需要修改配置檔案

# replicaof <masterip> <masterport>
replicaof 10.0.0.200 6300

測試1

#主機負責寫,從機負責讀
#master
127.0.0.1:6300> keys *
(empty list or set)
127.0.0.1:6300> set k1 v1
OK
127.0.0.1:6300> set k2 v2
OK
127.0.0.1:6300>

#slave
127.0.0.1:6300> keys *
1) "k2"
2) "k1"
127.0.0.1:6300> get k1    #可以讀取值
"v1"
127.0.0.1:6300> set k3 v3
(error) READONLY You can't write against a read only replica. #不能寫

測試2 主機宕機

#master
127.0.0.1:6300> SHUTDOWN
not connected>

#slave 可以讀取資料
127.0.0.1:6300> keys *
1) "k2"
2) "k1"
127.0.0.1:6300> get k1
"v1"

#master 好了
[root@redhat79 ~]# !445
redis-server /redis/redis.conf
2887:C 24 Nov 2022 15:42:35.161 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2887:C 24 Nov 2022 15:42:35.162 # Redis version=5.0.14, bits=64, commit=00000000, modified=0, pid=2887, just started
2887:C 24 Nov 2022 15:42:35.162 # Configuration loaded
[root@redhat79 ~]# !446
redis-cli -p 6300
127.0.0.1:6300> ping
PONG
127.0.0.1:6300> keys *
1) "k2"
2) "k1"
127.0.0.1:6300> set k3 v3
OK
127.0.0.1:6300>

#slave
127.0.0.1:6300> get k3   #新設定k3可以查到
"v3"

測試3 從機宕機

#slave
127.0.0.1:6300> SHUTDOWN
not connected>

#master 設定新的值
127.0.0.1:6300> set k4 v4
OK
127.0.0.1:6300> set k5 v5
OK
127.0.0.1:6300> keys *
1) "k4"
2) "k3"
3) "k2"
4) "k5"
5) "k1"

#slave好了 看看能不能看到k4 k5
127.0.0.1:6300> keys *
1) "k2"
2) "k3"
3) "k1"
4) "k5"
5) "k4"
127.0.0.1:6300> get k4
"v4"

注意

如果只是使用命令列配置主從關係,這個時候如果從機重啟了,從機會變回master。

複製原理

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

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

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

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

但是隻要是重新連線master,一次完全同步將被自動執行

要配置主--從--從的配置

#node3配置成node2的從機
#node1
127.0.0.1:6300> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.0.0.31,port=6300,state=online,offset=1804,lag=1

#node2
127.0.0.1:6300> info replication
# Replication
role:slave #依舊是從節點 無法寫入
master_host:10.0.0.200
master_port:6300
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_repl_offset:1776
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=10.0.0.32,port=6300,state=online,offset=1776,lag=1

#node3
127.0.0.1:6300> info replication
# Replication
role:slave
master_host:10.0.0.31
master_port:6300

測試

1 主節點還是負責寫,從節點負責讀

2 主節點宕機 node2能不能當master

#node2能不能當master
127.0.0.1:6300> INFO replication
# Replication
role:slave
master_host:10.0.0.200
master_port:6300
master_link_status:down     #node1掛了

#node2
127.0.0.1:6300> SLAVEOF no one #自己當老大
127.0.0.1:6300> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.0.0.32,port=6300,state=online,offset=2336,lag=0

#node3
127.0.0.1:6300> INFO replication
# Replication
role:slave
master_host:10.0.0.31
master_port:6300
master_link_status:up   #此時是up的,如果node2沒有執行SLAVEOF no one,這個狀態是down的

node1節點回來了也只能重新配置主從關係

哨兵模式

概述

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

哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的程序。作為程序,他會獨立執行,其原理是哨兵通過傳送命令,等待Redis伺服器響應,從而監控執行的多個Redis例項。如果故障了根據投票數自動將從庫轉換為主庫

 

這裡的哨兵有兩個作用

  • 通過傳送命令,讓Redis伺服器返回監控其執行狀態。包括主伺服器和從伺服器。

  • 當哨兵檢測到master宕機,會自動將slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機。

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

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

測試

目前是一主二從

1 配置哨兵配置檔案sentinel.conf

[root@redhat86 redis]# vi sentinel.conf
sentinel monitor mymaster 10.0.0.200 6300 2
sentinel monitor mymaster 10.0.0.31 6300 2
sentinel monitor mymaster 10.0.0.32 6300 2
[root@redhat86m redis]# vim sentinel.conf
sentinel monitor mymaster 10.0.0.200 6300 2
sentinel monitor mymaster 10.0.0.31 6300 2
sentinel monitor mymaster 10.0.0.32 6300 2
[root@redhat79 redis]# vim sentinel.conf
sentinel monitor mymaster 10.0.0.200 6300 2
sentinel monitor mymaster 10.0.0.31 6300 2
sentinel monitor mymaster 10.0.0.32 6300 2

2 啟動哨兵

#啟動redis群集
[root@redhat79 redis]# redis-cli -h 10.0.0.200 -p 6300
10.0.0.200:6300> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.0.0.32,port=6300,state=online,offset=140,lag=1
slave1:ip=10.0.0.31,port=6300,state=online,offset=140,lag=1
master_replid:f9c69eb802d121d95234250f51dfabaaf510d93d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:140
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:140

#啟動哨兵
[root@redhat79 redis]# redis-sentinel /redis/sentinel.conf
[root@redhat86 redis]# redis-server /redis/redis.conf
[root@redhat86m redis]# redis-sentinel /redis/sentinel.conf

#10.0.0.200模仿宕機

3 切換

master宕機後,哨兵會在slave中隨機挑一個作為master,當原來的master回來了,哨兵把它設定為slave。

優缺點

優點:

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

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

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

缺點:

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

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

Redis快取穿透和雪崩

Redis快取使用的時候,極大的提升了應用程式的效能和效率,特別是資料查詢方面。但同時,它也帶來了一些問題。其中,最要害的問題,就是資料的一致性問題,從嚴格意義上講,這個問題無解,如果對資料庫的一致性要求很高,那麼就不能使用快取。

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

快取穿透(查不到)

概念

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

解決方案

布隆過濾器

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

快取空物件

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

這個方法會存在兩個問題:

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

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

快取擊穿(查太多)

概述

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

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

解決方案

設定熱點資料永不過期

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

加互斥鎖

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

快取雪崩

概念

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

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

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

解決方案

Redis高可用

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

限流降級

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

資料預熱

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