Spring Redis Error Handle - error-handling

I am using Spring + Redis as my cache component in the new project. The spring config xml file is:
<!-- Jedis Connection -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.ip}" p:port="${redis.port}" p:use-pool="${redis.use-pool}" />
<!-- Redis Template -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
</bean>
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:template-ref="redisTemplate"/>
<cache:annotation-driven mode="proxy" proxy-target-class="true" cache-manager="cacheManager" />
The usage is
#Cacheable(value = "cacheManager", key="#userId")
public User getUser(String userId) {
System.out.println("execute==");
return userAdminMapper.getUser(userId);
}
My test case is:
#Test
public void testCacheUser2() {
String id = "test";
User user = userService.getUser(id);
System.out.println(user);
user.setUserCreateDate(new Date());
userService.updateUser(user);
User user2 = userService.getUser(id);
System.out.println(user2);
User user3 = userService.getUser(id);
System.out.println(user3);
}
If the Redis server is running, the code is running correctly. But my question is if I shutdown the Redis server, it will throw the exception:
org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:140)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:229)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:57)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:177)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152)
at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:87)
at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:297)
at org.springframework.cache.interceptor.CacheAspectSupport.findInAnyCaches(CacheAspectSupport.java:287)
at org.springframework.cache.interceptor.CacheAspectSupport.collectPutRequests(CacheAspectSupport.java:266)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:199)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:178)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:60)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
at sg.infolab.common.admin.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$c7f982a7.getUser(<generated>)
at sg.infolab.admin.test.RedisServiceTest.testCacheUser2(RedisServiceTest.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect
at redis.clients.jedis.Connection.connect(Connection.java:150)
at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:71)
at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1783)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:137)
... 50 more
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:529)
at redis.clients.jedis.Connection.connect(Connection.java:144)
... 53 more
I want to ask if the client couldn't connect Redis Server, why will it throw exception? Can we config the scenario like this -- if the cache layer(Redis Server) cannot connect(maybe it is crashed or network is not up), it should directly connect to database and fetch data.

I had the very same problem. I'm developing some data services against a database, using Redis as the cache store by way of Spring Caching annotations. If the Redis server becomes unavailable, I want the services to continue to operate as if uncached, rather than throwing exceptions.
At first I tried a custom CacheErrorHandler, a mechanism provided by Spring. It didn't quite work, because it only handles RuntimeExceptions, and still lets things like java.net.ConnectException blow things up.
In the end what I did is extend RedisTemplate, overriding a few execute() methods so that they log warnings instead of propagating exceptions. It seems like a bit of a hack, and I might have overridden too few execute() methods or too many, but it works like a charm in all my test cases.
There's an important operational aspect to this approach, though. If the Redis server becomes unavailable you must flush it (clean out the entries) before making it available again. Otherwise there's a chance that you might start retrieving cache entries that have incorrect data because of updates that occurred in the meantime.
Below is the source. Feel free to use it. I hope it helps.
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* An extension of RedisTemplate that logs exceptions instead of letting them propagate.
* If the Redis server is unavailable, cache operations are always a "miss" and data is fetched from the database.
*/
public class LoggingRedisTemplate<K, V> extends RedisTemplate<K, V> {
private static final Logger logger = LoggerFactory.getLogger(LoggingRedisTemplate.class);
#Override
public <T> T execute(final RedisCallback<T> action, final boolean exposeConnection, final boolean pipeline) {
try {
return super.execute(action, exposeConnection, pipeline);
}
catch(final Throwable t) {
logger.warn("Error executing cache operation: {}", t.getMessage());
return null;
}
}
#Override
public <T> T execute(final RedisScript<T> script, final List<K> keys, final Object... args) {
try {
return super.execute(script, keys, args);
}
catch(final Throwable t) {
logger.warn("Error executing cache operation: {}", t.getMessage());
return null;
}
}
#Override
public <T> T execute(final RedisScript<T> script, final RedisSerializer<?> argsSerializer, final RedisSerializer<T> resultSerializer, final List<K> keys, final Object... args) {
try {
return super.execute(script, argsSerializer, resultSerializer, keys, args);
}
catch(final Throwable t) {
logger.warn("Error executing cache operation: {}", t.getMessage());
return null;
}
}
#Override
public <T> T execute(final SessionCallback<T> session) {
try {
return super.execute(session);
}
catch(final Throwable t) {
logger.warn("Error executing cache operation: {}", t.getMessage());
return null;
}
}
}

I have added the answer for Spring boot v2 using LettuceConnectionFactory
#Configuration
#EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer {
#Value("${redis.hostname:localhost}")
private String redisHost;
#Value("${redis.port:6379}")
private int redisPort;
#Value("${redis.timeout.secs:1}")
private int redisTimeoutInSecs;
#Value("${redis.socket.timeout.secs:1}")
private int redisSocketTimeoutInSecs;
#Value("${redis.ttl.hours:1}")
private int redisDataTTL;
// #Autowired
// private ObjectMapper objectMapper;
#Bean
public LettuceConnectionFactory redisConnectionFactory() {
// LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
// .commandTimeout(Duration.ofSeconds(redisConnectionTimeoutInSecs)).shutdownTimeout(Duration.ZERO).build();
//
// return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort), clientConfig);
final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofSeconds(redisSocketTimeoutInSecs)).build();
final ClientOptions clientOptions = ClientOptions.builder().socketOptions(socketOptions).build();
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(redisTimeoutInSecs)).clientOptions(clientOptions).build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(redisHost, redisPort);
final LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(serverConfig, clientConfig);
lettuceConnectionFactory.setValidateConnection(true);
return lettuceConnectionFactory;
}
#Bean
public RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
#Bean
public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
/**
* If we want to use JSON Serialized with own object mapper then use the below config snippet
*/
// RedisCacheConfiguration redisCacheConfiguration =
// RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
// .entryTtl(Duration.ofHours(redisDataTTL)).serializeValuesWith(RedisSerializationContext.SerializationPair
// .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
.entryTtl(Duration.ofHours(redisDataTTL))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.java()));
redisCacheConfiguration.usePrefix();
RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
.cacheDefaults(redisCacheConfiguration).build();
redisCacheManager.setTransactionAware(true);
return redisCacheManager;
}
#Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
RedisCacheErrorHandler.java is given below
public class RedisCacheErrorHandler implements CacheErrorHandler {
private static final Logger log = LoggerFactory.getLogger(RedisCacheErrorHandler.class);
#Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
handleTimeOutException(exception);
log.info("Unable to get from cache " + cache.getName() + " : " + exception.getMessage());
}
#Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
handleTimeOutException(exception);
log.info("Unable to put into cache " + cache.getName() + " : " + exception.getMessage());
}
#Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
handleTimeOutException(exception);
log.info("Unable to evict from cache " + cache.getName() + " : " + exception.getMessage());
}
#Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
handleTimeOutException(exception);
log.info("Unable to clean cache " + cache.getName() + " : " + exception.getMessage());
}
/**
* We handle redis connection timeout exception , if the exception is handled then it is treated as a cache miss and
* gets the data from actual storage
*
* #param exception
*/
private void handleTimeOutException(RuntimeException exception) {
if (exception instanceof RedisCommandTimeoutException)
return;
}
}

I have the same error. And I managed to solve it by adding two things:
timeout for connectionFactory
error handler
#Configuration
#ConditionalOnProperty(name = "redis.enabled", havingValue = "true")
#EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer {
#Value("${redis.host}")
private String host;
#Value("${redis.port}")
private Integer port;
#Value("${redis.expiration.timeout}")
private Integer expirationTimeout;
#Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
redisConnectionFactory.setHostName(host);
redisConnectionFactory.setPort(port);
redisConnectionFactory.setTimeout(10);
return redisConnectionFactory;
}
#Bean
public RedisTemplate<String, Set<String>> redisTemplate(#Autowired RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Set<String>> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
#Bean
public CacheManager cacheManager(#Autowired RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(expirationTimeout);
return cacheManager;
}
#Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
#Slf4j
public static class RedisCacheErrorHandler implements CacheErrorHandler {
#Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.info("Unable to get from cache " + cache.getName() + " : " + exception.getMessage());
}
#Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.info("Unable to put into cache " + cache.getName() + " : " + exception.getMessage());
}
#Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.info("Unable to evict from cache " + cache.getName() + " : " + exception.getMessage());
}
#Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.info("Unable to clean cache " + cache.getName() + " : " + exception.getMessage());
}
}
}

LettuceConnectionFactory is not necessary. Just using a custom CacheConfig extends CachingConfigurerSupport. And override the errorHandler() method.
You just need to implement a custom CacheErrorHandler, like #Tan mally do in his answer.

You can use CacheErrorHandler as suggested in other answers. But you should make sure to make
RedisCacheManager transactionAware to false in your Redis Cache Config(to make sure the transaction is committed early when executing the caching part and the error is caught by CacheErrorHandler and don't wait until the end of the execution which skips CacheErrorHandler part). The function to set transactionAware to false looks like this:
#Bean
public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(redisDataTTL))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
redisCacheConfiguration.usePrefix();
RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
redisCacheManager.setTransactionAware(false);
return redisCacheManager;
}

Related

How to use ConnectionListner and/or ChannelListner for logging failure/success of message delivery in RabbitMQ

I am trying to log any information or exception that occurs during message sending in RabbitMQ, for that I tried to add ConnectionListener on the existing connection factory.
kRabbitTemplate.getConnectionFactory().addConnectionListener(new ConnectionListener() {
#Override
public void onCreate(Connection connection) {
System.out.println("Connection Created");
}
#Override
public void onShutDown(ShutdownSignalException signal) {
System.out.println("Connection Shutdown "+signal.getMessage());
}
});
kRabbitTemplate.convertAndSend(exchange, routingkey, empDTO);
To test the exception scenario, I unbind and even deleted the queue from RabbitMQ console. But I did not get any exception or any shutdown method call.
Although, When I stopped RabbitMQ service, I got
Exception in thread "Thread-5" org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
But this exception is not from the listener I added.
I want to know
Why I did not get any exception or call from shutdown method
How can I use ConnectionListner and/or ChannelListner for logging failure/success of message delivery.
Can we use the AMQP appender, if yes how can we do that? (any example / tutorial)
What are the other approaches to ensure the message is sent?
Note: I do not want to use the publisher confirm the approach.
Connection Refused is not a ShutdownSignalException - the connection was never established because the broker is not present on the server/port.
You can't use the listeners to confirm delivery or return of individual messages; use publisher confirms and returns for that.
https://docs.spring.io/spring-amqp/docs/current/reference/html/#publishing-is-async
See the documentation for how to use the appenders.
https://docs.spring.io/spring-amqp/docs/current/reference/html/#logging
EDIT
To get notified of failures to connect, you currently need to use other techniques, depending on whether you are sending or receiving.
Here is an example that shows how:
#SpringBootApplication
public class So66882099Application {
private static final Logger log = LoggerFactory.getLogger(So66882099Application.class);
public static void main(String[] args) {
SpringApplication.run(So66882099Application.class, args);
}
#RabbitListener(queues = "foo")
void listen(String in) {
}
// consumer side listeners for no connection
#EventListener
void consumerFailed(ListenerContainerConsumerFailedEvent event) {
log.error(event + " via event listener");
if (event.getThrowable() instanceof AmqpConnectException) {
log.error("Broker down?");
}
}
// or
#Bean
ApplicationListener<ListenerContainerConsumerFailedEvent> eventListener() {
return event -> log.error(event + " via application listener");
}
// producer side - use a RetryListener
#Bean
RabbitTemplate template(ConnectionFactory cf) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(cf);
RetryTemplate retry = new RetryTemplate();
// configure retries here as needed
retry.registerListener(new RetryListener() {
#Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return true;
}
#Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
log.error("Send failed " + throwable.getMessage());
}
#Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
}
});
rabbitTemplate.setRetryTemplate(retry);
return rabbitTemplate;
}
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
return args -> {
try {
template.convertAndSend("foo", "bar");
}
catch (Exception e) {
e.printStackTrace();
}
};
}
}

Issue with #ConfigurationProperties while using spring-session with Redis

Not able to access the properties while using Spring-session with Redis.
Auto wiring is not happening hence this object is null. Not sure what wrong I'm doing here.
#Autowired
private RedisSentinelProperties redisSentinelProperties;
Without spring-session it works fine without any issue.
I have tried without spring-session and it works fine. Able to access the all the properties and Auto wiring happens properly
#Autowired
private RedisSentinelProperties redisSentinelProperties;
Custom properties configuration
#Component
#ConfigurationProperties(prefix = "app.redis")
#Validated
public class RedisSentinelProperties {
#NotNull
private String masterName;
#Valid
private Sentinel sentinel = new Sentinel();
////removed the getter and setter method for better readability
public static class Sentinel {
#NotEmpty
private List<String> nodes = new ArrayList<>();
//removed the getter and setter method for better readability
}
}
application.properties
app.redis.master-name=mymaster
app.redis.sentinel.nodes=192.168.56.50:26379,192.168.56.50:26380,192.168.56.50:26381
spring-session configuration
#SpringBootApplication
public class ConfigPropertiesDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigPropertiesDemoApplication.class, args);
}
}
public class RedisHttpSessionInitializer extends AbstractHttpSessionApplicationInitializer {
public RedisHttpSessionInitializer() {
super(RedisHttpSessionConfig.class);
}
}
#Configuration
#EnableRedisHttpSession
#EnableWebSecurity
public class RedisHttpSessionConfig {
#Autowired
private RedisSentinelProperties redisSentinelProperties;
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED)
.build();
private Set<String> sentinelHostAndPorts(){
Set<String> nodes = redisSentinelProperties.getSentinel().getNodes().stream().collect(Collectors.toSet());
return nodes;
}
//This is where NullPointerException is thrown line 37 in the stack trace
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration(redisSentinelProperties.getMasterName(), sentinelHostAndPorts());
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
}
Below is the stack trace
Caused by: java.lang.NullPointerException
at com.bt.consumer.configpropertiesdemo.config.RedisHttpSessionConfig.(RedisHttpSessionConfig.java:37)
at com.bt.consumer.configpropertiesdemo.config.RedisHttpSessionConfig$$EnhancerBySpringCGLIB$$f6d40824.()
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:172)

Redis PUBSUB Spring Data transnational reliability/retry

I been working on implementing a PUB/SUB service using spring-data-Redis.
I have been researching and following the web and got something to work fine.
my problem is that I need absolute reliability when a message is not processed ( either an Exception is thrown or a logic error occurs ).
In which case I need the message to return to the topic for a retry ( by another subscriber or even the same ).
I have looked at several questions, particularly the following:
Redis Pub/Sub with Reliability
and
How to implement Redis Multi-Exec by using Spring-data-Redis
I have understood that I should use multi, exec for managing a transaction, but I couldn't get it to work.
Here is a simplified version of my code
#Configuration
#PropertySource(value = { "classpath:application.properties" })
public class RedisConfig {
#Autowired
Environment env;
#Bean
public MessageListenerAdapter messageListener() {
MyMessageListenerAdapter messageListeneradapter = new MyMessageListenerAdapter(new RedisMessageSubscriber());
messageListeneradapter.afterPropertiesSet();
return messageListeneradapter;
}
#Bean(name="RedisMessagePublisherBean")
public RedisMessagePublisher messagePublisher() {
return new RedisMessagePublisher();
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String , Object> template = new RedisTemplate<>();
template.setValueSerializer(new GenericToStringSerializer<Object>(Object.class));
template.setEnableTransactionSupport(true);
template.setConnectionFactory(lettuceConnectionFactory());
return template;
}
#Bean
public RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container
= new RedisMessageListenerContainer();
container.setConnectionFactory(lettuceConnectionFactory());
container.addMessageListener(messageListener(), topic());
return container;
}
#Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
LettuceConnectionFactory factory = new LettuceConnectionFactory();
factory.setValidateConnection(true);
factory.setDatabase(1);
factory.afterPropertiesSet();
return factory;
}
#Bean
public ChannelTopic topic() {
return new ChannelTopic("MQ_TOPIC");
}
public class MyMessageListenerAdapter extends MessageListenerAdapter{
public MyMessageListenerAdapter(RedisMessageSubscriber redisMessageSubscriber) {
super(redisMessageSubscriber);
}
#Override
public void onMessage(Message message, byte[] pattern) {
RedisTemplate<?, ?> template = redisTemplate();
template.execute(new SessionCallback<String>() {
#Override
public <K, V> String execute(RedisOperations<K, V> operations) throws DataAccessException {
operations.multi();
System.out.println("got message");
String result = doSomeLogic(message);
if (result == null)
operations.discard();
else
operations.exec();
return null;
}
}) ;
}
}
}
My requirements are that if a message failed to process ( I can leave without runtime exceptions etc.. strictly logical error would suffice for now ), It will return to the topic.
Any help is appreciated, Thanks!

Spring Amqp: Mix SimpleRoutingConnectionFactory with #RabbitListener

I have an app that is gonna listen to multiple queues, which are declared on different vhost. I used a SimpleRoutingConnectionFactory to store a connectionFactoryMap, and I hope to set up my listener with #RabbitListener.
According to Spring AMQP doc:
Also starting with version 1.4, you can configure a routing connection
factory in a SimpleMessageListenerContainer. In that case, the list of
queue names is used as the lookup key. For example, if you configure
the container with setQueueNames("foo, bar"), the lookup key will be
"[foo,bar]" (no spaces).
I used #RabbitListener(queues = "some-key"). Unfortunately, spring complained "lookup key [null]". See below.
18:52:44.528 WARN --- [cTaskExecutor-1]
o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception,
processing can restart if the connection factory supports it
java.lang.IllegalStateException: Cannot determine target
ConnectionFactory for lookup key [null] at
org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory.determineTargetConnectionFactory(AbstractRoutingConnectionFactory.java:119)
at
org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory.createConnection(AbstractRoutingConnectionFactory.java:97)
at
org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$1.createConnection(ConnectionFactoryUtils.java:90)
at
org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.doGetTransactionalResourceHolder(ConnectionFactoryUtils.java:140)
at
org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactoryUtils.java:76)
at
org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:472)
at
org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1306)
at java.lang.Thread.run(Thread.java:745)
Did I do something wrong? If queues attribute is used as lookup key (for connection factory lookup), what am I supposed to use to specify which queue I'd like to listen to?
Ultimately, I hope to do programmatic/dynamic listener setup. If I use "Programmatic Endpoint Registration", am I supposed to drop "Annotation-driven listener endpoints"? I love "Annotation-driven listener endpoints", because a listener could have multiple message handles with different incoming data type as argument, which is very clean and tidy. If I use Programmatic Endpoint Registration, I would have to parse the Message input variable, and call my a particular custom message handler based on the message type/content.
EDIT:
Hi Gary,
I modified your code #2 a little bit, so that it uses Jackson2JsonMessageConverter to serialize class objects (in RabbitTemplate bean), and use it to un-serialize them back to objects (in inboundAdapter). I also removed #RabbitListener because all listeners would be added at runtime in my case. Now the fooBean can receive integer, string and TestData message without any problem! The only issue left behind is that the program constantly report warning:
"[erContainer#0-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it
java.lang.IllegalStateException: Cannot determine target ConnectionFactory for lookup key [null]". For the full stacktrace, please see the bottom.
Did I miss anything?
#SpringBootApplication
public class App2 implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(App2.class, args);
}
#Autowired
private IntegrationFlowContext flowContext;
#Autowired
private ConnectionFactory routingCf;
#Autowired
private RabbitTemplate template;
#Override
public void run(String... args) throws Exception {
// dynamically add a listener for queue qux
IntegrationFlow flow = IntegrationFlows.from(Amqp.inboundAdapter(this.routingCf, "qux").messageConverter(new Jackson2JsonMessageConverter()))
.handle(fooBean())
.get();
this.flowContext.registration(flow).register();
// now test it
SimpleResourceHolder.bind(this.routingCf, "[qux]");
this.template.convertAndSend("qux", 42);
this.template.convertAndSend("qux", "fizbuz");
this.template.convertAndSend("qux", new TestData(1, "test"));
SimpleResourceHolder.unbind(this.routingCf);
}
#Bean
RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(routingCf);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
#Bean
#Primary
public ConnectionFactory routingCf() {
SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
Map<Object, ConnectionFactory> map = new HashMap<>();
map.put("[foo,bar]", routedCf());
map.put("[baz]", routedCf());
map.put("[qux]", routedCf());
rcf.setTargetConnectionFactories(map);
return rcf;
}
#Bean
public ConnectionFactory routedCf() {
return new CachingConnectionFactory("127.0.0.1");
}
#Bean
public Foo fooBean() {
return new Foo();
}
public static class Foo {
#ServiceActivator
public void handleInteger(Integer in) {
System.out.println("int: " + in);
}
#ServiceActivator
public void handleString(String in) {
System.out.println("str: " + in);
}
#ServiceActivator
public void handleData(TestData data) {
System.out.println("TestData: " + data);
}
}
}
Full stack trace:
2017-03-15 21:43:06.413 INFO 1003 --- [ main] hello.App2 : Started App2 in 3.003 seconds (JVM running for 3.69)
2017-03-15 21:43:11.415 WARN 1003 --- [erContainer#0-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it
java.lang.IllegalStateException: Cannot determine target ConnectionFactory for lookup key [null]
at org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory.determineTargetConnectionFactory(AbstractRoutingConnectionFactory.java:119) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.connection.AbstractRoutingConnectionFactory.createConnection(AbstractRoutingConnectionFactory.java:97) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:1430) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1411) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1387) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.core.RabbitAdmin.initialize(RabbitAdmin.java:500) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.core.RabbitAdmin$11.onCreate(RabbitAdmin.java:419) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.connection.CompositeConnectionListener.onCreate(CompositeConnectionListener.java:33) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.createConnection(CachingConnectionFactory.java:571) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$1.createConnection(ConnectionFactoryUtils.java:90) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.doGetTransactionalResourceHolder(ConnectionFactoryUtils.java:140) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactoryUtils.java:76) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:505) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1382) ~[spring-rabbit-1.7.1.RELEASE.jar:na]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_112]
Please show your configuration - it works fine for me...
#SpringBootApplication
public class So42784471Application {
public static void main(String[] args) {
SpringApplication.run(So42784471Application.class, args);
}
#Bean
#Primary
public ConnectionFactory routing() {
SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
Map<Object, ConnectionFactory> map = new HashMap<>();
map.put("[foo,bar]", routedCf());
map.put("[baz]", routedCf());
rcf.setTargetConnectionFactories(map);
return rcf;
}
#Bean
public ConnectionFactory routedCf() {
return new CachingConnectionFactory("10.0.0.3");
}
#RabbitListener(queues = { "foo" , "bar" })
public void foobar(String in) {
System.out.println(in);
}
#RabbitListener(queues = "baz")
public void bazzer(String in) {
System.out.println(in);
}
}
Regarding your second question, you could build the endpoint manually but it's quite involved. It's probably easier to use a similar feature in a Spring Integration #ServiceActivator.
I will update this answer with details shortly.
EDIT
And here's the update using Spring Integration techniques to dynamically add a multi-method listener at runtime...
#SpringBootApplication
public class So42784471Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(So42784471Application.class, args);
}
#Autowired
private IntegrationFlowContext flowContext;
#Autowired
private ConnectionFactory routingCf;
#Autowired
private RabbitTemplate template;
#Override
public void run(String... args) throws Exception {
// dynamically add a listener for queue qux
IntegrationFlow flow = IntegrationFlows.from(Amqp.inboundAdapter(this.routingCf, "qux"))
.handle(fooBean())
.get();
this.flowContext.registration(flow).register();
// now test it
SimpleResourceHolder.bind(this.routingCf, "[qux]");
this.template.convertAndSend("qux", 42);
this.template.convertAndSend("qux", "fizbuz");
SimpleResourceHolder.unbind(this.routingCf);
}
#Bean
#Primary
public ConnectionFactory routingCf() {
SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
Map<Object, ConnectionFactory> map = new HashMap<>();
map.put("[foo,bar]", routedCf());
map.put("[baz]", routedCf());
map.put("[qux]", routedCf());
rcf.setTargetConnectionFactories(map);
return rcf;
}
#Bean
public ConnectionFactory routedCf() {
return new CachingConnectionFactory("10.0.0.3");
}
#RabbitListener(queues = { "foo" , "bar" })
public void foobar(String in) {
System.out.println(in);
}
#RabbitListener(queues = "baz")
public void bazzer(String in) {
System.out.println(in);
}
#Bean
public Foo fooBean() {
return new Foo();
}
public static class Foo {
#ServiceActivator
public void handleInteger(Integer in) {
System.out.println("int: " + in);
}
#ServiceActivator
public void handleString(String in) {
System.out.println("str: " + in);
}
}
}

Set Gemfire entry-ttl in Java Beans

I would like to create a Gemfire region in a Spring Boot application. Following this sample, it works well wihout adding database support. If I add database, it will shows error like " Error creating bean with name 'dataSource'". However, default gemfire cache bean works well with datasource integration.
#EnableAutoConfiguration
// Sprint Boot Auto Configuration
#ComponentScan(basePackages = "napo.demo")
#EnableCaching
#SuppressWarnings("unused")
public class Application extends SpringBootServletInitializer {
private static final Class<Application> applicationClass = Application.class;
private static final Logger log = LoggerFactory.getLogger(applicationClass);
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
/* **The commented code works well with database.**
#Bean
CacheFactoryBean cacheFactoryBean() {
return new CacheFactoryBean();
}
#Bean
ReplicatedRegionFactoryBean<Integer, Integer> replicatedRegionFactoryBean(final Cache cache) {
ReplicatedRegionFactoryBean<Integer, Integer> region= new ReplicatedRegionFactoryBean<Integer, Integer>() {{
setCache(cache);
setName("demo");
}};
return region;
} */
// This configuration will cause issue as beow
//
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$NonEmbeddedConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.NullPointerException
#Bean
GemfireCacheManager cacheManager(final Cache gemfireCache) {
return new GemfireCacheManager() {
{
setCache(gemfireCache);
}
};
}
// NOTE ideally, "placeholder" properties used by Spring's PropertyPlaceholderConfigurer would be externalized
// in order to avoid re-compilation on property value changes (so... this is just an example)!
#Bean
public Properties placeholderProperties() {
Properties placeholders = new Properties();
placeholders.setProperty("app.gemfire.region.eviction.action", "LOCAL_DESTROY");
placeholders.setProperty("app.gemfire.region.eviction.policy-type", "MEMORY_SIZE");
placeholders.setProperty("app.gemfire.region.eviction.threshold", "4096");
placeholders.setProperty("app.gemfire.region.expiration.entry.tti.action", "INVALIDATE");
placeholders.setProperty("app.gemfire.region.expiration.entry.tti.timeout", "300");
placeholders.setProperty("app.gemfire.region.expiration.entry.ttl.action", "DESTROY");
placeholders.setProperty("app.gemfire.region.expiration.entry.ttl.timeout", "60");
placeholders.setProperty("app.gemfire.region.partition.local-max-memory", "16384");
placeholders.setProperty("app.gemfire.region.partition.redundant-copies", "1");
placeholders.setProperty("app.gemfire.region.partition.total-max-memory", "32768");
return placeholders;
}
#Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer(
#Qualifier("placeholderProperties") Properties placeholders) {
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setProperties(placeholders);
return propertyPlaceholderConfigurer;
}
#Bean
public Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "SpringGemFireJavaConfigTest");
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "config");
return gemfireProperties;
}
#Bean
#Autowired
public CacheFactoryBean gemfireCache(#Qualifier("gemfireProperties") Properties gemfireProperties) throws Exception {
CacheFactoryBean cacheFactory = new CacheFactoryBean();
cacheFactory.setProperties(gemfireProperties);
return cacheFactory;
}
#Bean(name = "ExamplePartition")
#Autowired
public ReplicatedRegionFactoryBean<Object, Object> examplePartitionRegion(Cache gemfireCache,
#Qualifier("partitionRegionAttributes") RegionAttributes<Object, Object> regionAttributes) throws Exception {
ReplicatedRegionFactoryBean<Object, Object> examplePartitionRegion =
new ReplicatedRegionFactoryBean<Object, Object>();
examplePartitionRegion.setAttributes(regionAttributes);
examplePartitionRegion.setCache(gemfireCache);
examplePartitionRegion.setName("demo");
return examplePartitionRegion;
}
#Bean
#Autowired
public RegionAttributesFactoryBean partitionRegionAttributes(
EvictionAttributes evictionAttributes,
#Qualifier("entryTtiExpirationAttributes") ExpirationAttributes entryTti,
#Qualifier("entryTtlExpirationAttributes") ExpirationAttributes entryTtl) {
RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();
regionAttributes.setEvictionAttributes(evictionAttributes);
regionAttributes.setEntryIdleTimeout(entryTti);
regionAttributes.setEntryTimeToLive(entryTtl);
return regionAttributes;
}
#Bean
public EvictionAttributesFactoryBean defaultEvictionAttributes(
#Value("${app.gemfire.region.eviction.action}") String action,
#Value("${app.gemfire.region.eviction.policy-type}") String policyType,
#Value("${app.gemfire.region.eviction.threshold}") int threshold) {
EvictionAttributesFactoryBean evictionAttributes = new EvictionAttributesFactoryBean();
evictionAttributes.setAction(EvictionActionType.valueOfIgnoreCase(action).getEvictionAction());
evictionAttributes.setThreshold(threshold);
evictionAttributes.setType(EvictionPolicyType.valueOfIgnoreCase(policyType));
return evictionAttributes;
}
#Bean
public ExpirationAttributesFactoryBean entryTtiExpirationAttributes(
#Value("${app.gemfire.region.expiration.entry.tti.action}") String action,
#Value("${app.gemfire.region.expiration.entry.tti.timeout}") int timeout) {
ExpirationAttributesFactoryBean expirationAttributes = new ExpirationAttributesFactoryBean();
expirationAttributes.setAction(ExpirationActionType.valueOfIgnoreCase(action).getExpirationAction());
expirationAttributes.setTimeout(timeout);
return expirationAttributes;
}
#Bean
public ExpirationAttributesFactoryBean entryTtlExpirationAttributes(
#Value("${app.gemfire.region.expiration.entry.ttl.action}") String action,
#Value("${app.gemfire.region.expiration.entry.ttl.timeout}") int timeout) {
ExpirationAttributesFactoryBean expirationAttributes = new ExpirationAttributesFactoryBean();
expirationAttributes.setAction(ExpirationActionType.valueOfIgnoreCase(action).getExpirationAction());
expirationAttributes.setTimeout(timeout);
return expirationAttributes;
}
#Bean
public PartitionAttributesFactoryBean defaultPartitionAttributes(
#Value("${app.gemfire.region.partition.local-max-memory}") int localMaxMemory,
#Value("${app.gemfire.region.partition.redundant-copies}") int redundantCopies,
#Value("${app.gemfire.region.partition.total-max-memory}") int totalMaxMemory) {
PartitionAttributesFactoryBean partitionAttributes = new PartitionAttributesFactoryBean();
partitionAttributes.setLocalMaxMemory(localMaxMemory);
partitionAttributes.setRedundantCopies(redundantCopies);
partitionAttributes.setTotalMaxMemory(totalMaxMemory);
return partitionAttributes;
}
#Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder application) {
return application.sources(applicationClass);
}}
demoService java code:
#Service
public class demoService {
#Autowired
private demoMapper demoMapper;
#Cacheable("demo")
public Fund getDemo(String code) {
Demo demo= demoMapper.getDemo(Code);
return demo;
}
Here is an example of setting entry-ttl among other attributes: https://github.com/spring-projects/spring-gemfire-examples/blob/master/basic/java-config/src/main/java/org/springframework/data/gemfire/example/SpringJavaBasedContainerGemFireConfiguration.java