通過上次輕鬆搭建了一個Redis的環境並用Java代碼調通後。此次咱們要來看看Redis的一些坑以及Redis2.8之後帶來的一個新的特性即支持高可用特性功能的Sentinel(哨兵)。html
Redis是一個很優秀的NoSql,它支持鍵值對,查詢方便,被大量應用在Internet的應用中。它即可以用做Http Session的分離如上一次舉例中的和Spring Session的結合。還可以直接配置在Tomcat中和Tomcat容器結合並可以本身主動使用Redis做Session盛載器,同一時候它也可以做爲一個分佈式緩存。java
這邊的單線程不是指它就是順序式工做的,這邊的單線程主要關注的是Redis的一個很重要的功能即「持久化」工做機制。node
Redis一般會使用兩種持久化工做機制,這樣的工做機制假設在單個Redis Node下工做是沒有意義的,所以你必需要有兩個Redis Nodes,如:
linux
IP | 端口 | 身份 |
192.168.56.101 | 7001 | 主節點 |
192.168.56.101 | 7002 | 備節點 |
這樣的文件很適合用於進行備份: 比方說,你可以在近期的 24 小時內,每小時備份一次 RDB 文件。並且在每個月的每一天,也備份一個 RDB 文件。web
這樣的話。即便趕上問題,也可以隨時將數據集還原到不一樣的版本號。RDB 很適用於災難恢復(disaster recovery):它僅僅有一個文件,並且內容都很緊湊。可以(在加密後)將它傳送到別的數據中心,或者亞馬遜 S3 中。RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時惟一要作的就是 fork 出一個子進程,而後這個子進程就會處理接下來的所有保存工做,父進程無須運行不論什麼磁盤 I/O 操做。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
RDB 的缺點:
假設你需要儘可能避免在server故障時丟失數據,那麼 RDB 不適合你。 儘管 Redis 贊成你設置不一樣的保存點(save point)來控制保存 RDB 文件的頻率, 但是, 因爲RDB 文件需要保存整個數據集的狀態。 因此它並不是一個輕鬆的操做。redis
所以你可能會至少 5 分鐘才保存一次 RDB 文件。 在這樣的狀況下, 一旦發生問題停機, 你就可能會丟失好幾分鐘的數據。spring
每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工做。數據庫
在數據集比較龐大時, fork() 可能會很耗時。形成server在某某毫秒內中止處理client; 假設數據集很巨大。並且 CPU 時間很緊張的話,那麼這樣的中止時間甚至可能會長達整整一秒。 儘管 AOF 重寫也需要進行 fork() ,但不管 AOF 重寫的運行間隔有多長,數據的耐久性都不會有不論什麼損失。apache
AOF 的默認策略爲每秒鐘 fsync 一次,在這樣的配置下,Redis 仍然可以保持良好的性能,並且就算髮生問題停機,也最多僅僅會丟失一秒鐘的數據( fsync 會在後臺線程運行。因此主線程可以繼續努力地處理命令請求)。windows
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 文件的內容很easy被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也很easy: 舉個樣例。 假設你不當心運行了 FLUSHALL 命令, 但僅僅要 AOF 文件未被重寫, 那麼僅僅要中止server, 移除 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 的。
object=queryFromCache(); if(object==null||queryFromCache throw any exception) { object=queryFromDB(); }
。bla...bla...bla...這裏詳細就要看業務了。
。。此時這臺舊的master上的RDB文件和從slave位置被提高成master(new master)間的RDB文件的出入,是否是就會比較高啊。
。。
所以此時old master會試圖和新的master進行RDB間的數據同步。而這個同步。。。是很要命的,假設你的用戶併發量很大。在一瞬時內你的rdb增加的會很高。所以當兩個redis nodes在同步RDB文件時就會直接把你的現在的new master(原來的slave)搞死進而搞死你的old master(原來的master),因爲它是單線程的,大數據量在同步時它會ban掉不論什麼的訪問請求。
2) 假設 slave-serve-stale data設置成 'no' slave會返回"SYNC with master in progress"這樣的錯誤信息。
但 INFO 和SLAVEOF命令除外。
。。那麼你將會沒有一個可用的redis節點進而把整個環境搞死。
因爲頁表大幅較小(2MB / 4KB = 512倍),fork的耗時也會大幅下降。
而關閉THP後。僅僅有4次超時,緣由是與fork在同一事件循環的請求受到fork的影響。 關閉THP影響的僅僅是零星幾個請求,而開啓後,儘管超時時間短了,但是影響面擴大了進而致使了整個Linux系統的不穩定。
echo never > /sys/kernel/mm/transparent_hugepage/enabled
Redis配置文件裏的這一行表明Redis會使用系統內存,你不應去限制Redis的內存開銷如:JVM中的-xmx這個參數,而是要讓Redis本身主動去使用系統的內存以得到最高的性能,所以咱們會把這個值設成0即表明無限使用系統內存,系統內存有多少咱們用多少。默認它啓動後會消耗掉1個G的系統自有內存。
所以linux系統中有一個系統參數叫overcommit_memory,它表明的是內存分配策略,可選值爲:0、一、2。
0, 表示內核將檢查是否有足夠的可用內存供應用進程使用;假設有足夠的可用內存,內存申請贊成;不然,內存申請失敗,並把錯誤返回給應用進程。
1, 表示內核贊成分配所有的物理內存。而不管當前的內存狀態怎樣。
2, 表示內核贊成分配超過所有物理內存和交換空間總和的內存
因此咱們結合咱們的Redis使用如下的linux命令:
echo 1 > /proc/sys/vm/overcommit_memory
sysctl -p
有時位於系統訪問高峯時間段突發的大量請求致使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的配置。
* soft nofile = 65535 * hard nofile = 65535
ulimit -n 65535
經過上述一些設置,咱們基本完畢了Redis在作集羣前的準備工做了,如下就來使用Redis的Sentinel來作咱們的高可用方案吧。
考慮到大多數學習者環境有限。咱們使用例如如下配置:
IP | 端口 | 身份 |
192.168.56.101 | 7001 | master |
192.168.56.101 | 7002 | slave |
192.168.56.101 | 26379 | sentinel |
因此咱們在一臺server上安裝3個文件夾:
make PREFIX=/usr/local/redis1 install make PREFIX=/usr/local/redis2 install make PREFIX=/usr/local/redis-sentinel install
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
在配置Redis Sentinel作Redis的HA場景時,必定要注意如下幾個點:
這部分配置除了端口號。所在文件夾。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
slaveof 192.168.56.101 7001
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
<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>
<?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 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
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"; } }
<%@ 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>
jedispool to master at 192.168.56.101:7002,7002已經變成master了。
。
。7001上的服務不可用已經被咱們位於26379端口的哨兵探測到了,它已經把7002變成master了。
。。7001恢復後從原來的old master成了new slave了。