通过上次轻松搭建了一个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了。