linushstge/number-pool

为每个业务号码范围创建一个共享的号码池,以在MySQL Master/Master复制或galera集群上运行时使用原生的MySQL / MariaDB `FOR UPDATE` 原子锁。

0.9 2022-09-08 21:24 UTC

This package is auto-updated.

Last update: 2024-09-09 01:51:34 UTC


README

为每个业务号码范围创建一个共享的号码池,以在MySQL Master/Master复制或galera集群上运行时使用原生的MySQL / MariaDB `FOR UPDATE` 原子锁,或者如果您有多个消息队列工作进程正在消费相同的作业。

如果您在复制上运行,您的主自增很可能无法可靠地生成唯一的递增数字。使用此Eloquent特性,您可以在使用InnoDB的原生`FOR UPDATE`行锁的同时生成递增的唯一数字。

Galera集群上的三个节点的示例发票表

在主-主复制或galera集群中,您的主自增对于任何递增数字都是不可靠的。

安装

此包可以通过composer安装

composer require linushstge/number-pool

安装后,您必须使用artisan:make migration为您号码池创建一个新的迁移。

php artisan make:migration CreateNumberPool

示例

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('number_pool', function (Blueprint $table) {
            $table->id();
            $table->string('key')->unique()->index();
            $table->bigInteger('number');
            $table->string('description')->nullable();
            $table->dateTime('created_at');
            $table->dateTime('updated_at');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('number_pool');
    }
};

号码池存储字符串标识符键和应用程序使用的任何池中最后一个使用的号码。通过该键,您可以在多个Eloquent模型中使用相同的号码池。

用法

为您的第一个Eloquent模型创建一个新的NumberPool并将其持久化到数据库。

$numberPool = new NumberPool([
   'key' => 'invoice.number',
   'number => 999, // latest persisted number
   'description' => 'Pool for generating unique ascending invoice numbers'
]);
$numberPool->save();

NumberPool特性添加到您的现有模型之一,并实现抽象方法numberPoolKeynumberPoolAttribute以设置池和您希望保存递增唯一数字的本地模型属性。

<?php

namespace App\Models\Account;

use linushstge\NumberPool\Traits\NumberPool;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model
{
    use NumberPool;

    public function numberPoolKey(): string
    {
        // return your number pool key
        return 'invoice.number';
    }

    public function numberPoolAttribute(): string
    {
        // return your local model attribute where you want to store your number
        return 'number';
    }
}

在每个模型的creating事件上,此特性将在专用事务中执行原生的InnoDB FOR UPDATE锁,以确保新生成数字的唯一性。

自定义增量步长大小

如果您想指定步长大小,可以实施公共方法numberPoolStepSize以动态调整任何新生成增量的步长大小。您还可以使用rand在数字之间实现随机步长。

public function numberPoolStepSize(): int
{
    // return any positive integer
    return rand(10, 50);
}

请确保为步长返回一个正的integer。否则,将抛出NumberPoolException

静态Eloquent事件钩子的使用

如您所知,Laravel支持静态引导事件以在创建或创建事件中挂钩。您可以使用它们来构建与您的独特号码池整数的新逻辑。

例如

<?php

class Invoice
{
    // [..]

    protected static function booted()
    {
        static::creating(function ($invoice) {
        
            // your unique incremented number from your number pool is already 
            //available before the transaction has been committed.
            
            $uniqueNumber = $invoice->number;
        });
    }
}

常见问题解答

此包与Laravel Horizon兼容吗?

是的,您可以使用horizon,您自己的监控程序进程或本地的systemd服务。通过InnoDB的技术,保证原生的ROW READ LOCK。

我需要所有表都使用InnoDB引擎吗?

不,此包仅要求您的`number_pool`表使用InnoDB。

我需要redis吗?

不需要,redis不是必需的,但建议用于消息队列,尤其是如果您在多个工作进程中消费相同的作业。

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证