ホームページ > 記事 > ウェブフロントエンド > Nodejs を使用してフラッシュ セール システムを設計する方法についての簡単な説明
この記事では、nodejs を使用してフラッシュ セール システムを設計する方法を紹介します。一定の参考値があるので、困っている友達が参考になれば幸いです。
フロントエンドでは、「同時実行」シナリオが発生することはほとんどありません。この記事では、一般的なフラッシュ セール シナリオから実際のオンライン ノード アプリケーションの遭遇について説明します。 「同時実行」に使用されますか?この記事のサンプル コード データベースは MongoDB に基づいており、キャッシュは Redis に基づいています。 [関連する推奨事項: "nodejs チュートリアル "]
ルール: 1 人のユーザーができるのは、クーポンを取得してください。
まず最初に、レコード テーブルを使用してユーザーのクーポン レコードを保存することを考えています。ユーザーはクーポンを受け取ったときに、クーポンが受信されたかどうかを確認できます。テーブル。
レコード構造は次のとおりです
new Schema({ // 用户id userId: { type: String, required: true, }, });
ビジネス プロセスも非常にシンプルです:
##MongoDB の実装
サンプル コードは次のとおりです。async grantCoupon(userId: string) { const record = await this.recordsModel.findOne({ userId, }); if (record) { return false; } else { this.grantCoupon(); this.recordModel.create({ userId, }); } }postman でテストすると、問題ないようです。次に、同時実行シナリオを検討します。たとえば、「ユーザー」がボタンをクリックしてクーポンが発行されるのを待つのではなく、素早くクリックしたり、ツールを使用して同時にクーポン インターフェイスを要求したりする場合、プログラムに問題は発生するでしょうか? (同時実行の問題はロードすることでフロントエンドで回避できますが、ハッカーの攻撃を防ぐためにインターフェースを傍受する必要があります) その結果、ユーザーは複数のクーポンを受け取る可能性があります。問題は、
レコードのクエリと
クーポン コレクション レコードの追加にあります。これら 2 つの手順は別々に実行されます。つまり、ある時点で、クエリでユーザー A がクーポンを持っていないことがわかります。クーポンの発行後、ユーザー A が再度インターフェイスをリクエストしましたが、このとき、レコード テーブルへのデータ挿入操作が完了していないため、重複発行の問題が発生しました。
findOneAndUpdate を提供します。これは、検索と変更を意味します。書き直されたステートメントを見てみましょう:
async grantCoupon(userId: string) { const record = await this.recordModel.findOneAndUpdate({ userId, }, { $setOnInsert: { userId, }, }, { new: false, upsert: true, }); if (! record) { this.grantCoupon(); } }実際、これは mongo のアトミック操作です。最初のパラメータはクエリ ステートメント。userId のエントリをクエリします。2 番目のパラメータ $setOnInsert は、追加時に挿入されるフィールドを示します。3 番目のパラメータ upsert=true は、クエリされたエントリが存在しない場合に作成されることを示します。new=false は、変更されたエントリの代わりに、クエリされたエントリが返されます。その後、クエリされたレコードが存在しないと判断して解放ロジックを実行するだけで、クエリ ステートメントとともに挿入ステートメントが実行されます。この時点で同時リクエストが入っていたとしても、次のクエリは最後の挿入ステートメントの後に行われます。
アトミック(原子)とは本来「これ以上分割できない粒子」を意味します。アトミック操作とは、「中断できない 1 つまたは一連の操作」を意味します。2 つのアトミック操作を同時に同じ変数に作用させることはできません。
Redis の実装
MongoDB だけでなく、redis もこのロジックに非常に適しています。redis を使用して実装してみましょう:async grantCoupon(userId: string) { const result = await this.redis.setnx(userId, 'true'); if (result === 1) { this.grantCoupon(); } }同様に、setnx は redis のアトミックな操作であり、キーに値がない場合は値が設定されます。すでに値がある場合は処理されず、失敗が求められます。 。これは同時処理の単なるデモンストレーションであり、実際のオンライン サービスでは次のことも考慮する必要があります:
) ルール: クーポンの総在庫は確実であり、単一のクーポンはユーザーは受信できる数に制限されません
# を直接入力してください。 ##MongoDB の実装
stocks
テーブルを使用する発行されたクーポンの数を記録するには、もちろん、このレコードを識別するための CouponId フィールドが必要です
テーブル構造:
new Schema({ /* 券标识 */ couponId: { type: String, required: true, }, /* 已发放数量 */ count: { type: Number, default: 0, }, });
発行ロジック:
async grantCoupon(userId: string) { const couponId = 'coupon-1'; // 券标识 const total = 100; // 总库存 const result = await this.stockModel.findOneAndUpdate({ couponId, }, { $inc: { count: 1, }, $setOnInsert: { couponId, }, }, { new: true, // 返回modify后结果 upsert: true, // 不存在则新增 }); if (result.count <= total) { this.grantCoupon(); } }
Redis実装
incr: アトミック操作、値を設定します。キーを 1 に設定します。値が存在しない場合は 0 に初期化されます。
async grantCoupon(userId: string) { const total = 100; // 总库存 const result = await this.redis.incr('coupon-1'); if (result <= total) { this.grantCoupon(); } }
問題について考えてください。在庫はすべて消費されています。その後、count
フィールドはまだ増加しますか? ?どのように最適化すればよいのでしょうか?
#ルール: ユーザーが受け取ることができるクーポンは 1 つだけであり、総在庫数は制限されています
分析
「1ユーザーに1個しか受け取れない」「総在庫数制限」の問題を解決しますアトミック操作で処理可能 条件が 2 つある場合、「1 人のユーザーが 1 つだけ受け取る」と「総在庫数制限」を組み合わせたアトミック操作と同様、またはデータベースに近いものを 1 つ実装できます。 ? 取引"###数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成
mongoDB已经从4.0开始支持事务,但这里作为演示,我们还是使用代码逻辑来控制并发
业务逻辑:
代码:
async grantCoupon(userId: string) { const couponId = 'coupon-1';// 券标识 const totalStock = 100;// 总库存 // 查询用户是否已领过券 const recordByFind = await this.recordModel.findOne({ couponId, userId, }); if (recordByFind) { return '每位用户只能领一张'; } // 查询已发放数量 const grantedCount = await this.stockModel.findOne({ couponId, }); if (grantedCount >= totalStock) { return '超过库存限制'; } // 原子操作:已发放数量+1,并返回+1后的结果 const result = await this.stockModel.findOneAndUpdate({ couponId, }, { $inc: { count: 1, }, $setOnInsert: { couponId, }, }, { new: true, // 返回modify后结果 upsert: true, // 如果不存在就新增 }); // 根据+1后的的结果判断是否超出库存 if (result.count > totalStock) { // 超出后执行-1操作,保证数据库中记录的已发放数量准确。 this.stockModel.findOneAndUpdate({ couponId, }, { $inc: { count: -1, }, }); return '超过库存限制'; } // 原子操作:records表新增用户领券记录,并返回新增前的查询结果 const recordBeforeModify = await this.recordModel.findOneAndUpdate({ couponId, userId, }, { $setOnInsert: { userId, }, }, { new: false, // 返回modify后结果 upsert: true, // 如果不存在就新增 }); if (recordBeforeModify) { // 超出后执行-1操作,保证数据库中记录的已发放数量准确。 this.stockModel.findOneAndUpdate({ couponId, }, { $inc: { count: -1, }, }); return '每位用户只能领一张'; } // 上述条件都满足,才执行发放操作 this.grantCoupon(); }
其实我们可以舍去前两部查询records记录和查询库存数量,结果并不会出问题。从数据库优化来说,显然更改比查询更耗时,而且库存有限,最终库存消耗完,后面请求都会在前两步逻辑中走完。
什么情况下会走到第3步的左分支?
场景举例:库存仅剩1个,此时用户A和用户B同时请求,此时A稍快一点,库存+1后=100,B库存+1=101;
什么情况下会走到第4步的左分支?
场景举例:A用户同时发出两个请求,库存+1后均小于100,则稍快的一次请求会成功,另一个会查询到已有领券记录
思考:什么情况下会出现,先请求的用户没抢到券,反而靠后的用户能抢到券?
库存还剩4个,A用户发起大量请求,最终导致数据库记录的已发放库存大于100,-1操作还全部执行完成,而此时B、C、D用户也同时请求,则会返回超出库存,待到库存回滚操作完成,E、F、G用户后续请求的反而显示还有库存,成功抢到券,当然这只是理论上可能存在的情况。
设计一个秒杀系统,其实还要考虑很多情况。如大型电商的秒杀活动,一次有几万的并发请求,服务器可能都支撑不住,可能会再网关层直接舍弃部分用户请求,减少服务器压力,或结合kafka消息队列,或使用动态扩容等技术。
更多编程相关知识,请访问:编程入门!!
以上がNodejs を使用してフラッシュ セール システムを設計する方法についての簡単な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。