在对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. PDO 增删改查封装的类

    Selecting Data 你在mysql_*中是这样做的 <?php $result = mysql_query('SELECT * from table') or die(mysql_er ...

  2. Try Before Choosing

     Try Before Choosing Erik Doernenburg CREATing An AppliCATion REquiRES MAKing MAny dECiSionS. Some ...

  3. spring 判断非空提示断言

    org.springframework.util.Assert Assert.notNull(object, "Bean object must not be null");

  4. "___gxx_personality_v0", referenced from:

    这是因为里面有用到C++ 的一些东西.所以会出现这个问题 两种解决办法. 第一种.TARGETS -> Build Phases -> Link Binary With Libraries ...

  5. 【DB】部分MySQL操作记录

    工作中涉及到部分统计工作,恰好把之前的有些SQL再熟悉回顾一下. 一.涉及到时间统计部分: 求时间差: ), (SELECT CURDATE())) AS '试用时间'; ), (SELECT CUR ...

  6. 微信小程序 - 日期(起止)选择器组件

    2019-01-03 : 修复了日期day-1,新增了年月日(除去时分秒),删除了不必要的touchmove 新增: column: ""(年月日) 配置: pickerConfi ...

  7. 使用Spring框架入门三:基于XML配置的AOP的使用

    一.引入Jar包 <!--测试1使用--> <dependency> <groupId>org.springframework</groupId> &l ...

  8. windows快捷键补充?

      win + + 放大镜 win + r osk 虚拟键盘 win + r psr 自带屏幕录制   文章来源:刘俊涛的博客 欢迎关注,有问题一起学习欢迎留言.评论

  9. nodejs直接调用grunt(非调用批处理)

    在windows下,我们做js构建工作,都习惯安装grunt-cli,只需要命令行grunt...一切构建工作都自动完成了.这已经是很完美的情况了,不过最近要做一个服务器版的自动化构建系统,在node ...

  10. Centos 6下安装Oracle 11gR2

    一.安装环境 CentOS release 6.7 (Final) Oracle Database 11g Release 2 二.安装前准备 #修改主机名 修改/etc/sysconfig/netw ...