Maison  >  Article  >  base de données  >  Comment utiliser Redis pour résoudre une concurrence élevée

Comment utiliser Redis pour résoudre une concurrence élevée

WBOY
WBOYavant
2023-06-03 15:43:332555parcourir

NoSQL

Abréviation de Not Only SQL. NoSQL a été proposé pour résoudre l'incapacité des SGBDR traditionnels à résoudre certains problèmes.

C'est-à-dire que les bases de données non relationnelles ne garantissent pas les caractéristiques ACID des données relationnelles. Elles sont généralement très faciles à mettre en œuvre en termes d'expansion et ont des performances élevées.

Redis

redis est un représentant typique de nosql et une technologie incontournable pour les sociétés Internet actuelles.

Redis utilise principalement des tables de hachage pour implémenter le stockage de paires clé-valeur. La plupart du temps, il est utilisé directement sous forme de cache, de sorte que la requête n'accède pas directement au disque, donc l'efficacité est très bonne, et il peut pleinement répondre aux besoins des petites et moyennes entreprises. "Types de données courants"

La fréquence d'utilisation sur la chaîne et le hachage seront plus élevés. Chaque type a sa propre commande d'opération, qui n'est rien d'autre que l'ajout, la suppression, la modification et la vérification. Je trierai les commandes spécifiques plus tard. Points douloureux

    Lorsque de nombreuses requêtes se produisent simultanément dans des applications Web, cela peut provoquer des erreurs de lecture et de stockage des données, c'est-à-dire des lectures et une génération de données sales.
  • Dans les projets distribués, davantage de problèmes surgiront.

  • Penser
  • Pendant la simultanéité, l'essence est que plusieurs demandes arrivent en même temps et ne peuvent pas être traitées correctement.

  • Vous pouvez mettre toutes les requêtes dans une file d'attente, afin que les requêtes puissent arriver une par une afin d'exécuter la logique métier. L'utilisation de files d'attente de messages est actuellement une solution réalisable.Je compilerai un article sur la façon de gérer les files d'attente de messages à haute concurrence la prochaine fois
  • Une autre méthode consiste à convertir directement le parallélisme en sérialisation. Java fournit une synchronisation, c'est-à-dire une synchronisation, mais cette solution est. ne convient pas aux endroits ayant des exigences d'efficacité strictes ou aux projets distribués. Cela conduit à l'utilisation de Redis pour implémenter des verrous distribués afin de résoudre les problèmes de concurrence.

  • Verrouillage distribué
  • Dans les projets distribués, un identifiant unique, universel et efficace est utilisé pour représenter le verrouillage et le déverrouillage.

  • Redis est très simple à mettre en œuvre, c'est-à-dire qu'une clé existe ou non indique si elle est verrouillée ou déverrouillée.
  • Prenons le type de chaîne comme exemple :

    Integer stock = goodsMapper.getStock();
    if (stock > 0) {
        stock =- 1;
        goodsMapper.updateStock(stock);
    }
  • Ce qui précède est le pseudo-code le plus simple pour la suppression instantanée. Nous essayons d'utiliser redis pour implémenter des verrous distribués.
// 这里是错误代码,只是一个思考过程,请耐心看完哦
String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
String value = jedisUtils.get(key);
if (value != null) { // 未上锁
    // wingzingliu
    jedisUtils.set(key, 1); // 上锁
    Integer stock = goodsMapper.getStock();
    if (stock > 0) {
        stock =- 1;
        goodsMapper.updateStock(stock);
        jedisUtils.del(key); // 释放锁
    }
}

Il peut y avoir un problème avec le code ci-dessus, c'est-à-dire que lorsque plusieurs requêtes arrivent en même temps et que plusieurs requêtes à un certain moment obtiennent toutes la valeur vide, le thread A entre le if et va à // wingzingliu, il n'est pas encore verrouillé, d'autres demandes arrivent également, donc des données sales apparaîtront.

Le problème du code ici est que la question de l'atomicité n'est pas prise en compte.

Nous devons donc utiliser une commande setNx de redis, qui définit essentiellement une valeur, mais il s'agit d'une opération atomique. Après l'exécution, elle indiquera si le réglage est réussi.

redis> SETNX job "programmer"    # job 设置成功
(integer) 1
 
redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0
 
redis> GET job                   # 没有被覆盖
"programmer"

Concentrez-vous sur le moment où il y a une valeur, elle échouera et renverra 0. Notre code sera donc transformé comme suit.

// 这里是错误代码,只是一个思考过程,请耐心看完哦
String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
Long result = jedisUtils.setNx(key, 1);
if (result > 0) { // 上锁成功,进入逻辑
    // wingzingliu1
    Integer stock = goodsMapper.getStock();
    if (stock > 0) {
        stock =- 1;
        goodsMapper.updateStock(stock);
 
        System.out.println("购买成功!");
    } else {
        System.out.println("没有库存了!");
    }
    // wingzingliu2
    jedisUtils.del(key); // 释放锁
}

Avec ce qui précède, nous pouvons garantir l'atomicité et les traiter correctement dans l'ordre.

Mais il y a un autre problème caché, c'est-à-dire qu'après qu'un thread exécute avec succès le verrou, le programme lève une exception entre wingzingliu1 et wingzingliu2. Ensuite, le programme se termine et le verrou ne peut pas être libéré et les autres threads ne peuvent pas entrer.

La solution est d'ajouter un bloc try catch enfin et de libérer enfin le verrou.

Mais que se passe-t-il si c'est un temps d'arrêt ? Une fois le verrou verrouillé, l'ordinateur plante et le contenu du fichier final ne sera toujours pas exécuté. Sans traitement manuel, tous les threads ne pourront pas entrer à l'avenir.

Le délai d'expiration de redis est donc introduit, et il sera automatiquement déverrouillé à un certain moment.

// 这里是不够完善的代码,请耐心看完哦
try {
    String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
    Long result = jedisUtils.setNx(key, 1, 30); // 假设处理逻辑需要20s左右,设置了30秒自动过期
    if (result > 0) { // 上锁成功,进入逻辑
        Integer stock = goodsMapper.getStock();
        if (stock > 0) {
            stock =- 1;
            goodsMapper.updateStock(stock);
 
            System.out.println("购买成功!");
        } else {
            System.out.println("没有库存了!");
        }
    }
} catch (Exception e) {
    
} finally {
    jedisUtils.del(key); // 释放锁
}

Ce qui précède est un verrou distribué relativement complet, mais il y a encore un petit défaut. On suppose qu'une certaine requête A est traitée très lentement, mais cela prend 35 secondes lorsque le verrou expire dans 30 secondes. , d'autres demandes arriveront naturellement.

Cela entraîne non seulement une exécution simultanée, mais continue également à libérer le verrou après le traitement de la requête A, transmettant ainsi le verrou au thread suivant. Par analogie, tout le contrôle de concurrence sera gâché.

Théoriquement, vous pouvez définir un délai d'expiration de clé plus long, mais ce n'est pas la meilleure solution. Voici un concept : verrouiller la vie.

Le verrou prolonge la vie

Comme son nom l'indique, le verrou prolonge la vie. L'implémentation consiste à prolonger la durée du verrouillage lorsque le verrou est sur le point d'expirer. Supposons qu'un verrou de 30 secondes soit utilisé, avec une vérification toutes les 10 secondes pour voir si le verrou existe toujours. Si le verrou existe toujours, conservez-le pendant 30 secondes. Cela évite le problème possible ci-dessus.

Une tâche planifiée est utilisée ici et peut être appelée périodiquement.

Extension

La valeur que vous venez de définir pour la clé est 1. En fait, l'ID de la requête peut être utilisé pour l'enregistrer, afin que vous puissiez savoir de quelle requête provient le verrou et que vous puissiez éviter de déverrouiller le verrou sur d'autres threads. lors du déverrouillage. Il peut être transmis par le front-end ou généré par le serveur selon certaines règles.

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer