Redis Advanced Patterns¶
In-memory data store for caching, sessions, pub/sub, and real-time features.
Redis Data Structures¶
Strings¶
SET key value [EX seconds] [NX|XX]
GET key
MSET key1 value1 key2 value2
MGET key1 key2
INCR counter # Atomic increment
DECR counter # Atomic decrement
INCRBY counter 5 # Increment by 5
APPEND key "text" # Append to string
GETRANGE key 0 5 # Substring
STRLEN key # String length
Lists (FIFO/LIFO)¶
LPUSH list item1 item2 # Push to left (head)
RPUSH list item3 # Push to right (tail)
LPOP list # Pop from left
RPOP list # Pop from right
LLEN list # Length
LRANGE list 0 -1 # Get all items
LINDEX list 0 # Get by index
LTRIM list 0 10 # Keep first 11 items, delete rest (often used)
LINSERT list BEFORE pivot item # Insert before/after pivot
Use Cases: Job queue, activity feed, messaging
Sets (Unique Values, No Order)¶
SADD myset member1 member2 member3
SREM myset member1
SMEMBERS myset # All members
SCARD myset # Count
SISMEMBER myset member1 # Check membership
SINTER set1 set2 # Intersection (common elements)
SUNION set1 set2 # Union (all elements)
SDIFF set1 set2 # Difference (in set1 but not set2)
SRANDMEMBER myset # Random member
SPOP myset # Remove and return random
Use Cases: Unique visitors, followers, tags, deduplication
Sorted Sets (Ordered by Score)¶
ZADD users 100 john 200 jane 150 bob
ZRANGE users 0 -1 # All members in score order
ZREVRANGE users 0 -1 # Reverse order
ZRANGE users 0 -1 WITHSCORES # Include scores
ZRANK users john # Rank (0-indexed)
ZREVRANK users john # Reverse rank
ZCARD users # Count
ZSCORE users john # Score of member
ZINCRBY users 10 john # Increment score
ZCOUNT users 100 200 # Count members in score range
ZRANGEBYSCORE users 100 200 # Members in range
Use Cases: Leaderboards, priority queues, time-series data, rankings
Hashes (Key-Value Pairs)¶
HSET user:1 name john age 30 email john@example.com
HGET user:1 name # Get one field
HMGET user:1 name email # Get multiple fields
HGETALL user:1 # Get all fields
HDEL user:1 age # Delete field
HEXISTS user:1 name # Check field exists
HLEN user:1 # Count fields
HKEYS user:1 # All field names
HVALS user:1 # All values
HINCRBY user:1 age 1 # Increment numeric field
Use Cases: User profiles, session data, object storage
Caching Patterns¶
Cache-Aside (Lazy Loading)¶
Application is responsible for loading cache:
public User getUser(Long id) {
String cacheKey = "user:" + id;
User cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached; // Cache hit
}
// Cache miss → load from DB
User user = userRepository.findById(id).orElse(null);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofHours(1));
}
return user;
}
Pros: Simple, doesn't require cache library
Cons: Cache misses hit DB, inconsistency possible
Write-Through¶
Write to cache and DB simultaneously:
public void updateUser(User user) {
// Write to cache first
redisTemplate.opsForValue().set("user:" + user.getId(), user);
// Then write to DB
userRepository.save(user);
}
Pros: Cache always consistent with DB
Cons: Slower writes, extra latency
Write-Behind (Write-Back)¶
Write to cache immediately, write to DB asynchronously:
public void updateUser(User user) {
// Write to cache immediately (fast)
redisTemplate.opsForValue().set("user:" + user.getId(), user);
// Write to DB asynchronously
asyncService.saveUserToDB(user);
}
Pros: Fast writes, high throughput
Cons: Risk of data loss if cache crashes before DB sync
Advanced Patterns¶
Distributed Locking¶
Prevent multiple processes from accessing same resource:
@Component
public class DistributedLock {
private RedisTemplate<String, String> redisTemplate;
public boolean acquireLock(String key, String value, long timeoutSeconds) {
// Try to SET only if not exists
return redisTemplate.opsForValue().setIfAbsent(
key, value, Duration.ofSeconds(timeoutSeconds)
);
}
public void releaseLock(String key, String value) {
// Only delete if value matches (prevent deleting others' locks)
String current = redisTemplate.opsForValue().get(key);
if (value.equals(current)) {
redisTemplate.delete(key);
}
}
}
// Usage
String lockKey = "order:123:processing";
String lockValue = UUID.randomUUID().toString();
if (distributedLock.acquireLock(lockKey, lockValue, 30)) {
try {
processOrder(123);
} finally {
distributedLock.releaseLock(lockKey, lockValue);
}
} else {
// Lock already held by another process
throw new LockException("Order is being processed");
}
Session Storage¶
Replace HTTP sessions with Redis:
// Sessions automatically stored in Redis
@PostMapping("/login")
public ResponseEntity<String> login(HttpSession session) {
session.setAttribute("userId", user.getId());
session.setAttribute("role", user.getRole());
return ResponseEntity.ok("Logged in");
}
@GetMapping("/profile")
public ResponseEntity<User> getProfile(HttpSession session) {
Long userId = (Long) session.getAttribute("userId");
return userService.findById(userId);
}
Rate Limiting (Sliding Window)¶
Limit requests using Redis counters:
@Component
public class RateLimiter {
private RedisTemplate<String, String> redisTemplate;
public boolean isAllowed(String userId, int maxRequests, int windowSeconds) {
String key = "rate:" + userId;
long now = System.currentTimeMillis() / 1000;
String score = String.valueOf(now);
// Add current request
redisTemplate.opsForZSet().add(key, score, now);
// Remove old requests outside window
redisTemplate.opsForZSet().removeRangeByScore(key, 0, now - windowSeconds);
// Count requests in window
Long requestCount = redisTemplate.opsForZSet().size(key);
// Set expiry on key
redisTemplate.expire(key, Duration.ofSeconds(windowSeconds + 1));
return requestCount <= maxRequests;
}
}
// Usage
@Component
public class RateLimitingInterceptor implements HandlerInterceptor {
private RateLimiter rateLimiter;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String userId = SecurityContextHolder.getContext().getAuthentication().getName();
if (!rateLimiter.isAllowed(userId, 100, 60)) { // 100 requests per minute
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
return false;
}
return true;
}
}
Leaderboard (Top N)¶
Store rankings using sorted sets:
@Component
public class LeaderboardService {
private RedisTemplate<String, Object> redisTemplate;
// Record user score
public void recordScore(String userId, int score) {
redisTemplate.opsForZSet().add("leaderboard", userId, score);
}
// Get top 10 (highest scores first)
public List<ZSetOperations.TypedTuple<Object>> getTopTen() {
return redisTemplate.opsForZSet().reverseRangeWithScores("leaderboard", 0, 9);
}
// Get user rank
public Long getUserRank(String userId) {
return redisTemplate.opsForZSet().reverseRank("leaderboard", userId);
}
}
Pub/Sub (Publish-Subscribe)¶
Real-time messaging:
// Publisher
@Component
public class OrderEventPublisher {
private RedisTemplate<String, String> redisTemplate;
public void publishOrderCreated(Order order) {
String message = objectMapper.writeValueAsString(order);
redisTemplate.convertAndSend("orders:created", message);
}
}
// Subscriber
@Component
public class OrderEventSubscriber {
@Bean
public MessageListenerAdapter orderListener() {
return new MessageListenerAdapter(new OrderMessageListener());
}
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(
orderListener(),
new PatternTopic("orders:*") // Subscribe to orders:created, orders:updated, etc.
);
return container;
}
}
@Component
public class OrderMessageListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String orderJson = new String(message.getBody());
Order order = objectMapper.readValue(orderJson, Order.class);
handleOrderEvent(order);
}
}
Redis Cluster & High Availability¶
Master-Slave Replication¶
Data replicated from master to slaves (read-only):
Sentinel (Automatic Failover)¶
Monitors master, promotes slave if master fails:
# sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
Spring Boot Config:
spring:
redis:
sentinel:
master: mymaster
nodes: 127.0.0.1:26379, 127.0.0.1:26380, 127.0.0.1:26381
timeout: 2000
Redis Cluster (Sharding)¶
Data split across multiple nodes: - Partitioning: Hash slot (0-16383) determines node - Replication: Each slot has master + slaves - Failover: Automatic if master fails
Cluster setup:
redis-server --port 7000 --cluster-enabled yes
redis-server --port 7001 --cluster-enabled yes
...
redis-cli --cluster create 127.0.0.1:7000 ... --cluster-replicas 1
Persistence & Backups¶
RDB (Redis Database)¶
Snapshot of data at point-in-time:
save 900 1 # Save if 1 change in 900 seconds
save 300 10 # Save if 10 changes in 300 seconds
save 60 10000 # Save if 10000 changes in 60 seconds
Pros: Compact file, fast recovery
Cons: Data loss between snapshots
AOF (Append-Only File)¶
Log every write command:
Pros: Minimal data loss, can replay commands
Cons: Larger file size, slower performance
Hybrid Approach¶
Use both RDB + AOF for best safety/performance.
Monitoring & Debugging¶
INFO Command¶
redis-cli INFO server # Server info
redis-cli INFO stats # Statistics (commands, keys, clients)
redis-cli INFO memory # Memory usage
redis-cli INFO clients # Connected clients
SLOWLOG¶
Track slow commands:
redis-cli SLOWLOG GET 10 # Last 10 slow commands
redis-cli SLOWLOG LEN # Count of slow commands
redis-cli SLOWLOG RESET # Clear slowlog
Monitor¶
Real-time command trace:
Memory Analysis¶
redis-cli --memkeys # Memory per key
redis-cli OBJECT ENCODING key # Encoding of value
redis-cli DEBUG OBJECT key # Memory info
Best Practices¶
✅ Do's - Set TTL on cache keys (don't store forever) - Use appropriate data structure (don't store sets as strings) - Monitor memory usage (Redis is in-memory) - Use Redis Cluster for high availability - Implement circuit breaker (if Redis down, gracefully degrade) - Version cache keys when schema changes - Use connection pooling
❌ Don'ts - Don't store sensitive data unencrypted - Don't rely on Redis for durable storage (use DB) - Don't use Redis without TTL (memory leaks) - Don't make Redis calls in loops (use pipeline) - Don't block on operations (use async) - Don't ignore Redis AUTH in production - Don't use Redis for large objects without compression
Spring Boot Integration¶
Dependency¶
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId> <!-- or jedis -->
<artifactId>lettuce-core</artifactId>
</dependency>
Configuration¶
spring:
redis:
host: localhost
port: 6379
password: your-password
timeout: 2000
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
Basic Operations¶
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// String operations
public void set(String key, Object value, long timeout) {
redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeout));
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
// Set operations
public void addToSet(String key, Object... members) {
redisTemplate.opsForSet().add(key, members);
}
public Set<Object> getSet(String key) {
return redisTemplate.opsForSet().members(key);
}
// Hash operations
public void putHash(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public Map<Object, Object> getHash(String key) {
return redisTemplate.opsForHash().getAll(key);
}
}
See also: Caching, AWS ElastiCache, Spring Data Redis