Laravel: Giảm thời gian phản hồi của máy chủ (TTFB) trong Laravel bằng cách lưu đối tượng phản hồi vào bộ nhớ đệm

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Laravel có lẽ là framework MVC phổ biến nhất hiện có trong PHP. Được sử dụng để xây dựng các ứng dụng lớn và phức tạp. Một ứng dụng phức tạp được xây dựng bằng Laravel có thể gặp sự cố với thời gian phản hồi của máy chủ (Server Response Times) - Time To First By (TTFB). Vì vậy, trong bài viết này V1Study sẽ hướng dẫn cách có thể đối phó với thời gian phản hồi của máy chủ không tốt, bất kể quy mô và bao nhiêu truy vấn SQL đang được thực thi trong vòng đời yêu cầu.

Có rất nhiều bài viết trên các blog khác nhau giải thích một số kỹ thuật để giảm thời gian phản hồi của máy chủ của Ứng dụng Laravel. Ví dụ như:

  1. Tải các model liên quan.
  2. Lưu các route bằng lệnh php artisan reoute:cache.
  3. Lưu trữ các tệp cấu hình bằng lệnh php artisan config:cache.
  4. Loại bỏ các dịch vụ không sử dụng.
  5. Sử dụng máy chủ có SSD.

Tôi đã thực hiện các kỹ thuật này. Tuy nhiên, tôi vẫn không hài lòng với thời gian phản hồi của ứng dụng Laravel của mình. Vì vậy, tôi quyết định làm theo cách của riêng mình. Trước khi bạn bắt đầu, đây là một số điều kiện tiên quyết cần được đáp ứng.

Điều kiện tiên quyết:

  1. Một dự án Laravel thực hiện rất nhiều truy vấn cơ sở dữ liệu.
  2. Bạn chỉ nên quan tâm đến việc tối ưu hóa các trang phục vụ nội dung công khai. Đó là trang phải cung cấp cùng một nội dung cho tất cả người dùng. Nói cách khác, trang không được hiển thị bất kỳ thông tin nào dành riêng cho người dùng. Ví dụ: hiển thị tên người dùng trong thanh điều hướng hoặc hiển thị nội dung được đề xuất cho một người dùng cụ thể.
  3. Bài viết này chỉ nhằm giảm thời gian phản hồi của máy chủ chứ không phải để tăng điểm tốc độ trang tổng thể của google.

Lưu ý và các bước thực thi:

Nguyên tắc đằng sau kỹ thuật này là khi bạn có một trang có nhiều lưu lượng truy cập và nó đang cung cấp cùng một nội dung cho tất cả người dùng. Vì vậy, không có ích gì khi tự động tạo phản hồi cho tất cả người dùng.

Bước 1

Hãy bắt đầu bằng cách tạo một middleware có tên là “V1StudyResponse”. Bằng cách sử dụng lệnh sau:

php artisan make:middleware V1StudyResponse

Middleware này trước tiên sẽ kiểm tra xem người dùng đã đăng nhập hay chưa. Nếu người dùng đã đăng nhập thì đối tượng phản hồi sẽ không được trả về từ bộ nhớ đệm. Như được mô tả trong điều kiện tiên quyết, trang không được gửi phản hồi cụ thể cho người dùng. Phản hồi cũng sẽ không được lưu vào bộ nhớ đệm cho các yêu cầu POST. Đây là câu lệnh if thực hiện các kiểm tra này.

if (auth()->user() != null || $request->isMethod('post')) 
    return $next($request);

Bước 2

Bây giờ chúng ta sẽ tạo một khóa từ URL hiện tại. Sau đó ta sẽ tìm kiếm trong bộ nhớ cache để tìm phản hồi tương ứng với khóa này. Khóa này cũng sẽ được sử dụng để lưu phản hồi trong bộ nhớ đệm.

$params = $request->query(); unset($params['_method']); ksort($params); 
$key = md5(url()->current().'?'.http_build_query($params));

Trong đoạn mã trên, trước tiên, chúng ta lấy chuỗi truy vấn và bỏ đặt tham số _method (chúng ta sẽ thảo luận về nó sau). Sau khi truy xuất các tham số get trong một mảng, chúng ta sẽ sắp xếp mảng này theo các tên khóa theo thứ tự bảng chữ cái. Đối với điều này, chúng ta sử dụng hàm ksort() của PHP.

Ở đây ta sắp xếp các tham số get vì ta muốn tạo cùng một khóa cho hai URL giống nhau có cùng đường dẫn và cùng một tham số được sắp xếp theo các thứ tự khác nhau. Ví dụ: hãy xem xét hai URL sau.

http://example.com/path/abc?a=10&b=20
http://example.com/path/abc?b=20&a=10

Các URL này sẽ trả về cùng một nội dung. Nhưng băm md5 của hai URL này sẽ khác nhau. Do đó chúng ta phải sắp xếp các tham số get theo thứ tự bảng chữ cái.

Bước 3

Bây giờ ta sẽ thảo luận về tham số “_method” trong các tham số truy vấn. Tham số này được sử dụng để xóa bộ nhớ cache cho một URL cụ thể một cách rõ ràng. Đoạn code sau sẽ kiểm tra xem yêu cầu có tham số “_method” được đặt thành “purge” hay không, sau đó nó sẽ xóa bộ nhớ cache cho trang này và một phản hồi mới sẽ được tạo và lưu vào bộ nhớ cache cho các yêu cầu trong tương lai.

if($request->get('_method') == 'purge')
  Cache::forget($key);

Lưu ý:  Nếu bạn đang sử dụng tham số có tên “_method” cho bất kỳ mục đích nào khác thì bạn nên đổi tên tham số “_method” thành một số thuật ngữ có liên quan khác.

Bước 4

Bây giờ ta sẽ kiểm tra xem có nội dung được lưu trong bộ nhớ cache cho yêu cầu này hay không. Nếu ta đã lưu nội dung vào bộ nhớ đệm cho trang này thì sẽ tạo một đối tượng phản hồi từ nội dung này. Ngoài ra, ta sẽ đặt một tiêu đề để đánh dấu rằng nội dung này được trả về từ bộ nhớ cache.

if(Cache::has($key)){
  $cache = Cache::get($key);
  $response = response($cache['content']);
  $response->header('X-Proxy-Cache', 'HIT');
}

Bước 5

Bây giờ ta sẽ viết phần khác của câu lệnh if từ đoạn mã trên. Trong phần else, ta sẽ để yêu cầu được xử lý bởi controller vì ta không tìm thấy bộ nhớ cache cho yêu cầu này.

Sau khi phản hồi cho yêu cầu này được tạo, ta sẽ kiểm tra để đảm bảo rằng nội dung không trống trong phản hồi này. Việc bạn nhận được phản hồi với nội dung trống không thường xuyên xảy ra. Bạn có thể bỏ qua câu lệnh if này. Nhưng tôi đã đặt nó ở đây vì tôi thực hiện tất cả các thay đổi trên dự án trực tiếp. Vì vậy, bất cứ khi nào tôi cập nhật bất kỳ file blade nào. FileZilla trước tiên sẽ xóa nội dung khỏi tệp cũ và sau đó đặt tệp mới. Do đó nếu ai đó mở một trang mà tôi vừa cập nhật và FileZilla đang cập nhật trang đó, thì phản hồi được tạo ra với một trang trống. Vì vậy, tôi không muốn một trang trống được lưu vào bộ nhớ đệm. Nhưng nếu bạn cũng thực hiện các thay đổi trên trang web trực tiếp thì bạn cũng nên giữ câu lệnh if này.

Vì vậy, nếu nội dung không trống thì ta sẽ đưa nó vào bộ nhớ cache với thời gian hết hạn. Chúng ta sẽ chỉ định thời gian hết hạn trong biến $ttl. TTL là viết tắt của Time To Live. Ta sẽ chỉ định TTL theo phút.

Cuối cùng, ta sẽ đặt một tiêu đề để đánh dấu phản hồi này là một sự bỏ lỡ từ bộ nhớ cache của chúng ta.

else {
    $response = $next($request);
    if(!empty($response->content()))
      Cache::put($key, ['content' => $response->content()], $ttl);
    $response->header('X-Proxy-Cache', 'MISS');
}

Lưu ý:  Biến $ttl sẽ được chuyển làm đối số cho middleware này.

Bước 6

Cuối cùng, chúng ta sẽ chỉ trả về đối tượng phản hồi của ta bất kể nó là từ bộ nhớ cache hay được tạo động.

return $response;

Toàn bộ file V1StudyResponse.php:

Bây giờ sau các bước trên, chúng ta đã tạo thành công một middleware sẽ lưu vào bộ đệm đối tượng phản hồi cho các yêu cầu trong tương lai. Dưới đây là file V1StudyResponse.php đầy đủ.

<?php

namespace App\Http\Middleware;

use Closure;
use Cache;

class V1StudyResponse
{
  /**
   * Handle an incoming request.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  \Closure  $next
   * @return mixed
   */
  public function handle($request, Closure $next, $ttl=1440)
  {
    if(auth()->user() != null || $request->isMethod('post'))
      return $next($request);
    $params = $request->query();
    unset($params['_method']);
    ksort($params);
    $key = md5(url()->current().'?'.http_build_query($params));
    if($request->get('_method') == 'purge')
      Cache::forget($key);
    if(Cache::has($key)){
      $cache = Cache::get($key);
      $response = response($cache['content']);
      $response->header('X-Proxy-Cache', 'HIT');
    }
    else {
      $response = $next($request);
      if(!empty($response->content()))
        Cache::put($key, ['content' => $response->content()], $ttl);
      $response->header('X-Proxy-Cache', 'MISS');
    }

    return $response;
  }
}

Bây giờ ta đăng ký middleware này trong file "app/Http/Kernel.php" trong mảng $routeMiddleware:

'cacheable'=>\App\Http\Middleware\V1StudyResponse::class,

Cách sử dụng:

Để sử dụng middleware này ta mở file "routes/web.php" ra và áp dụng middleware cho các route mà bạn muốn được lưu vào bộ nhớ đệm. Bạn cũng có thể chỉ định thời gian bộ nhớ cache sẽ được lưu trữ. Ví dụ, hãy xem route sau đây:

Route::get('/','HomeController@index')->middleware('cacheable:5');

Route này sẽ được lưu vào bộ nhớ cache trong 5 phút.

Xóa một trang khỏi bộ nhớ cache:

Hãy nhớ rằng ta đang kiểm tra tham số “_method” trong chuỗi truy vấn và nếu tham số này bằng “purge”. Vậy nên ta chỉ cần xóa bộ nhớ cache cho yêu cầu này. Do đó, để xóa một trang khỏi bộ nhớ cache, chỉ cần đưa vào URL của trang có tham số “_method” bằng “purge”. Ví dụ: để xóa một trang tại URL sau:

http://example.com/abc/page

thì ta chỉ cần đưa vào URL sau đây:

http://example.com/abc/page?_method=purge 

« Trước: Cách sửa tình huống web.php không thực thi những update
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!