You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Java在不重启服务器且无数据库依赖的情况下刷新缓存

Database-Agnostic Java Cache Refresh Strategies

Awesome question! When you're moving away from database-specific features like DatabaseChangeNotification (DCN) and need a database-agnostic way to refresh caches in Java, there are several solid strategies to choose from. Let's break down the most practical ones with examples:

1. Event-Driven Cache Invalidation/Update (Direct Integration with Business Logic)

This is the most straightforward approach: whenever you modify data (create/update/delete), explicitly trigger a cache refresh or eviction right in your business code. It keeps cache and database state in sync with minimal delay.

Example (with Spring + Redis):

@Service
public class UserService {
    private final UserRepository userRepo;
    private final RedisTemplate<String, User> redisTemplate;

    // Constructor injection (preferred for testability)
    public UserService(UserRepository userRepo, RedisTemplate<String, User> redisTemplate) {
        this.userRepo = userRepo;
        this.redisTemplate = redisTemplate;
    }

    public User updateUser(User user) {
        // First update the database
        User updatedUser = userRepo.save(user);
        
        // Then refresh the cache: either delete the old entry or overwrite with new data
        String cacheKey = "user:" + updatedUser.getId();
        redisTemplate.delete(cacheKey);
        // Or directly set the new value: redisTemplate.opsForValue().set(cacheKey, updatedUser);
        
        return updatedUser;
    }
}

Pros: Simple, immediate consistency, full control over cache behavior.
Cons: Tight coupling between business logic and cache code; easy to miss cache updates if data is modified from multiple places.

2. Lazy Loading + TTL (Time-To-Live)

With this approach, you don't proactively refresh cache. Instead:

  • Set an expiration time (TTL) on cache entries.
  • When a cache entry expires or is missing, fetch fresh data from the database and repopulate the cache.

Add a distributed lock to prevent "thundering herd" issues (multiple threads hitting the database at once for the same missing entry).

Example:

public User getUserById(Long id) {
    String cacheKey = "user:" + id;
    User user = redisTemplate.opsForValue().get(cacheKey);

    if (user == null) {
        // Use a distributed lock to avoid concurrent database calls
        try (RedisLock lock = redisLockFactory.getLock("user-lock:" + id)) {
            if (lock.tryLock(Duration.ofSeconds(3))) {
                // Double-check cache in case another thread already populated it
                user = redisTemplate.opsForValue().get(cacheKey);
                if (user == null) {
                    // Fetch fresh data from DB
                    user = userRepo.findById(id)
                            .orElseThrow(() -> new RuntimeException("User not found"));
                    // Save to cache with TTL (e.g., 1 hour)
                    redisTemplate.opsForValue().set(cacheKey, user, Duration.ofHours(1));
                }
            }
        }
    }
    return user;
}

Pros: Database-agnostic, minimal code overhead, avoids unnecessary refreshes.
Cons: Short window of potential stale data (before TTL expires); requires handling cache edge cases like thundering herds.

3. Asynchronous Refresh via Message Queue

Decouple cache updates from your main business flow by using a message queue (MQ) like Kafka or RabbitMQ. When data is modified, send an event to the MQ; a separate consumer service then listens for these events and refreshes the cache.

Example (Kafka):

Business Service (Producer):

@Service
public class UserService {
    private final UserRepository userRepo;
    private final KafkaTemplate<String, Long> kafkaTemplate;

    public UserService(UserRepository userRepo, KafkaTemplate<String, Long> kafkaTemplate) {
        this.userRepo = userRepo;
        this.kafkaTemplate = kafkaTemplate;
    }

    public User updateUser(User user) {
        User updatedUser = userRepo.save(user);
        // Send event to trigger cache refresh
        kafkaTemplate.send("cache-refresh-user", updatedUser.getId());
        return updatedUser;
    }
}

Cache Refresh Consumer:

@Component
public class CacheRefreshConsumer {
    private final UserRepository userRepo;
    private final RedisTemplate<String, User> redisTemplate;

    public CacheRefreshConsumer(UserRepository userRepo, RedisTemplate<String, User> redisTemplate) {
        this.userRepo = userRepo;
        this.redisTemplate = redisTemplate;
    }

    @KafkaListener(topics = "cache-refresh-user", groupId = "cache-refresh-group")
    public void handleUserCacheRefresh(Long userId) {
        User user = userRepo.findById(userId).orElse(null);
        String cacheKey = "user:" + userId;
        
        if (user != null) {
            redisTemplate.opsForValue().set(cacheKey, user);
        } else {
            redisTemplate.delete(cacheKey); // Clean up cache for deleted users
        }
    }
}

Pros: Complete decoupling of business and cache logic; async processing doesn't block main workflows.
Cons: Adds infrastructure complexity (managing MQ); requires handling message reliability (avoid lost events causing cache inconsistency).

4. Annotation-Driven Cache Management (Spring Cache)

If you're using the Spring ecosystem, leverage Spring Cache's annotations to handle cache operations without writing explicit cache code. This works with any cache provider (Redis, Caffeine, etc.) and is fully database-agnostic.

Example:

@Service
public class UserService {
    private final UserRepository userRepo;

    public UserService(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    // Fetch from cache first; if missing, hit DB and populate cache
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        return userRepo.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
    }

    // Update DB and refresh cache with new value
    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepo.save(user);
    }

    // Delete DB entry and evict from cache
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepo.deleteById(id);
    }
}

Pros: Clean, declarative code; easy to switch cache providers; minimal boilerplate.
Cons: Less control over low-level cache behavior compared to manual implementations.

5. Scheduled Batch Refresh

For static or semi-static data that doesn't change frequently, use a scheduled task to periodically refresh the cache. This works well for datasets like reference tables (e.g., country lists, product categories).

Example (Spring @Scheduled):

@Service
public class CacheRefreshScheduler {
    private final UserRepository userRepo;
    private final RedisTemplate<String, User> redisTemplate;

    public CacheRefreshScheduler(UserRepository userRepo, RedisTemplate<String, User> redisTemplate) {
        this.userRepo = userRepo;
        this.redisTemplate = redisTemplate;
    }

    // Run every day at 2 AM to refresh user cache
    @Scheduled(cron = "0 0 2 * * ?")
    public void refreshUserCache() {
        List<User> users = userRepo.findAll();
        users.forEach(user -> {
            String cacheKey = "user:" + user.getId();
            redisTemplate.opsForValue().set(cacheKey, user, Duration.ofHours(24));
        });
    }
}

Pros: Simple to implement; works well for infrequently changing data.
Cons: Potential for stale data between refreshes; can be resource-intensive if refreshing large datasets.


Which One to Choose?

  • Need strong consistency: Go with event-driven invalidation or Spring Cache's @CachePut.
  • Can tolerate eventual consistency: Lazy loading + TTL or MQ-based async refresh.
  • Static/semi-static data: Scheduled batch refresh.
  • Spring ecosystem: Annotation-driven Spring Cache is the most maintainable option.

内容的提问来源于stack exchange,提问作者Divya Reddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:42:41