Skip to content

Instantly share code, notes, and snippets.

@ugur93
Last active April 7, 2024 15:51
Show Gist options
  • Save ugur93/4e047c03c0d152d245e391d70788829a to your computer and use it in GitHub Desktop.
Save ugur93/4e047c03c0d152d245e391d70788829a to your computer and use it in GitHub Desktop.
Example of Spring Redis Cache Configuration using Spring-Data-Redis Lettuce Driver
import com.lambdaworks.redis.resource.DefaultClientResources;
import com.lambdaworks.redis.resource.Delay;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePool;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Redis Cache config
*/
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
private static final String MASTER_NAME = "mymaster";
@Value("${app.name}")
private String appName;
private final CustomRedisSerializer customRedisSerializer = new CustomRedisSerializer();
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
redisCacheManager.setDefaultExpiration(TimeUnit.DAYS.toSeconds(2));
return redisCacheManager;
}
@Bean
public RedisTemplate<?, ?> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
redisTemplate.setDefaultSerializer(customRedisSerializer);
redisTemplate.setEnableDefaultSerializer(true);
return redisTemplate;
}
@Bean
public LettuceConnectionFactory lettuceConnectionFactory(LettucePool lettucePool) {
LettuceConnectionFactory factory = new LettuceConnectionFactory(lettucePool);
factory.setShareNativeConnection(false);
return factory;
}
@Bean
public LettucePool lettucePool() {
CustomLettucePool lettucePool = new CustomLettucePool(new RedisSentinelConfiguration()
.master(MASTER_NAME).sentinel(new RedisNode("rfs-" + appName, 26379)));
lettucePool.setClientResources(DefaultClientResources.builder()
.reconnectDelay(Delay.constant(200, TimeUnit.MILLISECONDS))
.build());
lettucePool.setTimeout(200);
lettucePool.afterPropertiesSet();
return lettucePool;
}
@Bean
@Override
public CacheErrorHandler errorHandler(){
return new CustomCacheErrorHandler();
}
}
@Slf4j
public class CustomCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("Cache get operation failed")
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
}
}
/**
*
* Copy of {@Link DefaultLettucePool} with some modifications on the redisClient in afterPropertiesSet method
*/
public class CustomLettucePool implements LettucePool, InitializingBean {
@SuppressWarnings("rawtypes") //
private GenericObjectPool<StatefulConnection<byte[], byte[]>> internalPool;
private RedisClient client;
private int dbIndex = 0;
private GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private String hostName = "localhost";
private int port = 6379;
private String password;
private long timeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS);
private RedisSentinelConfiguration sentinelConfiguration;
private ClientResources clientResources;
/**
* Constructs a new <code>CustomLettucePool</code> instance with default settings.
*/
public CustomLettucePool() {
}
/**
* Uses the {@link } and {@link RedisClient} defaults for configuring the connection pool
*
* @param hostName The Redis host
* @param port The Redis port
*/
public CustomLettucePool(String hostName, int port) {
this.hostName = hostName;
this.port = port;
}
/**
* Uses the {@link RedisSentinelConfiguration} and {@link RedisClient} defaults for configuring the connection pool
* based on sentinels.
*
* @param sentinelConfiguration The Sentinel configuration
* @since 1.6
*/
public CustomLettucePool(RedisSentinelConfiguration sentinelConfiguration) {
this.sentinelConfiguration = sentinelConfiguration;
}
/**
* Uses the {@link RedisClient} defaults for configuring the connection pool
*
* @param hostName The Redis host
* @param port The Redis port
* @param poolConfig The pool {@link GenericObjectPoolConfig}
*/
public CustomLettucePool(String hostName, int port, GenericObjectPoolConfig poolConfig) {
this.hostName = hostName;
this.port = port;
this.poolConfig = poolConfig;
}
/**
* @return true when {@link RedisSentinelConfiguration} is present.
* @since 1.6
*/
public boolean isRedisSentinelAware() {
return sentinelConfiguration != null;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@SuppressWarnings({"rawtypes"})
public void afterPropertiesSet() {
if (clientResources != null) {
this.client = RedisClient.create(clientResources, getRedisURI());
} else {
this.client = RedisClient.create(getRedisURI());
}
/** Custom code **/
this.client.setOptions(ClientOptions.builder()
.autoReconnect(true)
.cancelCommandsOnReconnectFailure(true)
.pingBeforeActivateConnection(true)
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
.suspendReconnectOnProtocolFailure(false)
.socketOptions(SocketOptions.builder().connectTimeout(200, TimeUnit.MILLISECONDS).build())
.build());
client.setDefaultTimeout(timeout, TimeUnit.MILLISECONDS);
this.internalPool = new GenericObjectPool<StatefulConnection<byte[], byte[]>>(new CustomLettucePool.LettuceFactory(client, dbIndex),
poolConfig);
}
/**
* @return a RedisURI pointing either to a single Redis host or containing a set of sentinels.
*/
private RedisURI getRedisURI() {
RedisURI redisUri = isRedisSentinelAware()
? LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration) : createSimpleHostRedisURI();
if (StringUtils.hasText(password)) {
redisUri.setPassword(password);
}
return redisUri;
}
private RedisURI createSimpleHostRedisURI() {
return RedisURI.Builder.redis(hostName, port).withTimeout(timeout, TimeUnit.MILLISECONDS).build();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.Pool#getResource()
*/
@Override
@SuppressWarnings("unchecked")
public StatefulConnection<byte[], byte[]> getResource() {
try {
return internalPool.borrowObject();
} catch (Exception e) {
throw new PoolException("Could not get a resource from the pool", e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.Pool#returnBrokenResource(java.lang.Object)
*/
@Override
public void returnBrokenResource(final StatefulConnection<byte[], byte[]> resource) {
try {
internalPool.invalidateObject(resource);
} catch (Exception e) {
throw new PoolException("Could not invalidate the broken resource", e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.Pool#returnResource(java.lang.Object)
*/
@Override
public void returnResource(final StatefulConnection<byte[], byte[]> resource) {
try {
internalPool.returnObject(resource);
} catch (Exception e) {
throw new PoolException("Could not return the resource to the pool", e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.Pool#destroy()
*/
@Override
public void destroy() {
try {
client.shutdown();
internalPool.close();
} catch (Exception e) {
throw new PoolException("Could not destroy the pool", e);
}
}
/**
* @return The Redis client
*/
public RedisClient getClient() {
return client;
}
/**
* @return The pool configuration
*/
public GenericObjectPoolConfig getPoolConfig() {
return poolConfig;
}
/**
* @param poolConfig The pool configuration to use
*/
public void setPoolConfig(GenericObjectPoolConfig poolConfig) {
this.poolConfig = poolConfig;
}
/**
* Returns the index of the database.
*
* @return Returns the database index
*/
public int getDatabase() {
return dbIndex;
}
/**
* Sets the index of the database used by this connection pool. Default is 0.
*
* @param index database index
*/
public void setDatabase(int index) {
Assert.isTrue(index >= 0, "invalid DB index (a positive index required)");
this.dbIndex = index;
}
/**
* Returns the password used for authenticating with the Redis server.
*
* @return password for authentication
*/
public String getPassword() {
return password;
}
/**
* Sets the password used for authenticating with the Redis server.
*
* @param password the password to set
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Returns the current host.
*
* @return the host
*/
public String getHostName() {
return hostName;
}
/**
* Sets the host.
*
* @param host the host to set
*/
public void setHostName(String host) {
this.hostName = host;
}
/**
* Returns the current port.
*
* @return the port
*/
public int getPort() {
return port;
}
/**
* Sets the port.
*
* @param port the port to set
*/
public void setPort(int port) {
this.port = port;
}
/**
* Returns the connection timeout (in milliseconds).
*
* @return connection timeout
*/
public long getTimeout() {
return timeout;
}
/**
* Sets the connection timeout (in milliseconds).
*
* @param timeout connection timeout
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Get the {@link ClientResources} to reuse infrastructure.
*
* @return {@literal null} if not set.
* @since 1.7
*/
public ClientResources getClientResources() {
return clientResources;
}
/**
* Sets the {@link ClientResources} to reuse the client infrastructure. <br />
* Set to {@literal null} to not share resources.
*
* @param clientResources can be {@literal null}.
* @since 1.7
*/
public void setClientResources(ClientResources clientResources) {
this.clientResources = clientResources;
}
@SuppressWarnings("rawtypes")
private static class LettuceFactory extends BasePooledObjectFactory<StatefulConnection<byte[], byte[]>> {
private final RedisClient client;
private int dbIndex;
public LettuceFactory(RedisClient client, int dbIndex) {
super();
this.client = client;
this.dbIndex = dbIndex;
}
/*
* (non-Javadoc)
* @see org.apache.commons.pool2.BasePooledObjectFactory#activateObject(org.apache.commons.pool2.PooledObject)
*/
@Override
public void activateObject(PooledObject<StatefulConnection<byte[], byte[]>> pooledObject) throws Exception {
if (pooledObject.getObject() instanceof StatefulRedisConnection) {
((StatefulRedisConnection) pooledObject.getObject()).sync().select(dbIndex);
}
}
/*
* (non-Javadoc)
* @see org.apache.commons.pool2.BasePooledObjectFactory#destroyObject(org.apache.commons.pool2.PooledObject)
*/
@Override
public void destroyObject(final PooledObject<StatefulConnection<byte[], byte[]>> obj) throws Exception {
try {
obj.getObject().close();
} catch (Exception e) {
// Errors may happen if returning a broken resource
}
}
/*
* (non-Javadoc)
* @see org.apache.commons.pool2.BasePooledObjectFactory#validateObject(org.apache.commons.pool2.PooledObject)
*/
@Override
public boolean validateObject(final PooledObject<StatefulConnection<byte[], byte[]>> obj) {
try {
if (obj.getObject() instanceof StatefulRedisConnection) {
((StatefulRedisConnection) obj.getObject()).sync().ping();
}
return true;
} catch (Exception e) {
return false;
}
}
/*
* (non-Javadoc)
* @see org.apache.commons.pool2.BasePooledObjectFactory#create()
*/
@Override
public StatefulConnection<byte[], byte[]> create() throws Exception {
return client.connect(ByteArrayCodec.INSTANCE);
}
/*
* (non-Javadoc)
* @see org.apache.commons.pool2.BasePooledObjectFactory#wrap(java.lang.Object)
*/
@Override
public PooledObject<StatefulConnection<byte[], byte[]>> wrap(StatefulConnection<byte[], byte[]> obj) {
return new DefaultPooledObject<StatefulConnection<byte[], byte[]>>(obj);
}
}
}
public class CustomRedisSerializer implements RedisSerializer<Object> {
private final KryoPool kryoPool;
private static final Integer MIN_BUFFER_SIZE=1024;
public CustomRedisSerializer() {
this.kryoPool = new KryoPool.Builder(Kryo::new).build();
}
@Override
public byte[] serialize(Object o) {
ByteBufferOutput output = new ByteBufferOutput(MIN_BUFFER_SIZE, -1); //-1 means maximum possible buffer size on VM.
Kryo kryo = kryoPool.borrow();
try {
kryo.writeClassAndObject(output, o);
} finally {
kryoPool.release(kryo);
output.close();
}
return output.toBytes();
}
@Override
public Object deserialize(byte[] bytes) {
if(bytes.length == 0) {
return null;
}
Kryo kryo = kryoPool.borrow();
Object o;
try {
o = kryo.readClassAndObject(new ByteBufferInput(bytes));
} finally {
kryoPool.release(kryo);
}
return o;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment