当前位置: 首页 > 文档资料 > Swoole 中文文档 >

Swoole\Table

优质
小牛编辑
137浏览
2023-12-01

由于PHP语言不支持多线程,因此Swoole使用多进程模式,在多进程模式下存在进程内存隔离,在工作进程内修改global全局变量和超全局变量时,在其他进程是无效的。

设置worker_num=1时,不存在进程隔离,可以使用全局变量保存数据

$fds = array();
$server->on('connect', function ($server, $fd){
    echo "connection open: {$fd}\n";
    global $fds;
    $fds[] = $fd;
    var_dump($fds);
});

$fds 虽然是全局变量,但只在当前的进程内有效。Swoole服务器底层会创建多个Worker进程,在var_dump($fds)打印出来的值,只有部分连接的fd

对应的解决方案就是使用外部存储服务:

  • 数据库,如:MySQLMongoDB
  • 缓存服务器,如:RedisMemcache
  • 磁盘文件,多进程并发读写时需要加锁

普通的数据库和磁盘文件操作,存在较多IO等待时间。因此推荐使用:

  • Redis 内存数据库,读写速度非常快,但是有tcp连接等问题,性能也不是最高的。
  • /dev/shm 内存文件系统,读写操作全部在内存中完成,无IO消耗,性能极高,但是数据不是格式化的,还有数据同步的问题。

?> 除了上述使用存储之外,推荐使用共享内存来保存数据,Swoole\Table一个基于共享内存和锁实现的超高性能,并发数据结构。用于解决多进程/多线程数据共享和同步加锁问题。Table的内存容量不受PHPmemory_limit控制

!> 不要使用数组方式读写Table,一定要使用文档中提供的API来进行操作;
数组方式取出的Swoole\Table\Row对象为一次性对象,请勿依赖其进行过多操作。

  • 优势

    • 性能强悍,单线程每秒可读写200万次;
    • 应用代码无需加锁,Table内置行锁自旋锁,所有操作均是多线程/多进程安全。用户层完全不需要考虑数据同步问题;
    • 支持多进程,Table可以用于多进程之间共享数据;
    • 使用行锁,而不是全局锁,仅当2个进程在同一CPU时间,并发读取同一条数据才会进行发生抢锁。
  • 遍历

    !> 请勿在遍历期间进行删除操作(可将所有key取出后进行删除)

    Table类实现了迭代器和Countable接口,可以使用foreach进行遍历,使用count计算当前行数。

    foreach($table as $row)
    {
        var_dump($row);
    }
    echo count($table);

属性

memorySize

获取实际占用内存的尺寸,单位为字节。

Swoole\Table->memorySize;

方法

__construct()

构造函数。创建内存表。

Swoole\Table->__construct(int $size, float $conflict_proportion = 0.2);
  • 参数

    • int $size

      • 功能:表格占用的共享内存大小(并不是总的容量,参考下文的容量计算)
      • 默认值:无
      • 其它值:无

      !> 由于Table是在共享内存之上,所以无法动态扩容所以这个$size必须在创建前自己计算设置好,Table能存储的最大行数与$size正相关,但不完全一致,如$size1024实际可存储的行数小于1024$size过大如果机器内存不足table会创建失败。

    • float $conflict_proportion

      • 功能:哈希冲突的最大比例
      • 默认值0.2 (即20%)
      • 其它值:最小为0.2,最大为1
  • 容量计算

    • 如果$size不是为2N次方,如1024819265536等,底层会自动调整为接近的一个数字,如果小于1024则默认成1024,即1024是最小值。
    • Table占用的内存总数为 (HashTable结构体长度 + KEY长度64字节 + $size值) (1 + $conflict_proportion值作为hash冲突) (列尺寸)。
    • 如果你的数据Key和Hash冲突率超过20%,预留的冲突内存块容量不足,set新的数据就会报Unable to allocate memory错误,并返回false,存储失败,此时需要调大$size值重启服务。
    • 在内存足够的情况下尽量将此值设置的大一些。

column()

内存表增加一列。

Swoole\Table->column(string $name, int $type, int $size = 0);
  • 参数

    • int $name

      • 功能:指定字段的名称
      • 默认值:无
      • 其它值:无
    • int $type

      • 功能:指定字段类型
      • 默认值:无
      • 其它值Table::TYPE_INT, Table::TYPE_FLOAT, Table::TYPE_STRING
    • int $size

      • 功能:指定字符串字段的最大长度【字符串类型的字段必须指定$size
      • 值单位:字节
      • 默认值:无
      • 其它值:无
  • $type 类型

类型说明
Table::TYPE_INT默认为4个字节,可以设置1,2,4,8一共4种长度
Table::TYPE_STRING设置后,设置的字符串不能超过此长度
Table::TYPE_FLOAT会占用8个字节的内存
  • 整型溢出

    !> 由于Swoole底层使用有符号整型,如果传入的数值超过最大长度,可能会出现溢出。因此整数类型安全的值范围是:

类型说明
1byte(int8)-127 ~ 127
2byte(int16)-32767 ~ 32767
4byte(int32)-2147483647 ~ 2147483647
8byte(int64)不会溢出

create()

创建内存表。定义好表的结构后,执行create向操作系统申请内存,创建表。

Swoole\Table->create(): bool;

使用create方法创建表后,可以读取memorySize属性获取实际占用内存的尺寸

  • 提示

    • 调用create之前不能使用setget等数据读写操作方法
    • 调用create之后不能使用column方法添加新字段
    • 系统内存不足,申请失败,create返回false
    • 申请内存成功,create返回true

    !> Table使用共享内存来保存数据,在创建子进程前,务必要执行Table->create()
    Server中使用TableTable->create() 必须在Server->start()前执行。

  • 使用示例

    $table = new Swoole\Table(1024);
    $table->column('id', Swoole\Table::TYPE_INT, 4);       //1,2,4,8
    $table->column('name', Swoole\Table::TYPE_STRING, 64);
    $table->column('num', Swoole\Table::TYPE_FLOAT);
    $table->create();
    
    $worker = new Swoole\Process('child1', false, false);
    $worker->start();
    
    //$serv = new Swoole\Server('127.0.0.1', 9501);
    //$serv->start();

set()

设置行的数据。Table使用key-value的方式来访问数据。

Swoole\Table->set(string $key, array $value): bool;
  • 参数

    • string $key

      • 功能:数据的key
      • 默认值:无
      • 其它值:无

      !> 相同的$key对应同一行数据,如果set同一个key,会覆盖上一次的数据,key最大长度不得超过63字节

    • array $value

      • 功能:数据的value
      • 默认值:无
      • 其它值:无

      !> 必须是一个数组,必须与字段定义的$name完全相同

  • 返回值

    • 设置成功返回true
    • 失败返回false,可能是由于Hash冲突过多导致动态空间无法分配内存,可以调大构造方法第二个参数

!> -Table->set() 可以设置全部字段的值,也可以只修改部分字段;
-Table->set() 未设置前,该行数据的所有字段均为空;
-set/get/del 是自带行锁,所以不需要调用lock加锁;
-Key 非二进制安全,必须为字符串类型,不得传入二进制数据。

  • 使用示例

    $table->set('1', ['id' => 1, 'name' => 'test1', 'age' => 20]);
    $table->set('2', ['id' => 2, 'name' => 'test2', 'age' => 21]);
    $table->set('3', ['id' => 3, 'name' => 'test3', 'age' => 19]);
  • 设置超过最大长度字符串

    如果传入字符串长度超过了列定义时设定的最大尺寸,底层会自动截断。

    $table->column('str_value', Swoole\Table::TYPE_STRING, 5);
    $table->set('hello', array('str_value' => 'world 123456789'));
    • str_value列最大尺寸为5字节,但set设置了超过5字节的字符串
    • 底层会自动截取5字节的数据,最终str_value的值为world

incr()

原子自增操作。

Swoole\Table->incr(string $key, string $column, mixed $incrby = 1): int;
  • 参数

    • string $key

      • 功能:数据的key【如果$key对应的行不存在,默认列的值为0
      • 默认值:无
      • 其它值:无
    • string $column

      • 功能:指定列名【仅支持浮点型和整型字段】
      • 默认值:无
      • 其它值:无
    • string $incrby

      • 功能:增量 【如果列为int$incrby必须为int型,如果列为float型,$incrby必须为float类型】
      • 默认值1
      • 其它值:无
    • 返回值

      返回最终的结果数值

decr()

原子自减操作。

Swoole\Table->decr(string $key, string $column, mixed $decrby = 1): int;
  • 参数

    • string $key

      • 功能:数据的key【如果$key对应的行不存在,默认列的值为0
      • 默认值:无
      • 其它值:无
    • string $column

      • 功能:指定列名【仅支持浮点型和整型字段】
      • 默认值:无
      • 其它值:无
    • string $decrby

      • 功能:增量 【如果列为int$decrby必须为int型,如果列为float型,$decrby必须为float类型】
      • 默认值1
      • 其它值:无
  • 返回值

    返回最终的结果数值

    !> 数值为0时递减会变成负数

get()

获取一行数据。

Swoole\Table->get(string $key, string $field = null): array|false;
  • 参数

    • string $key

      • 功能:数据的key【必须为字符串类型】
      • 默认值:无
      • 其它值:无
    • string $field

      • 功能:当指定了$field时仅返回该字段的值,而不是整个记录
      • 默认值:无
      • 其它值:无
  • 返回值

    • $key不存在,将返回false
    • 成功返回结果数组
    • 当指定了$field时仅返回该字段的值,而不是整个记录

exist()

检查table中是否存在某一个key。

Swoole\Table->exist(string $key): bool;
  • 参数

    • string $key
      • 功能:数据的key【必须为字符串类型】
      • 默认值:无
      • 其它值:无

count()

返回table中存在的条目数。

Swoole\Table->count(): int;

del()

删除数据。

!> Key非二进制安全,必须为字符串类型,不得传入二进制数据;请勿在遍历时删除

Swoole\Table->del(string $key): bool;
  • 返回值

    • $key对应的数据不存在,将返回false
    • 成功删除返回true

完整示例

<?php
$table = new Swoole\Table(1024);
$table->column('fd', Swoole\Table::TYPE_INT);
$table->column('reactor_id', Swoole\Table::TYPE_INT);
$table->column('data', Swoole\Table::TYPE_STRING, 64);
$table->create();

$serv = new Swoole\Server('127.0.0.1', 9501);
$serv->set(['dispatch_mode' => 1]);
$serv->table = $table;

$serv->on('receive', function ($serv, $fd, $reactor_id, $data) {

    $cmd = explode(" ", trim($data));

    //get
    if ($cmd[0] == 'get')
    {
        //get self
        if (count($cmd) < 2)
        {
            $cmd[1] = $fd;
        }
        $get_fd = intval($cmd[1]);
        $info = $serv->table->get($get_fd);
        $serv->send($fd, var_export($info, true)."\n");
    }
    //set
    elseif ($cmd[0] == 'set')
    {
        $ret = $serv->table->set($fd, array('reactor_id' => $data, 'fd' => $fd, 'data' => $cmd[1]));
        if ($ret === false)
        {
            $serv->send($fd, "ERROR\n");
        }
        else
        {
            $serv->send($fd, "OK\n");
        }
    }
    else
    {
        $serv->send($fd, "command error.\n");
    }
});

$serv->start();