Last active
April 7, 2024 15:51
-
-
Save ugur93/4e047c03c0d152d245e391d70788829a to your computer and use it in GitHub Desktop.
Example of Spring Redis Cache Configuration using Spring-Data-Redis Lettuce Driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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) { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* 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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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