Heim >PHP-Framework >Laravel >Design des Flash-Kill-Systems

Design des Flash-Kill-Systems

步履不停
步履不停Original
2019-06-25 15:06:333704Durchsuche

Design des Flash-Kill-Systems

Ich habe zuvor einen Artikel über das Design des Werbesystems geschrieben und Flash-Verkäufe/Direktrabatte/Juhuasuan erwähnt, aber in der eigentlichen Arbeit habe ich noch nie wirklich ein Flash-Verkaufssystem erstellt, also Ich habe hypothetisch ein einfaches Flash-Sale-System erstellt, um „das Verlangen zu stillen“, und die Werbeideen folgten immer noch dem Design des vorherigen Artikels.

Analyse

Während des Flash-Sales fließt eine große Menge an Traffic ein. Die Abfrage wird häufig aktualisiert, bevor der Flash-Sale beginnt. Wenn sofort eine große Menge an Traffic in die Datenbank gelangt, ist dies der Fall leicht zum Zusammenbruch der Datenbank führen. Daher besteht die Hauptaufgabe des Flash-Sales darin, den Traffic Schicht für Schicht zu filtern und schließlich so wenig und sanftem Traffic wie möglich in die Datenbank zuzulassen.

Übliche Flash-Verkäufe erfordern, dass eine große Anzahl von Benutzern eine kleine Menge an Waren kauft. Bei solchen Anforderungen kann das einfache Zwischenspeichern des Inventars eine große Menge an Datenverkehr filtern, bevor tatsächlich eine Bestellung erstellt wird.

Aber, aber, es scheint überhaupt keine Herausforderung zu sein! Um die Schwierigkeit ein wenig zu erhöhen, nehmen wir an, dass unser Flash-Sale so ist, als würden wir uns Xiaomi-Handys schnappen. Was wäre, wenn 1 Million Menschen sich 100.000 Handys schnappen würden? Das Anstehen bei Xiaomi-Eilverkäufen ist eine Methode (obwohl die Erfahrung nicht sehr gut ist), und unser Flash-Sale-Design wird in Zukunft auf dieser Idee basieren.

Wenn es um Xiaomi geht, muss ich sagen, dass es mir gezeigt hat, dass „Glück auch ein Teil der Stärke ist!“

Front-End-Strombegrenzungsmethode: zufällig( 0, 1) ? axios.post: wait(30, 'Alles fertig!')

Beginnen wir mit der Analyse einiger Codedetails. Grundsätzlich sollte die ursprüngliche Geschäftslogik möglichst wenig geändert werden wie möglich. Darüber hinaus gibt es im folgenden Artikel kein erweitertes Gameplay wie Service-Leistungsschalter oder mehrstufiges Caching, es handelt sich lediglich um ein relativ einfaches Geschäftsdesign.

Start

Der Betreiber fügt im Hintergrund eine Variante zur Flash-Sale-Aktion hinzu und legt den Lagerbestand/Flash-Sale-Rabattsatz/Startzeit und Endzeit usw. des Flash-Sales fest. Wir kann so etwas wie solche Daten bekommen.

// promotion_variant (促销和变体表「sku」的一个中间表)
{
    'id': 1,
    'variant_id': 1,
    'promotion_id': 1,
    'promotion_type': 'snap_up',
    'discount_rate': 0.5,
    'stock': 100, // 秒杀库存
    'sold': 0, // 秒杀销量
    'quantity_limit': 1, // 限购
    'enabled': 1,
    'product_id': 1,
    'rest': {
        variant_name: 'xxx', // 秒杀期间变体名称
        image: 'xxx', // 秒杀期间变体图片
    }
}

Der erste Schritt besteht darin, die Aktionsinformationen zwischenzuspeichern, nachdem die Flash-Sale-Aktion erfolgreich erstellt wurde

# PromotionVariantObserver.php

public function saved(PromotionVariant $promotionVariant)
{
  if ($promotionVariant->promotion_type === PromotionType::SNAP_UP) {
    $seconds = $promotionVariant->ended_at->getTimestamp() - time();

    \Cache::put(
      "promotion_variants:$promotionVariant->id",
      $promotionVariant,
      $seconds
    );
  }
}

Bestellung aufgeben

Die vorhandene Bestellschnittstelle nach Erhalt der Varianteninformationen Wir wissen nicht, welche der aktuellen Variantenlisten an der Aktion teilnimmt. Der Beurteilungsvorgang erfordert hier eine große Anzahl von Datenbankabfragevorgängen.

Hier schreiben wir also eine neue API für den Flash-Sale. Wenn das Frontend erkennt, dass sich die aktuelle Variante in der Flash-Sale-Aktion befindet, wechselt es zur Flash-Sale-Bestell-API.

Selbstverständlich verwenden wir weiterhin die ursprüngliche Bestell-API und es ist kein Problem, ein Logo im Frontend anzubringen.

Ein Punkt, der einer Erklärung bedarf, ist, dass das Aufgeben einer Bestellung normalerweise in zwei Schritte unterteilt ist.

Der erste Schritt ist „Zur Kasse gehen“, um eine Checkout-Bestellung zu generieren Adresse für die Kassenbestellung, Gutscheine, Zahlungsmethoden usw.

Der zweite Schritt ist „Bestätigen“. Zu diesem Zeitpunkt wird die Bestellung bestätigt, der Lagerbestand wird gesperrt und der Benutzer kann die Zahlung vornehmen. Wenn die Zahlung nicht innerhalb der vereinbarten Frist erfolgt, wird die Bestellung normalerweise storniert und der Lagerbestand entsperrt.

Im ersten Schritt werden die Benutzer also gefiltert und in die Warteschlange gestellt, um zu verhindern, dass sich nachfolgende Vorgänge wie die Auswahl von Adressen und Coupons auf die Datenbank auswirken.

# CheckoutController.php

/**
 * @param Request $request
 * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
 * @throws StockException
 */
public function snapUpCheckout(Request $request)
{
    $variantId = $request->input('variant_id');
    $quantity = $request->input('quantity', 1);

    // 加锁防止超卖
    $lock = \Cache::lock('snap_up:' . $variantId, 10);

    try {
        // 未获取锁的消费者将阻塞在这里
        $lock->block(10);

        $promotionVariant = \Cache::get('promotion_variants:' . $variantId);

        if ($promotionVariant->quantity release();

            throw new StockException('库存不足');
        }

        $promotionVariant->quantity -= $quantity;

        $seconds = $promotionVariant->ended_at->getTimestamp() - time();
        \Cache::put(
            "promotion_variants:$promotionVariant->id",
            $promotionVariant,
            $seconds
        );

    } catch (LockTimeoutException $e) {
        throw new StockException('库存不足');

    } finally {
        optional($lock)->release();
    }

    CheckoutOrder::dispatch([
        'user_id' => \Auth::id(),
        'variant_id' => $variantId,
        'quantity' => $quantity
    ]);

    return response('结账订单创建中');
}

Sie können sehen, dass an der Flash-Sale-Checkout-API kein Datenbankvorgang beteiligt ist. Und die Aufgabe, eine Bestellung zu erstellen, wird durch den Versand an die Warteschlange verteilt, und Benutzer warten in der Reihenfolge, in der sie in die Warteschlange eintreten, für die entsprechende Zeit in der Warteschlange.

Die Frage ist nun, wie kann der Kunde benachrichtigt werden, nachdem die Bestellung erfolgreich erstellt wurde?

Client-Benachrichtigung

Die Lösung hier ist nichts anderes als Polling oder Websocket. Hier wählen wir einen Websocket, der weniger Serverleistung verbraucht, und verwenden Laravel-Echo, das von Laravel bereitgestellt wird (Laravel-Echo-Server). Wenn der Flash-Verkauf des Benutzers erfolgreich ist, stellen das Front-End und das Back-End eine Websocket-Verbindung her. Nachdem die Back-End-Kaufbestellung erfolgreich erstellt wurde, wird das Front-End benachrichtigt, mit dem nächsten Schritt fortzufahren.

Backend

Als nächstes muss das Backend ein „OrderChecked“-Ereignis an den entsprechenden Kanal des Websockets senden, um den Checkout anzuzeigen, nachdem die Bestellung im „CheckoutOrder“-Job erfolgt erfolgreich erstellt Die Bestellung wurde erstellt und der Benutzer kann mit dem nächsten Schritt fortfahren.

# Job/CheckoutOrder.php

// ...

public function handle()
{
  // 创建结账订单
  // ...

  // 通知客户端. websocket 编程本身就是以事件为导向的,和 laravel 的 event 非常契合。
  event(new OrderChecked($this->data->user_id));
}

// ...
# Event/OrderChecked.php

class OrderChecked implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    private $userId;

    /**
     * Create a new event instance.
     *
     * @param $userId
     */
    public function __construct($userId)
    {
        $this->userId = $userId;
    }

    /**
     * App.User.{id} 是 laravel 初始化时,默认的私有频道,直接使用即可
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('App.User.' . $this->userId);
    }
}

Angenommen, die Benutzer-ID, die gerade zum Kauf drängt, ist 1. Zusammenfassend besteht der obige Code darin, ein „OrderChecked“-Ereignis an den privaten Kanal „App.User.1“ des Websockets zu senden.

Frontend

Der folgende Code ist das Standardprojekt, das mit dem vue-cli-Tool initialisiert wurde.

// views/products/show.vue

<script>

import Echo from &#39;laravel-echo&#39;
import io from &#39;socket.io-client&#39;
window.io = io

export default {
  name: &#39;App&#39;,
  methods: {
    async snapUpCheckout () {
      try {
        // await post -> snap-up-checkout
        this.toCheckout()
      } catch (error) {
        // 秒杀失败
      }
    },
    toCheckout () {
      // 建立 websocket 连接
      const echo = new Echo({
        broadcaster: &#39;socket.io&#39;,
        host: &#39;http://api.e-commerce.test:6001&#39;,
        auth: {
          headers: {
            Authorization: &#39;Bearer &#39; + this.store.auth.token
          }
        }
      })

      // 监听私有频道 App.User.{id} 的 OrderChecked 事件
      echo.private(&#39;App.User.&#39; + this.store.user.id).listen(&#39;OrderChecked&#39;, (e) => {
        // redirect to checkou page
      })
    }
  }
}
</script>

Bei der Verwendung von Laravel-Echo ist zu beachten, dass Laravel-Echo aufgrund der Verwendung eines privaten Kanals standardmäßig eine Post-Anfrage an die Server-API /broadcasting/auth zur Authentifizierung sendet. Da jedoch die Front-End- und Back-End-Kategorien anstelle von Blade-Vorlagen verwendet werden, können wir das CSRF-Token und die Sitzung nicht einfach abrufen, um einige notwendige Authentifizierungen durchzuführen.

Daher muss die Konfiguration von Broadcast und Laravel-Echo-Server leicht geändert werden

# BroadcastServiceProvider.php

public function boot()
{
  // 将认证路由改为 /api/broadcasting/auth 从而避免 csrf 验证
  // 添加中间件 auth:api (jwt 使用 api.auth) 进行身份验证,避免访问 session ,并使 Auth::user() 生效。
  Broadcast::routes(["prefix" => "api", "middleware" => ["auth:api"]]);

  require base_path('routes/channels.php');
}
// laravel-echo-server.json

// 认证路由添加 api 前缀,与上面的修改对应
"authEndpoint": "/api/broadcasting/auth"

Inventar-Freischaltung

Wenn das „Inventar“ für diese Bestellung gesperrt wurde, wenn Wenn der Benutzer den WebSocket abtrennt oder ihn für längere Zeit verlässt, muss er das Inventar entsperren, um eine bedeutungslose Belegung des Inventars zu verhindern.

Das Inventar bezieht sich hier auf das Cache-Inventar, nicht auf das Datenbankinventar. Denn auch wenn die Bestellung zu diesem Zeitpunkt erfolgreich erstellt wurde, befindet sie sich noch im Checkout-Status (keine Adresse, Zahlungsart etc. ausgewählt) und ist im persönlichen Center nicht sichtbar. Der Datenbankbestand wird erst gesperrt, wenn der Benutzer die Bestellung bestätigt.

Die ideale Implementierung besteht hier also darin, den gesperrten Bestand für diese Bestellung zurückzugeben, nachdem der Benutzer die WebSocket-Verbindung getrennt hat. Nachdem die Checkout-Bestellung erstellt wurde, wird eine Verzögerungswarteschlange erstellt, um den Bestand an Bestellungen zurückzugeben, die längere Zeit nicht bearbeitet wurden.

Laravel-Echo ist jedoch ein Broadcast-System und bietet keine Rückrufe für Client-Trennungsereignisse. Es gibt einige Methoden zum Implementieren von Client-Ereignissen, die Laravel abhört, z. B. das Hinzufügen von Hooks zu Laravel-Echo-Server Notify Laravel , aber die Implementierung des Laravel-Echo-Servers muss hier nicht näher erläutert werden. Der Schwerpunkt liegt auf der Bereitstellung von Flash-Sale-Ideen.

Zusammenfassung

Design des Flash-Kill-Systems

Das Bild oben ist die logische Zusammenfassung des Flash-Kill-Systems. Zu diesem Zeitpunkt ist der gesamte Flash-Sale-Prozess abgeschlossen. Im Allgemeinen ist die Codemenge nicht groß und die Logik relativ einfach.

Wie aus der Abbildung ersichtlich ist, interagiert es im gesamten Prozess nur in der Warteschlange mit MySQL. Durch die Strombegrenzung der Warteschlange kann es sich an die Ausdauer von MySQL anpassen das maximale Ausmaß. Wenn die MySQL-Leistung ausreichend ist, können Benutzer Bestellungen über eine große Anzahl von Warteschlangen gleichzeitig verarbeiten, ohne dass der Benutzer den Warteschlangenvorgang überhaupt bemerkt.

Wenn Sie Fragen oder bessere Ideen haben, hinterlassen Sie bitte eine Nachricht zur Diskussion~

Weitere technische Artikel zu Laravel finden Sie in der Spalte Laravel-Tutorial, um mehr zu erfahren!

Das obige ist der detaillierte Inhalt vonDesign des Flash-Kill-Systems. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn