Le système de destruction flash se caractérise par une grande concurrence. Des milliers et des dizaines de milliers de demandes peuvent arriver en une seconde. n'est pas utilisé. Avec quelques astuces, le système s'effondrera en quelques minutes. Discutons de la façon de concevoir un système Flash Kill pouvant être joué.
1. Limite actuelle :
Tout d'abord, quelle que soit la logique métier, s'il existe l'interface la plus simple suivante :
@GetMapping("/test") public String test() { return "success"; }
Bien que cette interface C'est très simple, il n'y a pas de logique, mais le serveur plantera également si des milliers de requêtes y accèdent en même temps. Par conséquent, la première chose qu’un système à haute concurrence devrait faire est de limiter le flux de courant. Les projets Springcloud peuvent utiliser Hystrix pour la limitation de courant, et Springcloud Alibaba peut utiliser Sentinel pour la limitation de courant. Qu'en est-il des projets non-Springcloud ? Guava nous fournit une classe d'outils RateLimiter qui peut limiter le courant. Il comprend principalement l'algorithme de seau de fuite et l'algorithme de seau de jetons.
Algorithme de seau qui fuit : un seau troué est rempli d'eau sous le robinet. S'il est rempli d'eau, il fuira un peu, mais. si l'eau du robinet est très grande et l'eau dans le seau débordera tôt ou tard. S'il déborde, le débit sera restreint. Ceci convient pour limiter les taux de téléchargement et de téléchargement.
Algorithme du compartiment de jetons : placez les jetons dans le compartiment à un rythme constant. Chaque fois qu'une demande arrive, elle doit d'abord être supprimée. du bucket Si le jeton n’est pas obtenu, la demande sera bloquée. Ceci convient à la limitation de courant, c'est-à-dire à la limitation du QPS.
L'algorithme du token bucket doit être utilisé ici pour limiter le flux. Si le token n'est pas obtenu, il renverra directement "Trop de personnes, peut-être". "ne vous enfoncez pas" conseils.
2. Vérifiez si l'utilisateur est connecté :
Après la première étape de limitation de courant, les requêtes entrantes doivent vérifier si l'utilisateur est connecté. Ce projet utilise JWT, c'est-à-dire qu'il demande d'abord l'interface de connexion, renvoie le jeton après la connexion et demande à toutes les autres interfaces d'inclure le jeton dans l'en-tête de la demande, puis les informations utilisateur peuvent être obtenues via le jeton. Lorsque les informations utilisateur ne sont pas obtenues, l'utilisateur est invité à se reconnecter : token invalide.
3. Vérifiez si le produit est épuisé :
Si les deux premières étapes de vérification sont réussies, vous devez vérifier si le produit est épuisé. Si c'est le cas. épuisé, renvoyez un message d'invite "Désolé, le produit a été épuisé". Notez que vous ne pouvez pas consulter la base de données pour vérifier si le produit est épuisé, sinon cela sera très lent. Vous pouvez utiliser un dictionnaire pour stocker les ID de produit, en utilisant l'ID de produit comme clé. Si l'article est épuisé, définissez sa valeur sur True, sinon sur False.
4. Ajoutez les produits participant à la vente flash à redis :
Définissez d'abord la valeur de ISINREDIS
的key,表示商品是否已经加到redis中了,避免每个请求进来都重复此操作。如果ISINREDIS
值为false,表示redis中还没有秒杀商品。那么就查询出所有参加秒杀的商品,商品id作为key,商品库存作为value,存到redis中,同时将商品id作为key,false作为value,放到第三步的map中,表示该商品没有售完。最后将ISINREDIS
sur true, ce qui signifie que tous les produits participant à la des ventes flash ont été ajoutées. C'est en redis.
5. Retenue d'inventaire :
Utilisez la fonction decr de redis pour réduire la quantité de marchandises et juger de la valeur réduite. Si le résultat après l'auto-décrémentation est inférieur à 0, cela signifie que le produit a été épuisé, alors la valeur de l'ID de produit correspondant dans la carte est définie sur vrai et une invite « Il est tard, le produit a été épuisé" est retourné.
6. Déterminez si la vente flash est répétée :
Si la vente flash de l'utilisateur réussit, une fois la commande de vente flash enregistrée, l'identifiant de l'utilisateur et l'identifiant du produit seront utilisée comme clé et true sera stockée comme valeur Dans Redis, cela signifie que l'utilisateur a déjà tué le produit. Nous allons donc ici vers redis en fonction de l'ID utilisateur et de l'ID produit pour déterminer si la vente flash est répétée. Si tel est le cas, l'invite « Ne pas répéter la vente flash » est renvoyée.
7. Traitement asynchrone :
Si les vérifications ci-dessus sont réussies, alors la vente flash peut être traitée. Si vous déduisez l'inventaire et créez des commandes pour chaque demande de vente flash, non seulement la vitesse sera très lente, mais cela peut également provoquer un crash de la base de données. Nous pouvons donc le traiter de manière asynchrone, c'est-à-dire qu'après avoir réussi la vérification ci-dessus, l'ID utilisateur et l'ID produit seront envoyés à MQ sous forme de messages, puis une invite de « mise en file d'attente » sera immédiatement renvoyée à l'utilisateur. Ensuite, consommez le message du côté consommateur de MQ, obtenez l'ID utilisateur et l'ID produit, et interrogez l'inventaire en fonction de l'ID produit pour garantir à nouveau un inventaire suffisant, vous pouvez également déterminer s'il faut répéter la vente flash ; Après avoir rendu le jugement, exploitez la base de données, déduisez l'inventaire et créez un ordre de vente flash. Notez que la déduction des stocks et la création de commandes de vente flash doivent être effectuées dans la même transaction.
8. Problème de survente :
Le problème de survente est un inventaire négatif de marchandises. Par exemple, si l'inventaire est de 1, alors 10 utilisateurs vendent instantanément en même temps. Lors de l'évaluation de l'inventaire, ils sont tous de 1, donc 10 personnes peuvent passer des commandes avec succès et l'inventaire final est de -9. Comment le résoudre ? En fait, un tel problème ne se produira pas du tout dans ce système, car redis est utilisé pour pré-réduire l'inventaire au début, et le module principal de commande redis est monothread, il peut donc être garanti qu'il ne sera pas survendu. Si redis n'est pas utilisé, vous pouvez également ajouter un champ de version au produit. Vérifiez la version avant de déduire l'inventaire à chaque fois. Ajoutez une condition au SQL pour déduire l'inventaire, c'est-à-dire que la version doit être égale à la version que vous venez de trouver.
@RestController @RequestMapping("/seckill") public class SeckillController { @Autowired private UserService userService; @Autowired private SeckillService seckillService; @Autowired private RabbitMqSender mqSender; // 用来标记商品是否已经加入到redis中的key private static final String ISINREDIS = "isInRedis"; // 用goodsId作为key,标记该商品是否已经卖完 private Map<integer> seckillOver = new HashMap<integer>(); // 用RateLimiter做限流,create(10),可以理解为QPS阈值为10 private RateLimiter rateLimiter = RateLimiter.create(10); @PostMapping("/{sgId}") public JsonResult> seckillGoods(@PathVariable("sgId") Integer sgId, HttpServletRequest httpServletRequest){ // 1. 如果QPS阈值超过10,即1秒钟内没有拿到令牌,就返回“人太多了,挤不进去”的提示 if (!rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) { return new JsonResult(SeckillGoodsEnum.TRY_AGAIN.getCode(), SeckillGoodsEnum.TRY_AGAIN.getMessage()); } // 2. 检查用户是否登录(用户登录后,访问每个接口都应该在请求头带上token,根据token再去拿user) String token = httpServletRequest.getHeader("token"); String userId = JWT.decode(token).getAudience().get(0); User user = userService.findUserById(Integer.valueOf(userId)); if (user == null) { return new JsonResult(SeckillGoodsEnum.INVALID_TOKEN.getCode(), SeckillGoodsEnum.INVALID_TOKEN.getMessage()); } // 3. 如果商品已经秒杀完了,就不执行下面的逻辑,直接返回商品已秒杀完的提示 if (!seckillOver.isEmpty() && seckillOver.get(sgId)) { return new JsonResult(SeckillGoodsEnum.SECKILL_OVER.getCode(), SeckillGoodsEnum.SECKILL_OVER.getMessage()); } // 4. 将所有参加秒杀的商品信息加入到redis中 if (!RedisUtil.isExist(ISINREDIS)) { List<seckillgoods> goods = seckillService.getAllSeckillGoods(); for (SeckillGoods seckillGoods : goods) { RedisUtil.set(String.valueOf(seckillGoods.getSgId()), seckillGoods.getSgSeckillNum()); seckillOver.put(seckillGoods.getSgId(), false); } RedisUtil.set(ISINREDIS, true); } // 5. 先自减,预扣库存,判断预扣后库存是否小于0,如果是,表示秒杀完了 Long stock = RedisUtil.decr(String.valueOf(sgId)); if (stock (SeckillGoodsEnum.SECKILL_OVER.getCode(), SeckillGoodsEnum.SECKILL_OVER.getMessage()); } // 6. 判断是否重复秒杀(成功秒杀并创建订单后,会将userId和goodsId作为key放到redis中) if (RedisUtil.isExist(userId + sgId)) { return new JsonResult(SeckillGoodsEnum.REPEAT_SECKILL.getCode(), SeckillGoodsEnum.REPEAT_SECKILL.getMessage()); } // 7. 以上校验都通过了,就将当前请求加入到MQ中,然后返回“排队中”的提示 String msg = userId + "," + sgId; mqSender.send(msg); return new JsonResult(SeckillGoodsEnum.LINE_UP.getCode(), SeckillGoodsEnum.LINE_UP.getMessage()); } } </seckillgoods></integer></integer>
用jmeter模拟并发请求,测试高并发情况下系统能否扛得住。由于只有一个id为1的商品,所以商品id固定写死1。但是每个用户都要先请求登录接口获取到token才能进行秒杀请求,有点儿麻烦,所以可以先把jwt模块注释掉,把userId当成参数传进去。jmeter配置如下图:
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!