キャッシュの管理にRedisを使って、ランキング機能をつくった際のメモです。
環境は以下の通りです。
- laravel 5.8
- php 7.2
- predis 1.1
Redisのインストール
Redisを使うためにRedisのPHP拡張をインストールします。Laravel 5.8 Redis
ここではLaravelは5.8
であるためpredisを使用しますが、'6.0'ではpredisは非推奨となっています。Laravel 6.x Redis
なので6.0
の場合はphpredisを使用しましょう。(本記事ではphpredisは扱いません)
composerでpredisのインストール
predis/predisパッケージをcomposerでインストールします。
$ composer require predis/predis
Redisの設定
データベースの設定ファイルを編集します。自分の場合、redisをdockerコンテナで運用していたためホスト名はコンテナ名となっています。
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', 'redis'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
]
CACHE_DRIVER=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
動作確認
Tinkerを使って動作確認をしてみます。
$ php artisan tinker
Psy Shell v0.9.12 (PHP 7.3.14 — cli) by Justin Hileman
>>> use Redis
>>> Redis::set('test', 'TEST');
=> Predis\Response\Status {#3050}
>>> Redis::get('test')
=> "TEST"
設計
機能を大きく二つに分けると
キャッシュへの記事の閲覧数の保存
・keyに記事ごとのid、valueに閲覧数を保存
記事の閲覧があった場合
->既にkeyの保存があれば、閲覧数をインクリメント(valueを+1)。
->まだkeyの保存がなければ、新しくkeyを作成し、value=1で保存。
キャッシュからランキングの結果を取得し、配列で返す
・すべてのkeyを取得し、valueの降順にソートした配列を返す。
RankingService
ランキング機能はRankingServiceクラスとし、ライブラリとして切り出します。
<?php
namespace app\Libraries;
use Illuminate\Http\Request;
use Redis;
class RankingService
{
//keyの保存、閲覧数のインクリメント
public function incrementViewRanking($id)
{
$key = "ranking-"."id:".$id;
$value = Redis::get($key);
if(empty($value)){
Redis::set($key, "1");
Redis::expire($key, 3600*24);
} else {
Redis::set($key, $value + 1);
}
}
//ランキング結果を配列で取得
public function getRankingAll()
{
$keys = Redis::keys('ranking-*');
$results = Array();
if(empty($keys) != true){
for($i = 0; $i < sizeof($keys); $i++){
$id = explode(':', $keys[$i])[1];
$point = Redis::get('ranking-id:'. $id);
$results[$id] = $point;
}
arsort($results, SORT_NUMERIC);
}
return $results;
}
}
keyの保存、閲覧数のインクリメント
public function incrementViewRanking($id)
{
$key = "ranking-"."id:".$id;
$value = Redis::get($key);
if(empty($value)){
Redis::set($key, "1");
Redis::expire($key, 3600*24);
} else {
Redis::set($key, $value + 1);
}
}
incrementViewRanking
では閲覧のあった記事のidを引数として受け取り閲覧数の管理を行います。
一つずつ見ていきます。
$key = "ranking-"."id:".$id;
$value = Redis::get($key);
keyからvalueの取得をします。
RedisではRedis::get()
でvalueの取得ができます。
if(empty($value)){
Redis::set($key, "1");
Redis::expire($key, 3600*24);
} else {
Redis::set($key, $value + 1);
}
keyがあればvalueをインクリメントします。
なければ記事のidをranking-id:$id
としてキーを保存します。
Redis::expire()
でキャッシュする時間を指定します。今回は3600秒×24で24時間に指定します。
ランキング結果の取得
public function getRankingAll()
{
$keys = Redis::keys('ranking-*');
$results = Array();
if(!empty($keys)){
for($i = 0; $i < sizeof($keys); $i++){
$id = explode(':', $keys[$i])[1];
$point = Redis::get('ranking-id:'. $id);
$results[$id] = $point;
}
arsort($results, SORT_NUMERIC);
}
return $results;
}
getRankingAll
ではidと閲覧数をランキングの順で配列に格納します。
保存したすべてのkeyから、arsort()
によってvalueの降順にソートしています。
Redis::keys('ranking-*')
はkeyの取得です。*
でワイルドカードの指定ができます。
ここは正直ごちゃごちゃしてるので改善の余地がありそう。
各コントローラ内での使用
閲覧数のインクリメント
記事の表示があるたびにインクリメントを行うので、incrementViewRanking()
はコントローラの表示メソッド内で使います。
public function show(Article $article)
{
$ranking = new RankingService;
$ranking->incrementViewRanking($article->id); //インクリメント
$movie->getArticle($article->id); //記事の取得
return view('articles.show', [
'article' => $article
]);
}
ランキングの取得
public function ranking(Article $article)
{
$ranking = new RankingService;
$results = $ranking->getRankingAll();
$article_ranking = $article->getArticleRanking($results);
return view('article.ranking', [
'article_ranking' => $article_ranking
]);
}
getRankingAll()
で取得したランキングの配列から記事を取得します。
public function getArticleRanking(Array $results)
{
$article_ids = array_keys($results);
$ids_order = implode(',', $article_ids);
$article_ranking = $this->whereIn('id', $article_ids)
->orderByRaw(DB::raw("FIELD(id, $ids_order)"))
->paginate(10);
return $article_ranking;
}
配列の順序のまま記事を取得したいので、こんな風に生のクエリを書くことになりました。普通にwhereIn
だけで取得すると自動的にLaravelがid順に並べ替えてしまうのです。
ごちゃごちゃしてますね。