我们试想这样的一个应用场景 一个文章或者帖子的浏览次数的统计 如果只是每次增加一个浏览量
,当浏览量暴增时对数据库的消耗就不言而喻了,那我们是不是可以有其他的解决方案
- 我们使用redis缓存数据库来处理,即使你的网站的请求量很大,那么每次增加一个访问量就在缓存中去进行更改,至于刷新数据库可以自定义为达到某个值时才去更新数据库
首先我们创建一个新的项目
composer create-project laravel/laravel redis_article --prefer-dist
在.env文件下配置mysql以及redis
然后创建控制器 模型
php artisan make:controller ArticleController
php artisan make:model Article -m
// 此命令会在batabase/migrations里生成迁移文件
// 迁移文件
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->increments('id');
$table->string("title");
$table->text("content");
$table->integer('page_view')->unsigned();
$table->timestamps();
});
}
// 使用laravel 模型工厂添加测试数据
php artisan make:seeder ArticleSeeder
php artisan make:factory ArticleFactory
// 运行完会在batabase下的seeder和factory产生两个文件
// ArticleSeeder写法如下 生成50篇文章
public function run()
{
Article::factory()
->count(50)
->create();
}
// ArticleFactory写法如下 默认title,content,page_view数据否则报错
public function definition()
{
return [
'title' => $this->faker->sentence,
'content' => $this->faker->paragraph,
'page_view' => 0
];
}
// 后运行命令
php artisan migrate --path=/database/migrations/迁移文件名.php
php artisan db:seed --class=ArticleSeeder
// 至此测试数据完成
在routes/api.php添加文章路由
Route::get('/articls/{id}', [\App\Http\Controllers\ArticleController::class,'showPost']);
创建浏览器监听事件
在app\EventServiceProvider.php下的$listen添加一个数组
'App\Events\ArticlsViewEvent' => [
'App\Listeners\ArticlsEventListener',
]
// 生成 app\Events\ArticlsViewEvent.php 、 app\Listeners\ArticlsEventListener.php
接着我们书写ArticleController.php控制器
<?php
namespace App\Http\Controllers;
use App\Events\ArticlsViewEvent;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class ArticleController extends Controller
{
public function __construct()
{
$this->cacheExpires = 5;
}
public function showPost(Request $request,$id)
{
//Redis缓存中没有该post,则从数据库中取值,并存入Redis中,该键值key='post:cache'.$id生命时间5分钟
// $Articls = Cache::remember('Article:cache:'.$id, $this->cacheExpires, function () use ($id) {
//
// return Article::whereId($id)->first();
//
// });
$Articls = Article::whereId($id)->first();
//获取客户端请求的IP
$ip = $request->ip();
//触发浏览次数统计时间 并把文章缓存和$ip传递过去
event(new ArticlsViewEvent($Articls, $ip));
return view('blog.show', compact('Articls'));
}
}
app\Events\ArticlsViewEvent.php
<?php
namespace App\Events;
use App\Models\Article;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ArticlsViewEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(Article $article, $ip)
{
// 在构造函数中把参数保存在$this中让ArticlsEventListener接收
$this->article = $article;
//
$this->ip = $ip;
}
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
app\Listeners\ArticlsEventListener.php
<?php
namespace App\Listeners;
use App\Events\ArticlsViewEvent;
use App\Models\Article;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class ArticlsEventListener
{
/**
* 限定redis最大存储数,达到这个数后更新数据库
*/
const ArticlsViewLimit = 5;
/**
* 同一用户浏览同一个帖子的过期时间-秒
*/
const ArticlsExpireTime = 30;
// const
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param \App\Events\ArticlsViewEvent $event
* @return void
*/
public function handle(ArticlsViewEvent $event)
{
$articls = $event->article;
$ip = $event->ip;
$id = $articls->id;
// 如果一个用户在ArticlsExpireTime时间内访问多次则只记录一次
if($this->ArticlsRedisTime($ip,$id))
{
var_dump('浏览有效,记录');
$this->updateCacheViewCount($id, $ip);
}
}
public function ArticlsRedisTime($ip,$id)
{
$key = "Articls:Ip:time:$id:$ip";
// Redis命令SISMEMBER检查集合类型Set中有没有该键,Set集合类型中值都是唯一
$rulest = Redis::command('SISMEMBER',[$key,$ip]);
var_dump("存在则说明访问频繁不记录,不存在则真实有效");
var_dump("存在?$rulest,");
// 如果不存在则创建
if(!$rulest)
{
Redis::command('SADD',[$key,$ip]);
//并给该键设置生命时间,这里设置ArticlsExpireTime,ArticlsExpireTime秒后同一IP访问就当做是新的浏览量了
Redis::command('EXPIRE', [$key, self::ArticlsExpireTime]);
return true;
}
return false;
}
/**
* 达到要求更新数据库的浏览量
*/
public function updateCacheViewCount($id,$ip)
{
$key = "Articls:View";
// 如果这个key存在 +1
if(Redis::command('HEXISTS',[$key,$id]))
{
// 存在+1 返回总数
$save_count = Redis::command('HINCRBY', [$key, $id, 1]);
var_dump($save_count);
if($save_count == self::ArticlsViewLimit)
{
// 达到更新数据库的条件,更新数据库
var_dump("达到更新数据库的条件,更新数据库");
$this->updataMysql($id,$save_count);
var_dump("更新数据库后,清除redis && 清除laravel的缓存");
// 更新数据库后,清除redis && 清除laravel的缓存
Redis::command('HDEL', [$key, $id]);
Cache::pull('Article:cache:'.$id);
}
}
else{
Redis::command('HSET', [$key, $id, '1']);
}
}
public function updataMysql($id,$save_count)
{
$data = Article::find($id);
$data->page_view = bcadd($data->page_view,$save_count);
$data->save();
var_dump("更新数据库成功");
}
}
最后视图resources\views\blog\show.blade.php
<div style="text-align: center">
<h1>{{$Articls->title}}</h1>
<br>
浏览量:{{$Articls->page_view}}
<br>
内容:Content:{{$Articls->content}}
<br>
</div>