簡介
經過上次輕鬆搭建了一個Redis的環境並用Java代碼調通後,這次我們要來看看Redis的一些坑以及Redis2.8以後帶來的一個新的特性即支持高可用特性功能的Sentinel(哨兵)。
Redis的一些坑
Redis是一個很優秀的NoSql,它支持鍵值對,查詢方便,被大量應用在Internet的應用中,它即可以用作Http Session的分離如上一次舉例中的和Spring Session的結合,還可以直接配置在Tomcat中和Tomcat容器結合並可以自動使用Redis作Session盛載器,同時它也可以作爲一個分佈式緩存。
Redis是單線程工作的
這邊的單線程不是指它就是順序式工作的,這邊的單線程主要關注的是Redis的一個很重要的功能即“持久化”工作機制。Redis一般會使用兩種持久化工作機制,這種工作機制如果在單個Redis Node下工作是沒有意義的,因此你必須要有兩個Redis Nodes,如:
IP | 端口 | 身份 |
192.168.56.101 | 7001 | 主節點 |
192.168.56.101 | 7002 | 備節點 |
- RDB模式
- AOF模式
RDB
RDB 是一個非常緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。 這種文件非常適合用於進行備份: 比如說,你可以在最近的 24 小時內,每小時備份一次 RDB 文件,並且在每個月的每一天,也備份一個 RDB 文件。 這樣的話,即使遇上問題,也可以隨時將數據集還原到不同的版本。RDB 非常適用於災難恢復(disaster recovery):它只有一個文件,並且內容都非常緊湊,可以(在加密後)將它傳送到別的數據中心,或者亞馬遜 S3 中。RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然後這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
RDB 的缺點:
如果你需要儘量避免在服務器故障時丟失數據,那麼 RDB 不適合你。 雖然 Redis 允許你設置不同的保存點(save point)來控制保存 RDB 文件的頻率, 但是, 因爲RDB 文件需要保存整個數據集的狀態, 所以它並不是一個輕鬆的操作。 因此你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種情況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工作。 在數據集比較龐大時, fork() 可能會非常耗時,造成服務器在某某毫秒內停止處理客戶端; 如果數據集非常巨大,並且 CPU 時間非常緊張的話,那麼這種停止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。
AOF
使用 AOF 持久化會讓 Redis 變得非常耐久(much more durable):你可以設置不同的 fsync 策略,比如無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的性能,並且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,所以主線程可以繼續努力地處理命令請求)。AOF 文件是一個只進行追加操作的日誌文件(append only log), 因此對 AOF 文件的寫入不需要進行 seek , 即使日誌因爲某些原因而包含了未寫入完整的命令(比如寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也可以輕易地修復這種問題。
Redis 可以在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因爲 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。
AOF 的缺點:
對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。AOF 在過去曾經發生過這樣的 bug : 因爲個別命令的原因,導致 AOF 文件在重新載入時,無法將數據集恢復成保存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引起過這樣的 bug 。) 測試套件裏爲這種情況添加了測試: 它們會自動生成隨機的、複雜的數據集, 並通過重新載入這些數據來確保一切正常。 雖然這種 bug 在 AOF 文件中並不常見, 但是對比來說, RDB 幾乎是不可能出現這種 bug 的。
RDB 和 AOF間的選擇
object=queryFromCache();
if(object==null||queryFromCache throw any exception)
{
object=queryFromDB();
}
如果因爲緩存服務不存在而在queryFromCache時拋錯一個exception以致於頁面直接回一個HTTP 500 error給用戶那是相當的不合理的。
2) 如果 slave-serve-stale data設置成 'no' slave會返回"SYNC with master in progress"這樣的錯誤信息。 但 INFO 和SLAVEOF命令除外。
THP(Transparent Huge Pages)
- 開啓THP的優勢在於:
- 減少page fault。一次page fault可以加載更大的內存塊.。
- 更小的頁表。相同的內存大小,需要更少的頁。
- 由於頁表更小,虛擬地址到物理地址的翻譯也更快。
- 劣勢在於:
- 降低分配內存效率。需要大塊、連續內存塊,內核線程會比較激進的進行compaction,解決內存碎片,加劇鎖爭用。
- 降低IO吞吐。由於swapable huge page,在swap時需要切分成原有的4K的頁。Oracle的測試數據顯示會降低30%的IO吞吐。
- 對於redis而言,開啓THP的優勢:fork子進程的時間大幅減少。fork進程的主要開銷是拷貝頁表、fd列表等進程數據結構。由於頁表大幅較小(2MB / 4KB = 512倍),fork的耗時也會大幅減少。
- 劣勢在於: fork之後,父子進程間以copy-on-write方式共享地址空間。如果父進程有大量寫操作,並且不具有locality,會有大量的頁被寫,並需要拷貝。同時,由於開啓THP,每個頁2MB,會大幅增加內存拷貝。
- fork時間對比 開啓THP後,fork大幅減少。
- 超時次數對比 開啓THP後,超時次數明顯增多,但是每次超時時間較短。而關閉THP後,只有4次超時,原因是與fork在同一事件循環的請求受到fork的影響。 關閉THP影響的只是零星幾個請求,而開啓後,雖然超時時間短了,但是影響面擴大了進而導致了整個Linux系統的不穩定。
echo never > /sys/kernel/mm/transparent_hugepage/enabled
Redis的maxmemory 0的問題
Redis配置文件中的這一行代表Redis會使用系統內存,你不該去限制Redis的內存開銷如:JVM中的-xmx這個參數,而是要讓Redis自動去使用系統的內存以獲得最高的性能,因此我們會把這個值設成0即代表無限使用系統內存,系統內存有多少我們用多少。默認它啓動後會消耗掉1個G的系統自有內存。
因此linux系統中有一個系統參數叫overcommit_memory,它代表的是內存分配策略,可選值爲:0、1、2。
0, 表示內核將檢查是否有足夠的可用內存供應用進程使用;如果有足夠的可用內存,內存申請允許;否則,內存申請失敗,並把錯誤返回給應用進程。
1, 表示內核允許分配所有的物理內存,而不管當前的內存狀態如何。
2, 表示內核允許分配超過所有物理內存和交換空間總和的內存
所以我們結合我們的Redis使用以下的linux命令:
echo 1 > /proc/sys/vm/overcommit_memory
上述兩條命令發完後不要完了刷新系統內存策略,因此我們接着發出一條命令
sysctl -p
Redis在Linux系統中Too many open files的問題
有時位於系統訪問高峯時間段突發的大量請求導致redis連接數過大,你會收到這樣的錯誤信息:
Too many open files.
這是因爲頻繁訪問Redis時造成了TCP連接數打開過大的主要原因, 這是因爲Redis源碼中在accept tcp socket時的實現裏面遇到句柄數不夠的處理方法爲:留在下次處理,而不是斷開TCP連接。
但這一行爲就會導致監聽套接字不斷有可讀消息,但卻accept無法接受,從而listen的backlog被塞滿;從而導致後面的連接被RST了。
這裏我多囉嗦一下也就是Redis和Memcached的比較,memcached對於這種情況的處理有點特殊,或者說周到!
如果memcache accept 的時候返回EMFILE,那麼它會立即調用listen(sfd, 0) , 也就是將監聽套接字的等待accept隊列的backlog設置爲0,從而拒絕掉這部分請求,減輕系統負載,保全自我。
因此爲了對付這個too many open files問題我們需要在Linux下做點小動作來改變ulimit的配置。
- 修改/etc/security/limits.conf
通過 vi /etc/security/limits.conf修改其內容,在文件最後加入(數值也可以自己定義):
* soft nofile = 65535
* hard nofile = 65535
- 修改/etc/profile
通過vi /etc/profile修改,在最後加入以下內容
ulimit -n 65535
通過上述一些設置,我們基本完成了Redis在做集羣前的準備工作了,下面就來使用Redis的Sentinel來做我們的高可用方案吧。
使用Redis Sentinel來做HA
sentinel是一個分佈式系統,在源碼包的src目錄下會有redis-sentinel命令,你甚至還可以在多臺機器上部署sentinel進程,共同監控redis實例。
- 一個Master可以有多個Slave;
- Redis使用異步複製。從2.8開始,Slave會週期性(每秒一次)發起一個Ack確認複製流(replication stream)被處理進度;
- 不僅主服務器可以有從服務器, 從服務器也可以有自己的從服務器, 多個從服務器之間可以構成一個圖狀結構;
- 複製在Master端是非阻塞模式的,這意味着即便是多個Slave執行首次同步時,Master依然可以提供查詢服務;
- 複製在Slave端也是非阻塞模式的:如果你在redis.conf做了設置,Slave在執行首次同步的時候仍可以使用舊數據集提供查詢;你也可以配置爲當Master與Slave失去聯繫時,讓Slave返回客戶端一個錯誤提示;
- 當Slave要刪掉舊的數據集,並重新加載新版數據時,Slave會阻塞連接請求(一般發生在與Master斷開重連後的恢復階段);
- 複製功能可以單純地用於數據冗餘(data redundancy),也可以通過讓多個從服務器處理只讀命令請求來提升擴展性(scalability): 比如說, 繁重的 SORT 命令可以交給附屬節點去運行。
- 可以通過修改Master端的redis.config來避免在Master端執行持久化操作(Save),由Slave端來執行持久化。
Redis Sentinel規劃
考慮到大多數學習者環境有限,我們使用如下配置:
IP | 端口 | 身份 |
192.168.56.101 | 7001 | master |
192.168.56.101 | 7002 | slave |
192.168.56.101 | 26379 | sentinel |
所以我們在一臺服務器上安裝3個目錄:
- redis1-對應master
- redis2-對應slave
- redis-sentinel對應sentinel,它使用26379這個端口來監控master和slave
make PREFIX=/usr/local/redis1 install
make PREFIX=/usr/local/redis2 install
make PREFIX=/usr/local/redis-sentinel install
Sentinel中的配置
port 26379
daemonize yes
logfile "/var/log/redis/sentinel.log"
sentinel monitor master1 192.168.56.101 7001 1
sentinel down-after-milliseconds master1 1000
sentinel failover-timeout master1 5000
#sentinel can-failover master1 yes #remove from 2.8 and aboved version
- daemonize yes – 以後臺進程模式運行
- port 26379 – 哨兵的端口號,該端口號默認爲26379,不得與任何redis node的端口號重複
- logfile “/var/log/redis/sentinel.log“ – log文件所在地
- sentinel monitor master1 192.168.56.101 7001 1 – (第一次配置時)哨兵對哪個master進行監測,此處的master1爲一“別名”可以任意如sentinel-26379,然後哨兵會通過這個別名後的IP知道整個該master內的slave關係。因此你不用在此配置slave是什麼而由哨兵自己去維護這個“鏈表”。
- sentinel monitor master1 192.168.56.101 7001 1 – 這邊有一個“1”,這個“1”代表當新master產生時,同時進行“slaveof”到新master並進行同步複製的slave個數。在salve執行salveof與同步時,將會終止客戶端請求。此值較大,意味着“集羣”終止客戶端請求的時間總和和較大。此值較小,意味着“集羣”在故障轉移期間,多個salve向客戶端提供服務時仍然使用舊數據。我們這邊只想讓一個slave來做此時的響應以取得較好的客戶端體驗。
- sentinel down-after-milliseconds master1 1000 – 如果master在多少秒內無反應哨兵會開始進行master-slave間的切換,使用“選舉”機制
- sentinel failover-timeout master1 5000 – 如果在多少秒內沒有把宕掉的那臺master恢復,那哨兵認爲這是一次真正的宕機,而排除該宕掉的master作爲節點選取時可用的node然後等待一定的設定值的毫秒數後再來探測該節點是否恢復,如果恢復就把它作爲一臺slave加入哨兵監測節點羣並在下一次切換時爲他分配一個“選取號”。
- #sentinel can-failover master1 yes #remove from 2.8 and aboved version – 該功能已經從2.6版以後去除,因此註釋掉,網上的教程不適合於redis-stable版
在配置Redis Sentinel做Redis的HA場景時,一定要注意下面幾個點:
- 除非有多機房HA場景的存在,堅持使用單向鏈接式的master->slave的配置如:node3->node2->node1,把node1設爲master
- 如果sentinel(哨兵)或者是HA羣重啓,一定要使用如此順序:先啓master,再啓slave,再啓哨兵
- 第一次配置完成“哨兵”HA羣時每次啓動不需要手動再去每個redis node中去更改master slave這些參數了,哨兵會在第一次啓動後記錄和動態修改每個節點間的關係,第一次配置好啓動“哨兵”後由哨兵以後自行維護一般情況下不需要人爲干涉,如果切換過一次master/slave後也因該記得永遠先起master再起slave再起哨兵這個順序,具體當前哪個是master可以直接看哨兵的sentinel.conf文件中最末尾哨兵自行的記錄
Redis Master和Redis Slave的配置
這部分配置除了端口號,所在目錄,pid文件與log文件不同其它配置相同,因此下面只給出一份配置:
daemonize yes
pidfile "/var/run/redis/redis1.pid"
port 7001
tcp-backlog 511
timeout 0
tcp-keepalive 0
loglevel notice
logfile "/var/log/redis/redis1.log"
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error no
rdbcompression yes
rdbchecksum yes
dbfilename "dump.rdb"
dir "/usr/local/redis1/data"
slave-serve-stale-data yes
slave-read-only yes #slave只讀,當你的應用程序試圖向一個slave寫數據時你會得到一個錯誤
repl-diskless-sync no
repl-disable-tcp-nodelay no
slave-priority 100
maxmemory 0
appendonly no
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"
# appendfsync always
#appendfsync everysec
appendfsync no #關閉AOF
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events "gxE"
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
其中:
- slave-read-only yes 我們把slave設成只讀,當你的應用程序試圖向一個slave寫數據時你會得到一個錯誤
- appendfsync no 我們關閉了AOF功能
slaveof 192.168.56.101 7001
配完了master, slave和sentinel後,我們按照這個順序來啓動redis HA:
redis-cli -p 26379 -h 192.168.56.101
進入我們配置好的sentinel後並使用: info命令來查看我們的redis sentinel HA配置。redis-cli -p 7001 -h 192.168.56.101
我們還可以通過命令:
redis-cli -h 192.168.56.101 -p 7002
進入到7002中並通過info replication來查看7002內的情況:
好了,環境有了,我們接下來要使用:
- 模擬代碼
- 模擬併發測試工具
使用 Spring Data + Jedis來訪問我們的Redis Sentinel
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>webpoc</groupId>
<artifactId>webpoc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<jetty.version>9.3.3.v20150827</jetty.version>
<slf4j.version>1.7.7</slf4j.version>
<spring.version>4.2.1.RELEASE</spring.version>
<spring.session.version>1.0.2.RELEASE</spring.session.version>
<javax.servlet-api.version>2.5</javax.servlet-api.version>
<activemq_version>5.8.0</activemq_version>
<poi_version>3.8</poi_version>
</properties>
<dependencies>
<!-- poi start -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi_version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>${poi_version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>${poi_version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi_version}</version>
</dependency>
<!-- poi end -->
<!-- active mq start -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>${activemq_version}</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!-- active mq end -->
<!-- servlet start -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- servlet end -->
<!-- redis start -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>1.0.2</version>
</dependency>
<!-- redis end -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- spring conf start -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>${spring.session.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring conf end -->
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:/spring/redis.properties" />
<context:component-scan base-package="org.sky.redis">
</context:component-scan>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg index="0" ref="redisSentinelConfiguration" />
<constructor-arg index="1" ref="jedisPoolConfig" />
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
<property name="testOnReturn" value="${redis.testOnReturn}" />
</bean>
<bean id="redisSentinelConfiguration"
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="master1" />
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.56.101" />
<constructor-arg name="port" value="26379" />
</bean>
</set>
</property>
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
<!--將session放入redis -->
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>
<bean id="customExceptionHandler" class="sample.MyHandlerExceptionResolver" />
</beans>
其中:
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="master1" />
</bean>
</property>
redis.properties文件
# Redis settings
redis.host.ip=192.168.56.101
redis.host.port=7001
redis.maxTotal=1000
redis.maxIdle=100
redis.maxWait=2000
redis.testOnBorrow=false
redis.testOnReturn=true
redis.sentinel.addr=192.168.56.101:26379
SentinelController.java文件
package sample;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import util.CountCreater;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* Created by xin on 15/1/7.
*/
@Controller
public class SentinelController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("/sentinelTest")
public String sentinelTest(final Model model,
final HttpServletRequest request, final String action) {
return "sentinelTest";
}
@ExceptionHandler(value = { java.lang.Exception.class })
@RequestMapping("/setValueToRedis")
public String setValueToRedis(final Model model,
final HttpServletRequest request, final String action)
throws Exception {
CountCreater.setCount();
String key = String.valueOf(CountCreater.getCount());
Map mapValue = new HashMap();
for (int i = 0; i < 1000; i++) {
mapValue.put(String.valueOf(i), String.valueOf(i));
}
try {
BoundHashOperations<String, String, String> boundHashOperations = redisTemplate
.boundHashOps(key);
boundHashOperations.putAll(mapValue);
logger.info("put key into redis");
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new Exception(e);
}
return "sentinelTest";
}
}
sentinelTest.jsp文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; UTF-8">
<title>test sentinel r/w</title>
</head>
<body>
</body>
</html>
測試代碼運行
測試master不可訪問時sentinel的自動切換
- master-7001
- slave-7002
現在,我們把7001重新“恢復”起來,因此我們發出如下的命令:
使用jmter模擬大併發用戶操作下的故障自動轉移
壓力測試計劃
- TPS值
- summary report
- 表格查看結果
- 樹形查看結果
人爲故意造成一次宕機
- 通過TPS我們可以發覺有藍色的線,這代表“出錯率”,這個出錯率應該是7002在“崩”掉後,7001從slave升級成master時redis對客戶端無法及時響應時拋出的HTTP 500即service unavailable的錯。
- 通過Summary Report我們可以看到在主從切換的那一刻我們的fail rate爲千分之0.5,這個fail rate是完全可以在接受範圍內的,一般錯誤率在千分之一就已經很好了。