Stratégie de double suppression différée pour la cohérence du cache Redis

Dans les environnements à forte concurernce, la synchronisation entre les mises à jour de la base de données et le rafraîchissement du cache Redis peut échouer. Par exemple, si les opérations se déroulent dans l'ordre 1, 3, 4, 2 au lieu de 1, 2, 3, 4, cela conduit à des incohérences entre la base de données et le cache.

Pour résoudre ce problème, on implémente une stratégie de double suppression différée. L'idée est de supprimer le cache avant l'opération de base de données, puis de le supprimer à nouveau après un délai pour couvrir les cas où une lecture intermédiaire pourrait recharger le cache avec des données obsolètes.

Voici un exemple de code illustrant cette approche avec Spring Boot et Redis.

Premièrement, une méthode contrôleur pour mettre à jour les données, annotée pour déclencher la suppression du cache :


@RequestMapping(value = "/personnel/modifier", method = RequestMethod.POST)
@ViderCacheDiffere(nomCle = "personnel")
public ResponseEntity<Void> mettreAJourPersonnel() {
    Personnel personnel = new Personnel();
    personnel.setIdPersonnel(42L);
    personnel.setDateEmbauche(new Date());
    personnelService.mettreAJour(personnel);
    return ResponseEntity.ok().build();
}

Ensuite, l'annotation personnalisée pour marquer les méthodes nécessitant la suppression du cache :


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ViderCacheDiffere {
    String nomCle() default "";
}

Enfin, l'aspect AOP qui gère la logique de double suppression différée :


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
public class AspectGestionCacheDiffere {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private final ScheduledExecutorService planificateur = Executors.newScheduledThreadPool(2);

    @Pointcut("@annotation(com.exemple.redis.ViderCacheDiffere)")
    public void cibleAnnotation() {}

    @Around("cibleAnnotation()")
    public Object gererCache(ProceedingJoinPoint pointJonction) throws Throwable {
        MethodSignature signature = (MethodSignature) pointJonction.getSignature();
        ViderCacheDiffere annotation = signature.getMethod().getAnnotation(ViderCacheDiffere.class);
        Object resultat = null;

        if (annotation != null) {
            String prefixeCle = annotation.nomCle();
            Set<String> clesTrouvees = redisTemplate.keys("*" + prefixeCle + "*");
            if (clesTrouvees != null && !clesTrouvees.isEmpty()) {
                redisTemplate.delete(clesTrouvees);
            }

            resultat = pointJonction.proceed();

            Runnable tacheSecondaire = () -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                    Set<String> clesSecondaires = redisTemplate.keys("*" + prefixeCle + "*");
                    if (clesSecondaires != null && !clesSecondaires.isEmpty()) {
                        redisTemplate.delete(clesSecondaires);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            };
            planificateur.schedule(tacheSecondaire, 1, TimeUnit.SECONDS);
        } else {
            resultat = pointJonction.proceed();
        }
        return resultat;
    }
}

Étiquettes: Redis Spring Boot AOP Cohérence du cache Suppression double retardée

Publié le 1 juin à 13h23