前言
先看一下計算機硬盤的緩存設(shè)計。硬盤的緩存主要起三種作用:
- 預(yù)讀取
當硬盤受到CPU指令控制開始讀取數(shù)據(jù)時,硬盤上的控制芯片會控制磁頭把正在讀取的簇的下一個或者幾個簇中的數(shù)據(jù)讀到緩存中(由于硬盤上數(shù)據(jù)存儲時是比較連續(xù)的,所以讀取命中率較高),當需要讀取下一個或者幾個簇中的數(shù)據(jù)的時候,硬盤則不需要再次讀取數(shù)據(jù),直接把緩存中的數(shù)據(jù)傳輸?shù)絻?nèi)存中就可以了,由于緩存的速率遠遠高于磁頭讀寫的速率,所以能夠達到明顯改善性能的目的。
- 寫入
當硬盤接到寫入數(shù)據(jù)的指令之后,并不會馬上將數(shù)據(jù)寫入到盤片上,而是先暫時存儲在緩存里,然后發(fā)送一個“數(shù)據(jù)已寫入”的信號給系統(tǒng),這時系統(tǒng)就會認為數(shù)據(jù)已經(jīng)寫入,并繼續(xù)執(zhí)行下面的工作,而硬盤則在空閑(不進行讀取或?qū)懭氲臅r候)時再將緩存中的數(shù)據(jù)寫入到盤片上。雖然對于寫入數(shù)據(jù)的性能有一定提升,但也不可避免地帶來了安全隱患——數(shù)據(jù)還在緩存里的時候突然掉電,那么這些數(shù)據(jù)就會丟失。對于這個問題,硬盤廠商們自然也有解決辦法:掉電時,磁頭會借助慣性將緩存中的數(shù)據(jù)寫入零磁道以外的暫存區(qū)域,等到下次啟動時再將這些數(shù)據(jù)寫入目的地。
- 臨時存儲
臨時存儲
有時候,某些數(shù)據(jù)是會經(jīng)常需要訪問的,像硬盤內(nèi)部的緩存(暫存器的一種)會將讀取比較頻繁的一些數(shù)據(jù)存儲在緩存中,再次讀取時就可以直接從緩存中直接傳輸。緩存就像是一臺計算機的內(nèi)存一樣,在硬盤讀寫數(shù)據(jù)時,負責數(shù)據(jù)的存儲、寄放等功能。這樣一來,不僅可以大大減少數(shù)據(jù)讀寫的時間以提高硬盤的使用效率。同時利用緩存還可以讓硬盤減少頻繁的讀寫,讓硬盤更加安靜,更加省電。更大的硬盤緩存,你將讀取游戲時更快,拷貝文件時候更快,在系統(tǒng)啟動中更為領(lǐng)先。
其實在做軟件設(shè)計的時候,要想在有限的資源情況下提高系統(tǒng)的效率,也可以參照計算機的設(shè)計思路做緩存。如果把數(shù)據(jù)庫中的數(shù)據(jù)比作是硬盤,那么我們的系統(tǒng)就需要類似硬盤緩存的一種方案。最好的就是將數(shù)據(jù)庫存儲引擎的數(shù)據(jù)加載到內(nèi)存中。目前內(nèi)存數(shù)據(jù)庫還處于發(fā)展階段,相信未來某天我們的數(shù)據(jù)不再依賴硬盤,或者說硬盤的讀寫速度發(fā)生了變革性的提升,不過在這一天到來之前,作為一個小程序員,還是好好學(xué)習(xí)學(xué)習(xí)緩存技術(shù)。
搭建Redis服務(wù)
這里使用yum的方式安裝redis。yum install -y redis.安裝的redis版本為3.2.12
[root@daice ~]# yum list installed | grep redis
redis.x86_64 3.2.12-2.el7 @epel
修改/etc/redis.conf配置文件
bind 127.0.0.1 #ip
port 6379 #端口
protected-mode no #設(shè)置為no就可以使其他的hosts來連接本機的redis
daemonize yes # 設(shè)置以守護進程的方式運行redis
pidfile /var/run/redis_6379.pid #如果設(shè)置了daemonize yes,則會生成這個pidfile.注意如果單機多個redis不要讓這個file有重名
logfile /var/log/redis/redis.log #redis的日志文件
requirepass 123456 #設(shè)置連接redis的密碼
直接使用systemctl start redis 啟動redis。啟動后查看redis服務(wù)的狀態(tài)
[root@daice ~]# systemctl status redis
● redis.service - Redis persistent key-value database
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/redis.service.d
└─limit.conf
Active: active (running) since Fri 2020-08-28 16:36:49 CST; 12min ago
Process: 29233 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=1/FAILURE)
Main PID: 29328 (redis-server)
Tasks: 3
Memory: 5.0M
CGroup: /system.slice/redis.service
└─29328 /usr/bin/redis-server *:6379
將redis服務(wù)加入到開機啟動
systemctl enable redis
連接redis
redis-cli -h 127.0.0.1 -p 6379 -a 123456
主從模式
在實際項目中一般不會使用單機版的redis.先從最基本的主從模式入手。主從模式有以下的特點
- 一般情況下主節(jié)點可讀可寫,從節(jié)點只讀。
- slave自動同步master的數(shù)據(jù)
- 一個master可以有多個slave,但一個slave只能對應(yīng)一個master
- slave掛了不影響其他slave的讀和master的讀和寫,重新啟動后會將數(shù)據(jù)從master同步過來
- master掛了以后,不影響slave的讀,但redis不再提供寫服務(wù),我們需要手動的重啟master或者重新設(shè)置master
主從模式的配置
這里是基于yum安裝的方式。資源有限,就在同一臺服務(wù)器上做了。復(fù)制一份/etc/redis.conf。 cp /etc/redis.conf /etc/redis_slave01.conf.這里需要注意一下這個文件的所屬用戶。如果我們使用的root,那么需要執(zhí)行一下
chown redis:root redis_slave01.conf
具體的用戶和用戶組根據(jù)實際情況來更改,保證和最初的redis.conf一致
[root@daice ~]# ll /etc/redis*
-rw-r----- 1 redis root 46725 Aug 26 14:38 /etc/redis.conf
-rw-r----- 1 redis root 7642 Oct 26 2018 /etc/redis-sentinel.conf
-rw-r----- 1 redis root 46706 Aug 28 16:35 /etc/redis_slave01.conf
修改redis_slave01.conf
port 6479 #端口設(shè)置為6479
pidfile /var/run/redis_6479.pid #修改pidfile
logfile /var/log/redis/redis_slave01.log #修改log文件
slaveof 127.0.0.1 6379 #設(shè)置master的ip port ,可以搜索# Master-Slave replication.找到master/slave的配置說明
masterauth 123456 #設(shè)置master的認證密碼
requirepass 123456 #設(shè)置連接該節(jié)點的認證密碼
修改好后,我們增加一個service.
使用yum安裝的好處就是,已經(jīng)有寫好的redis.service。我們復(fù)制一份 cp redis.service redis_slave01.service
修改redis_slave01.service,指定好配置文件
ExecStart=/usr/bin/redis-server /etc/redis_slave01.conf --supervised systemd
接下來我們先啟動master,在啟動slaver
systemctl restart redis
systemctl start redis_slave01
測試一下master:
[root@daice system]# redis
127.0.0.1:6379> set name jack
OK
127.0.0.1:6379> get name
"jack"
測試一下slave:
[root@daice system]# redis-cli -h 127.0.0.1 -p 6479 -a 123456
127.0.0.1:6479> keys *
1) "name"
127.0.0.1:6479> set city beijing
(error) READONLY You can't write against a read only slave.
127.0.0.1:6479> get name
"jack"
我們看到slave可以查詢到master設(shè)置的name,并且是只讀模式??梢允褂胕nfo replication查看集群的信息:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6479,state=online,offset=5509,lag=1
master_repl_offset:5509
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:5508
哨兵模式
因為主從模式中主節(jié)點掛掉后就無法提供寫服務(wù),我們就可以使用redis的哨兵模式,如果master掛掉,可以自動的從slave中選出一個來當master。哨兵模式的特點如下:
- 哨兵是一個單獨的進程來監(jiān)控redis集群
- 哨兵模式是基于主從模式的,如果master掛掉,會從slave中選舉一個修改他的配置作為master,同時其他的slave也會修改slaveof 指向新的master
- 當master重啟后,它將作為slave而不再是master
哨兵模式的實現(xiàn)
哨兵是一個單獨的進程,用于監(jiān)控redis的主從節(jié)點。既然哨兵也是一個進程,也有宕掉的風(fēng)險,所以實際使用中,也會啟用多個哨兵。當一個哨兵向master發(fā)送ping命令,如果master在配置的down-after-milliseconds時間內(nèi)沒有返回響應(yīng),那么就會將該master標記為主觀下線。當有一定數(shù)量的哨兵都做了標記,這時候會發(fā)起投票選舉出一個slave作為master并通過發(fā)布訂閱模式通知到其他的slave更改配置文件,切換master主機,這個時候原來的master則是客觀下線。
哨兵模式的配置
按照上面的主從配置在增加一個slave:
[root@daice system]# redis
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6479,state=online,offset=10227,lag=1
slave1:ip=127.0.0.1,port=6579,state=online,offset=10227,lag=1
master_repl_offset:10227
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:10226
配置三個哨兵:
在/etc 下有redis-sentinel.conf的配置文件,編輯這份文件:
protected-mode no
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2 #mymaster可以自定義名稱,后面是master的ip及端口。2 表示的是有多少個sentinel主觀下線后會開始更換新的master
sentinel auth-pass mymaster 963300 #認證信息
logfile "/var/log/redis/sentinel.log" #日志
復(fù)制兩份(注意使用的用戶).redis-sentinel-01.conf和redis-sentinel-02.conf。分別更改這兩份文件的端口及日志的配置即可,我這邊分別配置成了26389和26399.
切到/lib/systemd/system目錄,將redis.service復(fù)制兩份,redis-sentinel-01.service和redis-sentinel-02.service,分別編輯復(fù)制的兩份文件,將ExecStart關(guān)聯(lián)的配置文件配置為對應(yīng)的conf文件即可
ExecStart=/usr/bin/redis-sentinel /etc/redis-sentinel-01.conf --supervised systemd
ExecStart=/usr/bin/redis-sentinel /etc/redis-sentinel-02.conf --supervised systemd
systemctl分別啟動這三個服務(wù)即可。
啟動后查看日志:
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.12 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26389
| `-._ `._ / _.-' | PID: 2500
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
2500:X 31 Aug 14:14:18.064 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
2500:X 31 Aug 14:14:18.067 # Sentinel ID is fa2091eda9d965657db8f7394bc772ac39e07552
2500:X 31 Aug 14:14:18.067 # +monitor master mymaster 127.0.0.1 6379 quorum 2
2500:X 31 Aug 14:14:18.067 * +slave slave 127.0.0.1:6479 127.0.0.1 6479 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:14:18.070 * +slave slave 127.0.0.1:6579 127.0.0.1 6579 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:14:19.950 * +sentinel sentinel 42f525c6a03ea6154e87b92d0d52c4e88a48f67e 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:27:05.422 * +sentinel sentinel fb3ff1953636ae005e4bd529ff52c4408d7b4dab 127.0.0.1 26399 @ mymaster 127.0.0.1 6379
測試一下,systemctl stop redis關(guān)閉master節(jié)點。查看sentinel的日志:
2500:X 31 Aug 14:48:18.928 # +sdown master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:18.987 # +odown master mymaster 127.0.0.1 6379 #quorum 3/2
2500:X 31 Aug 14:48:18.987 # +new-epoch 1
2500:X 31 Aug 14:48:18.987 # +try-failover master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:18.990 # +vote-for-leader fa2091eda9d965657db8f7394bc772ac39e07552 1
2500:X 31 Aug 14:48:18.995 # 42f525c6a03ea6154e87b92d0d52c4e88a48f67e voted for fa2091eda9d965657db8f7394bc772ac39e07552 1
2500:X 31 Aug 14:48:18.995 # fb3ff1953636ae005e4bd529ff52c4408d7b4dab voted for fa2091eda9d965657db8f7394bc772ac39e07552 1
2500:X 31 Aug 14:48:19.045 # +elected-leader master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.045 # +failover-state-select-slave master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.116 # +selected-slave slave 127.0.0.1:6579 127.0.0.1 6579 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.116 * +failover-state-send-slaveof-noone slave 127.0.0.1:6579 127.0.0.1 6579 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.199 * +failover-state-wait-promotion slave 127.0.0.1:6579 127.0.0.1 6579 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.489 # +promoted-slave slave 127.0.0.1:6579 127.0.0.1 6579 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.489 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:19.570 * +slave-reconf-sent slave 127.0.0.1:6479 127.0.0.1 6479 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:20.122 # -odown master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:20.538 * +slave-reconf-inprog slave 127.0.0.1:6479 127.0.0.1 6479 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:20.538 * +slave-reconf-done slave 127.0.0.1:6479 127.0.0.1 6479 @ mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:20.599 # +failover-end master mymaster 127.0.0.1 6379
2500:X 31 Aug 14:48:20.599 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6579
2500:X 31 Aug 14:48:20.599 * +slave slave 127.0.0.1:6479 127.0.0.1 6479 @ mymaster 127.0.0.1 6579
2500:X 31 Aug 14:48:20.599 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6579
從日志中就可以看到主觀下線,超過3/2,開始失效轉(zhuǎn)移(failover),選舉,變更為6579.看一下配置文件,發(fā)現(xiàn)slaveod已經(jīng)是6579端口的redis了。重新啟動后,再看一下6379的信息:
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6579
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1598858335
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
發(fā)現(xiàn)他的role已經(jīng)是slave了。
Redis Cluster集群
上面的模式實現(xiàn)了redis的高可用,如果數(shù)據(jù)量非常大,一臺服務(wù)器已經(jīng)無法滿足使用了,主從模式就顯得不足了。我們就需要對數(shù)據(jù)分片,將數(shù)據(jù)分散到多臺redis服務(wù)器上。這就是redis的cluster集群模式。
redis cluster的設(shè)計
- cluster是一種去中心化的集群,每個節(jié)點保存數(shù)據(jù)和整個集群的狀態(tài),各個節(jié)點之間互相連接。
- 如果集群中超過半數(shù)的節(jié)點都檢測到某個節(jié)點不可用,那么就會認為該節(jié)點失效。
- 客戶端連接集群中的任意一個節(jié)點即可
- redis-cluster會將所有的物理節(jié)點映射到0-16383間的slot上,當需要在集群中放入一個key-value時,根據(jù) CRC16(key) mod 16384的值,決定將一個key放到哪個桶中。
- 對于每一個物理節(jié)點也可以配置主從模式,某個節(jié)點失效,就可以轉(zhuǎn)移到slave節(jié)點上
redis cluster搭建
集群中至少有奇數(shù)個節(jié)點,以三個節(jié)點為例,每個再增加一個slave,我們需要6個redis實例。
仍然是復(fù)制redis.conf出6份,修改:
daemonize yes #后臺啟動
port 7001 #修改端口號,從7001到7006
cluster-enabled yes #開啟cluster,去掉注釋
cluster-config-file nodes.conf #自動生成
cluster-node-timeout 15000 #節(jié)點通信時間
appendonly yes #持久化方式
分別新建這六個服務(wù)后啟動。
接下來需要使用一個ruby腳本來講這六個節(jié)點加入到cluster集群中。由于是使用的yum安裝,并沒有找到這個腳本在哪,所以就下載了一個redis壓縮包,到src下找到redis-trib.rb這個文件。這個就是我們要使用的腳本。接下來安裝
gem install redis
需要注意的是要安裝ruby來運行redis的腳本文件來創(chuàng)建cluster集群。如果遇到ruby版本太低,升級可以參考http://www.itdecent.cn/p/a1a4d59490d7
./redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7000
--replicas 1 參數(shù)表示為每個主節(jié)點創(chuàng)建一個從節(jié)點,其他參數(shù)是實例的地址集合。
==安裝過程中的坑==
- 如果你的conf中設(shè)置了密碼,那么需要修改一個文件:/usr/local/rvm/gems/ruby-2.6.5/gems/redis-4.2.1/lib/redis/client.rb. 找到里面default里password的配置,配置成你定義的密碼
- 如果出現(xiàn)Node is not empty. Either the node already knows other nodes的錯誤,需要我們清除一下node.conf 及aod,rdb的備份文件。文件路徑是在conf配置中dir配置的。我的是在/var/lib/redis下的。然后分別登陸每一個節(jié)點。分別執(zhí)行 flushdb
- 如果出現(xiàn)有一個槽已經(jīng)被占用,分別登陸每一個節(jié)點,執(zhí)行flushall,cluster reset命令
- 如果你是用的是jediscluster連接的集群,出現(xiàn)了JedisClusterMaxAttemptsException: No more cluster attempts left。在檢查集群正常的情況下,可能是ip關(guān)系。jedis使用的是公網(wǎng)ip,而我們使用redis腳本指定的是內(nèi)網(wǎng)ip,就會出現(xiàn)上面的情況。我們需要使用公網(wǎng)ip重新設(shè)置集群。注意redis.conf中bind ip的配置,會限制訪問ip的,我們需要注銷掉。
cluster集群的擴容 https://www.cnblogs.com/yfacesclub/p/11860927.html
分片與一致性hash
數(shù)據(jù)存?。?/strong>
現(xiàn)在我們是三個主節(jié)點分別是:A, B, C 三個節(jié)點,它們可以是一臺機器上的三個端口,也可以是三臺不同的服務(wù)器。那么,采用哈希槽 (hash slot)的方式來分配16384個slot 的話,它們?nèi)齻€節(jié)點分別承擔的slot 區(qū)間是:
節(jié)點A覆蓋0-5460;
節(jié)點B覆蓋5461-10922;
節(jié)點C覆蓋10923-16383.
如果存入一個值,按照redis cluster哈希槽的算法: CRC16('key')384 = 6782。 那么就會把這個key 的存儲分配到 B 上了。同樣,當我連接(A,B,C)任何一個節(jié)點想獲取'key'這個key時,也會這樣的算法,然后內(nèi)部跳轉(zhuǎn)到B節(jié)點上獲取數(shù)據(jù)。
看一下槽的分配情況:
127.0.0.1:5004> cluster nodes
ff22d6799b1731661fc158019af29c94bcb8293e 127.0.0.1:5002 master - 0 1598945599575 2 connected 5461-10922
55cec2805497dc43209792bc2e492e4184593ec4 127.0.0.1:5005 slave ff22d6799b1731661fc158019af29c94bcb8293e 0 1598945598572 5 connected
3e4c9d461d39acd684531fc6743e9bb8b946a0bb 127.0.0.1:5004 myself,slave 00404f89f54eb594a7758b5179b7d5e1ea6a21fc 0 0 4 connected
abf5bfdcca6b24faad0bcc4f4368a5f98dca49b1 127.0.0.1:5006 slave b62faa0c70604f1d39acfd5fd790fee2b123a1ed 0 1598945601578 6 connected
00404f89f54eb594a7758b5179b7d5e1ea6a21fc 127.0.0.1:5001 master - 0 1598945597069 1 connected 0-5460
b62faa0c70604f1d39acfd5fd790fee2b123a1ed 127.0.0.1:5003 master - 0 1598945602580 3 connected 10923-16383
如果想要擴容新增一個節(jié)點呢? 新增一個節(jié)點D,redis cluster的這種做法是從各個節(jié)點的前面各拿取一部分slot到D上。大致就會變成這樣:
節(jié)點A覆蓋1365-5460
節(jié)點B覆蓋6827-10922
節(jié)點C覆蓋12288-16383
節(jié)點D覆蓋0-1364,5461-6826,10923-12287
這樣就會按照新的槽位規(guī)則來存取了。
redis分片帶來的問題
如上面的所示,當我們新增或者刪除一個節(jié)點的時候,都會重新分配槽,同時還會產(chǎn)生數(shù)據(jù)的遷移。如果每臺服務(wù)器中數(shù)據(jù)量都比較大,那么數(shù)據(jù)遷移工作是非常耗時的。
一致性hash算法
- 首先算出緩存節(jié)點的hash值,并將其配置到0~2^32的圓上
- 用同樣的方法算出存儲數(shù)據(jù)的key的哈希值,并映射到相同的圓上
-
然后從數(shù)據(jù)映射的位置開始順時針查找,將數(shù)據(jù)映射到找到的第一個服務(wù)器上。
image -
如果我們新增一個節(jié)點
image
從圖片中我們發(fā)現(xiàn)node1,node2,node3的數(shù)據(jù)是不受影響的.node5插入后一部分屬于node4的數(shù)據(jù)現(xiàn)在是計入node5上了,如果我們不做數(shù)據(jù)遷移,那么這一部分緩存就會失效,但是對于整個緩存系統(tǒng)系統(tǒng)而言,已經(jīng)是將損失降到了最低。
- 一致性hash也會帶來數(shù)據(jù)偏移的問題。尤其是當我們系統(tǒng)中節(jié)點較少時,可能會發(fā)生大量的數(shù)據(jù)都命中到一臺服務(wù)器上。