Redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的key-value形式存储系统,它可以用作数据库、缓存和消息中间件。Redis内置了复制(replication)、LUA脚本、LRU事件驱动、事务和不同级别的磁盘持久化,并可通过Redis哨兵(Sentinel)或者集群(Cluster)提供高可用性(HA)。
系统环境:SUSE Linux Enterprise Server 11 (x86_64)。
下载Redis:redis是以源码发行的,先下载源码,然后在linux下编译,下载地址为http://www.redis.io/download,先到这里下载Stable稳定版,目前最新稳定版本是Redis 3.2.5,此处下载了Redis 3.2.4版本(redis-3.2.4.tar.gz)作为演示
(1)将redis-3.2.4.tar.gz 源码包上传到linux服务,然后运行以下命令解压:
jtykf-41-85:/ # tar –xzvf redis-3.2.4.tar.gz
(2)进入到解压好的redis-3.2.4文件夹,执行./configure命令,来生成Makefile,为下一步编译做准备。
jtykf-41-85:/redis-3.2.4 # ./configure
(3)在redis-3.2.4文件夹当前目录下,执行make命令,来进行编译。
jtykf-41-85:/redis-3.2.4 # make
注:make命令需要linux上安装gvv
(4)最后在redis-3.2.4文件夹当前目录下执行make install命令,来进行安装。
jtykf-41-85:/redis-3.2.4 # make install
Redis文件目录结构见下图:
其中redis.conf是redis的主要配置文件,编辑redis.conf配置文件。
redis的配置修改,主要对一些关键配置项进行修改:
(1).#bind 绑定主机,默认值127.0.0.1,注释掉,如果只想让redis服务在一个网络接口上监听,那就绑定一个IP或者多个IP
# bind:127.0.0.1(注释掉)
(2). protected-mode是3.2之后加入的新特性 yes:处于保护模式 redis时只能通过本地来连接,改成no.
protected-mode no
(3). port指定Redis监听端口,默认端口为6379
port 6379
(4). daemonize默认情况下 redis 不是作为守护进程运行的,如果你想让它在后台运行,你就把它改成 yes
daemonize yes
(5). pidfile当redis作为守护进程运行的时候,它会把 pid 默认写到 /var/run/redis.pid 文件里面,但是你可以在这里自己制定它的文件位置
pidfile "redis_6379.pid"
(6) loglevel定义日志级别, debug (适用于开发或测试阶段), notice (适用于生产环境), warning (仅仅一些重要的消息被记录)
loglevel notice
(7)logfile, 指定日志文件的位置。若需改为其它目录(如./log/redis-running.log),则日志文件的父路径必须事先mkdir出来,否则会启动失败.
logfile "redis_6379.log"
(8) dbfilename,设置 dump 的文件位置,存储数据的文件,RBD是一个非常紧凑的文件,它保存了Redis在某个时间点上的数据集。
dbfilename "dump.rdb"
(9) dir, 工作目录, 例如上面的 dbfilename 只指定了文件名, 但是它会写入到这个目录下。这个配置项一定是个目录,而不能是文件名
dir "./
修改并保存好配置文件(redis.conf)后,然后就可以启动Redis服务 "
执行redis启动命令:
jtykf-41-85:/redis-3.2.4 # redis-server redis.conf
使用redis-cli客户端验证:
tykf-41-85:/redis-3.2.4 # reids-cli -p 6379
注:如果远程连接Redis服务可使用:redis-cli –h IP -p PORT
可以通过设置set与get命令来测试缓存是否正常启动
SPEED4J平台提供了两种访问redis服务的客户端。
首先对redis.propertied文件进行配置
######### RedisPoolConfig配置 #############
#最大连接数
redis.pool.maxTotal=200
#最大空闲数
redis.pool.maxIdle=-1
#最小空闲数
redis.pool.minIdle=-1
#最大等待时间(ms)
redis.pool.maxWaitMillis=100000
#使用连接时,检测连接是否成功
redis.pool.testOnBorrow=false
#返回连接时,检测连接是否成功
redis.pool.testOnReturn=false
########## 单台Redis服务器,确保redis服务器可读可写 ##########
#主机地址
redis.host=10.129.41.85
#端口号
redis.port=6379
#超时时间,非必要
redis.timeout=10000
#密码,非必要
#redis.password=
方式一:Jedis,官方推荐的Redis java客户端
平台进一步对Jedis进行了封装,代码如下:
Jedis工具类:JedisUtil.java
public class JedisUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JedisSentinelUtil.class);
private static JedisPool pool = null;
private static JedisUtil ru = new JedisUtil();
public JedisUtil() {
if (pool == null) {
String host = PropertiesUtil.getValue("redis.properties", "redis.host");
String port = PropertiesUtil.getValue("redis.properties", "redis.port");
String maxTotal = PropertiesUtil.getValue("redis.properties", "redis.pool.maxTotal");
String maxIdle = PropertiesUtil.getValue("redis.properties", "redis.pool.maxIdle");
String maxWaitMillis = PropertiesUtil.getValue("redis.properties", "redis.pool.maxWaitMillis");
String testOnBorrow = PropertiesUtil.getValue("redis.properties", "redis.pool.testOnBorrow");
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.valueOf(maxTotal));
config.setMaxIdle(Integer.valueOf(maxIdle));
config.setMaxWaitMillis(Integer.valueOf(maxWaitMillis));
boolean flag = true;
if("true".equalsIgnoreCase(testOnBorrow)){
flag = true;
}else if("false".equalsIgnoreCase(testOnBorrow)){
flag = false;
}
config.setTestOnBorrow(flag);
pool = new JedisPool(config, host, Integer.valueOf(port), 100000);
}
}
/**
* 通过key获取储存在redis中的value
* 并释放连接
* @param key
* @return 成功返回value 失败返回null
*/
public static String get(String key){
Jedis jedis = null;
String value = null;
try {
jedis = pool.getResource();
value = jedis.get(key);
} catch (Exception e) {
LOGGER.error(e.getMessage());
} finally {
returnResource(pool, jedis);
}
return value; }
………………
方式二:spring与redis整合,实现缓存
通过给方法或类加注解的形式,操作redis缓存。
Spring配置文件:applicationContext.xml
<cache:annotation-driven cache-manager="cacheManager" />
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="minIdle" value="${redis.pool.minIdle}" />
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
<property name="testOnReturn" value="${redis.pool.testOnReturn}" />
bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg index="0" ref="jedisPoolConfig" />
<property name="usePool" value="false" />
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="timeout" value="${redis.timeout}" />
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<constructor-arg name="connectionFactory" ref="jedisConnectionFactory" />
bean>
<bean class="org.springframework.cache.support.SimpleCacheManager" id="cacheManager">
<property name="caches">
<set>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="default" />
bean>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="commonCache" />
bean>
set>
property>
bean>
关于注解添加方式,请参考工具操作手册。
为了提升系统的可用性,redis提供了类似于mysql的master-slave主从模式,master节点写入cache后,会自动同步到slave上。而且redis提供了sentinel(哨兵)机制,通过sentinel模式启动redis后,自动监控master/slave的运行状态,基本原理是:心跳机制+投票裁决。
每个sentinel会向其它sentinal、master、slave定时发送消息,以确认对方是否“活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的“主观认为宕机” Subjective Down,简称SDOWN)。若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称ODOWN),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
主Redis服务(master node): 10.129.41.85:6379
从Redis服务(slave node): 10.129.41.86:6379
哨兵1(sentinel node1):10.129.41.83:26379
哨兵2(sentinel node2):10.129.41.83:26380
Redis安装步骤参考前面单机模式
注:哨兵可配一个或多个
服务器架构图:
创建主Redis服务配置文件(redis_85.conf),配置内容与单机模式中的配置相同,再创建从Reids服务配置文件(redis_86.conf),配置内容与主Redis服务相似,只需要修改slaveof配置项:Slaveof 10.129.41.85 6379。
另外注意 slave-read-only yes 这行,这表示slave只读不写,也是推荐设置。
启动master/slave这二台机器上的redis,在master上加一个缓存项,如图。
然后在slave上取出该缓存项
取到了,说明master上的cache自动复制到slave节点了。
配置哨兵节点1:配置文件为sentinel_26379.conf,最小化的Sentinel配置项为:
port 26379
daemonize yes
protected-mode no
logfile sentinel_26379.log
dir ./
sentinel monitor mymaster 10.129.41.85 6379 1
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
显示监控master节点10.129.41.85,master节点使用端口6379,最后一个数字表示投票需要的"最少法定人数",比如有10个sentinal哨兵都在监控某一个master节点,如果需要至少6个哨兵发现master挂掉后,才认为master真正down掉,那么这里就配置为6,最小配置1台master,1台slave,在二个机器上都启动sentinal的情况下,哨兵数只有2个,如果一台机器物理挂掉,只剩一个sentinal能发现该问题,所以这里配置成1;mymaster只是一个名字,可以随便起,但要保证配置中都使用同一个名字。
注意:无论设置多少个Sentinel同意才能判断一个 服务器失效,一个Sentinel都需要获得系统中多数Sentinel的支持,才能发起一次自动迁移,换句话说,在只有少数Sentinel进程正常运作的情况下,Sentinel是不能执行自动故障迁移的。
表示如果30s内mymaster没响应,就认为SDOWN
表示如果60秒后,mysater仍没活过来,则启动failover,从剩下的slave中选一个升级为master
表示如果master重新选出来后,其它slave节点能同时并行从新master同步缓存的台数有多少个,显然该值越大,所有slave节点完成同步切换的整体速度越快,但如果此时正好有人在访问这些slave,可能造成读取失败,影响面会更广。最保定的设置为1,只同一时间,只能有一台干这件事,这样其它slave还能继续服务,但是所有slave全部完成缓存更新同步的进程将变慢。
配置哨兵节点2:配置文件为sentinel_26380.conf,配置内容与sentinel_26380.conf基本一样,配置内容为:
port 26380
daemonize yes
protected-mode no
logfile sentinel_26380.log
dir ./
sentinel monitor mymaster 10.129.41.85 6379 1
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
注:一个sentinal可同时监控多个master,只要把部分配置重复多段,加以修改即可,部分配置为
sentinel monitor mymaster 10.129.41.85 6379 1
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
确保master、slave上的redis-server均已正常启动,哨兵配置文件所在位置如图
执行命令,启动两个哨兵:
jtykf-41-83:/redis-3.2.4 # redis-sentinel sentinel_26379.conf
jtykf-41-83:/redis-3.2.4 # redis-sentinel sentinel_26380.conf
在master上,redis-cli -p 6379 shutdown ,手动把master停掉,观察sentinel的输出
从标注部分可以看出,master发生了迁移
注意事项:发生master迁移后,如果遇到运维需要,想重启所有redis,必须最先重启“新的”master节点,否则sentinel会一直找不到master。如果想停止sentinel,可输入命令redis-cli -p port shutdown
SPEED4J平台提供了两种访问redis哨兵模式的客户端。
首先对redis.propertied文件进行配置
################# RedisPoolConfig配置 #########################
#最大连接数
redis.pool.maxTotal=200
#最大空闲数
redis.pool.maxIdle=-1
#最小空闲数
redis.pool.minIdle=-1
#最大等待时间(ms)
redis.pool.maxWaitMillis=100000
#使用连接时,检测连接是否成功
redis.pool.testOnBorrow=false
#返回连接时,检测连接是否成功
redis.pool.testOnReturn=true
######### 多台Redis服务器,采用主从模式,使用哨兵sentinel进行监听 ###########
#主 redis名
redis.master.name=mymaster
#sentinel.count 哨兵个数
redis.sentinel.count=2
#sentinel_1:哨兵1
redis.sentinel_1.ip=10.129.41.83
redis.sentinel_1.port=26379
#sentinel_2:哨兵2
redis.sentinel_2.ip=10.129.41.83
redis.sentinel_2.port=26380
方式一:Jedis,官方推荐的Redis java客户端
进一步对Jedis Sentinel操作进行了封装,代码如下:
Jedis 哨兵模式客户端工具类:JedisSentinelUtil.java
public class JedisSentinelUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JedisSentinelUtil.class);
private static JedisSentinelPool pool = null;
private static JedisSentinelUtil ru = new JedisSentinelUtil();
public JedisSentinelUtil() {
if (pool == null) {
Set
String sentinelCount = PropertiesUtil.getValue("redis.properties", "redis.sentinel.count");
String masterName = PropertiesUtil.getValue("redis.properties", "redis.master.name");
String maxTotal = PropertiesUtil.getValue("redis.properties", "redis.pool.maxTotal");
String maxIdle = PropertiesUtil.getValue("redis.properties", "redis.pool.maxIdle");
String maxWaitMillis = PropertiesUtil.getValue("redis.properties", "redis.pool.maxWaitMillis");
String testOnBorrow = PropertiesUtil.getValue("redis.properties", "redis.pool.testOnBorrow");
int count = Integer.valueOf(sentinelCount);
String sentinel_ip = null;
String sentinel_port = null;
for(int i=1;i<=count;i++){
sentinel_ip = PropertiesUtil.getValue("redis.properties", "redis.sentinel_"+i+".ip");
sentinel_port = PropertiesUtil.getValue("redis.properties", "redis.sentinel_"+i+".port");
Jedis jedis = new Jedis(sentinel_ip,Integer.valueOf(sentinel_port));
try{
jedis.sentinelGetMasterAddrByName(masterName);
sentinels.add(sentinel_ip+":"+sentinel_port);
}catch(Exception e){
LogManager.info(sentinel_ip+":"+sentinel_port+" sentinel 连接 失败....");
}
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.valueOf(maxTotal));
config.setMaxIdle(Integer.valueOf(maxIdle));
config.setMaxWaitMillis(Integer.valueOf(maxWaitMillis));
boolean flag = true;
if("true".equalsIgnoreCase(testOnBorrow)){
flag = true;
}else if("false".equalsIgnoreCase(testOnBorrow)){
flag = false;
}
config.setTestOnBorrow(flag);
pool = new JedisSentinelPool(masterName,sentinels,config);
}
}
/**
* 获取当前的master
* @return
*/
public static HostAndPort getCurrentMaster(){
return pool.getCurrentHostMaster();
}
/**
* 获取当前master的ip
* @return
*/
public static String getMasterHost(){
return pool.getCurrentHostMaster().getHost();
}
/**
* 获取当前master的port
* @return
*/
public static Integer getMasterPort(){
return pool.getCurrentHostMaster().getPort();
}
/**
* 通过key获取储存在redis中的value
* 并释放连接
* @param key
* @return 成功返回value 失败返回null
*/
public static String get(String key){
Jedis jedis = null;
String value = null;
try {
jedis = pool.getResource();
value = jedis.get(key);
} catch (Exception e) {
LOGGER.error(e.getMessage());
} finally {
returnResource(pool, jedis);
}
return value;
} ……………
方式二:spring与redis整合,实现缓存
通过给方法或类加注解的形式,操作redis缓存。
Spring配置文件:applicationContext.xml
<cache:annotation-driven cache-manager="cacheManager" />
<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="${redis.master.name}">property>
bean>
property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.sentinel_1.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.sentinel_1.port}">constructor-arg>
bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.sentinel_2.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.sentinel_2.port}">constructor-arg>
bean>
set>
property>
bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="redisSentinelConfiguration">constructor-arg>
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<constructor-arg name="connectionFactory" ref="jedisConnectionFactory" />
bean>
<bean class="org.springframework.cache.support.SimpleCacheManager" id="cacheManager">
<property name="caches">
<set>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="default" />
bean>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="commonCache" />
bean>
set>
property>
bean>
关于注解添加方式,请参考工具操作手册
1).节点自动发现
2).slave->master 选举,集群容错
3).Hot resharding:在线分片
4):集群管理:cluster xxx
5).基于配置(nodes-port.conf)的集群管理
6).ASK 转向/MOVED 转向机制
1)redis-cluster架构图
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的master节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key
2) redis-cluster选举:容错
(1)选举过程是集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作.
(2)什么时候整个集群不可用(cluster_state:fail)?
a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,可以理解成集群的slot映射[0-16383]不完整时进入fail状态.
注:redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败.
b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态.
注:当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
1.4.3.1安装redis cluster
安装redis-cluster依赖:redis-cluster的依赖库在使用时有兼容问题,在reshard时会遇到各种错误,需要按指定版本安装。
确保linux环境上安装了zlib,Ruby
(1)确保系统安装zlib,否则gem install会报(no such file to load -- zlib)
安装命令:
(2)安装ruby:version(1.8.7)
安装命令:
(3)安装rubygems:version(2.6.8)
安装命令:
(4)安装gem-redis:version(3.0.7)
安装命令:
1.4.3.2配置redis cluster
为了方便演示,在一台机器上进行演示,创建六个redis服务实例
使用包含(include)把通用配置和特殊配置分离,方便维护
redis-common.conf
redis-6380.conf
修改特殊配置对应的端口与文件名。
1.4.3.3 redis cluster 操作
1)初始化并构建集群
(1)启动集群相关redis服务节点
启动命令
(2):使用自带的ruby工具(redis-trib.rb)构建集群
命令
(3):检查集群状态
命令
最后输出如下信息,没有任何警告或错误,表示集群启动成功并处于ok状态
1.4.3.4 redis cluster 客户端
Redis参数配置文件:redis.properties
################# RedisPoolConfig配置 #########################
#最大连接数
redis.pool.maxTotal=200
#最大空闲数
redis.pool.maxIdle=-1
#最小空闲数
redis.pool.minIdle=-1
#最大等待时间(ms)
redis.pool.maxWaitMillis=100000
#使用连接时,检测连接是否成功
redis.pool.testOnBorrow=false
#返回连接时,检测连接是否成功
redis.pool.testOnReturn=true
######## 多台Redis服务器,采用集群模式 ##############
#cluster.count 集群个数
redis.cluster.count=6
#cluster_1
redis.cluster_1.ip=10.129.41.86
redis.cluster_1.port=7000
#cluster_2
redis.cluster_2.ip=10.129.41.86
redis.cluster_2.port=7001
#cluster_3
redis.cluster_3.ip=10.129.41.86
redis.cluster_3.port=7003
#cluster_4
redis.cluster_4.ip=10.129.41.86
redis.cluster_4.port=7003
#cluster_5
redis.cluster_5.ip=10.129.41.86
redis.cluster_5.port=7004
#cluster_6
redis.cluster_6.ip=10.129.41.86
redis.cluster_6.port=7005
方式一:Jedis客户端操作redis集群
JedisClusterUtil.java
public class JedisClusterUtil {
private static JedisCluster jc = null;
private static JedisClusterUtil ru = new JedisClusterUtil();
public JedisClusterUtil() {
if (jc == null) {
Set
String maxTotal = PropertiesUtil.getValue("redis.properties", "redis.pool.maxTotal");
String maxIdle = PropertiesUtil.getValue("redis.properties", "redis.pool.maxIdle");
String maxWaitMillis = PropertiesUtil.getValue("redis.properties", "redis.pool.maxWaitMillis");
String testOnBorrow = PropertiesUtil.getValue("redis.properties", "redis.pool.testOnBorrow");
//集群个数
String clusterCount = PropertiesUtil.getValue("redis.properties", "redis.cluster.count");
int count = Integer.valueOf(clusterCount);
String cluster_ip = null;
String cluster_port = null;
for(int i=1;i<=count;i++){
cluster_ip = PropertiesUtil.getValue("redis.properties", "redis.cluster_"+i+".ip");
cluster_port = PropertiesUtil.getValue("redis.properties", "redis.cluster_"+i+".port");
HostAndPort hostAndPort = new HostAndPort(cluster_ip,Integer.valueOf(cluster_port));
nodes.add(hostAndPort);
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.valueOf(maxTotal));
config.setMaxIdle(Integer.valueOf(maxIdle));
config.setMaxWaitMillis(Integer.valueOf(maxWaitMillis));
boolean flag = true;
if("true".equalsIgnoreCase(testOnBorrow)){
flag = true;
}else if("false".equalsIgnoreCase(testOnBorrow)){
flag = false;
}
config.setTestOnBorrow(flag);
jc = new JedisCluster(nodes,config);
}
}
/**
* 通过key获取储存在redis中的value
* 并释放连接
* @param key
* @return 成功返回value 失败返回null
*/
public static String get(String key){
return jc.get(key);
}
/**
* 向redis存入key和value,并释放连接资源
* 如果key已经存在 则覆盖
* @param key
* @param value
* @return 成功 返回OK 失败返回 0
*/
public static String set(String key,String value){
return jc.set(key, value);
}
……………
方式二:spring-data-redis操作redis缓存
要求Spring-data-redis版本1.7.0以上,spring版本4.0.0以上
Spring配置文件applicationContext.xml
<cache:annotation-driven cache-manager="cacheManager" />
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<list>
<value>classpath:redis.propertiesvalue>
list>
property>
bean>
<bean id="redisNode1" class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.cluster_1.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.cluster_1.port}" type="int">constructor-arg>
bean>
<bean id="redisNode2" class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.cluster_2.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.cluster_2.port}" type="int">constructor-arg>
bean>
<bean id="redisNode3" class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.cluster_3.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.cluster_3.port}" type="int">constructor-arg>
bean>
<bean id="redisNode4" class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.cluster_4.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.cluster_4.port}" type="int">constructor-arg>
bean>
<bean id="redisNode5" class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.cluster_5.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.cluster_5.port}" type="int">constructor-arg>
bean>
<bean id="redisNode6" class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.cluster_6.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.cluster_6.port}" type="int">constructor-arg>
bean>
<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<property name="clusterNodes">
<set>
<ref bean="redisNode1"/>
<ref bean="redisNode2"/>
<ref bean="redisNode3"/>
<ref bean="redisNode4"/>
<ref bean="redisNode5"/>
<ref bean="redisNode6"/>
set>
property>
bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="minIdle" value="${redis.pool.minIdle}" />
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
<property name="testOnReturn" value="${redis.pool.testOnReturn}" />
bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="redisClusterConfiguration">constructor-arg>
<constructor-arg ref="jedisPoolConfig">constructor-arg>
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<constructor-arg name="connectionFactory" ref="jedisConnectionFactory" />
bean>
<bean class="org.springframework.cache.support.SimpleCacheManager" id="cacheManager">
<property name="caches">
<set>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="default" />
bean>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="commonCache" />
bean>
set>
property>
bean>
参考1.2 Redis单机模式安装步骤,并启动Redis服务。
启动命令,以10.129.41.85:6379为例,在redis安装目录执行命令:
Redis参数配置文件在Web子工程中
src/main/resources/redis.properties:Web应用启动使用
src/test/resources/redis.properties:用于Redis单元测试
由于通过单元测试类来演示单机模式Redis缓存,配置src/test/resources/redis.properties:
################# RedisPoolConfig配置 ##################
#最大连接数
redis.pool.maxTotal=200
#最大空闲数
redis.pool.maxIdle=-1
#最小空闲数
redis.pool.minIdle=-1
#最大等待时间(ms)
redis.pool.maxWaitMillis=100000
#使用连接时,检测连接是否成功
redis.pool.testOnBorrow=false
#返回连接时,检测连接是否成功
redis.pool.testOnReturn=true
######## 单台Redis服务器,确保redis服务器可读可写 ##############
#主机地址(必配)
redis.host=10.129.41.85
#端口号(必配)
redis.port=6379
#超时时间,非必要
redis.timeout=100000
#密码,非必要
#redis.password=
1.5.3.1缓存方式一 :通过工具类缓存数据
TestJedisUtil.java
public class TestJedisUtil {
/**
* 存放缓存
*/
@Test
public void setValue(){
Person p = new Person();
p.setId("001");
p.setName("zhangsan");
p.setAddress("jiangsu");
List
persons.add(p);
System.out.println(JedisUtil.setObjToSerialization("persons", persons));
}
/**
* 取出缓存
*/
@Test
public void getValue(){
System.out.println(JedisUtil.getObjFromSerialization("persons", List.class));
}
}
setValue方法是将一个list集合存放到缓存中,key为”persons”
存放缓存验证,查看redis-client:
getVlalue方法,是将刚放入到缓存中的list集合取出来
控制台输出:
1.5.3.2缓存方式二 :通过注解方式添加缓存数据
首先配置springRedisContextTest.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:jaxws="http://cxf.apache.org/jaxws" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://cxf.apache.org/bindings/soap
http://cxf.apache.org/schemas/configuration/soap.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<cache:annotation-driven cache-manager="cacheManager" />
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<list>
<value>classpath:redis.propertiesvalue>
list>
property>
bean>
<bean id="springContextUtil" class="com.spdb.speed4j.common.SpringContextUtil" />
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="minIdle" value="${redis.pool.minIdle}" />
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
<property name="testOnReturn" value="${redis.pool.testOnReturn}" />
bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg index="0" ref="jedisPoolConfig" />
<property name="usePool" value="false" />
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="timeout" value="${redis.timeout}" />
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<constructor-arg name="connectionFactory" ref="jedisConnectionFactory" />
bean>
<bean class="org.springframework.cache.support.SimpleCacheManager" id="cacheManager">
<property name="caches">
<set>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="default" />
bean>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="commonCache" />
bean>
set>
property>
bean>
<bean id="redisCacheUtil" class="com.spdb.speed4j.cache.redis.RedisCacheUtil">
<property name="redisTemplate" ref="redisTemplate"/>
bean>
<bean name="userService" class="com.spdb.test.redis.UserServiceImpl">bean>
beans>
新建用于测试的类
Person.java
public class Person implements Serializable{
private String id;
private String name;
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", address=" + address + "]";
}
}
UserServiceImpl.java
/**
* 用于测试缓存注解的service层
* @author T-zhangkf
*
*/
public class UserServiceImpl {
@Cacheable(value = "default", key = "#id")
public Person getPersonInfo(String id) {
Person p = new Person();
p.setId("003");
p.setName("wangwu");
p.setName("lisi");
return p;
}
@CachePut(value = "default", key = "#id")
public Person getPersonInfo1(String id) {
Person p = new Person();
p.setId("003");
p.setName("wangwu");
return p;
}
@CacheEvict(value = "default", key = "#id", allEntries = false, beforeInvocation = false)
public void deletePerson(String id) {
}
}
注解@Cacheable会将 “#id”(调用方法的传值)与方法的返回值,以key-value的形式存储到redis缓存中
单元测试类:TestRedisCacheAnnotation.java
/**
* 测试:redis整合spring
* 测试缓存注解的功能
* @author T-zhangkf
*
*/
public class TestRedisCacheAnnotation {
ApplicationContext context = null;
UserServiceImpl userService = null;
@Before
public void setup() {
context = new ClassPathXmlApplicationContext("springRedisContextTest.xml");
userService = (UserServiceImpl) context.getBean("userService");
}
/**
* 测试@Cacheable注解
* 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
*/
@Test
public void testCacheable() {
Person person = (Person) userService.getPersonInfo("003");
System.out.println(person);
}
/**
* 测试@CachePut注解
* 和 @Cacheable类似,不同的是,它每次都会触发真实方法的调用
*/
@Test
public void testCachePut() {
Person person = (Person) userService.getPersonInfo1("003");
System.out.println(person);
}
/**
* 测试@CacheEvict注解
* 和 @Cacheable类似,不同的是,它每次都会触发真实方法的调用
*/
@Test
public void testEvict(){
userService.deletePerson("003");
}
}
testCacheable方法查询用户信息并保存到Redis缓存中
@CachePut与@Cacheable功能相同,只不过@CachePut每次调用方法都会重新将该数据往缓存中添加一次。
testEvict删除数据,同时清除缓存数据 (若allEntries = true,则清空所有缓存数据)
执行该方法清除” 003”缓存:
架构图如下:
(1)先启动主Redis服务器,以10.129.41.85:6379为主redis服务
通过info replication命令可以看出85为主redis服务
(2)在83上启动两个哨兵
哨兵一配置文件:sentinel_26379.conf
port 26379
daemonize yes
protected-mode no
logfile sentinel_26379.log
dir ./
sentinel monitor mymaster 10.129.41.85 6379 1
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
哨兵二配置文件:sentinel_26380.conf
port 26380
daemonize yes
protected-mode no
logfile sentinel_26380.log
dir ./
sentinel monitor mymaster 10.129.41.85 6379 1
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
将sentinel_26379.conf,sentinel_26380.conf两个哨兵配置文件放到redis安装目录
启动两个哨兵:
################# RedisPoolConfig配置 ###################
#最大连接数
redis.pool.maxTotal=200
#最大空闲数
redis.pool.maxIdle=-1
#最小空闲数
redis.pool.minIdle=-1
#最大等待时间(ms)
redis.pool.maxWaitMillis=100000
#使用连接时,检测连接是否成功
redis.pool.testOnBorrow=false
#返回连接时,检测连接是否成功
redis.pool.testOnReturn=true
#### 两台台Redis服务器,采用主从模式,使用哨兵sentinel进行监听 #######
#主 redis名
redis.master.name=mymaster
#sentinel.count 哨兵个数
redis.sentinel.count=2
#sentinel_1:哨兵1
redis.sentinel_1.ip=10.129.41.83
redis.sentinel_1.port=26379
#sentinel_2:哨兵2
redis.sentinel_2.ip=10.129.41.83
redis.sentinel_2.port=26380
1.6.3.1缓存方式一:通过工具类缓存
单元测试类:TestJedisSentinelUtil.java
public class TestJedisSentinelUtil {
@Test
public void testSetObjToJSON(){
System.out.println(JedisSentinelUtil.getCurrentMaster());
/*往缓存中放对象,以json字符串的形式存放到缓存中*/
Person p = new Person();
p.setId("001");
p.setName("zhangsan");
p.setAddress("jiangsu");
System.out.println(JedisSentinelUtil.setObjToJSON("person"+p.getId(), p));
List
persons.add(p);
System.out.println(JedisSentinelUtil.setObjToJSON("persons", persons));
Map
map.put("p1", p);
System.out.println(JedisSentinelUtil.setObjToJSON("map", map));
}
@Test
public void testObjFromJSON(){
/*当value值为json字符串时,获取他对应的Object*/
System.out.println(JedisSentinelUtil.getObjFromJSON("person1",Person.class ));
System.out.println(JedisSentinelUtil.getObjFromJSON("persons",ArrayList.class ));
System.out.println(JedisSentinelUtil.getObjFromJSON("map",HashMap.class ));
}
@Test
public void testSetObjToSerialization(){
/*往缓存中放对象,以json字符串的形式存放到缓存中*/
Person p = new Person();
p.setId("001");
p.setName("lisi");
p.setAddress("sichuan");
System.out.println(JedisSentinelUtil.setObjToSerialization("person"+p.getId(), p));
List
persons.add(p);
System.out.println(JedisSentinelUtil.setObjToSerialization("persons", persons));
Map
map.put("p1", p);
System.out.println(JedisSentinelUtil.setObjToSerialization("map", map));
}
@Test
public void testObjFromSerialization(){
/*当value值为json字符串时,获取他对应的Object*/
System.out.println(JedisSentinelUtil.getObjFromSerialization("person1",Person.class ));
System.out.println(JedisSentinelUtil.getObjFromSerialization("persons",ArrayList.class ));
System.out.println(JedisSentinelUtil.getObjFromSerialization("map",HashMap.class ));
}
}
testSetObjToJSON方法将对象person001,map,集合perons缓存到redis中:
testObjFromJSON方法取出person001,map,集合perons缓存
1.6.3.2缓存方式二:通过注解缓存
首先配置springRedisSentinelContextTest.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:jaxws="http://cxf.apache.org/jaxws" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://cxf.apache.org/bindings/soap
http://cxf.apache.org/schemas/configuration/soap.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<cache:annotation-driven cache-manager="cacheManager" />
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<list>
<value>classpath:redis.propertiesvalue>
list>
property>
bean>
<bean id="springContextUtil" class="com.spdb.speed4j.common.SpringContextUtil" />
<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="${redis.master.name}">property>
bean>
property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.sentinel_1.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.sentinel_1.port}">constructor-arg>
bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg index="0" value="${redis.sentinel_2.ip}">constructor-arg>
<constructor-arg index="1" value="${redis.sentinel_2.port}">constructor-arg>
bean>
set>
property>
bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="redisSentinelConfiguration">constructor-arg>
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<constructor-arg name="connectionFactory" ref="jedisConnectionFactory" />
bean>
<bean class="org.springframework.cache.support.SimpleCacheManager" id="cacheManager">
<property name="caches">
<set>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="default" />
bean>
<bean class="com.spdb.speed4j.cache.redis.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="commonCache" />
bean>
set>
property>
bean>
<bean id="redisCacheUtil" class="com.spdb.speed4j.cache.redis.RedisCacheUtil">
<property name="redisTemplate" ref="redisTemplate"/>
bean>
<bean name="userService" class="com.spdb.speed4j.test.redis.UserServiceImpl">bean>
beans>
单元测试类:
/**
* 测试:redis整合spring
* 测试缓存注解的功能
* @author T-zhangkf
*
*/
public class TestRedisCacheAnnotation {
ApplicationContext context = null;
UserServiceImpl userService = null;
@Before
public void setup() {
//context = new ClassPathXmlApplicationContext("springRedisContextTest.xml");
context = new ClassPathXmlApplicationContext("springRedisSentinelContextTest.xml");
userService = (UserServiceImpl) context.getBean("userService");
}
/**
* 测试@Cacheable注解
* 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
*/
@Test
public void testCacheable() {
Person person = (Person) userService.getPersonInfo("003");
System.out.println(person);
}
/**
* 测试@CachePut注解
* 和 @Cacheable类似,不同的是,它每次都会触发真实方法的调用
*/
@Test
public void testCachePut() {
Person person = (Person) userService.getPersonInfo1("003");
System.out.println(person);
}
/**
* 测试@CacheEvict注解
* 和 @Cacheable类似,不同的是,它每次都会触发真实方法的调用
*/
@Test
public void testEvict(){
userService.deletePerson("003");
}
}
注解功能与单机模式中的注解功能一样
执行testCacheable方法后查询缓存数据:
执行testEvict方法后查询缓存数据:
以10.129.41.86:6379为从redis服务
启动10.129.41.86:6379:
通过info replication命令可以看出86为从redis服务,它的主redis为10.129.41.85:6379
查看主redis服务(10.129.41.85:6379)的info replication,见下图:
85与86主从关系已经建立:
查看85,86缓存数据:
从86 redis服务器已经同步85主redis服务器中的数据。
此时关闭85中的redis服务,或者直接宕掉85主机。
关闭85redis服务:
等待30秒,查看86 redis服务的角色:
可以看出86已经变为主redis
运行1.6.3.1中的测试类:TestJedisSentinelUtil.java,执行查询缓存的方法: testObjFromJSON(),
控制台输出如下:
此时的master为86,仍然可以查询出缓存数据。
再次启动85中的redis服务,并查看info replication
此时85已经变为86的从机,85、86实现了主从的切换。
可以查看哨兵的日志文件信息,如下:
+swith-master mymaster 10.129.41.85 6379 10.129.41.86 6379表示主从进行了切换