alvin0 / redis-model
介绍 Redis Model - 一个连接到 Redis 并类似于 Eloquent Model 的 Laravel 扩展包,提供高效的数据操作和检索功能。
Requires
- php: ^8.1
- illuminate/console: ^10|^11
- illuminate/database: ^10|^11
- predis/predis: ^2.2
Requires (Dev)
- pestphp/pest: ^2.2
README
Redis Model 可以帮助在 Redis 中创建具有相同前缀的多个键,并将这些键作为一个 SQL 数据库中的表分组。Redis Model 将创建一个类似于 Laravel 中的 Eloquent Model 的实例。它还将提供完整的方法,用于添加、删除、更新和检索具有类似 Eloquent 方法的数据数组。
无关系:Redis 不是存储复杂关系数据的最佳选择,Redis 强调其快速访问数据的能力,因此,在模型之间构建关系方法是不必要的,这将花费大量时间来检索数据。
支持
支持的 Laravel 版本
模型支持
键结构
Laravel 中 Redis 模型的示例键结构
{redis_prefix}{redis_database_name}{model_name}:{primary_key}:{sub_key_1}:{sub_key_2}:...:{sub_key_n}
- redis_prefix:Redis 配置文件中设置的 Redis 前缀。
- redis_database_name:用于模型的数据库名称。
- model_name:模型名称。
- primary_key:模型的键。
- sub_keys:属于模型的附加键,可以在模型的 'subKey' 属性中定义。
示例键
laravel_redis_model_users:email:email@example:name:alvin:role:admin
在这个例子中
- Redis 前缀是 'laravel'
- Redis 数据库名称是 'redis_model'
- 模型名称是 'users'
- 模型的键是 'email'
- 模型的子键是 'name' 和 'role'。
注意:Redis 前缀和数据库名称可以在 'redis-model' 配置文件中配置。模型的主键和子键可以在模型的 'primaryKey' 和 'subKeys' 属性中分别定义。
安装
您可以通过 Composer 包管理器安装 Redis Model
composer require alvin0/redis-model
您应该使用 vendor:publish
Artisan 命令发布 RedisModel 配置和迁移文件。配置文件将被放置在您的应用的 config
目录下
php artisan vendor:publish --provider="Alvin0\RedisModel\RedisModelServiceProvider"
生成模型
php artisan redis-model:model User
模型约定
主键、子键和可填充字段
主键是默认由 uuid 分配的属性,并确定模型的搜索行为。请确保主键的值是唯一的,以避免在根据条件检索数据时产生混淆。子键是可以重复的键,它们将帮助您在模型中使用 where 方法进行搜索。为了声明模型属性,您需要在 fillable
变量中声明它们。您还应在此变量中声明 primaryKey
和 subKeys
。
use Alvin0\RedisModel\Model; class User extends Model { /** * The model's sub keys for the model. * * @var array */ protected $subKeys = [ 'name', 'role', ]; /** * The attributes that are mass assignable. * * @var array<string> */ protected $fillable = [ 'id', 'email', 'name', 'role', 'address' ]; }
如果可能的话,请关闭模型键的自动 UUID 生成功能,并选择数据键以确保键搜索的简便性。请确保模型的主键是唯一的。
use Alvin0\RedisModel\Model; class User extends Model { /** * The primary key for the model. * * @var bool */ protected $primaryKey = 'email'; /** * Indicates if the IDs are auto-incrementing. * * @var bool */ public $incrementing = false; /** * The model's sub keys for the model. * * @var array */ protected $subKeys = [ 'name', 'role', ]; /** * The attributes that are mass assignable. * * @var array<string> */ protected $fillable = [ 'email', 'name', 'role', 'address' ]; }
表名
因此,在这种情况下,RedisModel 将假定 User
模型将记录存储在 users
表中。
use Alvin0\RedisModel\Model; class User extends Model { // ... }
在创建哈希码之前,最终的名称基于 表的名称前缀 + 表名
。如果您的模型对应的数据库表不遵循此约定,您可以通过在模型上定义表属性来手动指定模型的表名。
use Alvin0\RedisModel\Model; class User extends Model { /** * The model's table. * * @var array */ protected $table = ""; /** * The model's prefixTable. * * @var array */ protected $prefixTable = null; }
时间戳
默认情况下,RedisModel 预期您的模型对应的数据库表存在 created_at
和 updated_at
列。当模型被创建或更新时,RedisModel 会自动设置这些列的值。如果您不希望 RedisModel 自动管理这些列,请在您的模型上定义一个 $timestamps
属性,并将其值设置为 false
。
use Alvin0\RedisModel\Model; class User extends Model { /** * Indicates if the model should be timestamped. * * @var bool */ public $timestamps = false; }
配置连接模型
您可以更改模型连接的连接名称。请确保它在 redis-model
配置文件中声明。默认情况下,模型将使用 redis_model_default
连接名称。
use Alvin0\RedisModel\Model; class User extends Model { /** * @var string|null */ protected $connectionName = null; }
检索模型
构建
由于搜索模型属性的局限性,where 方法是唯一支持的方法。where 方法将简化在模型表的搜索主键和子键。您可以在查询中添加额外的约束,然后调用 get
方法来检索结果:where 方法只能搜索 主键
和 子键
字段。
use App\RedisModels\User; User::where('email', 'email@gmail.com') ->where('role', 'admin') ->get();
提示:where("field", "something_*") 您可以使用 * 来匹配所需的任何关键字之后的任何关键字。看起来就像 SQL 中的相同位置。
use App\RedisModels\User; User::where('name', "user_*")->get(); // result collection // [ // ["name" => "user_1"], // ["name" => "user_2"], // ["name" => "user_3"], // ["name" => "user_4"], // ]
集合
正如我们所看到的,Eloquent 的 all
和 get
等方法可以从 Redis 检索多个记录。然而,这些方法不返回纯 PHP 数组。相反,返回 Alvin0\RedisModel\Collection
的实例。
- 方法 all()
use App\RedisModels\User; User::all();
- 方法 get()
use App\RedisModels\User; User::where('name', "user_*")->get();
分块结果
如果尝试使用 all
或 get
方法加载成千上万的 Eloquent 记录,则您的应用程序可能会耗尽内存。相反,可以使用 chunk 方法更有效地处理大量模型。
- 方法 chunk
use App\RedisModels\User; use Alvin0\RedisModel\Collection; User::where('user_id', 1) ->chunk(10, function (Collection $items) { foreach ($items as $item) { dump($item); } });
检索单个模型
除了检索与给定查询匹配的所有记录外,您还可以使用 find
、first
方法检索单个记录。与返回模型集合不同,这些方法返回单个模型实例。
use App\RedisModels\User; // Retrieve a model by its primary key... $user = User::find('value_primary_key'); // Retrieve the first model matching the query constraints... $user = User::where('email', 'email@gmail.com')->first();
插入 & 更新模型
插入
我们还需要插入新记录。幸运的是,Eloquent 使之变得简单。要将新记录插入到数据库中,应创建一个新的模型实例并设置模型上的属性。然后,在模型实例上调用 save 方法。
use App\RedisModels\User; $user = new User; $user->email = 'email@gmail.com'; $user->name = 'Alvin0'; $user->token = '8f8e847890354d23b9a762f4d2612ce5'; $user->token = now(); $user->save()
创建模型
或者,您可以使用 create 方法使用单个 PHP 语句 save
新模型。插入的模型实例将由 create 方法返回。
use App\RedisModels\User; $user = User::create([ 'email' => 'email@gmail.com', 'name' => 'Alvin0' 'token' => '8f8e847890354d23b9a762f4d2612ce5', 'expire_at' => now(), ]) $user->email //email@gmail.com
强制创建模型
默认情况下,如果主键重复,create 方法将自动抛出错误(Alvin0\RedisModel\Exceptions\KeyExistException
)。如果您想忽略此错误,可以尝试以下方法:
- 将属性
preventCreateForce
更改为false
。
use Alvin0\RedisModel\Model; class User extends Model { /** * Indicates when generating but key exists * * @var bool */ protected $preventCreateForce = true; }
- 使用方法 forceCreate。
use App\RedisModels\User; User::forceCreate([ 'email' => 'email@gmail.com', 'name' => 'Alvin0' 'token' => '8f8e847890354d23b9a762f4d2612ce5', 'expire_at' => now(), ]); $user->email //email@gmail.com
插入语句
为了解决将多个项目插入到表中的问题,可以使用 inserts 函数。建议使用数组分块以确保性能。
use App\RedisModels\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; $seed = function ($limit) { $users = []; for ($i = 0; $i < $limit; $i++) { $users[] = [ 'email' => Str::random(10) . '@gmail.com', 'name' => Str::random(8), 'token' => md5(Str::random(10)), 'expire_at' => now(), ]; } return $users; }; User::insert($seed(10));
我认为您应该使用事务方法来确保在插入多个数据的过程中发生错误时,您的数据将被回滚。
use App\RedisModels\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; $seed = function ($limit) { $users = []; for ($i = 0; $i < $limit; $i++) { $users[] = [ 'email' => Str::random(10) . '@gmail.com', 'name' => Str::random(8), 'token' => md5(Str::random(10)), 'expire_at' => now(), ]; } return $users; }; User::transaction(function ($conTransaction) use ($data) { User::insert($seed(10), $conTransaction); });
更新模型
可以使用 save
方法更新已存在于数据库中的模型。要更新模型,您应该检索它并设置您希望更新的任何属性。然后,应调用模型的 save
方法。同样,updated_at
时间戳将自动更新,因此无需手动设置其值。
use App\RedisModels\User; $user = User::find('email@gmail.com'); $user->name = 'Alvin1'; $user->save();
不支持对集合进行方法更新以进行更改。请使用现有实例来代替
$user = User::find('email@gmail.com')->update(['name' => 'Alvin1']);
删除模型
要删除模型,可以在模型实例上调用删除方法
use App\RedisModels\User; $user = User::find('email@gmail.com')->delete();
删除语句
查询构建器的删除方法可以用于从redis模型中删除记录。
User::where('email', '*@gmail.com')->destroy(); //or remove all data model User::destroy();
过期
与Redis
一起工作的特殊之处在于,您可以设置键的过期时间,并且对于模型实例,将有一个用于设置
和获取
该过期时间的方法
。
设置过期模型
use App\RedisModels\User; $user = User::find('email@gmail.com')->setExpire(60); // The instance will have a lifespan of 60 seconds
获取过期模型
use App\RedisModels\User; $user = User::find('email@gmail.com')->getExpire(); // The remaining time to live of the instance is 39 seconds.
事务
Redis模型的交易方法提供了Redis本地MULTI
和EXEC
命令的方便包装器。交易方法接受一个闭包作为其唯一参数。此闭包将接收Redis连接实例,并可以向该实例发出任何想要的命令。在闭包中发出的所有Redis命令将作为一个单独的、原子的交易执行。
当定义Redis交易时,您不能从Redis连接中检索任何值。记住,您的交易作为一个单独的、原子的操作执行,并且该操作只有在您的整个闭包完成执行其命令后才会执行。
use App\RedisModels\User; use Redis; $data [ 'users:id:1:email:email@example:name:alvin:role:admin' => [ 'id' => 1, 'email'=>'email@example' 'name' => 'alvin', 'role'=>'admin' ] 'users:id:2:email:email@example:name:alvin:role:admin' => [ 'id' => 2, 'email'=>'email@example' 'name' => 'alvin', 'role'=>'admin' ] 'users:id:3:email:email@example:name:alvin:role:admin' => [ 'id' => 3, 'email'=>'email@example' 'name' => 'alvin', 'role'=>'admin' ] ]; $user = User::transaction(function (Redis $conTransaction) use($data) { foreach($data as $key => $value) { $conTransaction->hMSet($key, $value); } // $conTransaction->discard(); });