关于Redisson RMap Write Through缓存写入顺序及异常处理的技术咨询
First off, let's clarify what's happening here: Redisson's default WRITE_THROUGH mode is designed to update the Redis cache first, then trigger your MapWriter to sync the change to the database. This is intentional per the Write Through pattern definition, but as you've noticed, if the database operation fails, the cache already holds the new value—creating an inconsistency between cache and DB.
To achieve your desired flow (update DB first, then cache only if DB succeeds), here are two reliable approaches:
Approach 1: Manual Control (Simplest & Most Reliable)
The cleanest solution is to bypass Redisson's built-in Write Through entirely and handle the order yourself. This gives you full control over the transaction flow and avoids any unexpected behavior from Redisson's internal logic.
Here's a quick code example:
// Inject your RMap, database service, and any necessary transaction managers @Autowired private RMap<String, YourEntity> yourRMap; @Autowired private YourDbService dbService; public void updateData(String key, YourEntity newValue) { // Wrap in a database transaction if needed (e.g., Spring @Transactional) try { // Step 1: Update the database first dbService.updateEntity(key, newValue); // Step 2: Only update the cache if the DB operation succeeded yourRMap.put(key, newValue); } catch (DbException e) { // Log the failure and propagate the error—cache remains untouched log.error("Failed to update database for key: {}", key, e); throw new ServiceException("Data update failed", e); } }
If you're using Spring, adding the @Transactional annotation to this method will ensure the DB rolls back if any exception occurs, and the cache update is skipped entirely.
Approach 2: Custom MapWriter with Cache Rollback
If you want to keep using Redisson's Write Through infrastructure, you can create a custom MapWriter that rolls back the cache change if the DB operation fails. Note: This approach requires handling concurrency carefully to avoid race conditions.
Here's how you could implement it:
public class RollbackableMapWriter implements MapWriter<String, YourEntity> { private final RMap<String, YourEntity> rMap; private final YourDbService dbService; public RollbackableMapWriter(RMap<String, YourEntity> rMap, YourDbService dbService) { this.rMap = rMap; this.dbService = dbService; } @Override public void write(Map<String, YourEntity> entries) { // Capture the old cache values before proceeding Map<String, YourEntity> oldValues = rMap.getAll(entries.keySet()); try { // Execute the database update dbService.batchUpdate(entries); } catch (DbException e) { // Rollback the cache to its previous state restoreCache(oldValues, entries.keySet()); throw new RuntimeException("Database write failed; cache rolled back", e); } } @Override public void delete(Collection<String> keys) { Map<String, YourEntity> oldValues = rMap.getAll(keys); try { dbService.batchDelete(keys); } catch (DbException e) { restoreCache(oldValues, keys); throw new RuntimeException("Database delete failed; cache rolled back", e); } } private void restoreCache(Map<String, YourEntity> oldValues, Collection<String> keys) { if (!oldValues.isEmpty()) { // Restore existing entries to their previous values rMap.putAll(oldValues); } else { // Remove any newly added keys that didn't exist before rMap.fastRemove(keys.toArray()); } } }
Important Notes for Approach 2:
- Concurrency: To prevent race conditions (e.g., another thread modifying the same key while you're rolling back), wrap the entire write/rollback logic in a Redisson distributed lock for each key.
- Performance: Capturing old values and rolling back adds overhead compared to Approach 1, so only use this if you need to retain Redisson's Write Through integration for other reasons.
内容的提问来源于stack exchange,提问作者Megatron




