Redis 基礎部分

一、安裝

1. 官網下載源碼

image

2. 安裝依賴包

yum install gcc tcl   

3. 下載源碼包

wget http://download.redis.io/releases/redis-4.0.10.tar.gz

3. 解壓安裝

tar -xf redis-4.0.10.tar.gz
cd redis-4.0.10
make && make install

4. 配置 redis

mkdir /etc/redis
cd redis-4.0.10/
cp redis.conf /etc/redis/6379.conf

守護進程的方式啟動服務時,即使執行啟動服務命令的終端關閉,服務仍然可以在后臺運行。

配置 centos7 systemd 管理 redis 服務

  1. /lib/systemd/system目錄下創建一個腳本文件redis.service,里面的內容如下:
[Unit]
Description=Redis
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/redis-server /etc/redis/6379.conf  --daemonize no
ExecStop=/usr/local/bin/redis-cli -p 6379 shutdown

[Install]
WantedBy=multi-user.target

[Unit] 表示這是基礎信息配置塊
Description 是描述
After 開啟自啟動時候的順序, 指定的服務必須先于次此服務啟動,一般是網絡服務啟動后啟動
[Service] 表示這里是服務信息配置塊
Type 指定啟動服務的類型, simple 是默認的方式
EnvironmentFile 指定服務啟動時用到的配置文件
ExecStart 是啟動服務的命令
ExecStop 是停止服務的指令
[Install] 表示這是是安裝信息配置塊
WantedBy 是以哪種方式啟動:multi-user.target表明當系統以多用戶方式(默認的運行級別)啟動時,這個服務需要被自動運行。

授權在主機啟動的時候同時啟動服務

systemctl enable redis.service

關于 server 文件的詳細參數介紹參考這里

  1. 使用 systemctl 操作

刷新配置,讓 systemd 識別剛剛添加的 redis 服務

systemctl daemon-reload

啟動服務

systemctl start redis

關于配置文件中的配置

設置監聽地址

shell> vi /etc/redis/6379.conf
# bind 127.0.0.1 192.168.1.10             

bind 參數若都注釋掉,則會監聽服務器上的所有 ip
可以指定一個或者多個,打開注釋。
注意此配置項可能在 71 行左右。默認是 bind 127.0.0.1

檢查并測試

檢查默認端口 6379 是否監聽
``

image
shell> redis-cli
127.0.0.1:6379> info
# Server
redis_version:4.0.10
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:cfb22f7d67db356d
... 略 ...

手動使用命令指定配置文件啟動服務

/usr/local/bin/redis-server /etc/redis/6379.conf

這種方式執行,默認 Redis 服務侯會在前臺運行。

設置使用守護進程都方式運行服務
需要編輯配置文件 /etc/redis/6379.conf

daemonize yes   # 守護進程的方式啟動服務

客戶端指定端口訪問

redis-cli -p 6379

手動停止服務

redis-cli -p 6379 shutdown

假如重啟后出現如下錯誤信息,就按照提示操作

1044:M 27 Feb 14:47:21.993 # Server initialized
1044:M 27 Feb 14:47:21.993 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1044:M 27 Feb 14:47:21.993 # Short read or OOM loading DB. Unrecoverable error, aborting now.
1044:M 27 Feb 14:47:21.993 # Internal error in RDB reading function at rdb.c:1666 -> Unexpected EOF reading RDB file

echo "vm.overcommit_memory = 1"  >> /etc/sysctl.conf

sysctl -p

問題二

 error, aborting now.
1344:M 27 Feb 14:59:33.466 # Internal error in RDB reading function at rdb.c:1666 -> Unexpected EOF reading RDB file

移動 dump.db 文件或者更名,可以刪除。

rm dump.rdb

二、數據類型

1. 數據類型的基本介紹

image

2. 數據類型的基本操作

a.String

set

127.0.0.1:6379> help set
SET key value [EX seconds] [PX milliseconds] [NX|XX]

在 Redis 中設置值,默認,不存在則創建,存在則修改
參數:
ex,過期時間(秒)
px,過期時間(毫秒)
nx,假如設置為True,則只有 name 不存在時,當前 set 操作才執行
xx,假如設置為True,則只有 name 存在時,當前 set 操作才執行

Example

127.0.0.1:6379> set name shark EX 10
OK
127.0.0.1:6379> get name
"shark"

setnx

127.0.0.1:6379> help setnx
  SETNX key value

設置值,只有name 不存在時,執行設置操作(添加)

Example

127.0.0.1:6379> setnx age 10
(integer) 1
127.0.0.1:6379> get age
"10"
127.0.0.1:6379> setnx age 20
(integer) 0
127.0.0.1:6379> get age
"10"
127.0.0.1:6379>

setex

127.0.0.1:6379> help setex
  SETEX key seconds value

設置 key 和 value,并且指的過期時間(單位: 秒)

Example

127.0.0.1:6379> setex name 5 shark
OK

get

獲取一個 key 的 value

127.0.0.1:6379> get name
"shark"

ttl

查看一個 key 的過期時間

127.0.0.1:6379> ttl age
(integer) -1

  • -1 永不過期
  • -2 已經過期

expire

設置一個 key 的過期時間(單位: 秒)

127.0.0.1:6379> EXPIRE age 10
(integer) 1
127.0.0.1:6379> ttl age
(integer) 7

persist key

移除 key 的過期時間

mset

一次添加多個值

127.0.0.1:6379> mset name shark age 10
OK

mget

一次獲取多個 key 的值

127.0.0.1:6379> MGET name age
1) "shark"
2) "10

incr

對一個 key 的值自增 1

127.0.0.1:6379> incr age
(integer) 11

decr

對一個 key 的值自減 1

127.0.0.1:6379> DECR age
(integer) 10

append

向一個 key 的值后面追加內容

127.0.0.1:6379> get n
"10"
127.0.0.1:6379> APPEND n 10
(integer) 4
127.0.0.1:6379> get n
"1010"

getrange

127.0.0.1:6379> GETRANGE n 0 -1
"1010"

del

刪除 一個或者多個 key

127.0.0.1:6379> del name age
(integer) 2

EXISTS

判斷一個 key 是否存在, 返回 1 表示存在, 0 表示不存在

127.0.0.1:6379> exists age
(integer) 1
127.0.0.1:6379> exists aaa
(integer) 0
127.0.0.1:6379>

TYPE

返回key存儲的類型,如果不存在則返回none

type key

keys

通過通配符來獲取匹配到的 key
一般不在生產環境中使用此命令

  • * 匹配所有
  • ? 匹配任意一個
127.0.0.1:6379> keys *
1) "num"
2) "age"
3) "n"
127.0.0.1:6379> keys n*
1) "num"
2) "n"
127.0.0.1:6379> keys a[f-g]
(empty list or set)
127.0.0.1:6379> keys a[e-g]?
1) "age"

scan

dbsize

返回數據庫種 key 的總數

dbsize

EXPIRE

設置key的過期時間,如果key不存在則返回0,否則返回1.如果key已經存在過期時間則再設置會覆蓋之前的過期時間

b. List 操作

lpush

向列表左端添加元素,values是按左到右依次插入的,返回值為列表中元素個數,列表元素可以重復

最后加入到元素,在列表的第一位

127.0.0.1:6379> LPUSH list a b c
(integer) 3
127.0.0.1:6379> LPUSH list a b c
(integer) 6

rpush

向列表右端依次的添加元素,最后加入的元素在列表的最后位置

127.0.0.1:6379> RPUSH list d e f
(integer) 9

LINDEX

通過元素在列表中的位置獲取到這個元素,位置稱為索引號/下標,

位置支持正整數和負整數

列表中元素的位置中,第一位是 0,最后一位是列表總長度減 1 或者是 -1

image
127.0.0.1:6379> LINDEX list 0
"c"

LRANGE

獲取列表表一個區間的值

127.0.0.1:6379> LRANGE list 0 2
1) "c"
2) "b"
3) "a"

LPUSHX

向列表左端添加元素,只有key存在時才可以添加

127.0.0.1:6379> EXISTS list1 
(integer) 0
127.0.0.1:6379> LPUSHX list1 a
(integer) 0
127.0.0.1:6379> LPUSHX list g
(integer) 10

RPUSHX

向列表右端添加元素,其他與LPUSHX相同

LPOP

將左端列表元素彈出,會將其從列表中刪除,如果key不存在則返回(nil)

127.0.0.1:6379> LPOP list
"g"
127.0.0.1:6379> LPOP list
"c"

RPOP

將右端列表元素彈出,其他同LPOP

LLEN

返回列表的長度,如果列表不存在則返回0

127.0.0.1:6379> LLEN list
(integer) 8
127.0.0.1:6379> LLEN list1
(integer) 0

LREM

lrem  key count value

刪除列表中指定的值,返回值為刪除的元素的個數,count值有以下幾種:

  • count > 0: 從列表的頭開始,向尾部搜索,移除與value相等的元素,移除count個
  • count < 0: 從列表尾部開始,向頭部搜索,移除與value相等的元素,移除-count個
  • count == 0: 移除列表中所有與value相等的

c. Hash

image

HSET key field value

將哈希表key中的域 (field) 設置成指定的value,如果key不存在則新建一個hash表,如果域不存在則新建域,如果域已存在則更新域,如果field不存在返回1表示新建,存在則返回0表示更新

127.0.0.1:6379> HSET userinfo username 'shark'
(integer) 1
127.0.0.1:6379> HSET userinfo userpsw '123456'
(integer) 1
127.0.0.1:6379> HSET userinfo userpsw '654321'
(integer) 0

HGET key field

獲取哈希表key中的域field的值,如果key或者field不存在則返回(nil)

127.0.0.1:6379> HGET userinfo2 username
(nil)
127.0.0.1:6379> HGET userinfo username
"stronger"
127.0.0.1:6379> HGET userinfo email
(nil)

HSETNX key field value

將哈希表中的域field設置成指定的值,只有field不存在時才可以成功,如果field存在操作無效,返回0

127.0.0.1:6379> HGET userinfo username
"stronger"
127.0.0.1:6379> HSETNX userinfo username 'fish'
(integer) 0
127.0.0.1:6379> HGET userinfo username
"stronger"

HMSET key field vale [field value]

同時將多個field-value設定到hash表中,如果field已存在值則會被覆蓋掉

127.0.0.1:6379> HMSET userinfo email 'yangdm@gmail.com' sex 'male'
OK

HMGET key field [field]

同時獲得key存儲的hansh表中多個field的值,如果不存在則返回(nil)

127.0.0.1:6379> HMGET userinfo email sex age
1) "yangdm@gmail.com"
2) "male"
3) (nil)

HGETALL key

返回key存儲的所有field及value

127.0.0.1:6379> HGETALL userinfo
1) "username"
2) "stronger"
3) "userpsw"
4) "654321"
5) "email"
6) "yangdm@gmail.com"
7) "sex"
8) "male"
127.0.0.1:6379> HGETALL userinfo2
(empty list or set)

HKEYS key

返回hash的所有域

127.0.0.1:6379> HKEYS userinfo
1) "username"
2) "userpsw"
3) "email"
4) "sex"

HVALS key

返回hash的所有域的值

127.0.0.1:6379> HVALS userinfo
1) "stronger"
2) "654321"
3) "yangdm@gmail.com"
4) "male"

HEXISTS key field

檢測key中存儲的hash中field是否存在,存在返回1,否則返回0

127.0.0.1:6379> HEXISTS userinfo username
(integer) 1
127.0.0.1:6379> HEXISTS userinfo age
(integer) 0

HLEN key

返回key中存儲的hash表中field的數量

127.0.0.1:6379> HLEN userinfo
(integer) 4

HINCRBY key field increment

給key中存儲的hash表field增加increment,如果此field不存在,則創建值為0的field,然后增加increment。操作的字段必須是整數,參照字符串處理

127.0.0.1:6379> HINCRBY userinfo age 10
(integer) 10

HINCRBYFLOAT key field increment

給key中存儲的hash表field增加increment,可以為浮點數,參照字符串處理

127.0.0.1:6379> HINCRBYFLOAT userinfo salary 150.56
"150.56"

HDEL key field [field]

刪除key中存儲的hash表的field,可以刪除一個或多個,成功返回被移除域的數量

127.0.0.1:6379> HKEYS userinfo
1) "username"
2) "userpsw"
3) "email"
4) "sex"
5) "age"
6) "salary"
127.0.0.1:6379> HDEL userinfo salary age
(integer) 2
127.0.0.1:6379> HKEYS userinfo
1) "username"
2) "userpsw"
3) "email"
4) "sex"

Set

Redis 的 Set 是 String 類型的無序集合。集合成員是唯一的,這就意味著集合中不能出現重復的數據。

Redis 中集合是通過哈希表實現的。

// 向集合中添加一個或者多個元素
SADD key member [member ...]

// 返回一個集合中的所有成員
SMEMBERS key

// 獲取集合中元素的個數
SCARD key

// 返回所有集合的差集,就是存在于第一個集合中,且不存在于其他集合中的成員
SDIFF key [key ...]

// 交集,就是所有集合共有的元素
SINTER key [key ...]

// 并集, 就是所有集合的元素合并在一起,并去重
SUNION key [key ...]

// 差集, 返回第一集合中獨有的元素
SDIFF  key  [key...]

Example

127.0.0.1:6379> sadd s1 a b
(integer) 2
127.0.0.1:6379> sadd s2 a b c d
(integer) 4
127.0.0.1:6379> sadd s3 c d e f
(integer) 4

127.0.0.1:6379> sdiff s2 s1
1) "d"
2) "c"

127.0.0.1:6379> SINTER s1 s2
1) "b"
2) "a"

127.0.0.1:6379> SUNION s1 s2 s3
1) "b"
2) "c"
3) "d"
4) "f"
5) "a"
6) "e"

127.0.0.1:6379> SMEMBERS s1
1) "b"
2) "a"

Sort Set 操作

有序集合,在集合的基礎上,為每元素排序;元素的排序需要根據另外一個值來進行比較,所以,對于有序集合,每一個元素有兩個值,即:值和分數,分數專門用來做排序。

// 向有序集合添加一個或多個成員,或者更新已存在成員的分數
ZADD key score1 member1 [score2 member2]

// score1 是成員的分數
// member 是有序集合中的成員

// 獲取有序集合的元素個數
ZCARD key 

// 返回有序集合中的所有成員
127.0.0.1:6379> zrange sort_s 0 -1
1) "no"
2) "hello"

// 返回有序集合中的所有成員及其索引號(分數)
127.0.0.1:6379> zrange sort_s 0 -1  withscores
1) "no"
2) "1"
3) "hello"
4) "2"
127.0.0.1:6379>

出現下面的錯誤,是操作的命令和這個命令所能操作的數據類型不符合。

(error) WRONGTYPE Operation against a key holding the wrong kind of value

三、Redis 的認證連接

// 在配置文件中找到以下配置項,大約在第 `500` 行
shell> vi /etc/redis/6379.conf
requirepass mypassword

mypassword 就是密碼了,更改好后重啟服務

使用設置好的密碼認證

// 使用 auth 進行密碼認證
127.0.0.1:6379> info
NOAUTH Authentication required.
127.0.0.1:6379> auth  mypassword
OK
127.0.0.1:6379> info
# Server
redis_version:4.0.10
...略...

或者在 shell 命令行里使用 -a 選項指定密碼,會出現警告信息

[root@localhost ~]# redis-cli  -a foobared info
Warning: Using a password with '-a' option on the command line interface may not be safe.
# Server
redis_version:4.0.10
...略...

四、php-redis

開始在 PHP 中使用 Redis 前, 我們需要確保已經安裝了 redis 服務及 PHP redis 驅動,且你的機器上能正常使用 PHP。

安裝 phpredis 驅動

點我進入下載頁面,,注意選擇版本

1. 下載解壓后,進入解壓后的目錄

[root@s2 ~]# wget https://github.com/phpredis/phpredis/archive/4.2.0.tar.gz
shell> 
shell> cd php7-redis

2. 安裝 php

安裝 php , 只需要使用 YUM 安裝 php-devel 即可。

yum install php-devel

3. 執行如下命令,生成配置工具

在解壓后的 php 目錄中執行如下命令

shell> phpize

image

4. 使用生成的配置工具命令 configre 進行配置并編譯安裝

配置

[root@s2 phpredis-4.2.0]# find / -name php-config
/usr/bin/php-config
[root@s2 phpredis-4.2.0]# ./configure --with-php-config=/usr/bin/php-config
   ... 略...
checking whether to build shared libraries... yes
checking whether to build static libraries... no
configure: creating ./config.status
config.status: creating config.h
config.status: executing libtool commands

編譯安裝

[root@s2 phpredis-4.2.0]# make && make install
  ...略...
Build complete.
Don't forget to run 'make test'.

Installing shared extensions:     /usr/lib64/php/modules/

5. 測試安裝是否成功

[root@s2 phpredis-4.2.0]# php -v
PHP 5.4.16 (cli) (built: Oct 30 2018 19:30:51)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies

6. 在php.ini中添加 extension=redis.so

[root@s2 phpredis-4.2.0]# find / -name php.ini
/etc/php.ini
[root@s2 phpredis-4.2.0]# vi /etc/php.ini
[root@s2 phpredis-4.2.0]# tail /etc/php.ini
;mcrypt.modes_dir=

[dba]
;dba.default_handler=

; Local Variables:
; tab-width: 4
; End:

extension=redis.so
[root@s2 phpredis-4.2.0]# php -m | grep redis
redis

7. php -m | grep redis或者phpinfo查看安裝是否成功

[root@s2 phpredis-4.2.0]# php -m | grep redis
redis

五、持久化存儲

1. 持久化存儲的方式介紹

Redis 分別提供了 RDB 和 AOF 兩種持久化機制:

  • RDB 將數據庫的快照(snapshot)以二進制的方式保存到磁盤中。

  • AOF 則以協議文本的方式,將所有對數據庫進行過寫入的命令(及其參數)記錄到 AOF 文件,以此達到記錄數據庫狀態的目的。

2. RDB

a. 什么是RDB

和 MySQL 中的 mysqldump 差不多一個道理。

image

b. 什么情況下會觸發 RDB

第一種情況,主動執行 save 命令(同步,阻塞 ,就是save 命令執行完畢后才能執行后續的其他命令操作)

image

阻塞

image
保存 RDB 文件的策略

每次創建新的文件,并且替換原來舊文件(假如存在舊的文件)

第二種情況,主動執行 bgsave 命令 (異步,非阻塞 )

image
  • 文件策略和 save 相同

第三種情況,自動觸發

自動觸發,就是通過對 Redis 的配置文件重相關選項的修改,當達到某個配置好的條件后,自動生成 RDB 文件
,其內部使用的是 bgsave 命令。

配置文件中相關選項的默認值如下表:

配置 seconds changes 含義
save 900 1 每隔 900 秒檢查一次,假如至少有 1 條數據改變,就生成新的 RDB 文件
save 300 10 每隔 300 秒檢查一次,假如至少有 10 條數據改變,就生成新的 RDB 文件
save 60 10000 每隔 60 秒檢查一次,假如至少有 10000 條數據改變,就生成新的 RDB 文件

每次檢查都會建立一個新的檢查點,以便用于下次檢查作為參考信息。

關于 RDB 文件的配置信息

默認文件名
dbfilename dump.rdb

默認文件保存位置
dir ./

假如 bgsave 執行中發生錯誤,是否停止寫入,默認是 yes , 表示假如出錯,就停止寫入。
stop-writes-on-bgsave-error yes

是否使用壓縮|
rdbcompression yes

是否進行數據的校驗
rdbchecksum yes

建議的最佳配置

關閉自動生成 RDB 文件
在配置文件中注釋掉如下內容

#save 900 1
#save 300 10
#save 60    10000

使用不同端口號進行區分,因為,有可能會在同一臺主機上開啟多個 Redis 實例。
防止多個實例產生的數據信息寫到一個文件中。
dbfilename dump-${port}.rdb

指定一個大硬盤的路徑
dir /redis_data

假如出現錯誤,停止繼續寫入
stop-writes-on-bgsave-error yes

采用壓縮
rdbcompression yes

進行校驗
rdbchecksum yes

實驗

修改配置文件中的相關選項,使其成為如下內容中顯示的值:

dbfilename dump-6379.rdb
dir /redis_data   # 此目錄需要自己創建
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes

假如你的 Redis 服務器允許客戶端可以從非本機訪問,應該在配置文件中,把 protected-mode 的值設置問 no

這樣的話,客戶端就可以從其他主機訪問 Redis 服務器了,并且不需要密碼。

重啟服務后,在 Rdis 命令行客戶端中輸入 save 命令。

[root@s1 ~]# redis-cli
127.0.0.1:6379> save
OK
127.0.0.1:6379>

該命令將在配置文件重配置的指定目錄中創建 dump-6379.rdb文件。

恢復數據時,只需要保證此文件完好,并且在配置文件中指定的目錄下即可。這樣 Rdis 啟動時就會把此文件中的數據恢復到當前的服務器中。

bgsave 命令和 save基本一樣,就是 bgsave 命令不會產生阻塞

127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379>

查看當前服務器的數據文件目錄

127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/"

2. AOF

什么是 AOF

AOF 文件保存了 Redis 的數據庫狀態, 而文件里面包含的都是符合 Redis 通訊協議格式的命令文本。

image

AOF 保存的模式

Redis 目前支持三種 AOF 保存模式,它們分別是:

  1. AOF_FSYNC_NO :不保存。
  2. AOF_FSYNC_EVERYSEC :每一秒鐘保存一次。(生產中一般選這種)
  3. AOF_FSYNC_ALWAYS :每執行一個命令保存一次

不保存

在這種模式下, SAVE 只會在以下任意一種情況中被執行:

Redis 被關閉
AOF 功能被關閉
系統的寫緩存被刷新(可能是緩存已經被寫滿,或者定期保存操作被執行)
這三種情況下的 SAVE 操作都會引起 Redis 主進程阻塞。

每執行一個命令保存一次

在這種模式下,每次執行完一個命令之后, WRITE 和 SAVE 都會被執行。

另外,因為 SAVE 是由 Redis 主進程執行的,所以在 SAVE 執行期間,主進程會被阻塞,不能接受命令請求。

AOF 三種保存模式的比較

因為阻塞操作會讓 Redis 主進程無法持續處理請求, 所以一般說來, 阻塞操作執行得越少、完成得越快, Redis 的性能就越好。

模式 1 的保存操作只會在AOF 關閉或 Redis 關閉時執行, 或者由操作系統觸發, 在一般情況下, 這種模式只需要為寫入阻塞, 因此它的寫入性能要比后面兩種模式要高, 當然, 這種性能的提高是以降低安全性為代價的: 在這種模式下, 如果運行的中途發生停機, 那么丟失數據的數量由操作系統的緩存沖洗策略決定。

模式 2 在性能方面要優于模式 3 , 并且在通常情況下, 這種模式最多丟失不多于 2 秒的數據, 所以它的安全性要高于模式 1 , 這是一種兼顧性能和安全性的保存方案。

模式 3 的安全性是最高的, 但性能也是最差的, 因為服務器必須阻塞直到命令信息被寫入并保存到磁盤之后, 才能繼續處理請求。

綜合起來,三種 AOF 模式的操作特性可以總結如下:

模式 WRITE 是否阻塞? SAVE 是否阻塞? 停機時丟失的數據量
AOF_FSYNC_NO 阻塞 阻塞 操作系統最后一次對 AOF 文件觸發 SAVE 操作之后的數據。
AOF_FSYNC_EVERYSEC 阻塞 不阻塞 一般情況下不超過 2 秒鐘的數據。
AOF_FSYNC_ALWAYS 阻塞 阻塞 最多只丟失一個命令的數據。

AOF 方式下的數據還原

Redis 讀取 AOF 文件并還原數據庫的詳細步驟如下:

創建一個不帶網絡連接的偽客戶端(fake client)。
讀取 AOF 所保存的文本,并根據內容還原出命令、命令的參數以及命令的個數。
根據命令、命令的參數和命令的個數,使用偽客戶端執行該命令。
執行 2 和 3 ,直到 AOF 文件中的所有命令執行完畢。
完成第 4 步之后, AOF 文件所保存的數據庫就會被完整地還原出來。

注意, 因為 Redis 的命令只能在客戶端的上下文中被執行, 而 AOF 還原時所使用的命令來自于 AOF 文件, 而不是網絡, 所以程序使用了一個沒有網絡連接的偽客戶端來執行命令。

當程序讀入這個 AOF 文件時, 它首先執行 SELECT 0 命令 —— 這個 SELECT 命令是由 AOF 寫入程序自動生成的, 它確保程序可以將數據還原到正確的數據庫上。

注意:
為了避免對數據的完整性產生影響, 在服務器載入數據的過程中, 只有和數據庫無關的訂閱與發布功能可以正常使用, 其他命令一律返回錯誤。

AOF 的重寫機制

為什么需要重寫機制

AOF 文件通過同步 Redis 服務器所執行的命令, 從而實現了數據庫狀態的記錄, 但是, 這種同步方式會造成一個問題: 隨著運行時間的流逝, AOF 文件會變得越來越大。

  1. 對同一個鍵的狀態的多次不同操作,而最終得到一個結果。比如對列表的添加刪除元素。

  2. 被頻繁操作的鍵。比如累加

重新機制是如何實現的

實際上, AOF 重寫并不需要對原有的 AOF 文件進行任何寫入和讀取, 它針對的是數據庫中鍵的當前值,也就是源數據從目前的內存中獲取。

考慮這樣一個情況, 如果服務器對鍵 list 執行了以下四條命令:

RPUSH list 1 2 3 4      // [1, 2, 3, 4]

RPOP list               // [1, 2, 3]

LPOP list               // [2, 3]

LPUSH list 1            // [1, 2, 3]

那么當前列表鍵 list 在數據庫中的值就為 [1, 2, 3] 。

如果我們要保存這個列表的當前狀態, 并且盡量減少所使用的命令數, 那么最簡單的方式不是去 AOF 文件上分析前面執行的四條命令, 而是直接讀取 list 鍵在數據庫的當前值, 然后用一條 RPUSH 1 2 3 命令來代替前面的四條命令。

除了列表之外,集合、字符串、有序集、哈希表等鍵也可以用類似的方法來保存狀態。

根據鍵的類型, 使用適當的寫入命令來重現鍵的當前值, 這就是 AOF 重寫的實現原理。

基本都步驟

for  遍歷所有數據庫:
      if  如果數據庫為空:
             那么跳過這個數據庫
      else:
            寫入 SELECT 命令,用于切換數據庫
            for  選擇一個庫后,遍歷這個庫的所有鍵
                   if 如果鍵帶有過期時間,并且已經過期,那么跳過這個鍵
                   if 根據數據的類型,進行相關操作。

AOF 重寫的實現方式

方式 區別
bgrewriteaof 命令 不需要重啟服務,不便于統一管理
配置文件實現 需要重啟服務,便于進行統一管理

bgrewriteaof

image

配置文件實現

image
觸發條件,必須同時滿足如下條件
image

aof_current_sizeaof_base_size 可以通過命令 info persistence 查看到

重寫流程圖

image

對于上圖有四個關鍵點補充一下:

在重寫期間,由于主進程依然在響應命令,為了保證最終備份的完整性;因此它依然會寫入舊的AOF file中,如果重寫失敗,能夠保證數據不丟失。當然這個是可以通過配置來決定在重寫期間是否進行主進程普通的 AOF 操作。
為了把重寫期間響應的寫入信息也寫入到新的文件中,因此也會為子進程保留一個buf,防止新寫的file丟失數據。
重寫是直接把當前內存的數據生成對應命令,并不需要讀取老的AOF文件進行分析、命令合并。
AOF文件直接采用的文本協議,主要是兼容性好、追加方便、可讀性高可認為修改修復。

注意:無論是RDB還是AOF都是先寫入一個臨時文件,然后通過 rename 完成文件的替換工作。

配置示例

// 要想使用 AOF 的全部功能,需要設置為  yes
appendonly yes

// AOF 文件名,路徑才看之前的 `dir` 配置項
appendfilename "appendonly.aof"

// 平常普通的 AOF 的策略
appendfsync everysec

// 當執行 AOF 重寫時,是否繼續執行平常普通的 AOF 操作。
// 這里設置文件  yes , 表示不執行
// 因為假如,同時執行,兩種操作都會對磁盤 I/O 進行訪問,造成
// I/O 訪問量過大,產生性能衰減
no-appendfsync-on-rewrite yes

// AOF 文件容量的增長率
auto-aof-rewrite-percentage 100

// AOF 文件的最低容量,就是當前文件的大小大于此值時,就會進行重寫。當然這只是其中一個條件。
auto-aof-rewrite-min-size 64mb

添加鍵值對數據,觀察 AOF 文件

這里在命令行中設置,以便立刻生效

[root@s1 ~]# redis-cli
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"
127.0.0.1:6379> config set appendonly yes
OK
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"

進行簡單的數據添加操作

127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set hello python
OK
127.0.0.1:6379> set hello redis
OK
127.0.0.1:6379> incr nums
(integer) 1
127.0.0.1:6379> incr nums
(integer) 2
127.0.0.1:6379> incr nums
(integer) 3
127.0.0.1:6379> incr nums
(integer) 4
127.0.0.1:6379> rpush li a
(integer) 1
127.0.0.1:6379> rpush li b
(integer) 2
127.0.0.1:6379> rpush li b
(integer) 3
127.0.0.1:6379> rpush li c
(integer) 4
127.0.0.1:6379> exit

查看 AOF 文件

[root@s1 ~]# head appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
SET
$5
hello

主動觸發

先備份一份目前的 AOF 文件

[root@s1 ~]# cp /appendonly.aof{,.bak}

執行命令 bgrewriteaof

127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started

最后對比兩個文件的內容的不同之處。

RDB 和 AOF

區別

image

如何抉擇

從服務器開啟 RDB

始終開啟 AOF

不要使用主機的全部內存

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容