Maison >Java >javaDidacticiel >Quel est le schéma de cohérence des données de base de données et de cache pour la programmation simultanée Java ?
Dans les systèmes simultanés distribués, la cohérence des données des bases de données et du cache est une difficulté technique difficile. En supposant qu'il existe une solution complète de transactions distribuées de qualité industrielle, la cohérence des données de la base de données et du cache sera facilement résolue. En fait, les transactions distribuées sont actuellement immatures.
Dans la solution de cohérence des données de base de données et de cache, il existe différentes voix.
Opération base de données d'abord, puis cache ou cache d'abord, puis base de données
Le cache doit-il être mis à jour ou supprimé
Dans un système simultané, dans le scénario de double écriture. de la base de données et du cache, afin de poursuivre Avec une plus grande concurrence, l'exploitation de la base de données et du cache ne se fera évidemment pas simultanément. La première opération réussit et la seconde est effectuée de manière asynchrone.
En tant que solution de stockage de données mature de qualité industrielle, la base de données relationnelle dispose d'un mécanisme complet de traitement des transactions. Une fois les données placées sur le disque, quelle que soit la panne matérielle, on peut affirmer de manière responsable que les données ne seront pas perdues.
Le soi-disant cache n'est rien de plus que des données stockées en mémoire. Une fois le service redémarré, toutes les données mises en cache seront perdues. Comme cela s’appelle la mise en cache, préparez-vous à tout moment à la perte des données mises en cache. Bien que Redis dispose d'un mécanisme de persistance, peut-il garantir une persistance à 100 % ? Redis conserve les données de manière asynchrone sur le disque. Le cache est un cache et la base de données est une base de données. Ce sont deux choses différentes. Utiliser un cache comme base de données est extrêmement dangereux.
Du point de vue de la sécurité des données, la base de données est exploitée en premier, puis le cache est exploité de manière asynchrone pour répondre aux demandes des utilisateurs.
Que le cache soit mis à jour ou supprimé, correspond au style paresseux et au style complet. Du point de vue des pratiques de sécurité des threads, la suppression de l'opération de cache est relativement difficile. Si les performances des requêtes sont satisfaites dans le cadre de la suppression du cache, la suppression du cache est alors préférable.
Bien que la mise à jour du cache puisse améliorer l'efficacité des requêtes, les données sales simultanées causées par les threads sont plus difficiles à traiter. La préface introduit d'autres middleware de messages tels que MQ, elle n'est donc pas recommandée sauf si nécessaire.
La clé pour comprendre les problèmes causés par la concurrence des threads est d'abord de comprendre les interruptions du système Lorsque le système d'exploitation planifie des tâches, des interruptions se produisent à tout moment. En prenant comme exemple les processeurs à 4 et 8 threads, jusqu'à 8 threads peuvent être traités en même temps. Cependant, le système d'exploitation gère bien plus de 8 threads, de sorte que les threads se déroulent de manière apparemment parallèle.
Dans un environnement non simultané, il n'y a rien de mal à interroger les données de la manière suivante : interrogez d'abord le cache. Si les données mises en cache n'existent pas, interrogez la base de données, mettre à jour le cache et renvoyer les résultats.
public BuOrder getOrder(Long orderId) { String key = ORDER_KEY_PREFIX + orderId; BuOrder buOrder = RedisUtils.getObject(key, BuOrder.class); if (buOrder != null) { return buOrder; } BuOrder order = getById(orderId); RedisUtils.setObject(key, order, 5, TimeUnit.MINUTES); return order; }
S'il y a une faille grave dans un environnement à haute concurrence : lorsque le cache échoue, un grand nombre de requêtes de requêtes affluent, toutes atteignant la base de données en un instant. Au moins, les ressources de connexion à la base de données sont épuisées, le. le client répond avec une erreur 500 et, dans le pire des cas, la base de données est sous pression. Panne de service excessive.
Par conséquent, dans un environnement concurrent, le code ci-dessus doit être modifié et des verrous distribués sont utilisés. Lorsqu'un grand nombre de requêtes affluent, le thread qui obtient le verrou a la possibilité d'accéder à la base de données pour interroger les données, et les threads restants sont bloqués. Lorsque les données sont interrogées et que le cache est mis à jour, le verrou est libéré. Le thread en attente revérifie le cache et constate que les données peuvent être obtenues, et répond directement aux données mises en cache.
Les verrous distribués sont mentionnés ici, devons-nous donc utiliser des verrous de table ou des verrous de lignes ? Utilisez des verrous de ligne distribués pour augmenter la simultanéité ; utilisez un mécanisme de vérification secondaire pour garantir que les threads en attente d'obtenir le verrou peuvent renvoyer rapidement des résultats
@Override public BuOrder getOrder(Long orderId) { /* 如果缓存不存在,则添加分布式锁更新缓存 */ String key = ORDER_KEY_PREFIX + orderId; BuOrder order = RedisUtils.getObject(key, BuOrder.class); if (order != null) { return order; } String orderLock = ORDER_LOCK + orderId; RLock lock = redissonClient.getLock(orderLock); if (lock.tryLock()) { order = RedisUtils.getObject(key, BuOrder.class); if (order != null) { LockOptional.ofNullable(lock).ifLocked(RLock::unlock); return order; } BuOrder buOrder = getById(orderId); RedisUtils.setObject(key, buOrder, 5, TimeUnit.MINUTES); LockOptional.ofNullable(lock).ifLocked(RLock::unlock); } return RedisUtils.getObject(key, BuOrder.class); }
public Boolean editOrder(BuOrder order) { /* 更新数据库 */ updateById(order); /* 删除缓存 */ RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId()); return true; }
2. Environnement simultané
L'utilisation de verrous distribués peut garantir que le trafic simultané accède à la base de données de manière ordonnée. Étant donné que le verrouillage optimiste a été utilisé au niveau de la base de données, le deuxième thread et les suivants qui obtiennent le verrou exploitent la base de données comme un trafic non valide.
Le thread adopte une stratégie de sortie de délai d'attente lors de l'acquisition du verrou. Le thread en attente du verrou expirera et se terminera rapidement, répondra rapidement aux demandes de l'utilisateur et réessayera l'opération de mise à jour des données.
public Boolean editOrder(BuOrder order) { String orderLock = ORDER_LOCK + order.getOrderId(); RLock lock = redissonClient.getLock(orderLock); try { /* 超时未获取到锁,快速失败,用户端重试 */ if (lock.tryLock(1, TimeUnit.SECONDS)) { /* 更新数据库 */ updateById(order); /* 删除缓存 */ RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId()); /* 释放锁 */ LockOptional.ofNullable(lock).ifLocked(RLock::unlock); return true; } } catch (InterruptedException e) { e.printStackTrace(); } return false; }
Dépend de l'environnement
<dependency> <groupId>xin.altitude.cms</groupId> <artifactId>ucode-cms-common</artifactId> <version>1.4.3.2</version> </dependency>Effectuez les opérations ultérieures en fonction de l'état du verrou.
LockOptional
4. Base de données d'abord, puis cache
(1) Le cache vient d'expirer
(2) Demande à A d'interroger la base de données et d'obtenir une ancienne valeur (3) Demande à B d'écrire la nouvelle valeur dans la base de données
(4) Demande à B de supprimer le cache
( 5) La requête A trouvera L'ancienne valeur est écrite dans le cache
La clé du problème de concurrence ci-dessus est que l'étape 5 se produit après les étapes 3 et 4. Il ressort des facteurs incertains d'interruption du système d'exploitation que cette situation peut se produire.
D'après la situation réelle, l'écriture de données dans Redis est beaucoup plus courte que l'écriture de données dans la base de données, bien que la probabilité d'occurrence soit faible, cela se produira quand même.
(1) Augmentez le délai d'expiration du cache
L'augmentation du délai d'expiration du cache permet aux données sales d'exister dans une certaine plage de temps jusqu'à ce que la prochaine mise à jour simultanée se produise, et des données sales peuvent apparaître. Des données sales existent périodiquement.
(2) Les mises à jour et les requêtes partagent un verrou de ligne
Les mises à jour et les requêtes partagent un verrou distribué de ligne, et les problèmes ci-dessus n'existent plus. Lorsque la demande de lecture acquiert le verrou, la demande d'écriture est dans un état bloqué (le délai d'attente échouera et reviendra rapidement), garantissant que l'étape 5 est effectuée avant l'étape 3.
(3) Retarder la suppression du cache
Utilisez RabbitMQ pour retarder la suppression du cache afin de supprimer l'impact de l'étape 5. L'utilisation d'une méthode asynchrone n'a quasiment aucun impact sur les performances.
La base de données dispose d'un mécanisme de transaction pour garantir le succès de l'opération : une seule instruction Redis est atomique, mais la combinaison n'a pas de caractéristiques atomiques. Plus précisément, l'opération de la base de données réussit, puis l'application se bloque anormalement. , provoquant l'échec de la suppression du cache Redis. Ce problème se produit lorsque la connexion réseau du service Redis expire.
Si un délai d'expiration du cache est défini, les données sales existeront toujours avant l'expiration du cache. Si le délai d'expiration n'est pas défini, les données sales existeront jusqu'à la prochaine modification des données. (Les données de la base de données ont changé et le cache n'a pas encore été mis à jour)
Avant d'utiliser la base de données, écrivez un message de suppression différée du cache sur RabbitMQ, puis effectuez l'opération de base de données et effectuez l'opération de suppression du cache. Que le cache au niveau du code soit supprimé avec succès ou non, MQ supprime le cache en tant qu'opération garantie.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!