-
-
Notifications
You must be signed in to change notification settings - Fork 1
隊列(Queue)改善
為了改善原本的系統架構,我們再撰寫第二版本的路由(Router),這裡稱之為「v2」版本,並且路由器(Router)去指定一個控制器(Controller),事件作用隨機從使用者當中抽選一名使用者、隨機抽選數個商品,並且建立訂單,與系統架構的功能一樣,差別在於實做的方式不同。
首先路由器(Router)的部分,指定 /testing/v2/shopping
為主要負責處理建立訂單的控制器(Controller),另外指定 /testing/v2/shopping/delete
為主要負責處理建立訂單,建立完畢後自動刪除資料的控制器(Controller)。
/*
* Shopping Testing Controllers
* All route names are prefixed with 'frontend.testing'.
*/
Route::group([
'prefix' => 'testing',
'as' => 'testing.',
], function () {
/**
* All route names are prefixed with 'frontend.testing.v2'.
*/
Route::group([
'prefix' => 'v2',
'as' => 'v2.',
], function () {
Route::get('shopping', [ShoppingV2Controller::class, 'shopping'])
->name('shopping');
Route::get('shopping/delete', [ShoppingV2Controller::class, 'shoppingDelete'])
->name('shopping.delete');
});
});
控制器(Controller)的部分,主要撰寫 shopping
負責處理建立訂單的事件,以及 shoppingDelete
負責處理建立訂單並刪除的事件,這邊會發現沒有負責處理新增訂單的 createOrder
方法,我們把這件是移動到了 AsyncCreateOrder
與 AsyncCreateAndDeleteOrder
去隊列(Queue)處理。
/**
* Class ShoppingV2Controller.
*/
class ShoppingV2Controller extends Controller
{
/**
* @return \Illuminate\View\View
*/
public function shopping()
{
$number = Str::random(32);
AsyncCreateOrder::dispatch($number);
return view('frontend.order.index-v2')
->with('number', $number);
}
/**
* @return \Illuminate\View\View
*/
public function shoppingDelete()
{
$number = Str::random(32);
AsyncCreateAndDeleteOrder::dispatch($number);
return view('frontend.order.index-v2')
->with('number', $number);
}
}
首先負責建立訂單的 AsyncCreateOrder
隊列(Queue),會明顯看到原本 createOrder
方法內的程式被移動到此處,並稍加修改,負責做隨機抽選商品及數量並建立訂單的動作。
/**
* Class AsyncCreateOrder.
*/
class AsyncCreateOrder implements ShouldQueue
{
use Dispatchable,
InteractsWithQueue,
Queueable,
SerializesModels;
/**
* @var string
*/
protected $number;
/**
* Create a new job instance.
*
* @param string $number
*
* @return void
*/
public function __construct(string $number)
{
$this->number = $number;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$container = Container::getInstance();
$orderService = $container->make(OrderService::class);
$productService = $container->make(ProductService::class);
$products = array();
for ($i = 0; $i < mt_rand(1, 10); $i++) {
$product = $productService->firstActive();
if ($product->count >= 10) {
array_push($products, array(
'id' => $product->id,
'count' => mt_rand(1, 10),
));
} else {
array_push($products, array(
'id' => $product->id,
'count' => mt_rand(1, $product->count),
));
}
}
$orderService->store(array(
'model_id' => mt_rand(2, 11),
'type' => Orders::UNPAID,
'active' => true,
'items' => $products,
'number' => $this->number,
));
}
}
另外 AsyncCreateAndDeleteOrder
的部分則是多了刪除的功能。
/**
* Class AsyncCreateAndDeleteOrder.
*/
class AsyncCreateAndDeleteOrder implements ShouldQueue
{
use Dispatchable,
InteractsWithQueue,
Queueable,
SerializesModels;
/**
* @var string
*/
protected $number;
/**
* Create a new job instance.
*
* @param string $number
*
* @return void
*/
public function __construct(string $number)
{
$this->number = $number;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$container = Container::getInstance();
$orderService = $container->make(OrderService::class);
$productService = $container->make(ProductService::class);
$products = array();
for ($i = 0; $i < mt_rand(1, 10); $i++) {
$product = $productService->firstActive();
if ($product->count >= 10) {
array_push($products, array(
'id' => $product->id,
'count' => mt_rand(1, 10),
));
} else {
array_push($products, array(
'id' => $product->id,
'count' => mt_rand(1, $product->count),
));
}
}
$order = $orderService->store(array(
'model_id' => mt_rand(2, 11),
'type' => Orders::UNPAID,
'active' => true,
'items' => $products,
'number' => $this->number,
));
$orderService->delete($order);
$orderService->destroy($order);
}
}
最後因為是異步處理,所以控制器(Controller)去觸發隊列(Queue)之後,並不會等待隊列(Queue)完成任務才接著進行,而是觸發隊列(Queue)之後,繼續進行接下來的項目,所以我們需要讓前端(Frontend)有個等待的機制,不能回應就直接抓資料,這會導致資料可能是空白的,因此我們在這邊寫了一個 Vue 元件(Component)負責處理這段。
<template>
<article class="card">
<header class="card-header"> My Orders / Tracking </header>
<div class="card-body">
<h1 class="text-center py-5">Thank you for your order!</h1>
<h6>Order ID: {{ number }}</h6>
<article class="card">
<div class="card-body row">
<div class="col"> <strong>Estimated Delivery time:</strong> <br>-</div>
<div class="col"> <strong>Shipping BY:</strong> <br>-</div>
<div class="col"> <strong>Status:</strong> <br>{{ order.type }}</div>
<div class="col"> <strong>Tracking:</strong> <br>-</div>
</div>
</article>
<hr>
<article class="card">
<header class="card-header"> Payment Summary </header>
<div class="row row-main p-2 mt-2" v-for="(item, index) in order.items" v-bind:key="index">
<div class="col-2 text-center">
<img class="img-fluid" style="height: 128px;" src="/img/product/default.png">
</div>
<div class="col-8">
<div class="row d-flex">
<p class="w-100"><b>{{ item.name.substr(0, 24) }} ...</b></p>
</div>
<div class="row d-flex">
<p class="w-100 text-muted">{{ item.description.substr(0, 128) }} ...</p>
</div>
</div>
<div class="col-2 d-flex justify-content-end">
<p class="text-center">
<b>{{ item.count }}</b> × <b>$ {{ item.price.toLocaleString('en-US') }}.00</b>
</p>
</div>
</div>
<hr class="mx-3">
<div class="total p-2">
<div class="row">
<div class="col"><b>Total:</b></div>
<div class="col d-flex justify-content-end"><b>$ {{ order.price.toLocaleString('en-US') }}.00</b></div>
</div>
</div>
</article>
<hr>
<a href="/" class="btn btn-warning" data-abc="true">
<i class="fa fa-chevron-left"></i> Back to Dashborad
</a>
</div>
</article>
</template>
<script>
export default {
name: "OrderInfoV2",
props:{
number: {
type: String,
require: true,
},
},
data() {
return {
order: {
type: null,
price: null,
items: [],
},
}
},
mounted() {
axios.get(`/api/testing/v2/order/${this.number}`)
.then(response => {
this.order = response.data.data;
})
.catch(error => console.log(error));
},
}
</script>
為了讓 Vue 元件(Component)能夠讀取訂單資料,我們需要再撰寫一支應用程式介面(Application Programming Interface, API),其功能是將訂單資料回應出來。
/**
* Class ShoppingV2Controller.
*/
class ShoppingV2Controller extends Controller
{
/**
* @var OrderService
*/
protected $orderService;
/**
* ShoppingV2Controller constructor.
*
* @param OrderService $orderService
*/
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
/**
* @param string $number
*
* @return \Illuminate\View\View
*/
public function order(string $number)
{
if ($order = $this->orderService->findByNumber($number)) {
return new OrderResource($order);
}
return [
'data' => [],
];
}
}
這樣我們就完成了具有隊列(Queue)功能的「v2」版本,接下來我們需要測試這個版本的數值,首先從 10 個連線 300 次連續點擊開始。
This is ApacheBench, Version 2.3 <$Revision: 1874286 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking laravel-typical-high-load-exam.herokuapp.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Finished 300 requests
Server Software: nginx
Server Hostname: laravel-typical-high-load-exam.herokuapp.com
Server Port: 80
Document Path: /testing/v2/shopping
Document Length: 9408 bytes
Concurrency Level: 10
Time taken for tests: 65.461 seconds
Complete requests: 300
Failed requests: 0
Total transferred: 3182100 bytes
HTML transferred: 2822400 bytes
Requests per second: 4.58 [#/sec] (mean)
Time per request: 2182.046 [ms] (mean)
Time per request: 218.205 [ms] (mean, across all concurrent requests)
Transfer rate: 47.47 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 210 215 3.8 214 232
Processing: 347 1908 197.0 1935 2478
Waiting: 344 1213 476.2 1282 1961
Total: 564 2123 197.3 2149 2696
Percentage of the requests served within a certain time (ms)
50% 2149
66% 2156
75% 2161
80% 2164
90% 2168
95% 2172
98% 2182
99% 2187
100% 2696 (longest request)
接著測試 100 個連線 600 次連續點擊,我們可以發現連線時間(Connection Times)的部分急遽上升,但數值比起原本的系統架構,反而更趨近於初始數值。
This is ApacheBench, Version 2.3 <$Revision: 1874286 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking laravel-typical-high-load-exam.herokuapp.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Finished 600 requests
Server Software: nginx
Server Hostname: laravel-typical-high-load-exam.herokuapp.com
Server Port: 80
Document Path: /testing/v2/shopping
Document Length: 9408 bytes
Concurrency Level: 100
Time taken for tests: 128.526 seconds
Complete requests: 600
Failed requests: 0
Total transferred: 6364200 bytes
HTML transferred: 5644800 bytes
Requests per second: 4.67 [#/sec] (mean)
Time per request: 21420.971 [ms] (mean)
Time per request: 214.210 [ms] (mean, across all concurrent requests)
Transfer rate: 48.36 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 208 213 3.8 211 227
Processing: 340 19336 4671.4 21083 21397
Waiting: 330 10535 6117.7 10477 21200
Total: 550 19549 4671.6 21297 21613
Percentage of the requests served within a certain time (ms)
50% 21297
66% 21312
75% 21322
80% 21327
90% 21351
95% 21391
98% 21410
99% 21414
100% 21613 (longest request)
最後我們測試 300 個連線 1500 次連續點擊,這裡我們可以清楚看到除了數值比起原本的系統架構,反而更趨近於初始數值以外,平均每秒可回應要求(Requests per second)從原本大約 4 上升到大約 4.6 個回應,更接近於初始數值的 5 個回應。
This is ApacheBench, Version 2.3 <$Revision: 1874286 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking laravel-typical-high-load-exam.herokuapp.com (be patient)
Completed 150 requests
Completed 300 requests
Completed 450 requests
Completed 600 requests
Completed 750 requests
Completed 900 requests
Completed 1050 requests
Completed 1200 requests
Completed 1350 requests
Completed 1500 requests
Finished 1500 requests
Server Software: nginx
Server Hostname: laravel-typical-high-load-exam.herokuapp.com
Server Port: 80
Document Path: /testing/v2/shopping
Document Length: 9408 bytes
Concurrency Level: 300
Time taken for tests: 320.740 seconds
Complete requests: 1500
Failed requests: 0
Total transferred: 15910500 bytes
HTML transferred: 14112000 bytes
Requests per second: 4.68 [#/sec] (mean)
Time per request: 64147.904 [ms] (mean)
Time per request: 213.826 [ms] (mean, across all concurrent requests)
Transfer rate: 48.44 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 208 213 3.9 211 234
Processing: 435 57364 15166.5 63695 64168
Waiting: 423 31848 18444.4 31841 63909
Total: 646 57577 15166.3 63908 64386
Percentage of the requests served within a certain time (ms)
50% 63908
66% 63951
75% 64003
80% 64017
90% 64040
95% 64052
98% 64069
99% 64078
100% 64386 (longest request)
接著我們需要改善查詢訂單的功能,這邊我們不直接透過訂單 UUID 數值來查找模型(Model),這會直接接觸到資料庫,我們先透過 Redis 去查找訂單資料有沒有已經先存入快取(Cache),如果沒有存入(Cache),才會接著往下查找資料庫的資訊,並將結果存入 Redis 的快取(Cache)當中。
/**
* Class ShoppingV2Controller.
*/
class ShoppingV2Controller extends Controller
{
/**
* @param string $uuid
*
* @return \Illuminate\View\View
*/
public function order(string $uuid)
{
if ($order = Redis::get('order:' . $uuid)) {
return view('frontend.order.index-v2r')
->with('order', json_decode($order));
}
if ($order = Orders::uuid($uuid)->first()) {
$items = $order->items;
foreach ($items as $item) {
$item->product;
}
Redis::set("order:$uuid", $order);
return view('frontend.order.index-v2r')
->with('order', $order);
}
return redirect()->route('frontend.index')->withFlashDanger(__('Order is Not Found.'));
}
}
最後單獨測試 300 個連線 1500 次連續點擊,這裡可以明顯看到平均每秒可回應要求(Requests per second)與寫入的測試相對接近,也跟原本架構所跑出來的數值差不多,推測有可能是抓得資料不夠多、運算量不夠大而導致。
This is ApacheBench, Version 2.3 <$Revision: 1874286 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking laravel-typical-high-load-exam.herokuapp.com (be patient)
Completed 150 requests
Completed 300 requests
Completed 450 requests
Completed 600 requests
Completed 750 requests
Completed 900 requests
Completed 1050 requests
Completed 1200 requests
Completed 1350 requests
Completed 1500 requests
Finished 1500 requests
Server Software: nginx
Server Hostname: laravel-typical-high-load-exam.herokuapp.com
Server Port: 80
Document Path: /testing/v2/order/76c399c1-042a-46e9-81f3-75845368ca24
Document Length: 16341 bytes
Concurrency Level: 300
Time taken for tests: 303.997 seconds
Complete requests: 1500
Failed requests: 0
Total transferred: 26310000 bytes
HTML transferred: 24511500 bytes
Requests per second: 4.93 [#/sec] (mean)
Time per request: 60799.313 [ms] (mean)
Time per request: 202.664 [ms] (mean, across all concurrent requests)
Transfer rate: 84.52 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 198 202 2.4 202 217
Processing: 495 54337 14381.2 60340 61004
Waiting: 334 30156 17474.1 30133 60436
Total: 698 54539 14381.2 60541 61203
Percentage of the requests served within a certain time (ms)
50% 60541
66% 60572
75% 60606
80% 60621
90% 60662
95% 60673
98% 60679
99% 60682
100% 61203 (longest request)