当前位置:

Redis应用场景-统计文章浏览数(Laravel+redis)

本文最后更新于2022-08-24,已超过 1年没有更新,如果文章内容、图片或者下载资源失效,请留言反馈,我会及时处理,谢谢!

温馨提示:本文共4234个字,读完预计11分钟。

文章目录
 

需求和背景

一篇文章或者帖子的浏览次数的统计
如果只是每次增加一个浏览量 就到数据库新增/修改一个数据
请求频繁 用户量一多就出问题了

解决方案

1.每次增加一个访问量就在缓存中去进行更改
2.达到一定数量后刷新改变Mysql数据库
这样数据也是准确的 效率也比直接每次刷新数据库要高出许多

实践

创建对应的控制器

php artisan make:controller PostController

具体代码如下

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use App\Post;
use App\Events\PostViewEvent;
use Illuminate\Support\Facades\Cache;
class PostController extends Controller
{
    private $cacheExpires = 60;//post文章数据缓存时间 s
    public function showPost(Request $request,$id){
        //存入缓存中,该键值key='post:cache'.$id生命时间60分钟
        $post = Cache::remember('post:cache:'.$id, $this->cacheExpires, function () use ($id) {
            return Post::whereId($id)->first();
        });

        //获取客户端请求的IP
        $ip = $request->ip();
//        $ip = "127.0.1.1";//手动更改ip 以不同ip访问,计数
        //触发浏览次数统计时间
        event(new PostViewEvent($post, $ip));
        dump("当前预览文章的名称:".$post->title);
        dump("当前预览数:".$post->view_count);
    }
}

创建对应Model

php artisan make:model Post

创建对应表

CREATE TABLE `bs_posts` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `content` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `view_count` int(10) unsigned NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

使用seeder或手动随便插入些数据

创建路由

Route::get('/post/{id}', 'PostController@showPost');

使用laravel的事件监听

app/providers/EventServiceProvider 中$listen加入

protected $listen = [
        'App\Events\PostViewEvent' => [
            'App\Listeners\PostEventListener',
        ],
    ];

生成事件监听

php artisan event:generate

事件PostViewEvent实现

<?php

namespace App\Events;

use App\Post;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class PostViewEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $ip;
    public $post;


    /**
     * PostViewEvent constructor.
     * @param Post $post
     * @param $ip
     */
    public function __construct(Post $post, $ip)
    {
        $this->post = $post;
        $this->ip = $ip;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

监听事件实现

<?php

namespace App\Listeners;

use App\Events\PostViewEvent;
use App\Post;
use Illuminate\Support\Facades\Redis;

class PostEventListener
{
    /**
     * 一个帖子的最大访问数
     */
    const postViewLimit = 2;
    /**
     * 同一用户浏览同一个帖子的过期时间
     */
    const ipExpireSec = 200;
    /**
     * Create the event listener.
     */
    public function __construct()
    {
    }

    /**
     * @param PostViewEvent $event
     */
    public function handle(PostViewEvent $event)
    {
        $post = $event->post;
        $ip = $event->ip;
//        $ip = "127.2.1.31";
        $id = $post->id;
        //首先判断下ipExpireSec = 200秒时间内,同一IP访问多次,仅仅作为1次访问量
        if($this->ipViewLimit($id, $ip)){
            //一个IP在300秒时间内访问第一次时,刷新下该篇post的浏览量
            $this->updateCacheViewCount($id, $ip);
        }
    }
    /**
     * 限制同一IP一段时间内得访问,防止增加无效浏览次数
     * @param $id
     * @param $ip
     * @return bool
     */
    public function ipViewLimit($id, $ip)
    {
        $ipPostViewKey = 'post:ip:limit:'.$id;
        //Redis命令SISMEMBER检查集合类型Set中有没有该键,Set集合类型中值都是唯一
        $existsInRedisSet = Redis::command('SISMEMBER', [$ipPostViewKey, $ip]);
        //如果集合中不存在这个建 那么新建一个并设置过期时间
        if(!$existsInRedisSet){
            //SADD,集合类型指令,向ipPostViewKey键中加一个值ip
            Redis::command('SADD', [$ipPostViewKey, $ip]);
            //并给该键设置生命时间,这里设置300秒,300秒后同一IP访问就当做是新的浏览量了
            Redis::command('EXPIRE', [$ipPostViewKey, self::ipExpireSec]);
            return true;
        }
        return false;
    }

    /**
     * 达到要求更新数据库的浏览量
     * @param $id
     * @param $count
     */
    public function updateModelViewCount($id, $count)
    {
        //访问量达到300,再进行一次SQL更新
        $post = Post::find($id);
        $post->view_count += $count;
        $post->save();
    }

    /**
     * 不同用户访问,更新缓存中浏览次数
     * @param $id
     * @param $ip
     */
    public function updateCacheViewCount($id, $ip)
    {
        $cacheKey = 'post:view:'.$id;
        //这里以Redis哈希类型存储键,就和数组类似,$cacheKey就类似数组名 如果这个key存在
        if(Redis::command('HEXISTS', [$cacheKey, $ip])){
            //哈希类型指令HINCRBY,就是给$cacheKey[$ip]加上一个值,这里一次访问就是1
            $save_count = Redis::command('HINCRBY', [$cacheKey, $ip, 1]);
            //redis中这个存储浏览量的值达到30后,就去刷新一次数据库
            if($save_count == self::postViewLimit){
                $this->updateModelViewCount($id, $save_count);
                //本篇post,redis中浏览量刷进MySQL后,就把该篇post的浏览量清空,重新开始计数
                Redis::command('HDEL', [$cacheKey, $ip]);
                Redis::command('DEL', ['laravel:post:cache:'.$id]);
            }
        }else{
            //哈希类型指令HSET,和数组类似,就像$cacheKey[$ip] = 1;
            Redis::command('HSET', [$cacheKey, $ip, '1']);
        }
    }
}

结果

Redis应用场景-统计文章浏览数(Laravel+redis)-Mr.Li's Blog
Redis应用场景-统计文章浏览数(Laravel+redis)-Mr.Li's Blog

 
本文链接:,转发请注明来源!
评论已关闭。