在对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. OpenGL book list

      From: https://www.codeproject.com/Articles/771225/Learning-Modern-OpenGL   A little guide about mo ...

  2. java 上传图片压缩图片

    package com.bitspace.flame.util; import java.io.File; import java.awt.Image;import java.awt.image.Bu ...

  3. ObservableCollection<T> 类

    命名空间: System.Collections.ObjectModel程序集: System(在 System.dll 中)XAML 的 XMLNS: 未映射到 xmlns. 添加:using Sy ...

  4. 微信小程序 - 自定义switch切换(示例)

    点击下载:switch示例 ,适用于表单,官方switch 说明 .

  5. Spring MVC 实现文件的上传和下载

    前些天一位江苏经贸的学弟跟我留言问了我这样一个问题:“用什么技术来实现一般网页上文件的上传和下载?是框架还是Java中的IO流”.我回复他说:“使用Spring MVC框架可以做到这一点,因为Spri ...

  6. 利用Percona monitoring for zabbix监控MySQL

    系统要求 被监控的主机及mysql监控账号,zabbix agent 2.0或以上(最好与zabbix-server版本相同),php, php-mysql(php版本没有限制,经测试5.3是可以的. ...

  7. [nQSError: 37001]Could not connect to the Oracle BI Server Instance

    [nQSError: 37001]Could not connect to the Oracle BI Server Instance 使用本机的OBIEE Client 的Oracle BI管理工具 ...

  8. iOS下Symbol not found: ___sincosf_stret错误

    在调试一个带第三方库的多媒体应用中,发如今iOS6.1.2的版本号上必定crash.显示Symbol not found: ___sincosf_stret错误,这个错误非常少见. watermark ...

  9. jqPlot图表插件学习之阴阳烛图

    一.准备工作 首先我们需要到官网下载所需的文件: 官网下载(笔者选择的是jquery.jqplot.1.0.8r1250.zip这个版本) 然后读者需要根据自己的情况新建一个项目并且按照如下的方式加载 ...

  10. mysql查询高级用法

    联合查询 select a.* , b.adress , b.name from a , b where a.sbNo = b.cdid order by a.sbNo , a.STime selec ...