alvin0/redis-model

介绍 Redis Model - 一个连接到 Redis 并类似于 Eloquent Model 的 Laravel 扩展包,提供高效的数据操作和检索功能。

1.0 2024-07-21 14:49 UTC

This package is auto-updated.

Last update: 2024-09-21 15:19:45 UTC


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 变量中声明它们。您还应在此变量中声明 primaryKeysubKeys

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_atupdated_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 的 allget 等方法可以从 Redis 检索多个记录。然而,这些方法不返回纯 PHP 数组。相反,返回 Alvin0\RedisModel\Collection 的实例。

  • 方法 all()
use App\RedisModels\User;

User::all();
  • 方法 get()
use App\RedisModels\User;

User::where('name', "user_*")->get();

分块结果

如果尝试使用 allget 方法加载成千上万的 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);
        }
    });

检索单个模型

除了检索与给定查询匹配的所有记录外,您还可以使用 findfirst 方法检索单个记录。与返回模型集合不同,这些方法返回单个模型实例。

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本地MULTIEXEC命令的方便包装器。交易方法接受一个闭包作为其唯一参数。此闭包将接收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(); 
});