Das Flash-Kill-System zeichnet sich durch eine große Menge an Parallelität aus. Wenn einige Maßnahmen nicht ergriffen werden, bricht das System zusammen. Lassen Sie uns besprechen, wie man ein Flash-Kill-System entwirft, das gespielt werden kann.
1. Aktuelle Einschränkung:
Zuallererst, unabhängig von der Geschäftslogik, wenn es die folgende einfachste Schnittstelle gibt:
@GetMapping("/test") public String test() { return "success"; }
Obwohl diese Schnittstelle sehr einfach ist und keine Logik hat, wenn Tausende von Anfragen auf die zugreifen Server gleichzeitig, der Server Es wird auch abstürzen. Daher sollte ein System mit hoher Parallelität zunächst den Stromfluss begrenzen. Springcloud-Projekte können Hystrix zur Strombegrenzung verwenden, und Springcloud Alibaba kann Sentinel zur Strombegrenzung verwenden. Was ist mit Nicht-Springcloud-Projekten? Guava stellt uns eine RateLimiter-Toolklasse zur Verfügung, die den Strom begrenzen kann. Es umfasst hauptsächlich den Leaky-Bucket-Algorithmus und den Token-Bucket-Algorithmus.
Leckiger Eimer-Algorithmus: Ein Eimer mit Löchern wird unter dem Wasserhahn mit Wasser gefüllt. Wenn das Wasser im Wasserhahn jedoch sehr groß ist, läuft das Wasser im Eimer früher oder später über. und der Überlauf ist begrenzt. Dies eignet sich zur Begrenzung der Upload- und Downloadraten.
Token-Bucket-Algorithmus: Jedes Mal, wenn eine Anfrage eingeht, muss das Token aus dem Bucket entnommen werden, wird die Anfrage blockiert. Dies eignet sich zur Strombegrenzung, also zur Begrenzung von QPS.
Der Token-Bucket-Algorithmus sollte hier verwendet werden, um den Fluss zu begrenzen. Wenn der Token nicht erhalten wird, wird direkt die Meldung „Zu viele Leute können sich nicht hineinquetschen“ zurückgegeben.
2. Überprüfen Sie, ob der Benutzer angemeldet ist:
Nach dem ersten Schritt der Flussbegrenzung sollten eingehende Anfragen prüfen, ob der Benutzer angemeldet ist. Dieses Projekt verwendet JWT, das heißt, es fordert zuerst die Anmeldeschnittstelle an und gibt das Token zurück Nachdem Sie sich angemeldet haben, können Sie alle anderen Schnittstellen anfordern, indem Sie das Token in den Anforderungsheader einfügen. Anschließend können Sie die Benutzerinformationen über das Token abrufen. Wenn die Benutzerinformationen nicht abgerufen werden, wird der Benutzer aufgefordert, sich erneut anzumelden: ungültiges Token.
3. Überprüfen Sie, ob das Produkt ausverkauft ist:
Wenn die ersten beiden Schritte der Überprüfung bestanden sind, müssen Sie überprüfen, ob das Produkt ausverkauft ist „ausverkauft“ wird zurückgegeben. Beachten Sie, dass Sie die Datenbank nicht überprüfen können, um zu überprüfen, ob das Produkt ausverkauft ist, da es sonst sehr langsam ist. Sie können ein Wörterbuch zum Speichern von Produkt-IDs verwenden und dabei die Produkt-ID als Schlüssel verwenden. Wenn der Artikel ausverkauft ist, setzen Sie seinen Wert auf True, andernfalls auf False.
4. Fügen Sie die am Flash-Sale teilnehmenden Produkte zu Redis hinzu:
Setzen Sie zunächst den Wert von ISINREDIS
的key,表示商品是否已经加到redis中了,避免每个请求进来都重复此操作。如果ISINREDIS
值为false,表示redis中还没有秒杀商品。那么就查询出所有参加秒杀的商品,商品id作为key,商品库存作为value,存到redis中,同时将商品id作为key,false作为value,放到第三步的map中,表示该商品没有售完。最后将ISINREDIS
auf true, was bedeutet, dass alle am Flash-Sale teilnehmenden Produkte zu Redis hinzugefügt wurden.
5. Lagerbestand einbehalten:
Verwenden Sie die Decr-Funktion von Redis, um die Warenmenge zu reduzieren und den reduzierten Wert zu beurteilen. Wenn das Ergebnis nach der Selbstabnahme kleiner als 0 ist, bedeutet dies, dass das Produkt ausverkauft ist. Dann wird der Wert der entsprechenden Produkt-ID in der Karte auf „true“ gesetzt und die Meldung „Es ist spät, das Produkt war schon“ wird angezeigt ausverkauft“ wird zurückgegeben.
6. Bestimmen Sie, ob der Flash-Sale wiederholt wird:
Wenn der Flash-Sale des Benutzers erfolgreich ist, werden nach dem Speichern des Flash-Sale-Auftrags in der Datenbank die Benutzer-ID und die Produkt-ID als Schlüssel verwendet und „true“ gespeichert in Redis, was darauf hinweist, dass der Benutzer das Produkt bereits geflasht hat. Hier gehen wir also zu Redis, basierend auf der Benutzer-ID und der Produkt-ID, um festzustellen, ob der Flash-Sale wiederholt wird. Wenn ja, wird die Aufforderung „Flash-Sale nicht wiederholen“ zurückgegeben.
7. Asynchrone Verarbeitung:
Wenn die oben genannten Überprüfungen bestanden werden, kann der Flash-Sale verarbeitet werden. Wenn Sie für jede Flash-Sale-Anfrage Lagerbestände abziehen und Bestellungen erstellen, ist nicht nur die Geschwindigkeit sehr langsam, sondern es kann auch zum Absturz der Datenbank führen. So können wir es asynchron verarbeiten, das heißt, nach bestandener Überprüfung werden die Benutzer-ID und die Produkt-ID als Nachrichten an MQ gesendet und anschließend wird sofort eine „Warteschlangen“-Eingabeaufforderung an den Benutzer zurückgegeben. Konsumieren Sie dann die Nachricht auf der Verbraucherseite von MQ, rufen Sie die Benutzer-ID und die Produkt-ID ab und fragen Sie den Lagerbestand anhand der Produkt-ID ab, um erneut einen ausreichenden Lagerbestand sicherzustellen. Anschließend können Sie auch bestimmen, ob der Flash-Verkauf wiederholt werden soll. Nachdem Sie das Urteil gefällt haben, betreiben Sie die Datenbank, ziehen den Lagerbestand ab und erstellen einen Flash-Sale-Auftrag. Beachten Sie, dass das Abziehen des Lagerbestands und das Erstellen von Flash-Sale-Bestellungen in derselben Transaktion erfolgen müssen.
8. Überverkaufsproblem:
Das Überverkaufsproblem ist ein negativer Warenbestand. Wenn der Lagerbestand beispielsweise 1 beträgt, verkaufen 10 Benutzer sofort gleichzeitig. Bei der Beurteilung des Lagerbestands sind alle 1, sodass 10 Personen erfolgreich Bestellungen aufgeben können, und der endgültige Lagerbestand beträgt -9. Wie kann man es lösen? Tatsächlich wird ein solches Problem in diesem System überhaupt nicht auftreten, da Redis zu Beginn zur Vorabreduzierung des Lagerbestands verwendet wird und das Redis-Befehlskernmodul Single-Threaded ist, sodass garantiert werden kann, dass es nicht überverkauft wird. Wenn Redis nicht verwendet wird, können Sie dem Produkt auch ein Versionsfeld hinzufügen, bevor Sie den Bestand jedes Mal abziehen. Fügen Sie der SQL eine Bedingung für den Abzug des Bestands hinzu, dh die Version muss mit der gerade gefundenen Version übereinstimmen.
@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配置如下图:
Das obige ist der detaillierte Inhalt vonSo implementieren Sie das Flash-Sale-System in Redis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!