#3 - Added samples for Spring Data Redis.

We added basic samples showing KEYS and SCAN command as well as configuration options for Redis Sentinel.

Original pull request: #9.
This commit is contained in:
Christoph Strobl
2014-08-27 13:01:21 +02:00
committed by Oliver Gierke
parent 7cfd45920f
commit 91565e3e21
15 changed files with 690 additions and 0 deletions

View File

@@ -25,6 +25,11 @@ We have separate folders for the samples of individual modules:
* `starbucks` - A sample REST web-service built with Spring Data REST and MongoDB.
* `multi-store` - A sample REST web-service based on both Spring Data JPA and Spring Data MongoDB.
## Spring Data Redis
* `example` - Example for basic Sprign Data Redis setup.
* `cluster-sentinel` - Example for Redis cluster and Sentinel support.
## Miscellaneous
* `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in one project.

View File

@@ -20,6 +20,7 @@
<module>jpa</module>
<module>mongodb</module>
<module>rest</module>
<module>redis</module>
</modules>
<properties>

View File

@@ -0,0 +1,28 @@
# Spring Data Redis - Sentinel and Cluster Examples
This project contains samples of Sentinel and CLuster specific features of Spring Data Redis.
## Support for Sentinel
```java
@Configuration
public class RedisSentinelApplicationConfig {
static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster") //
.sentinel("localhost", 26379) //
.sentinel("localhost", 26380) //
.sentinel("localhost", 26381);
@Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(sentinelConfig());
}
@Bean
public RedisSentinelConfiguration sentinelConfig() {
return SENTINEL_CONFIG;
}
}
```

View File

@@ -0,0 +1,29 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-redis-cluster-sentinel-example</artifactId>
<name>Spring Data Redis - Cluster/Sentinel Example</name>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-redis-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-redis-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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 example.springdata.redis.sentinel;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StopWatch;
/**
* @author Christoph Strobl
*/
@Configuration
public class RedisSentinelApplicationConfig {
static final RedisSentinelConfiguration SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster") //
.sentinel("localhost", 26379) //
.sentinel("localhost", 26380) //
.sentinel("localhost", 26381);
@Autowired RedisConnectionFactory factory;
public static void main(String[] args) throws Exception {
ApplicationContext context = SpringApplication.run(RedisSentinelApplicationConfig.class, args);
RedisConnectionFactory factory = context.getBean(RedisConnectionFactory.class);
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
template.afterPropertiesSet();
template.opsForValue().set("loop-forever", "0");
StopWatch stopWatch = new StopWatch();
while (true) {
try {
String value = "IT:= " + template.opsForValue().increment("loop-forever", 1);
printBackFromErrorStateInfoIfStopWatchIsRunning(stopWatch);
System.out.println(value);
} catch (RuntimeException e) {
System.err.println(e.getCause().getMessage());
startStopWatchIfNotRunning(stopWatch);
}
Thread.sleep(1000);
}
}
@Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(sentinelConfig());
}
@Bean
public RedisSentinelConfiguration sentinelConfig() {
return SENTINEL_CONFIG;
}
/**
* Clear database before shut down.
*/
@PreDestroy
public void flushTestDb() {
factory.getConnection().flushDb();
}
private static void startStopWatchIfNotRunning(StopWatch stopWatch) {
if (!stopWatch.isRunning()) {
stopWatch.start();
}
}
private static void printBackFromErrorStateInfoIfStopWatchIsRunning(StopWatch stopWatch) {
if (stopWatch.isRunning()) {
stopWatch.stop();
System.err.println("INFO: Recovered after: " + stopWatch.getLastTaskInfo().getTimeSeconds());
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>

4
redis/example/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Spring Data Redis - Examples
This project contains samples of specific features of Spring Data Redis.

31
redis/example/pom.xml Normal file
View File

@@ -0,0 +1,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-redis-example</artifactId>
<name>Spring Data Redis - Example</name>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-redis-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-redis-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
/*
* 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 example.springdata.redis;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/**
* @author Christoph Strobl
*/
@Configuration
@EnableAutoConfiguration
public class RedisTestConfiguration {
@Autowired RedisConnectionFactory factory;
/**
* Clear database before shut down.
*/
@PreDestroy
public void flushTestDb() {
factory.getConnection().flushDb();
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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 example.springdata.redis.commands;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import example.springdata.redis.RedisTestConfiguration;
import example.springdata.redis.test.util.RequiresRedisServer;
/**
* Show usage of operations on redis keys using low level API provided by {@link RedisConnection}.
*
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RedisTestConfiguration.class })
public class KeyOperationsTests {
// we only want to run this tests when redis is up an running
public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost();
private static final String PREFIX = KeyOperationsTests.class.getSimpleName();
private static final String KEY_PATTERN = PREFIX + "*";
@Autowired RedisConnectionFactory connectionFactory;
private RedisConnection connection;
private RedisSerializer<String> serializer = new StringRedisSerializer();
@Before
public void setUp() {
this.connection = connectionFactory.getConnection();
}
/**
* Uses {@code KEYS} command for loading all matching keys. <br />
* Note that {@code KEYS} is a blocking command that potentially might affect other operations execution time. <br />
* All keys will be loaded within <strong>one single</strong> operation.
*/
@Test
public void iterateOverKeysMatchingPrefixUsingKeysCommand() {
generateRandomKeys(1000);
Set<byte[]> keys = this.connection.keys(serializer.serialize(KEY_PATTERN));
printKeys(keys.iterator());
}
/**
* Uses {@code SCAN} command for loading all matching keys. <br />
* {@code SCAN} uses a cursor on server side returning only a subset of the available data with the possibility to
* ripple load further elements using the cursors position. <br />
* All keys will be loaded using <strong>multiple</strong> operations.
*/
@Test
public void iterateOverKeysMatchingPrefixUsingScanCommand() {
generateRandomKeys(1000);
Cursor<byte[]> cursor = this.connection.scan(ScanOptions.scanOptions().match(KEY_PATTERN).build());
printKeys(cursor);
}
private void printKeys(Iterator<byte[]> keys) {
int i = 0;
while (keys.hasNext()) {
System.out.println(new String(keys.next()));
i++;
}
System.out.println(String.format("Total No. found: %s", i));
}
private void generateRandomKeys(int nrKeys) {
for (int i = 0; i < nrKeys; i++) {
this.connection.set((PREFIX + "-" + i).getBytes(), UUID.randomUUID().toString().getBytes());
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<root level="warn">
<appender-ref ref="console" />
</root>
</configuration>

38
redis/pom.xml Normal file
View File

@@ -0,0 +1,38 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-redis-examples</artifactId>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Data Redis - Examples</name>
<description>Sample projects for Spring Data Redis</description>
<modules>
<module>util</module>
<module>cluster-sentinel</module>
<module>example</module>
</modules>
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.5.2</version>
</dependency>
</dependencies>
</project>

26
redis/util/pom.xml Normal file
View File

@@ -0,0 +1,26 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-redis-example-utils</artifactId>
<name>Spring Data Redis - Example utilities</name>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-redis-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,183 @@
/*
* 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 example.springdata.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 RequiresRedisSentinel implements TestRule {
public enum SentinelsAvailable {
ALL_ACTIVE, ONE_ACTIVE, NONE_ACTIVE
}
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 SentinelsAvailable requiredSentinels;
protected RequiresRedisSentinel(RedisSentinelConfiguration config) {
this.sentinelConfig = config;
}
/**
* Create new {@link RedisSentinelRule} for given {@link RedisSentinelConfiguration}.
*
* @param config
* @return
*/
public static RequiresRedisSentinel forConfig(RedisSentinelConfiguration config) {
return new RequiresRedisSentinel(config != null ? config : DEFAULT_SENTINEL_CONFIG);
}
/**
* Create new {@link RedisSentinelRule} using default configuration.
*
* @return
*/
public static RequiresRedisSentinel withDefaultConfig() {
return new RequiresRedisSentinel(DEFAULT_SENTINEL_CONFIG);
}
public RequiresRedisSentinel sentinelsDisabled() {
this.requiredSentinels = SentinelsAvailable.NONE_ACTIVE;
return this;
}
/**
* Verifies all {@literal Sentinel} nodes are available.
*
* @return
*/
public RequiresRedisSentinel allActive() {
this.requiredSentinels = SentinelsAvailable.ALL_ACTIVE;
return this;
}
/**
* Verifies at least one {@literal Sentinel} node is available.
*
* @return
*/
public RequiresRedisSentinel oneActive() {
this.requiredSentinels = SentinelsAvailable.ONE_ACTIVE;
return this;
}
/**
* Will only check {@link RedisSentinelConfiguration} configuration in case {@link RequiresRedisSentinel} is detected
* on test method.
*
* @return
*/
public RequiresRedisSentinel dynamicModeSelection() {
this.requiredSentinels = null;
return this;
}
/*
* (non-Javadoc)
* @see org.junit.rules.TestRule#apply(org.junit.runners.model.Statement, org.junit.runner.Description)
*/
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
if (description.isTest()) {
if (RequiresRedisSentinel.this.requiredSentinels != null) {
verify(RequiresRedisSentinel.this.requiredSentinels);
}
} else {
verify(RequiresRedisSentinel.this.requiredSentinels);
}
base.evaluate();
}
};
}
private void verify(SentinelsAvailable verificationMode) {
int failed = 0;
for (RedisNode node : sentinelConfig.getSentinels()) {
if (!isAvailable(node)) {
failed++;
}
}
if (failed > 0) {
if (SentinelsAvailable.ALL_ACTIVE.equals(verificationMode)) {
throw new AssumptionViolatedException(String.format(
"Expected all Redis Sentinels to respone but %s of %s did not responde", failed, sentinelConfig
.getSentinels().size()));
}
if (SentinelsAvailable.ONE_ACTIVE.equals(verificationMode) && sentinelConfig.getSentinels().size() - 1 < failed) {
throw new AssumptionViolatedException(
"Expected at least one sentinel to respond but it seems all are offline - Game Over!");
}
}
if (SentinelsAvailable.NONE_ACTIVE.equals(verificationMode) && 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;
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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 example.springdata.redis.test.util;
import java.net.InetSocketAddress;
import java.net.Socket;
import org.junit.internal.AssumptionViolatedException;
import org.junit.rules.ExternalResource;
/**
* Implementation of junit rule {@link ExternalResource} to verify Redis (or at least something on the defined host and
* port) is up and running.
*
* @author Christoph Strobl
*/
public class RequiresRedisServer extends ExternalResource {
private int timeout = 30;
private final String host;
private final int port;
private RequiresRedisServer(String host, int port) {
this.host = host;
this.port = port;
}
public static RequiresRedisServer onLocalhost() {
return new RequiresRedisServer("localhost", 6379);
}
/*
* (non-Javadoc)
* @see org.junit.rules.ExternalResource#before()
*/
@Override
protected void before() throws Throwable {
try (Socket socket = new Socket()) {
socket.setTcpNoDelay(true);
socket.setSoLinger(true, 0);
socket.connect(new InetSocketAddress(host, port), timeout);
} catch (Exception e) {
throw new AssumptionViolatedException(String.format("Seems as redis is not running at %s:%s.", host, port), e);
}
}
}