jrsaunders/shard-matrix

适用于 MYSQL 和/或 Postgres 的完整数据库分片系统。使用 Laravel 查询构建器轻松扩展您的应用程序。通过一个 YAML 配置文件配置整个解决方案。

1.4.2 2022-03-08 15:56 UTC

README

Latest Stable Version Total Downloads Latest Unstable Version License

ShardMatrix for PHP

MySQL 和 Postgres 的数据库分片系统

  • 需求

    • PHP 7.4^
  • 支持

    • 一个单一的 YAML 配置文件
    • 多个节点(数据库服务器)
    • Mysql
    • Postgres
    • MySQL 和 Postgres 可以一起使用 并进行热交换
    • 多个地理区域
    • UUIDs 集成了表及其所属节点的所有相关数据
    • Docker
    • Kubernetes
    • 快速异步数据库查询(使用专门构建的 GoThreaded 服务 https://github.com/jrsaunders/go-threaded | https://hub.docker.com/r/jrsaunders/gothreaded 或 PHP 分叉用于 crons 或开发工作)
    • 文件或 Redis 或 MemcacheD 缓存
    • 节点间唯一的表列
    • 表分组以确保数据存储在正确的分片中,以便可以进行连接
    • 使用 Laravel 的流行 ORM(尽管您的项目不一定要在 Laravel 中)https://laravel.net.cn/docs/7.x
    • 查询构建是数据库无关的
    • 使用缓存在节点间进行高效分页
    • 原始 SQL 查询

快速使用

一旦您按照下面的 安装 部分中的说明启动它 - 这里有一些快速使用示例。

如果您熟悉 Laravel 中的 ORM - 这只是其扩展。

创建表

  • 在所有适当的节点(MySQL 和 PostgreSQL)上创建表。这遵循您在 YAML 配置文件中提供的指导,说明哪些表属于哪些节点
use ShardMatrix\DB\Builder\Schema;

# Creates Table across all appropriate Nodes (Mysql and Postgres simultaneously).
# This follows the guidance you have given in your Yaml Config file as to what tables
# belong on what nodes

Schema::create( 'users',
    function ( \Illuminate\Database\Schema\Blueprint $table ) {
          
        $table->string( 'uuid', 50 )->primary();
        $table->string('username',255)->unique();
        $table->string('email',255)->unique();
        $table->integer('something');
        $table->dateTime( 'created' );

    } 
);

插入记录

  • 插入数据 - 系统将选择适当的分片节点并为其创建一个 UUID,该 UUID 将分配给适当的节点
use ShardMatrix\DB\Builder\DB;

# Insert Data - the system will choose an appropriate shard node and create a UUID for it that will be attributed to an appropriate node

$uuid = DB::table( 'users' )->insert( 
    [
	'username' => 'jack-malone',
	'password' => 'poootpooty',
	'created'   => (new \DateTime())->format('Y-m-d H:i:s'),
	'something' => 5,
	'email'    => 'jack.malone@yatti.com',
    ]
);

echo $uuid->toString();
# outputs 06a00233-1ea8af83-9b6f-6104-b465-444230303037

echo $uuid->getNode()->getName();
# outputs DB0007

echo $uuid->getTable()->getName();
# outputs users

插入的数据

uuid        06a00233-1ea8af83-9b6f-6104-b465-444230303037
username    jack-malone
password    poootpooty
email       jack.malone@yatti.com
created     2020-04-30 15:35:31.000000
something   5
  • 在此 PHP 进程中进行的任何其他插入都将插入到同一分片中,如果它位于正确的表组中

通过 UUID 获取记录并更新记录

  • 直接从正确的节点(分片)获取记录
  • 操作记录
  • 更新记录
    use ShardMatrix\DB\Builder\DB;
    use ShardMatrix\DB\Interfaces\DBDataRowTransactionsInterface;

    # Get the record directly from the correct node (shard)
    $record = DB::getByUuid( '06a00233-1ea8af83-9b6f-6104-b465-444230303037' );

    # Manipulate the record
    if ( $record && $record instanceof DBDataRowTransactionsInterface) {

        # As above you could run an additional check for the instance of the record returned, but it should always follow this interface through the query builder
        
    	echo $record->username;
    	# outputs jack-malone
    	
    	echo $record->email;
    	# outputs jack.malone@yatti.com
    	
    	# overwrite the email attribute
    	$record->email = 'anotheremail@yatti.com';
    
    	# Update the record
    	$record->save();
    }

查询数据并条件删除记录

  • 查询所有相关节点中的数据
  • 数据返回为可以迭代的集合
  • 有条件地使用数据
  • 操作记录并提交更改
use ShardMatrix\DB\Builder\DB;
use ShardMatrix\DB\Interfaces\DBDataRowTransactionsInterface;

# Query all relevant nodes for the data
$collection = DB::allNodesTable( 'users')->where('email','like','%yatti%')->limit(50)->get();

# Data returns as a Collection that can be iterated through
$collection->each( function(DBDataRowTransactionsInterface $record){

    # Use data conditionally
	if($record->username == 'a-bad-user'){
        
        # Manipulate the record and commit changes
        $record->delete();
	}

});

分页

所有分片的数据分页

use ShardMatrix\DB\Builder\DB;
use ShardMatrix\DB\Interfaces\DBDataRowTransactionsInterface;

$pagination = DB::allNodesTable( 'users' )
              ->orderBy( 'created', 'desc' )
              ->paginate();

$pagination->each( function ( DBDataRowTransactionsInterface $record) {

	echo $record->username;

	echo $record->getUuid();
});

echo $pagination->total();

echo $pagination->perPage();

echo $pagination->nextPageUrl();

echo $pagination->previousPageUrl();

通过 UUID 位置定义的单个分片的数据分页

use ShardMatrix\DB\Builder\DB;
use ShardMatrix\DB\Interfaces\DBDataRowTransactionsInterface;

$uuidFromCurrentUser = "06a00233-1ea8af83-d514-6a76-83ae-444230303037";

$pagination = DB::table( 'users' )
              ->uuidAsNodeReference($uuidFromCurrentUser)
              ->orderBy( 'created', 'desc' )
              ->paginate();

$pagination->each( function ( DBDataRowTransactionsInterface $record) {

	echo $record->username;

	echo $record->getUuid();
});

echo $pagination->total();

echo $pagination->perPage();

echo $pagination->nextPageUrl();

echo $pagination->previousPageUrl();

安装

安装 PHP 的 ShardMatrix

使用 Composer 安装 ShardMatrix,或从 GitHub 拉取存储库。

composer require jrsaunders/shard-matrix

准备 YAML 配置文件

ShardMatrix 需要知道您的表、列和数据库如何交互,因此此配置文件将使用简单的 YAML 文件定义这些内容。

示例

这是一个配置文件应该如何看起来完整示例。

version: 1

table_groups:
  user:
    - users
    - payments
    - offers
  tracking:
    - visitors
    - sign_ups
  published:
    - published_offers

unique_columns:
  users:
    - email
    - username

nodes:
  DB0001:
    dsn: mysql:dbname=shard;host=localhost:3301;user=root;password=password
    docker_network: DB0001:3306
    geo: UK
    table_groups:
      - user
      - published
  DB0002:
    dsn: mysql:dbname=shard;host=localhost:3302;user=root;password=password
    docker_network: DB0002:3306
    geo: UK
    table_groups:
      - user
      - published
  DB0003:
    dsn: mysql:dbname=shard;host=localhost:3303;user=root;password=password
    docker_network: DB0003:3306
    geo: UK
    table_groups:
      - user
      - published
  DB0004:
    dsn: mysql:dbname=shard;host=localhost:3304;user=root;password=password
    docker_network: DB0004:3306
    geo: UK
    table_groups:
      - published
  DB0005:
    dsn: mysql:dbname=shard;host=localhost:3305;user=root;password=password
    docker_network: DB0005:3306
    table_groups:
      - tracking
  DB0006:
    dsn: mysql:dbname=shard;host=localhost:3306;user=root;password=password
    docker_network: DB0006:3306
    geo: UK
    insert_data: false
    table_groups:
      - tracking
  DB0007:
    dsn: pgsql:dbname=shard;host=localhost:5407;user=postgres;password=password
    docker_network: DB0007:5432
    geo: UK
    table_groups:
      - user
      - tracking

配置文件结构

版本

定义版本。最新版本是 1。

version: 1

表组

定义表组。您向应用程序添加表时,需要在此处显式添加它们。

组名仅在 ShardMatrix 中使用。

表名归因于组。一个表一次只能属于一个组,并且一旦写入数据库,最好不要更改分配给组的任何表。

  • 表示配置文件中的表组部分
  • 表示表组的名称
  • 表示表名
# Denotes the table groups section on config

table_groups:

  # Denotes the name of a group of tables

  user:

    # Denotes the table name

    - users

此部分可能的外观。

table_groups:
  user:
    - users
    - payments
    - offers
  tracking:
    - visitors
    - sign_ups
  published:
    - published_offers

表中的唯一列

可以在此处定义唯一列。因此,在 users 表中,emailusername 必须在所有节点(分片数据库)中唯一。

unique_columns:
  users:
    - email
    - username
  facebook_users:
    - fb_id

节点

这是您定义数据库连接、凭证以及节点可能使用的表组和地理的地方。

节点可以根据需要进行扩展和添加。

节点名称必须保持不变,以及它们对应的表组。

节点部分的解剖结构。

  • 表示节点定义的位置
  • 节点名称
  • 连接到数据库的 DSN
  • 可选 Docker 服务名称和端口号
  • 可选地理 - 如果指定了地理,则应用程序插入数据将使用此选择写入此节点的节点
  • 可选停止在此处写入新数据,除非连接到此节点的现有 UUID
  • 使用此节点的表组必须在此处定义
  • 表组用户(包括用户、产品、支付表)
# Denotes the where the nodes are defined

nodes:

  # Node Name

  DBUK01:

    # DSN for connection to DB

    dsn: mysql:dbname=shard;host=localhost:3301;user=root;password=password

    # *optional docker service name and port number

    docker_network: DBUK:3306
    
    # *optional Geo - if a geo is stated the application inserting data will use this to choose this node to write new inserts to it

    geo: UK

    # *optional Stop new data being written here, unless connected to an existing UUID from this node

    insert_data: false

    # Table groups that use this node must be defined here

    table_groups:

      # Table group user (that consists of the users, offers, payments tables)

      - user
      
      - published

节点部分在配置 yaml 中的外观。

nodes:
  DBUK01:
    dsn: mysql:dbname=shard;host=localhost:3301;user=root;password=password
    docker_network: DBUK:3306
    geo: UK
    table_groups:
      - user
      - published
  postg1:
    dsn: pgsql:dbname=shard;host=localhost:5407;user=postgres;password=password
    docker_network: postg1_db:5432
    table_groups:
      - tracking
  DB0001:
    dsn: mysql:dbname=shard;host=localhost:3304;user=root;password=password
    docker_network: DB0001:3306
    insert_data: false
    table_groups:
      - user
      - published

一旦 Yaml 配置文件完成

文件 保存到应用程序所在的目录,无论是受保护的目录还是外部无法访问的目录。

或者它可以被制作成 Kubernetes Secret 并以这种方式提供给您的应用程序。

在 PHP 中启动

在这些示例中,我们已经将配置文件保存为 shard_matrix.yaml 并将其放置在我们的应用程序的 index php 目录中。

仅使用 PHP 和 Web 服务器资源进行基本设置

  • 我们的配置文件
  • 指定需要写入 db 数据的本地目录
use ShardMatrix\ShardMatrix;


# Our config file

ShardMatrix::initFromYaml( __DIR__ . '/shard_matrix.yaml' );  


# Specifying a local directory to write db data to when it needs to

ShardMatrix::setPdoCachePath( __DIR__ . '/shard_matrix_cache' );  

仅使用 GoThreaded 和 Redis 进行设置

  • 我们的配置文件
  • 将服务从 PHP 分叉异步查询更改为 GoThreaded
  • 当我们必须查询所有相关分片时,使用 GoThreaded 进行异步 DB 调用
  • 这覆盖了使用写入文件的方法的 PdoCache 服务,现在它使用 Redis 缓存
use ShardMatrix\ShardMatrix;


# Our config file

ShardMatrix::initFromYaml( __DIR__ . '/shard_matrix.yaml' );  


# Changes the service from PHP forking for asynchronous queries to GoThreaded

ShardMatrix::useGoThreadedForAsyncQueries();


# Uses GoThreaded for asynchronous DB calls when we have to query all relevant shards

ShardMatrix::setGoThreadedService( function () {
	return new \ShardMatrix\GoThreaded\Client( '127.0.0.1', 1534, 'gothreaded', 'password' );
} );

# This overwrites the PdoCache Service that was used to write to file, and now instead uses Redis caching

ShardMatrix::setPdoCacheService( function () {
	return new \ShardMatrix\PdoCacheRedis( new \Predis\Client( 'tcp://127.0.0.1:6379' ) );
} );