404 Not Found

404 Not Found


nginx

Spring Data Redis

Redis is the Swiss Army knife of caching—with its five data structures—String, Hash, List, Set, and ZSet—it covers everything from caching to sessions to messaging.

1. What You'll Learn


2. A True Story of a Cluster Architect

(1) Pain Point: Caches Across Multiple Instances Are Not Shared

After Alice scaled OrderFlow from a single instance to three instances, issues arose with the Caffeine local cache: Instance A updated the product prices and refreshed the local cache, but the local caches on Instances B and C still contained the old prices, resulting in inconsistent prices displayed to users. Bob tried using Redis to share sessions, but didn't know how to integrate Spring Cache with Redis.

(2) Solution using Spring Data Redis

As a distributed cache, Redis shares the following across all instances:

JAVA
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
    return productRepository.findById(id).orElseThrow();
}
// Cache stored in Redis, shared across all instances

(3) Revenue

After Bob replaced Caffeine with Redis as the distributed cache, the three instances maintained consistent caches, and product price updates were synchronized in real time. At the same time, Redis Pub/Sub was used to implement order event notifications, Redis was used to store the JWT blacklist, and login status was shared across multiple instances.


3. Working with Redis Data Structures

(1) Five Major Data Structures

Type Purpose Typical Scenarios
String Simple key-value pairs Cache, counters, sessions
Hash Object Properties Product Details, User Information
List Ordered list Message queue, recent list
Set Unordered Set Tags, Mutual Friends
ZSet Ordered Set Leaderboards, Latency Queues

(1) ▶ Example: Basic RedisTemplate Operations

JAVA
@Service
public class RedisOperationService {

    private final StringRedisTemplate redis;

    public RedisOperationService(StringRedisTemplate redis) {
        this.redis = redis;
    }

    // String operations
    public void cacheProduct(Long id, String json) {
        redis.opsForValue().set("product:" + id, json, Duration.ofHours(1));
    }

    public String getProduct(Long id) {
        return redis.opsForValue().get("product:" + id);
    }

    // Counter
    public Long incrementOrderCount() {
        return redis.opsForValue().increment("stats:daily-orders");
    }

    // Hash operations
    public void saveProductDetail(Long id, Map<String, String> details) {
        redis.opsForHash().putAll("product:detail:" + id, details);
    }

    public Map<Object, Object> getProductDetail(Long id) {
        return redis.opsForHash().entries("product:detail:" + id);
    }

    // List operations - Recent orders
    public void addRecentOrder(Long orderId) {
        redis.opsForList().leftPush("recent:orders", orderId.toString());
        redis.opsForList().trim("recent:orders", 0, 99);  // Keep last 100
    }

    public List<String> getRecentOrders() {
        return redis.opsForList().range("recent:orders", 0, -1);
    }

    // Set operations - Product tags
    public void addProductTag(Long productId, String tag) {
        redis.opsForSet().add("product:tags:" + productId, tag);
    }

    public Set<String> getProductTags(Long productId) {
        return redis.opsForSet().members("product:tags:" + productId);
    }

    // ZSet operations - Sales ranking
    public void updateSalesRank(Long productId, double salesCount) {
        redis.opsForZSet().add("sales:ranking", productId.toString(), salesCount);
    }

    public List<String> getTopSellingProducts(int limit) {
        return redis.opsForZSet()
            .reverseRange("sales:ranking", 0, limit - 1)
            .stream().toList();
    }
}

Output:

TEXT
// Execution Successful

4. Integrating @Cacheable with Redis

(1) Redis Cache Configuration

(1) ▶ Example: RedisCacheManager Configuration

XML
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Output:

TEXT
// Execution Successful
YAML
spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
JAVA
@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();

        Map<String, RedisCacheConfiguration> cacheConfigs = Map.of(
            "products", config.entryTtl(Duration.ofHours(1)),
            "product-list", config.entryTtl(Duration.ofMinutes(10)),
            "order-stats", config.entryTtl(Duration.ofMinutes(5))
        );

        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withInitialCacheConfigurations(cacheConfigs)
            .transactionAware()
            .build();
    }
}
Lettuce vs. Jedis Lettuce Jedis
Connection Model Asynchronous Non-Blocking (Netty) Synchronous Blocking
Thread-safe Yes (single connection shared by multiple threads) No (requires a connection pool)
Performance Better for high-concurrency scenarios Adequate for simple scenarios
Spring Boot Default ✅ Default Must be switched manually

5. Redis Publish/Subscribe

(1) Pub/Sub Message Model

100%
sequenceDiagram
    participant P as Publisher<br/>(OrderService)
    participant R as Redis<br/>Pub/Sub
    participant S1 as Subscriber 1<br/>(NotificationService)
    participant S2 as Subscriber 2<br/>(AnalyticsService)

    P->>R: PUBLISH order-events "ORDER_CREATED:123"
    R->>S1: Message: "ORDER_CREATED:123"
    R->>S2: Message: "ORDER_CREATED:123"

(1) ▶ Example: Order Placement Event

JAVA
@Service
public class OrderEventPublisher {

    private final StringRedisTemplate redis;

    public OrderEventPublisher(StringRedisTemplate redis) {
        this.redis = redis;
    }

    public void publishOrderCreated(Long orderId) {
        redis.convertAndSend("order-events",
            "ORDER_CREATED:" + orderId);
    }

    public void publishOrderCancelled(Long orderId) {
        redis.convertAndSend("order-events",
            "ORDER_CANCELLED:" + orderId);
    }
}

Output:

TEXT
// Execution Successful

(2) ▶ Example: Subscribing to Order Events

JAVA
@Component
public class OrderEventSubscriber {

    private static final Logger log = LoggerFactory.getLogger(OrderEventSubscriber.class);

    @RedisListener(topic = "order-events")
    public void handleOrderEvent(String message) {
        String[] parts = message.split(":");
        String eventType = parts[0];
        Long orderId = Long.parseLong(parts[1]);

        switch (eventType) {
            case "ORDER_CREATED" -> log.info("Processing order created: {}", orderId);
            case "ORDER_CANCELLED" -> log.info("Processing order cancelled: {}", orderId);
        }
    }
}

@Configuration
public class RedisPubSubConfig {

    @Bean
    public RedisMessageListenerContainer container(
            RedisConnectionFactory factory,
            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(listenerAdapter,
            new ChannelTopic("order-events"));
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(OrderEventSubscriber subscriber) {
        return new MessageListenerAdapter(subscriber, "handleOrderEvent");
    }
}

Output:

TEXT
// Execution Successful

6. Redis Shared Sessions and JWT Blacklists

(1) ▶ Example: JWT Blacklist Implementation

JAVA
@Service
public class TokenBlacklistService {

    private final StringRedisTemplate redis;

    public TokenBlacklistService(StringRedisTemplate redis) {
        this.redis = redis;
    }

    public void blacklistToken(String tokenId, Duration remainingTtl) {
        redis.opsForValue().set(
            "token:blacklist:" + tokenId, "1", remainingTtl);
    }

    public boolean isBlacklisted(String tokenId) {
        return Boolean.TRUE.equals(
            redis.hasKey("token:blacklist:" + tokenId));
    }
}

Output:

TEXT
// Execution Successful

(2) ▶ Example: Distributed Rate Limiting

JAVA
@Service
public class RateLimitService {

    private final StringRedisTemplate redis;

    public RateLimitService(StringRedisTemplate redis) {
        this.redis = redis;
    }

    public boolean isAllowed(String clientId, int maxRequests, Duration window) {
        String key = "rate-limit:" + clientId;
        Long count = redis.opsForValue().increment(key);
        if (count != null && count == 1) {
            redis.expire(key, window);
        }
        return count != null && count <= maxRequests;
    }
}

Output:

TEXT
// Execution Successful
Use Cases Redis Data Structures Key Design
Distributed Cache String / Hash cache:products:123
JWT Blacklist String + TTL token:blacklist:tokenId
Distributed Rate Limiting String + INCR + TTL rate-limit:clientId
Recent Orders List + LPUSH + TRIM recent:orders
Best Sellers ZSet sales:ranking
Event Notification Pub/Sub order-events

7. Comprehensive Example: Complete Integration of OrderFlow and Redis

JAVA
// RedisCacheConfig.java
@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(factory)
            .cacheDefaults(defaults)
            .withInitialCacheConfigurations(Map.of(
                "products", defaults.entryTtl(Duration.ofHours(1)),
                "product-list", defaults.entryTtl(Duration.ofMinutes(10))
            ))
            .build();
    }
}

// ProductService with Redis cache
@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Cacheable(value = "products", key = "#id")
    public Product getProduct(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product", id));
    }

    @CachePut(value = "products", key = "#result.id")
    @CacheEvict(value = "product-list", allEntries = true)
    public Product updateProduct(Long id, String name, BigDecimal price) {
        Product p = getProduct(id);
        p.setName(name);
        p.setPrice(price);
        return productRepository.save(p);
    }
}

// OrderEventPublisher.java
@Service
public class OrderEventPublisher {
    private final StringRedisTemplate redis;
    public OrderEventPublisher(StringRedisTemplate redis) { this.redis = redis; }

    public void publishOrderCreated(Long orderId) {
        redis.convertAndSend("order-events", "ORDER_CREATED:" + orderId);
    }
}
YAML
# application.yml
spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      lettuce:
        pool:
          max-active: 20
          max-idle: 10

❓ FAQ

Q What is the difference between RedisTemplate and StringRedisTemplate?
A StringRedisTemplate inherits from RedisTemplate<String, String>; both keys and values are Strings, and it uses StringRedisSerializer. RedisTemplate uses JDK serialization by default, which is less readable. We recommend using StringRedisTemplate or a custom serializer.
Q How do I choose between Redis caching and Caffeine caching?
A Use Caffeine for a single instance (fastest), and Redis for multiple instances (shared). Best practice: A two-tier cache system with Caffeine as L1 and Redis as L2, where the local cache is short-term (1-5 minutes) and the Redis cache is long-term (30-60 minutes).
Q Can Redis Pub/Sub messages be lost?
A Yes. Pub/Sub operates on a "send-and-forget" model, so messages are lost if subscribers are offline. If you need reliable messaging, use Redis Streams or a dedicated message queue (such as RabbitMQ or Kafka).
Q Does Lettuce require a connection pool?
A Lettuce uses single-connection multiplexing, so a connection pool is generally not required. However, in scenarios involving high-concurrency blocking operations, configuring a connection pool can improve throughput. Jedis must have a connection pool configured.
Q How should I choose a serialization method for Redis caching?
A We recommend JSON serialization (GenericJackson2JsonRedisSerializer) for its readability and cross-language compatibility. JDK serialization is unreadable and not recommended. You can also use StringRedisSerializer combined with manual JSON conversion.
Q How should I handle Redis connection failures?
A 1) By default, Spring Cache does not throw an exception when a failure occurs (it falls back to a database query); 2) Configure CacheErrorHandler to implement custom failure handling; 3) Use Redis Sentinel or Cluster to ensure high availability.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Configure Spring Data Redis for OrderFlow, use @Cacheable to store product cache in Redis, and verify cache sharing across multiple instances.

  2. Advanced Exercise (Difficulty: ⭐⭐): Implement Redis Pub/Sub order event notifications—publish the ORDER_CREATED event when an order is placed, and have the notification service subscribe to it and send an email. Implement a JWT blacklist token revocation feature.

  3. Challenge (Difficulty: ⭐⭐⭐): Implement a two-tier cache system consisting of L1 Caffeine and L2 Redis. When the Redis cache is updated, use Pub/Sub to notify all instances to clear their local Caffeine caches, ensuring consistency across both tiers.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏