Maison  >  Article  >  base de données  >  Comment utiliser Go et Lua pour résoudre les problèmes d'inventaire et de survente dans la vente flash Redis

Comment utiliser Go et Lua pour résoudre les problèmes d'inventaire et de survente dans la vente flash Redis

王林
王林avant
2023-05-26 15:57:411021parcourir

0. Introduction

  • Le langage Go connecte go-redis pour vous connecter à la base de données. Si vous ne comprenez pas encore cette partie, il est recommandé d'apprendre d'abord cette partie des connaissances.

  • De plus, cette vente flash résout principalement deux problèmes, le premier est le problème de survente, et l'autre est le problème des stocks.

  • Il n'y a pas de page spéciale conçue pour simuler la concurrence. Nous utilisons directement gorountine et restons 10 secondes avant d'appeler la requête.

  • Pour résoudre le problème de survente, introduisez simplement la montre go-redis avec traitement des transactions [équivalent au verrouillage optimiste].

Le problème d'inventaire est un peu plus gênant. Vous devez utiliser Lua pour modifier le script, mais vous n'avez pas besoin de télécharger l'environnement de compilation Lua sur votre propre machine. Go fournit le support associé. Pour cette partie, pas de panique, la structure de base est la suivante :

Comment utiliser Go et Lua pour résoudre les problèmes dinventaire et de survente dans la vente flash Redis

1 Version simple

Dans les situations de concurrence, des valeurs survendues et négatives peuvent apparaître dans la base de données Redis. Même si vous portez des jugements sur les données avant d'opérer.

func MsCode(uuid, prodid string) bool {  
   // 1、对uuid和prodid进行非空判断  
   if uuid == "" || prodid == "" {  
      return false  
   }  
  
   //2、获取连接  
   rdb := DB  
  
   //3、拼接key  
   kcKey := "kc:" + prodid + ":qt"  
   userKey := "sk:" + prodid + ":user"  
  
   //4、获取库存  
   str, err := rdb.Get(ctx, kcKey).Result()  
   if err != nil {  
      fmt.Println(err)  
      fmt.Println("秒杀还未开始.......")  
      return false  
   }  
  
   // 5、判断用户是否重复秒杀操作  
   flag, err := rdb.SIsMember(ctx, userKey, userKey).Result()  
   if err != nil {  
      fmt.Println(err)  
   }  
   if flag {  
      fmt.Println("你已经参加了秒杀,无法再次参加。。。。")  
      return false  
   }  
  
   // 6、判断商品数量,如果库存数量小于1,秒杀结束  
   str, err = rdb.Get(ctx, kcKey).Result()  
   if err != nil {  
      fmt.Println(err)  
   }  
   n, err := strconv.Atoi(str)  
   if err != nil {  
      fmt.Println(err)  
   }  
   if n < 1 {  
      fmt.Println("秒杀结束,请下次再来吧。。。。")  
      return false  
   }  
  
   // 7、秒杀过程  
   // 7.1、库存减1  
   num, err := rdb.Decr(ctx, kcKey).Result()  
   if err != nil {  
      fmt.Println(err)  
   }  
   if num != 0 {  
      // 7.2、添加用户  
      rdb.SAdd(ctx, userKey, uuid)  
   }  
   return true  
}

func main() {
    // 并发的版本
    for i := 0; i < 20; i++ {
        go func() {
            uuid := GenerateUUID()
            prodid := "1023"
            time.Sleep(10 * time.Second)
            MsCode(uuid, prodid)
        }()
    }
    time.Sleep(15 * time.Second)
}

2. Résolvez la survente

Utilisez la montre pour surveiller la clé. Les éléments clés sont les suivants. Cependant, cette situation posera un problème. Même s’il reste des stocks, il y aura des gens qui ne pourront pas les acheter.

err = rdb.Watch(ctx, func(tx *redis.Tx) error {  
   n, err := tx.Get(ctx, kcKey).Int()  
   if err != nil && err != redis.Nil {  
      return err  
   }  
   if n <= 0 {  
      return fmt.Errorf("抢购结束了!请下次早点来。。。。")  
   }  
   _, err = tx.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {  
      err := pipeliner.Decr(ctx, kcKey).Err()  
      if err != nil {  
         return err  
      }  
      err = pipeliner.SAdd(ctx, userKey, uuid).Err()  
      if err != nil {  
         return err  
      }  
      return nil  
   })  
   return err  
}, kcKey)

3. Résolvez le problème d'inventaire avec Lua

Lua peut mieux résoudre ce problème en exécutant Redis. Afin d'éviter les problèmes d'inventaire que le verrouillage pessimiste peut provoquer dans Redis, vous devez envisager d'utiliser le verrouillage optimiste. Étant donné que Redis ne prend pas en charge le verrouillage optimiste intégré, nous devons utiliser Lua pour écrire des scripts pertinents. Il présente principalement les avantages suivants :

  • Écrivez des opérations Redis complexes ou en plusieurs étapes sous forme de script et soumettez-les à Redis pour une exécution immédiate, réduisant ainsi le nombre de connexions Redis répétées. Améliorer les performances.

  • Le script Luan est similaire aux transactions Redis, a un certain degré d'atomicité, ne sera pas mis en file d'attente par d'autres commandes et peut effectuer certaines opérations transactionnelles Redis.

  • La fonction de script Lua de redis ne peut être utilisée que dans les versions supérieures à redis2.6.

  • Utilisez le script Lua pour éliminer les utilisateurs et résoudre le problème de survente.

  • Après la version 2.6 de redis, le problème de contention est résolu via le script Lua. En fait, redis utilise sa fonctionnalité monothread pour résoudre les problèmes de concurrence multitâches à l'aide de files d'attente de tâches.

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "net"
    "time"
)

func useLua(userid, prodid string) bool {
    //编写脚本 - 检查数值,是否够用,够用再减,否则返回减掉后的结果
    var luaScript = redis.NewScript(`
        local userid=KEYS[1];
        local prodid=KEYS[2];
        local qtKey="sk:"..prodid..":qt";
        local userKey="sk:"..prodid..":user";
        local userExists=redis.call("sismember",userKey,userid);
        if tonumber(userExists)==1 then
         return 2;
        end
        local num=redis.call("get",qtKey);
        if tonumber(num)<=0 then
         return 0;
        else
         redis.call("decr",qtKey);
         redis.call("SAdd",userKey,userid);
        end
        return 1;
    `)
    //执行脚本
    n, err := luaScript.Run(ctx, DB, []string{userid, prodid}).Result()
    if err != nil {
        return false
    }
    switch n {
    case int64(0):
        fmt.Println("抢购结束")
        return false
    case int64(1):
        fmt.Println(userid, ":抢购成功")
        return true
    case int64(2):
        fmt.Println(userid, ":已经抢购了")
        return false
    default:
        fmt.Println("发生未知错误!")
        return false
    }
    return true
}

func main() {
    // 并发的版本
    for i := 0; i < 20; i++ {
        go func() {
            uuid := GenerateUUID()
            prodid := "1023"
            time.Sleep(10 * time.Second)
            useLua(uuid, prodid)
        }()
    }
    time.Sleep(15 * time.Second)
}

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