上篇教程学院君已经给大家简单介绍了 Redis 的基本数据结构和常见使用场景,接下来我们就以 Laravel 项目为例来演示如何实现这些常见的业务功能。
首先从最简单的计数器开始,学院君这里将通过 Redis 来实现一个全站访问统计计数器。
你可以先阅读下 Laravel Redis 文档先熟悉下。
安装 PHP Redis 扩展
开始之前,我们先新建一个 Laravel 示例项目 redis-demo
:
laravel new redis-demo
要想在 Laravel/PHP 项目中使用 Redis,需要先安装 PHP Redis 扩展,在 Mac/Linux 系统中可以通过 pecl install redis
快速安装,如果使用的是 Laradock 集成开发环境,只需要在 Laradock 的 .env
环境配置文件中启用 Redis 扩展:
WORKSPACE_INSTALL_PHPREDIS=true
PHP_FPM_INSTALL_PHPREDIS=true
然后为 redis-demo
项目配置虚拟域名 redis-demo.test
,重新构建 nginx
镜像并重启 nginx
容器服务,最后通过打印 phpinfo
信息看到列表中包含 redis
,则表明扩展安装成功:
此外,还可以通过 Composer 安装 predis 扩展包在 Laravel/PHP 项目中使用 Redis,不过作者宣称已停止更新该扩展包,所以推荐使用 PHP Redis 扩展包,且该扩展包基于 C 语言编写,性能也更好。
如果你使用的是 Laravel 官方提供的 Sail 构建 Docker 开发环境,则 PHP Redis 扩展包已经默认安装:
Redis 客户端连接与配置
在 redis-demo
项目根目录下的 .env
环境配置文件中配置 Redis 连接信息:
REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
就可以在 Laravel 项目中与 Redis 服务器进行交互了,我们可以通过 Laravel 提供的 Redis
门面获取 Redis 客户端连接:
Route::get('/connection', function () {
dd(IlluminateSupportFacadesRedis::connection());
});
当然,门面本质上是对服务容器中 Redis 绑定对象的代理,对应的绑定代码位于 IlluminateRedisRedisServiceProvider
的 register
方法中:
public function register()
{
$this->app->singleton('redis', function ($app) {
$config = $app->make('config')->get('database.redis', []);
// 使用 phpredis 还是 predis 连接器取决于 database.redis.client 配置,默认值是 phpredis
return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
});
$this->app->bind('redis.connection', function ($app) {
return $app['redis']->connection();
});
}
所以你也可以通过服务容器解析访问 Redis 连接实例:
代码语言:javascript复制dd(app('redis')->connection());
// 或者
dd(app('redis.connection'));
在浏览器中访问 http://redis-demo.test/connection
,即可查看到对应的打印结果:
可以看到,由于 REDIS_CLIENT
配置值是 phpredis
,所以使用的是 PhpRedisConnector
与 Redis 服务器建立客户端连接(如果配置为 predis
,则对应的类文件是 PredisConnector
)。
与服务端建立连接的配置值位于 config
属性中,其中包含了 Redis 服务器 IP(redis
容器)、端口号(6379)、密码(默认为空)和数据库信息(默认是 0)等,此外还有一个 options
属性指定额外的连接选项,cluster
表示集群,prefix
表示键名前缀,所有这些配置项都是在 config/database.php
中完成配置的:
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
Redis 计数器功能实现
完成上述安装和配置工作后,接下来,我们就可以正式基于 Redis 实现全站访问计数器功能了。
我们可以基于 Laravel 全局中间件结合 Redis 的 INCR
指令来实现这个功能,创建一个名为 SiteVisits
的中间件:
php artisan make:middleware SiteVisits
编写其实现代码如下:
代码语言:javascript复制<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesRedis;
class SiteVisits
{
/**
* Handle an incoming request.
*
* @param IlluminateHttpRequest $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
Redis::incr('site_total_visits');
return $next($request);
}
}
非常简单,我们只需要在 handle
方法中通过 Redis
门面将 Redis 指令作为静态方法名进行调用(底层会通过网络调用将其转化为真正的 Redis 指令执行),传入键名作为参数即可。
全局访问计数器是一个自增的计数操作,每次自增步长是 1,所以调用 incr
方法即可,如果首次调用键值不存在,则先将其初始化为 0,再进行 1 操作。
此外,Redis 的 INCR
指令是原子操作,可以保证并发安全,所以用在这里再合适不过了。
在 app/Http/Kernel.php
中应用这个全局中间件:
protected $middleware = [
...
AppHttpMiddlewareSiteVisits::class,
];
这样一来,每次访问 Laravel Web 路由,就可以通过这个中间件统计全局访问量了。
获取 Redis 计数器的值
我们在 routes/web.php
中注册一个路由获取计数器的值进行测试:
Route::get('/site_visits', function () {
return '网站全局访问量:' . IlluminateSupportFacadesRedis::get('site_total_visits');
});
在浏览器中访问该路由,每次刷新页面计数器的值都会 1,说明计数器工作正常。
不过,如果你通过 Redis 命令行客户端进行访问的话,直接通过 site_total_visits
是无法获取到计数器的值的:
因为 Laravel 会给 Redis 所有键设置一个前缀 prefix
,其默认值是 laravel_database_
,所以在 Redis 底层,需要通过 laravel_database_site_total_visits
才能获取到对应计数器的值:
如果你初来乍到,不知道前缀是什么,可以通过 Redis 的 KEYS
指令进行模糊匹配:
然后通过匹配结果再去执行 GET
指令获取计数器的值。
那 Laravel 代码中为何可以直接使用 site_total_visits
键进行访问呢?因为在建立 Redis 连接的时候,会将键名前缀设置到 Redis 的连接属性 Redis::OPT_PREFIX
上(源码位于 PhpRedisConnector
中):
if (! empty($config['prefix'])) {
$client->setOption(Redis::OPT_PREFIX, $config['prefix']);
}
这样一来,每次不管是设置还是获取键值,都会自动应用这个键名前缀。