Lors de la synchronisation de données avec Redis, le volume peut varier de quelques milliers à des millions d'enregistrements. Un scénario typique implique de transférer des données vers Redis, puis de les exporter dans un fichier pour les clients.
Un jour, le chef de produit a contacté l'ingénieur "Xiao A" : "Je constate que notre processus global de distribution de données prend beaucoup de temps, près d'une heure. Le volume quotidien n'est pas énorme, pourriez-vous identifier les goulots d'étranglement et optimiser cela ?"
Xiao A a analysé la situation : "Actuellement, toutes les opérations Redis sont effectuées après la fin de toutes les tâches. De plus, le code existant n'effectue pas d'insertions groupées, mais traite les données une par une, ce qui ralentit considérablement le processus."
Chef de produit : "Une par une ? Combien de temps cela prendrait-il pour des millions d'anregistrements ? Veuillez l'optimiesr."
Xiao A a examiné le code précédent de synchronisation Redis. Bien qu'il utilisât des techniques telles que le traitement asynchrone, les pools de threads et le traitement par lots, la performance restait médiocre. Voici un aperçu du code avant optimisation :
try {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
for (JSONObject redisSynchro : json) {
// Synchronisation des données vers Redis
redisTemplate.opsForList().rightPush("XXX");
// Définition de la durée d'expiration
redisTemplate.expire();
}
return null;
}
});
} catch (Exception e) {
log.info("Anomalie lors de la synchronisation Redis : " + e.getMessage());
} finally {
countDownLatch.countDown();
}
Xiao A a recherché des méthodes pour le traitement groupé des données dans Redis et a découvert l'opération "pipeline". Le code utilisait déjà executePipelined, mais les performances n'étaient pas optimales. Il a approfondi ses recherches sur le fonctionnement interne de executePipelined.
- Pipeline : C'est un mécanisme d'optimisation fourni par Redis. Il permet au client d'envoyer plusieurs commandes à la fois au serveur Redis, au lieu d'envoyer une commande et d'attendre la réponse pour chaque opération. Cela réduit la latence réseau et améliore l'efficacité.
- La méthode
executePipelined()vise à encapsuler plusieurs opérations Redis pour une exécution groupée via un pipeline, et retourne les résultats de toutes les opérations. - Le processus : Le client envoie des commandes groupées → Le serveur exécute les commandes groupées → Le client reçoit tous les résultats en une seule fois.
Pourquoi l'exécution initiale n'utilisait-elle pas pleinement le pipeline ?
- Abstration trop élevée : Dans
executePipelined, les méthodes deredisTemplateétaient utilisées au lieu d'interagir directement avec laRedisConnectionpour exécuter les commandes Redis de bas niveau. - Commandes dispersées : Chaque appel à
rightPushetexpireétait indépendant. Même au sein d'une même méthodeexecutePipelined, ces opérations n'étaient pas optimisées par le mécanisme de pipeline natif de Redis.
Pour utiliser efficacement le pipeline Redis, il faut éviter les abstractions de haut niveau de redisTemplate. Xiao A a donc modifié la méthode d'exécution comme suit :
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
StringRedisSerializer serializer = new StringRedisSerializer();
for (ResultInfo result : tmps) {
// Assemblage des données clé/valeur
// Vérification de l'existence de l'élément
if (conditionDeVérification) {
// Si inexistant, insertion
connection.rPush(serializer.serialize(key), serializer.serialize(value));
}
// Définition de la durée d'expiration
connection.expire(serializer.serialize(key), 30 * 24 * 3600L);
}
return null;
});
Cette approche utilise StringRedisSerializer pour sérialiser les clés et les valeurs. En interagissant directement avec la connexion Redis de bas niveau, elle est généralement adoptée aux scénarios de haute performance.
Après des tests concrets, l'insertion de 130 000 enregistrements a pris 3 secondes, et 90 000 enregistrements ont été traités en 1 seconde.