Katakan anda sedang membina API untuk menyampaikan beberapa data, anda mendapati respons GET agak perlahan. Anda telah mencuba mengoptimumkan pertanyaan anda, mengindeks jadual pangkalan data anda dengan lajur yang sering ditanya dan anda masih tidak mendapat masa respons yang anda inginkan. Langkah seterusnya yang perlu diambil ialah menulis lapisan Caching untuk API anda. 'Lapisan cache' di sini hanyalah istilah mewah untuk perisian tengah yang menyimpan respons yang berjaya dengan pantas untuk mendapatkan semula kedai. cth. Redis, Memcached dsb. kemudian sebarang permintaan lanjut kepada API menyemak sama ada data tersedia di kedai dan memberikan respons.
Saya mengandaikan jika anda telah sampai di sini, anda tahu cara membuat aplikasi laravel. Anda juga harus mempunyai sama ada contoh Redis tempatan atau awan untuk disambungkan. Jika anda mempunyai docker tempatan, anda boleh menyalin fail karang saya di sini. Juga, untuk panduan tentang cara menyambung kepada pemacu cache Redis baca di sini.
Untuk membantu kami melihat lapisan caching kami berfungsi seperti yang diharapkan. sudah tentu kita memerlukan beberapa data, katakan kita mempunyai model bernama Post. jadi saya akan membuat beberapa siaran, saya juga akan menambah beberapa penapisan kompleks yang boleh menjadi intensif pangkalan data dan kemudian kita boleh mengoptimumkan dengan caching.
Sekarang mari kita mula menulis middleware kami:
Kami mencipta rangka middleware kami dengan menjalankan
php artisan make:middleware CacheLayer
Kemudian daftarkannya dalam apl/Http/Kernel.php anda di bawah kumpulan perisian tengah api seperti:
protected $middlewareGroups = [ 'api' => [ CacheLayer::class, ], ];
Tetapi jika anda menjalankan Laravel 11. daftarkannya dalam bootstrap/app.php
anda
->withMiddleware(function (Middleware $middleware) { $middleware->api(append: [ \App\Http\Middleware\CacheLayer::class, ]); })
Jadi pemacu cache ialah stor nilai kunci. jadi anda mempunyai kunci maka nilainya ialah json anda. Oleh itu, anda memerlukan kunci cache unik untuk mengenal pasti sumber, kunci cache unik juga akan membantu dalam ketidaksahihan cache iaitu mengalih keluar item cache apabila sumber baharu dibuat/kemas kini. Pendekatan saya untuk penjanaan kunci cache ialah menukar url permintaan, parameter pertanyaan dan badan menjadi objek. kemudian sirikannya kepada rentetan. Tambahkan ini pada perisian tengah cache anda:
class CacheLayer { public function handle(Request $request, Closure $next): Response { } private function getCacheKey(Request $request): string { $routeParameters = ! empty($request->route()->parameters) ? $request->route()->parameters : [auth()->user()->id]; $allParameters = array_merge($request->all(), $routeParameters); $this->recursiveSort($allParameters); return $request->url() . json_encode($allParameters); } private function recursiveSort(&$array): void { foreach ($array as &$value) { if (is_array($value)) { $this->recursiveSort($value); } } ksort($array); } }
Jom kita lalui kod baris demi baris.
Jadi bergantung pada sifat aplikasi yang anda sedang bina. Akan ada beberapa laluan GET yang anda tidak mahu cache jadi untuk ini kami mencipta pemalar dengan regex untuk memadankan laluan tersebut. Ini akan kelihatan seperti:
private const EXCLUDED_URLS = [ '~^api/v1/posts/[0-9a-zA-Z]+/comments(\?.*)?$~i' ' ];
Dalam kes ini, regex ini akan sepadan dengan semua ulasan siaran.
Untuk ini, tambahkan entri ini pada config/cache.php
anda
'ttl' => now()->addMinutes(5),
Sekarang kami telah menetapkan semua langkah awal kami, kami boleh menulis kod perisian tengah kami:
public function handle(Request $request, Closure $next): Response { if ('GET' !== $method) { return $next($request); } foreach (self::EXCLUDED_URLS as $pattern) { if (preg_match($pattern, $request->getRequestUri())) { return $next($request); } } $cacheKey = $this->getCacheKey($request); $exception = null; $response = cache() ->tags([$request->url()]) ->remember( key: $cacheKey, ttl: config('cache.ttl'), callback: function () use ($next, $request, &$exception) { $res = $next($request); if (property_exists($res, 'exception') && null !== $res->exception) { $exception = $res; return null; } return $res; } ); return $exception ?? $response; }
When new resources are created/updated, we have to clear the cache, so users can see new data. and to do this we will tweak our middleware code a bit. so in the part where we check the request method we add this:
if ('GET' !== $method) { $response = $next($request); if ($response->isSuccessful()) { $tag = $request->url(); if ('PATCH' === $method || 'DELETE' === $method) { $tag = mb_substr($tag, 0, mb_strrpos($tag, '/')); } cache()->tags([$tag])->flush(); } return $response; }
So what this code is doing is flushing the cache for non-GET requests. Then for PATCH and Delete requests we are stripping the {id}. so for example if the request url is PATCH /users/1/posts/2 . We are stripping the last id leaving /users/1/posts. this way when we update a post, we clear the cache of all a users posts. so the user can see fresh data.
Now with this we are done with the CacheLayer implementation. Lets test it
Let's say we want to retrieve all a users posts, that has links, media and sort it by likes and recently created. the url for that kind of request according to the json:api spec will look like: /posts?filter[links]=1&filter[media]=1&sort=-created_at,-likes. on a posts table of 1.2 million records the response time is: ~800ms
and after adding our cache middleware we get a response time of 41ms
Great success!
Another optional step is to compress the json payload we store on redis. JSON is not the most memory-efficient format, so what we can do is use zlib compression to compress the json before storing and decompress before sending to the client.
the code for that will look like:
$response = cache() ->tags([$request->url()]) ->remember( key: $cacheKey, ttl: config('cache.ttl'), callback: function () use ($next, $request, &$exception) { $res = $next($request); if (property_exists($res, 'exception') && null !== $res->exception) { $exception = $res; return null; } return gzcompress($res->getContent()); } ); return $exception ?? response(gzuncompress($response));
The full code for this looks like:
getMethod(); if ('GET' !== $method) { $response = $next($request); if ($response->isSuccessful()) { $tag = $request->url(); if ('PATCH' === $method || 'DELETE' === $method) { $tag = mb_substr($tag, 0, mb_strrpos($tag, '/')); } cache()->tags([$tag])->flush(); } return $response; } foreach (self::EXCLUDED_URLS as $pattern) { if (preg_match($pattern, $request->getRequestUri())) { return $next($request); } } $cacheKey = $this->getCacheKey($request); $exception = null; $response = cache() ->tags([$request->url()]) ->remember( key: $cacheKey, ttl: config('cache.ttl'), callback: function () use ($next, $request, &$exception) { $res = $next($request); if (property_exists($res, 'exception') && null !== $res->exception) { $exception = $res; return null; } return gzcompress($res->getContent()); } ); return $exception ?? response(gzuncompress($response)); } private function getCacheKey(Request $request): string { $routeParameters = ! empty($request->route()->parameters) ? $request->route()->parameters : [auth()->user()->id]; $allParameters = array_merge($request->all(), $routeParameters); $this->recursiveSort($allParameters); return $request->url() . json_encode($allParameters); } private function recursiveSort(&$array): void { foreach ($array as &$value) { if (is_array($value)) { $this->recursiveSort($value); } } ksort($array); } }
This is all I have for you today on caching, Happy building and drop any questions, commments and improvements in the comments!
Atas ialah kandungan terperinci Cara membina lapisan caching untuk API Laravel anda. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!