DATAREDIS-324 - Add Redis Sentinel support.
We’ve added RedisSentinelConfiguration holding required information for connecting to redis sentinels. This can be used to set up ConnectionFeactory for HA environments. **Using Jedis** Providing RedisSentinelConfiguration will force the JedisConnectionFactory to use JedisSentinelPool for managing resources. **Using Lettuce/JRedis/SRP** There’s currently no support for sentinel in those clients. **CI Build** We’ve added makefile to build and set up redis instances for testing sentinel support on travis-ci. There’s already a section for redis cluster. The cluster section is for whatever reason currently not working as the cluster nodes won’t start. This will be fixed when we add redis-cluster support. _side note:_ there’s an alternative fork of lettuce at mp911de/lettuce that already has sentinel support.
This commit is contained in:
committed by
Thomas Darimont
parent
c36a58da7f
commit
55ff415a71
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@ pom.xml
|
||||
.settings
|
||||
.idea
|
||||
out
|
||||
work
|
||||
*.rdb
|
||||
|
||||
@@ -3,6 +3,4 @@ jdk:
|
||||
- oraclejdk7
|
||||
- openjdk6
|
||||
- oraclejdk8
|
||||
services:
|
||||
- redis-server
|
||||
script: "gradle clean build -DrunLongTests=true"
|
||||
script: make test
|
||||
|
||||
99
Makefile
Normal file
99
Makefile
Normal file
@@ -0,0 +1,99 @@
|
||||
# Copyright 2011-2014 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
REDIS_VERSION:=2.8.13
|
||||
|
||||
#######
|
||||
# Redis
|
||||
#######
|
||||
.PRECIOUS: work/redis-%.conf
|
||||
|
||||
work/redis-%.conf:
|
||||
@mkdir -p $(@D)
|
||||
|
||||
echo port $* >> $@
|
||||
echo daemonize yes >> $@
|
||||
echo pidfile $(shell pwd)/work/redis-$*.pid >> $@
|
||||
echo logfile $(shell pwd)/work/redis-$*.log >> $@
|
||||
echo save \"\" >> $@
|
||||
echo slaveof 127.0.0.1 6379 >> $@
|
||||
|
||||
# Handled separately because it's the master and all others are slaves
|
||||
work/redis-6379.conf:
|
||||
@mkdir -p $(@D)
|
||||
|
||||
echo port 6379 >> $@
|
||||
echo daemonize yes >> $@
|
||||
echo pidfile $(shell pwd)/work/redis-6379.pid >> $@
|
||||
echo logfile $(shell pwd)/work/redis-6379.log >> $@
|
||||
echo save \"\" >> $@
|
||||
|
||||
work/redis-%.pid: work/redis-%.conf work/redis/bin/redis-server
|
||||
work/redis/bin/redis-server $<
|
||||
|
||||
redis-start: work/redis-6379.pid work/redis-6380.pid work/redis-6381.pid
|
||||
|
||||
redis-stop: stop-6379 stop-6380 stop-6381
|
||||
|
||||
##########
|
||||
# Sentinel
|
||||
##########
|
||||
.PRECIOUS: work/sentinel-%.conf
|
||||
|
||||
work/sentinel-%.conf:
|
||||
@mkdir -p $(@D)
|
||||
|
||||
echo port $* >> $@
|
||||
echo daemonize yes >> $@
|
||||
echo pidfile $(shell pwd)/work/sentinel-$*.pid >> $@
|
||||
echo logfile $(shell pwd)/work/sentinel-$*.log >> $@
|
||||
echo save \"\" >> $@
|
||||
echo sentinel monitor mymaster 127.0.0.1 6379 2 >> $@
|
||||
|
||||
work/sentinel-%.pid: work/sentinel-%.conf work/redis-6379.pid work/redis/bin/redis-server
|
||||
work/redis/bin/redis-server $< --sentinel
|
||||
|
||||
sentinel-start: work/sentinel-26379.pid work/sentinel-26380.pid work/sentinel-26381.pid
|
||||
|
||||
sentinel-stop: stop-26379 stop-26380 stop-26381
|
||||
|
||||
########
|
||||
# Global
|
||||
########
|
||||
clean:
|
||||
rm -rf work/*.conf work/*.log
|
||||
|
||||
clobber:
|
||||
rm -rf work
|
||||
|
||||
work/redis/bin/redis-cli work/redis/bin/redis-server:
|
||||
@mkdir -p work/redis
|
||||
|
||||
curl -sSL https://github.com/antirez/redis/archive/$(REDIS_VERSION).tar.gz | tar xzf - -C work
|
||||
$(MAKE) -C work/redis-$(REDIS_VERSION) -j
|
||||
$(MAKE) -C work/redis-$(REDIS_VERSION) PREFIX=$(shell pwd)/work/redis install
|
||||
rm -rf work/redis-$(REDIS_VERSION)
|
||||
|
||||
start: redis-start sentinel-start
|
||||
|
||||
stop-%: work/redis/bin/redis-cli
|
||||
-work/redis/bin/redis-cli -p $* shutdown
|
||||
|
||||
stop: redis-stop sentinel-stop
|
||||
|
||||
test:
|
||||
$(MAKE) start
|
||||
sleep 2
|
||||
$(PWD)/gradlew clean build -DrunLongTests=true
|
||||
$(MAKE) stop
|
||||
@@ -209,6 +209,21 @@
|
||||
|
||||
</section>
|
||||
|
||||
<section id="redis:sentinel">
|
||||
<title>Redis Sentinel Support</title>
|
||||
<para>For dealing with high available Redis there is support for <ulink url="http://redis.io/topics/sentinel">Redis Sentinel</ulink> using <classname>RedisSentinelConfiguration</classname>.</para>
|
||||
<note>Please note that currently only <ulink url="http://github.com/xetorthio/jedis">Jedis</ulink> supports Redis Sentinel.</note>
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public RedisConnectionFactory jedisConnectionFactory() {
|
||||
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
|
||||
.master("mymaster")
|
||||
.sentinel("127.0.0.1", 26379)
|
||||
.sentinel("127.0.0.1", 26380);
|
||||
return new JedisConnectionFactory(sentinelConfig);
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
|
||||
<section id="redis:template">
|
||||
<title>Working with Objects through <classname>RedisTemplate</classname></title>
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.4
|
||||
*/
|
||||
public interface NamedNode {
|
||||
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.4
|
||||
*/
|
||||
public class RedisNode {
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
public RedisNode(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
return host + ":" + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ObjectUtils.nullSafeHashCode(host);
|
||||
result = prime * result + ObjectUtils.nullSafeHashCode(port);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || !(obj instanceof RedisNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RedisNode other = (RedisNode) obj;
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.host, other.host)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.port, other.port)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configuration class used for setting up {@link RedisConnection} via {@link RedisConnectionFactory} using connecting
|
||||
* to <a href="http://redis.io/topics/sentinel">Redis Sentinel(s)</a>. Useful when setting up a high availability Redis
|
||||
* environment.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.4
|
||||
*/
|
||||
public class RedisSentinelConfiguration {
|
||||
|
||||
private NamedNode master;
|
||||
private Set<RedisNode> sentinels;
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisSentinelConfiguration}.
|
||||
*/
|
||||
public RedisSentinelConfiguration() {
|
||||
this.sentinels = new LinkedHashSet<RedisNode>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@literal Sentinels} to connect to.
|
||||
*
|
||||
* @param sentinels must not be {@literal null}.
|
||||
*/
|
||||
public void setSentinels(Iterable<RedisNode> sentinels) {
|
||||
|
||||
Assert.notNull(sentinels, "Cannot set sentinels to 'null'.");
|
||||
this.sentinels.clear();
|
||||
for (RedisNode sentinel : sentinels) {
|
||||
addSentinel(sentinel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Collections#unmodifiableSet(Set)} of {@literal Sentinels}.
|
||||
*
|
||||
* @return {@link Set} of sentinels. Never {@literal null}.
|
||||
*/
|
||||
public Set<RedisNode> getSentinels() {
|
||||
return Collections.unmodifiableSet(sentinels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sentinel.
|
||||
*
|
||||
* @param sentinel must not be {@literal null}.
|
||||
*/
|
||||
public void addSentinel(RedisNode sentinel) {
|
||||
|
||||
Assert.notNull(sentinel, "Sentinel must not be 'null'.");
|
||||
this.sentinels.add(sentinel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the master node via its name.
|
||||
*
|
||||
* @param name must not be {@literal null}.
|
||||
*/
|
||||
public void setMaster(final String name) {
|
||||
|
||||
Assert.notNull(name, "Name of sentinel master must not be null.");
|
||||
setMaster(new NamedNode() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the master.
|
||||
*
|
||||
* @param master must not be {@literal null}.
|
||||
*/
|
||||
public void setMaster(NamedNode master) {
|
||||
|
||||
Assert.notNull("Sentinel master node must not be 'null'.");
|
||||
this.master = master;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@literal Sentinel} master node.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public NamedNode getMaster() {
|
||||
return master;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #setMaster(String)
|
||||
* @param master
|
||||
* @return
|
||||
*/
|
||||
public RedisSentinelConfiguration master(String master) {
|
||||
this.setMaster(master);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #setMaster(NamedNode)
|
||||
* @param master
|
||||
* @return
|
||||
*/
|
||||
public RedisSentinelConfiguration master(NamedNode master) {
|
||||
this.setMaster(master);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #addSentinel(RedisNode)
|
||||
* @param sentinel
|
||||
* @return
|
||||
*/
|
||||
public RedisSentinelConfiguration sentinel(RedisNode sentinel) {
|
||||
this.addSentinel(sentinel);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #sentinel(RedisNode)
|
||||
* @param host
|
||||
* @param port
|
||||
* @return
|
||||
*/
|
||||
public RedisSentinelConfiguration sentinel(String host, Integer port) {
|
||||
return sentinel(new RedisNode(host, port));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
@@ -26,20 +31,26 @@ import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
|
||||
import org.springframework.data.redis.RedisConnectionFailureException;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisNode;
|
||||
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.JedisSentinelPool;
|
||||
import redis.clients.jedis.JedisShardInfo;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.clients.util.Pool;
|
||||
|
||||
/**
|
||||
* Connection factory creating <a href="http://github.com/xetorthio/jedis">Jedis</a> based connections.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
|
||||
|
||||
@@ -53,10 +64,11 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
private int timeout = Protocol.DEFAULT_TIMEOUT;
|
||||
private String password;
|
||||
private boolean usePool = true;
|
||||
private JedisPool pool = null;
|
||||
private Pool<Jedis> pool;
|
||||
private JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
private int dbIndex = 0;
|
||||
private boolean convertPipelineAndTxResults = true;
|
||||
private RedisSentinelConfiguration sentinelConfig;
|
||||
|
||||
/**
|
||||
* Constructs a new <code>JedisConnectionFactory</code> instance with default settings (default connection pooling, no
|
||||
@@ -80,7 +92,31 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
* @param poolConfig pool configuration
|
||||
*/
|
||||
public JedisConnectionFactory(JedisPoolConfig poolConfig) {
|
||||
this.poolConfig = poolConfig;
|
||||
this(null, poolConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link JedisConnectionFactory} instance using the given {@link JedisPoolConfig} applied to
|
||||
* {@link JedisSentinelPool}.
|
||||
*
|
||||
* @param sentinelConfig
|
||||
* @since 1.4
|
||||
*/
|
||||
public JedisConnectionFactory(RedisSentinelConfiguration sentinelConfig) {
|
||||
this(sentinelConfig, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link JedisConnectionFactory} instance using the given {@link JedisPoolConfig} applied to
|
||||
* {@link JedisSentinelPool}.
|
||||
*
|
||||
* @param sentinelConfig
|
||||
* @param poolConfig pool configuration. Defaulted to new instance if {@literal null}.
|
||||
* @since 1.4
|
||||
*/
|
||||
public JedisConnectionFactory(RedisSentinelConfiguration sentinelConfig, JedisPoolConfig poolConfig) {
|
||||
this.sentinelConfig = sentinelConfig;
|
||||
this.poolConfig = poolConfig != null ? poolConfig : new JedisPoolConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +150,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
return connection;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
if (shardInfo == null) {
|
||||
shardInfo = new JedisShardInfo(hostName, port);
|
||||
@@ -128,11 +168,46 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
}
|
||||
|
||||
if (usePool) {
|
||||
pool = new JedisPool(poolConfig, shardInfo.getHost(), shardInfo.getPort(), shardInfo.getTimeout(),
|
||||
shardInfo.getPassword());
|
||||
this.pool = createPool();
|
||||
}
|
||||
}
|
||||
|
||||
private Pool<Jedis> createPool() {
|
||||
|
||||
if (isRedisSentinelAware()) {
|
||||
return createRedisSentinelPool(this.sentinelConfig);
|
||||
}
|
||||
return createRedisPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link JedisSentinelPool}.
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
* @since 1.4
|
||||
*/
|
||||
protected Pool<Jedis> createRedisSentinelPool(RedisSentinelConfiguration config) {
|
||||
return new JedisSentinelPool(config.getMaster().getName(), convertToJedisSentinelSet(config.getSentinels()),
|
||||
getPoolConfig() != null ? getPoolConfig() : new JedisPoolConfig(), getShardInfo().getTimeout(), getShardInfo()
|
||||
.getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link JedisPool}.
|
||||
*
|
||||
* @return
|
||||
* @since 1.4
|
||||
*/
|
||||
protected Pool<Jedis> createRedisPool() {
|
||||
return new JedisPool(getPoolConfig(), getShardInfo().getHost(), getShardInfo().getPort(), getShardInfo()
|
||||
.getTimeout(), getShardInfo().getPassword());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() {
|
||||
if (usePool && pool != null) {
|
||||
try {
|
||||
@@ -144,6 +219,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisConnectionFactory#getConnection()
|
||||
*/
|
||||
public JedisConnection getConnection() {
|
||||
Jedis jedis = fetchJedisConnector();
|
||||
JedisConnection connection = (usePool ? new JedisConnection(jedis, pool, dbIndex) : new JedisConnection(jedis,
|
||||
@@ -152,6 +231,10 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
return postProcessConnection(connection);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
|
||||
*/
|
||||
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||
return EXCEPTION_TRANSLATION.translate(ex);
|
||||
}
|
||||
@@ -321,4 +404,28 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
|
||||
public void setConvertPipelineAndTxResults(boolean convertPipelineAndTxResults) {
|
||||
this.convertPipelineAndTxResults = convertPipelineAndTxResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true when {@link RedisSentinelConfiguration} is present.
|
||||
* @since 1.4
|
||||
*/
|
||||
public boolean isRedisSentinelAware() {
|
||||
return sentinelConfig != null;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (node != null) {
|
||||
convertedNodes.add(node.asString());
|
||||
}
|
||||
}
|
||||
return convertedNodes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
|
||||
import org.springframework.data.redis.test.util.RedisSentinelRule;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class JedisConnectionFactoryTests {
|
||||
|
||||
private static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster")
|
||||
.sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380);
|
||||
private JedisConnectionFactory factory;
|
||||
|
||||
public @Rule RedisSentinelRule sentinelRule = RedisSentinelRule.forConfig(SENTINEL_CONFIG).oneActive();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
factory = new JedisConnectionFactory(SENTINEL_CONFIG);
|
||||
factory.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
factory.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-324
|
||||
*/
|
||||
@Test
|
||||
public void shouldSendCommandCorrectlyViaConnectionFactoryUsingSentinel() {
|
||||
assertThat(factory.getConnection().ping(), equalTo("PONG"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
|
||||
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class JedisConnectionFactoryUnitTests {
|
||||
|
||||
private JedisConnectionFactory connectionFactory;
|
||||
|
||||
private static final RedisSentinelConfiguration SINGLE_SENTINEL_CONFIG = new RedisSentinelConfiguration().master(
|
||||
"mymaster").sentinel("127.0.0.1", 26379);
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-324
|
||||
*/
|
||||
@Test
|
||||
public void shouldInitSentinelPoolWhenSentinelConfigPresent() {
|
||||
|
||||
connectionFactory = initSpyedConnectionFactory(SINGLE_SENTINEL_CONFIG, new JedisPoolConfig());
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
verify(connectionFactory, times(1)).createRedisSentinelPool(Matchers.eq(SINGLE_SENTINEL_CONFIG));
|
||||
verify(connectionFactory, never()).createRedisPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-324
|
||||
*/
|
||||
@Test
|
||||
public void shouldInitJedisPoolWhenNoSentinelConfigPresent() {
|
||||
|
||||
connectionFactory = initSpyedConnectionFactory(null, new JedisPoolConfig());
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
verify(connectionFactory, times(1)).createRedisPool();
|
||||
verify(connectionFactory, never()).createRedisSentinelPool(Matchers.any(RedisSentinelConfiguration.class));
|
||||
}
|
||||
|
||||
private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfig,
|
||||
JedisPoolConfig poolConfig) {
|
||||
|
||||
// we have to use a spy here as jedis would start connecting to redis sentinels when the pool is created.
|
||||
JedisConnectionFactory factorySpy = spy(new JedisConnectionFactory(sentinelConfig, poolConfig));
|
||||
doReturn(null).when(factorySpy).createRedisSentinelPool(Matchers.any(RedisSentinelConfiguration.class));
|
||||
doReturn(null).when(factorySpy).createRedisPool();
|
||||
return factorySpy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.AbstractConnectionIntegrationTests;
|
||||
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import org.springframework.data.redis.test.util.RedisSentinelRule;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class JedisSentinelIntegrationTests extends AbstractConnectionIntegrationTests {
|
||||
|
||||
private static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster")
|
||||
.sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380);
|
||||
|
||||
public @Rule RedisSentinelRule sentinelRule = RedisSentinelRule.forConfig(SENTINEL_CONFIG).oneActive();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(SENTINEL_CONFIG);
|
||||
jedisConnectionFactory.afterPropertiesSet();
|
||||
connectionFactory = jedisConnectionFactory;
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
super.tearDown();
|
||||
((JedisConnectionFactory) connectionFactory).destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testScriptKill() throws Exception {
|
||||
super.testScriptKill();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void testEvalReturnSingleError() {
|
||||
connection.eval("return redis.call('expire','foo')", ReturnType.BOOLEAN, 0);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void testEvalArrayScriptError() {
|
||||
super.testEvalArrayScriptError();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void testEvalShaNotFound() {
|
||||
connection.evalSha("somefakesha", ReturnType.VALUE, 2, "key1", "key2");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void testEvalShaArrayError() {
|
||||
super.testEvalShaArrayError();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void testRestoreBadData() {
|
||||
super.testRestoreBadData();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void testRestoreExistingKey() {
|
||||
super.testRestoreExistingKey();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void testExecWithoutMulti() {
|
||||
super.testExecWithoutMulti();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void testErrorInTx() {
|
||||
super.testErrorInTx();
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.redis.listener;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.hasItems;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -33,6 +31,7 @@ import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
@@ -44,6 +43,7 @@ import org.springframework.data.redis.ObjectFactory;
|
||||
import org.springframework.data.redis.RedisTestProfileValueSource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
import org.springframework.data.redis.test.util.RedisSentinelRule;
|
||||
|
||||
/**
|
||||
* Base test class for PubSub integration tests
|
||||
@@ -54,6 +54,8 @@ import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
@RunWith(Parameterized.class)
|
||||
public class PubSubTests<T> {
|
||||
|
||||
public @Rule RedisSentinelRule sentinelRule = RedisSentinelRule.withDefaultConfig().sentinelsDisabled();
|
||||
|
||||
private static final String CHANNEL = "pubsub::test";
|
||||
|
||||
protected RedisMessageListenerContainer container;
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.test.util;
|
||||
|
||||
import org.junit.internal.AssumptionViolatedException;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.data.redis.connection.RedisNode;
|
||||
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class RedisSentinelRule implements TestRule {
|
||||
|
||||
enum VerificationMode {
|
||||
ALL_ACTIVE, ONE_ACTIVE, NO_SENTINEL
|
||||
}
|
||||
|
||||
private static final RedisSentinelConfiguration DEFAULT_SENTINEL_CONFIG = new RedisSentinelConfiguration()
|
||||
.master("mymaster").sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380).sentinel("127.0.0.1", 26381);
|
||||
|
||||
private RedisSentinelConfiguration sentinelConfig;
|
||||
private VerificationMode mode;
|
||||
|
||||
protected RedisSentinelRule(RedisSentinelConfiguration config) {
|
||||
this(config, VerificationMode.ALL_ACTIVE);
|
||||
}
|
||||
|
||||
protected RedisSentinelRule(RedisSentinelConfiguration config, VerificationMode mode) {
|
||||
|
||||
this.sentinelConfig = config;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link RedisSentinelRule} for given {@link RedisSentinelConfiguration}.
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public static RedisSentinelRule forConfig(RedisSentinelConfiguration config) {
|
||||
return new RedisSentinelRule(config != null ? config : DEFAULT_SENTINEL_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link RedisSentinelRule} using default configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static RedisSentinelRule withDefaultConfig() {
|
||||
return new RedisSentinelRule(DEFAULT_SENTINEL_CONFIG);
|
||||
}
|
||||
|
||||
public RedisSentinelRule sentinelsDisabled() {
|
||||
|
||||
this.mode = VerificationMode.NO_SENTINEL;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies all {@literal Sentinel} nodes are available.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public RedisSentinelRule allActive() {
|
||||
|
||||
this.mode = VerificationMode.ALL_ACTIVE;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies at least one {@literal Sentinel} node is available.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public RedisSentinelRule oneActive() {
|
||||
|
||||
this.mode = VerificationMode.ONE_ACTIVE;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement base, Description description) {
|
||||
|
||||
return new Statement() {
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
verify();
|
||||
base.evaluate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void verify() {
|
||||
|
||||
int failed = 0;
|
||||
for (RedisNode node : sentinelConfig.getSentinels()) {
|
||||
if (!isAvailable(node)) {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
if (VerificationMode.ALL_ACTIVE.equals(mode)) {
|
||||
throw new AssumptionViolatedException(String.format(
|
||||
"Expected all Redis Sentinels to respone but %s of %s did not responde", failed, sentinelConfig
|
||||
.getSentinels().size()));
|
||||
}
|
||||
|
||||
if (VerificationMode.ONE_ACTIVE.equals(mode) && sentinelConfig.getSentinels().size() - 1 < failed) {
|
||||
throw new AssumptionViolatedException(
|
||||
"Expected at least one sentinel to respond but it seems all are offline - Game Over!");
|
||||
}
|
||||
}
|
||||
|
||||
if (VerificationMode.NO_SENTINEL.equals(mode) && failed != sentinelConfig.getSentinels().size()) {
|
||||
throw new AssumptionViolatedException(String.format(
|
||||
"Expected to have no sentinels online but found that %s are still alive.", (sentinelConfig.getSentinels()
|
||||
.size() - failed)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAvailable(RedisNode node) {
|
||||
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
|
||||
jedis = new Jedis(node.getHost(), node.getPort());
|
||||
jedis.connect();
|
||||
jedis.ping();
|
||||
} catch (Exception e) {
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
|
||||
if (jedis != null) {
|
||||
try {
|
||||
|
||||
jedis.disconnect();
|
||||
jedis.close();
|
||||
} catch (Exception e) {
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user