在对Redis进行性能优化时,一直想对Redis进行读写分离。但由于项目底层采用spring-data-redis对redis进行操作,参考spring官网却发现spring-data-redis目前(1.7.0.RELEASE)及以前的版本并不支持读写分离。

 一、源码分析

spring-data-redis中关于JedisConnectionFactory的配置如下:

<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"> <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="mymaster"/>
<constructor-arg name="host" value="${redis.master.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.master.port}"></constructor-arg>
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel1.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel1.port}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel2.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel2.port}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel3.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel3.port}"></constructor-arg>
</bean>
</set>
</property>
</bean> <!-- 连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxActive}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<!-- <property name="maxWait" value="${redis.pool.maxWait}" /> -->
<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">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<constructor-arg ref="redisSentinelConfiguration"/>
</bean> <bean id="stringRedisTemplate"
class="org.springframework.data.redis.core.StringRedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"/>
</beans>

查看JedisConnectionFactory源码发现pool是Pool<Jedis>,而不是Pool<ShardedJedis>。因此我猜目前spring data redis是做不了读写分离的,stringRedisTemplate读写操作都是在master上。

二、读写分离改造

参考sentinel的主备选举机制对spring-data-redis的相关配置进行如下改造:

读写分离原理如下所示:

(1)Spring配置

<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"> <context:property-placeholder location="classpath:redis/redis.properties" ignore-unresolvable="true" /> <bean id="poolConfig" class="redis.client.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBuorrow" value="${redis.testOnBorrow}" />
</bean> <bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<constructor-arg index="">
<bean class="org.springframework.core.env.MapPropertySource">
<constructor-arg index="" value="RedisSentinelConffiguration" />
<constructor-arg index="">
<map>
<entry key="spring.redis.sentinel.master" value="${redis.sentinel.master}"></entry>
<entry key="spring.redis.sentinel.nodes" value="${redis.sentinel.nodes"}> </entry>
</map>
</constructor-arg>
</bean>
</constructor-arg>
</bean> <bean id="connectionFactory" class="com.test.data.redis.connection.TWJedisConnectionFactory">
<constructor-arg index="" ref="redisSentinelConfiguration" />
<constructor-arg index="" ref="poolConfig" />
<property name="password" value="${redis.pass}" />
<property name="databse" value="" />
</bean> <bean id="readOnlyConnectionFactory" class="com.test.data.redis.connection.TWReadOnlyJedisConnectionFactory">
<constructor-arg index="" ref="redisSentinelConfiguration" />
<constructor-arg index="" ref="poolConfig" />
<property name="password" value="${redis.pass}" />
<property name="databse" value="" />
</bean> <bean id="redisTemplate" class="com.test.data.redis.core.TWRedisTemplate"
<property name="connectionFactory" ref="connectionFactory"/>
<property name="readOnlyConnectionFactory" ref="readOnlyConnectionFactory" />
</bean>
</beans>

(2)TWJedisConnectionFactory

public class TWJedisConnectionFactory extends JedisConnectionFactory {

  public TWJedisConnectionFactory() {
super();
} public TWJedisConnectionFactory(JedisShardInfo shardInfo) {
super(shardInfo);
} public TWJedisConnectionFactory(JedisPoolConfig poolConfig){
this((RedisSentinelConfiguration)null,poolConfig);
} public TWJedisConnectionFactory(RedisSentinelConfiguration sentinelConfig){
this(sentinelConfig,null);
} public TWJedisConnectionFactory(RedisSentinelConfiguration sentinelConfig,JedisPoolConfig poolConfig){
super(sentinelConfig,poolConfig);
} public TWJedisConnectionFactory(RedisClusterConfiguration clusterConfig){
super(clusterConfig);
} public TWJedisConnectionFactory(RedisClusterConfiguration clusterConfig,JedisPoolConfig poolConfig){
super(clusterConfig,poolConfig);
} @Override
public void afterPropertiesSet() {
try {
super.afterPropertiesSet();
}catch(Exception e) {
}
}
}

(3)TWReadOnlyJedisConnectionFactory

public class TWReadOnlyJedisConnectionFactory extends JedisConnectionFactory {

  private static final Method GET_TIMEOUT_METHOD;

  static {
Method getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class,"getTimeout");
if(null == getTimeoutMethodCandidate) {
getTimeoutMethodCandidate = ReflectionUtils.findMethod(JedisShardInfo.class,"getTimeout");
}
GET_TIMEOUT_METHOD=getTimeoutMethodCandidate;
} public TWReadOnlyJedisConnectionFactory() {
super();
} public TWReadOnlyJedisConnectionFactory(JedisShardInfo shardInfo) {
super(shardInfo);
} public TWReadOnlyJedisConnectionFactory(JedisPoolConfig poolConfig){
this((RedisSentinelConfiguration)null,poolConfig);
} public TWReadOnlyJedisConnectionFactory(RedisSentinelConfiguration sentinelConfig){
this(sentinelConfig,null);
} public TWReadOnlyJedisConnectionFactory(RedisSentinelConfiguration sentinelConfig,JedisPoolConfig poolConfig){
super(sentinelConfig,poolConfig);
} public TWReadOnlyJedisConnectionFactory(RedisClusterConfiguration clusterConfig){
super(clusterConfig);
} public TWJedisConnectionFactory(RedisClusterConfiguration clusterConfig,JedisPoolConfig poolConfig){
super(clusterConfig,poolConfig);
} @Override
public void afterPropertiesSet() {
try {
super.afterPropertiesSet();
}catch(Exception e) {
}
} protected Pool<Jedis> createRedisSentinelPool(RedisSentinelConfiguration config){
return new JedisSentinelSlavePool(config.getMaster().getName(),
convertToJedisSentinelSet(config.getSentinels()),
getPoolConfig()!=null?getPoolConfig():new JedisPoolConfig(),
getTimeOutFrom(getShardInfo()),
getShardInfo().getPassword());
} private Set<String> convertToJedisSentinelSet(Collection<RedisNode> nodes) {
if(CollectionUtils.isEmpty(nodes)) {
return Collections.emptySet();
}
Set<String> convertedNodes = new LinkedHashSet<String>(nodes.size());
for(RedisNode node : nodes)
{
convertedNodes.add(node.asString());
}
return convertedNodes;
} private int getTimeOutFrom(JedisShardInfo shardInfo){
return (Integer) ReflectionUtils.invokeMethod(GET_TIMEOUT_METHOD,shardInfo);
} }

(4)TWRedisTemplate

public class TWRedisTemplate extends RedisTemplate {
public static enum Operation {read,write}; private static ThreadLocal<Operation> threadLocal = new ThreadLocal<TWRedisTemplate.Operation>(); private RedisConnectionFactory readOnlyConnectionFactory; public void setReadOnlyConnectionFactory(RedisConnectionFactory readOnlyConnectionFactory) {
this.readOnlyConnectionFactory = readOnlyConnectionFactory;
} public TWRedisTemplate() {
} @Override
public void afterPropertiesSet() {
try {
super.afterPropertiesSet();
}catch(Exception e) {
}
} public RedisConnectionFactory getConnectionFactory() {
Operation operation = threadLocal.get();
if(operation!=null && operation==Operation.read && readOnlyConnectionFactory!=null) {
return readOnlyConnectionFactory;
}
return super.getConnectionFactory();
} public void setOperation(Operation operation) {
threadLocal.set(operation);
}
}

(5)JedisReadOnlyPool

public class JedisReadOnlyPool extends Pool<Jedis> {
protected GenericObjectPoolConfig poolConfig;
protected int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
protected int soTimeout = Protocol.DEFAULT_TIMEOUT;
protected String password;
protected int database = Protocol.DEFAULT_TIMEOUT;
private volatile JedisFactory factory;
private volatile HostAndPort currentHostMaster; public JedisReadOnlyPool(final HostAndPort master, final GenericObjectPoolConfig poolConfig,
final int connectionTimeOut, final int soTimeout, final String password, final int databse,
final String clientName) {
this.poolConfig = poolConfig;
this.connectionTimeout = connectionTimeout;
this.soTimeout = soTimeout;
this.password = password;
this.database = database;
initPool(master);
}
public void destory() {
super.destroy();
} public void initPool(HostAndPort master) {
if(!master.equals(currentHostMaster) {
currentHostMaster = master;
if(factory == null) {
factory = new JedisFactory(currentHostMaster.getHost(),
currentHostMaster.getPort(),
connectionTimeout,
soTimeout,
password,
database,
null);
initPool(poolConfig,factory);
} else {
factory.setHostAndPort(currentHostMaster);
internalPool.clear();
}
log.info("Created JedisPool to master at" + master);
}
} private HostAndPort toHostAndPort(List<String> getMasterAddByNameResult) {
String host = getMasterAddrByNameResult.get();
int port = Integer.parseInt(getMasterAddrByNameResult.get());
return new HostAndPort(host,port);
} @Override
public Jedis getResource() {
while(true) {
Jedis jedis = super.getResource();
jedis.setDataSource(this);
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(),jedis.getClient().getPort());
if(currentHostMaster.equals(connection)) {
return jedis;
} else {
returnBrokenResource(jedis);
}
}
} @Override
@Deprecated
public void returnBrokenResource(final Jedis resource) {
if(resource!=null) {
returnBrokenResourceObject(resource);
}
} @Override
@Deprecated
public void returnResource(final Jedis resource) {
if(resource !=null ){
resource.resetState();
returnResourceObject(resource);
}
} }

(6)JedisSentinelSlavesPool

(7)RedisCacheService

在具体使用缓存服务时,在读、写缓存时分别加上其类型

...
@Autowired
private TWRedisTemplate twRedisTemplate; public List<String> getCachedByPredis(final String prefix) {
twRedisTemplate.setOperation(Operation.read);
...
finally {
destory();
}
} public void hset(xxx) {
twRedisTemplate.setOperation(Operation.write);
...
finally {
destory();
} } /**释放资源**/
private void destroy() {
twRedisTemplate.setOperation(null);
}

spring-data-redis读写分离的更多相关文章

  1. Spring Data Redis 详解及实战一文搞定

    SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能.它提供了与商店互动的低级别和高级别抽象,使用户免受 ...

  2. Spring Data Redis入门示例:数据序列化 (四)

    概述 RedisTemplate默认使用的是基于JDK的序列化器,所以存储在Redis的数据如果不经过相应的反序列化,看到的结果是这个样子的: 可以看到,出现了乱码,在程序层面上,不会影响程序的运行, ...

  3. spring+mybatis实现读写分离

    springmore-core spring+ibatis实现读写分离 特点 无缝结合spring+ibatis,对于程序员来说,是透明的 除了修改配置信息之外,程序的代码不需要修改任何东西 支持sp ...

  4. spring data redis RedisTemplate操作redis相关用法

    http://blog.mkfree.com/posts/515835d1975a30cc561dc35d spring-data-redis API:http://docs.spring.io/sp ...

  5. spring mvc Spring Data Redis RedisTemplate [转]

    http://maven.springframework.org/release/org/springframework/data/spring-data-redis/(spring-data包下载) ...

  6. Spring Data Redis简介以及项目Demo,RedisTemplate和 Serializer详解

    一.概念简介: Redis: Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写,详细的信息在Redis官网上面有,因为我自己通过google等各种渠道去学习Redis, ...

  7. Spring Data Redis—Pub/Sub(附Web项目源码)

    一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...

  8. Spring data redis的一个bug

    起因 前两天上线了一个新功能,导致线上业务的缓存总是无法更新,报错也是非常奇怪,redis.clients.jedis.exceptions.JedisConnectionException: Unk ...

  9. Spring Data Redis—Pub/Sub(附Web项目源码) (转)

    一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...

  10. spring data redis 理解

    前言 Spring Data Redis project,应用了Spring概念来开发使用键值形式的数据存储的解决方案.我们(官方)提供了一个 "template" ,这是一个高级 ...

随机推荐

  1. 一个ActiveX control的创建过程

    创建 根据这篇文章的介绍:http://www.cnblogs.com/time-is-life/p/6354152.html 来创建,里面包含了创建的基本过程以及属性事件方法的使用. 使用: 参考文 ...

  2. 【python】insertDB1.02

    #------------------------------------------------------------------------------------ # insertDB1.02 ...

  3. MySQL数据库设置远程访问权限方法小结

    http://www.jb51.net/article/42441.htm MySQL基础知识第一期,如何远程访问MySQL数据库设置权限方法总结,讨论访问单个数据库,全部数据库,指定用户访问,设置访 ...

  4. linux:C++实现ping

    转载自: //http://blog.chinaunix.net/uid-25909722-id-2762079.html #include <stdio.h> #include < ...

  5. Linux 远程和本地的一些解决方式

     有的小伙伴想Linux 远程登录 两台机器同一时候root登录.事实上能够同一时候多个用户的. Linux是多用户的多任务系统,能够同一时候多个用户登录到系统,也能够一个用户通过不同终端登录到一个系 ...

  6. Skyline开发4-IProject接口

    IProject接口可以访问工程设置和打开保存工程的基本方法. 属性 FileVersion:返回 ITEVersionInfo.表示当前运行的TerraExplorer的版本,可通过ITEVersi ...

  7. SHELL pv uv 统计事例

    #!/bin/sh #statistics newplive logs SOURCELOGS=$ ];then echo echo "please input file!" ech ...

  8. WIP 003 - Create page with a tablewalker

    Need ability to delete records by click the trash can Need ability to add new records to database an ...

  9. ORACLE 定时运行存储过程经常使用时间间隔

    君阁-笔记 1.建立存储过程.形如: create or replace procedure deleteTableProcedures is begin   delete POST_DAY; del ...

  10. SQL基础试题

    第3章  关系数据库标准语言SQL 一.选择题 1.SQL语言是                    的语言,易学习. A.过程化    B.非过程化    C.格式化    D.导航式    答案 ...