spring-data-redis读写分离
在对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读写分离的更多相关文章
- Spring Data Redis 详解及实战一文搞定
SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能.它提供了与商店互动的低级别和高级别抽象,使用户免受 ...
- Spring Data Redis入门示例:数据序列化 (四)
概述 RedisTemplate默认使用的是基于JDK的序列化器,所以存储在Redis的数据如果不经过相应的反序列化,看到的结果是这个样子的: 可以看到,出现了乱码,在程序层面上,不会影响程序的运行, ...
- spring+mybatis实现读写分离
springmore-core spring+ibatis实现读写分离 特点 无缝结合spring+ibatis,对于程序员来说,是透明的 除了修改配置信息之外,程序的代码不需要修改任何东西 支持sp ...
- spring data redis RedisTemplate操作redis相关用法
http://blog.mkfree.com/posts/515835d1975a30cc561dc35d spring-data-redis API:http://docs.spring.io/sp ...
- spring mvc Spring Data Redis RedisTemplate [转]
http://maven.springframework.org/release/org/springframework/data/spring-data-redis/(spring-data包下载) ...
- Spring Data Redis简介以及项目Demo,RedisTemplate和 Serializer详解
一.概念简介: Redis: Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写,详细的信息在Redis官网上面有,因为我自己通过google等各种渠道去学习Redis, ...
- Spring Data Redis—Pub/Sub(附Web项目源码)
一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...
- Spring data redis的一个bug
起因 前两天上线了一个新功能,导致线上业务的缓存总是无法更新,报错也是非常奇怪,redis.clients.jedis.exceptions.JedisConnectionException: Unk ...
- Spring Data Redis—Pub/Sub(附Web项目源码) (转)
一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...
- spring data redis 理解
前言 Spring Data Redis project,应用了Spring概念来开发使用键值形式的数据存储的解决方案.我们(官方)提供了一个 "template" ,这是一个高级 ...
随机推荐
- ASP入门(十一)-Session小案例
一般来说,在实际开发中,对于 Session 对象使用最多的就是用户登录部分了,这个案例将简单模拟一个用户登录表单.用户是否登录的判断以及用户退出的一系列功能,它一共分了以下几个页面. Login.a ...
- php代码收集
thinkphp <?php class HekaAction extends BaseAction{ public function index(){ require_once './wang ...
- Android 八款开源 Android 游戏引擎
原文地址 本文内容 Angle Rokon LGame AndEngine libgdx jPCT Alien3d Catcake 最近无意间看到一篇关于 Android 搜索引擎的文章,于是搜索了, ...
- centos下mysql自动备份
#!/bin/bashdb_user="root"db_passwd="123456"db_name="test_db"#进入备份目录将之前 ...
- C++中public、protected、private的差别
第一: private,public,protected的訪问范围: private: 仅仅能由该类中的函数.其友元函数訪问,不能被不论什么其它訪问.该类的对象也不能訪问. protected: ...
- gcc 0长数组学习
首先,我们要知道,0长度的数组在ISO C和C++的规格说明书中是不允许的.这也就是为什么在VC++2012下编译你会得到一个警告:“warning C4200: 使用了非标准扩展 : 结构/联合中的 ...
- [Python]将Excel文件中的数据导入MySQL
Github Link 需求 现有2000+文件夹,每个文件夹下有若干excel文件,现在要将这些excel文件中的数据导入mysql. 每个excel文件的第一行是无效数据. 除了excel文件中已 ...
- JAVA设计模式——第 3 章 单例模式【Singleton Pattern】(转)
这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,单例就是单一.独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?我们举个比较难复制的 ...
- 〖Linux〗gun screen 配置文件
screen 默认使用 bash shell,一般情况下screen只用于android build,tmux则是我的日常使用多终端管理工具. # Default Shell shell " ...
- centos7下安装nginx(转)
一.下载nginx安装包 访问地址:https://nginx.org 根据自己的需求下载合适的安装包 二.安装gcc gcc是用来编译下载下来的nginx源码 yum install gcc-c++ ...