网友回复
示例代码
<?php use Swoole\Table; //创建共享内存表对象 $size = 1024;//2的n次方 $table = new swoole_table($size); //共享内存表添加字段 $table->column("id", Table::TYPE_INT, 11); $table->column("name", Table::TYPE_STRING, 20); $table->column("money", Table::TYPE_FLOAT, 10); $table->create(); //共享内存表添加行 $table->set("alice", ["id"=>1, "name"=>"alice", "money"=>100.5]); //使用数组方式添加行 $table["ben"] =["id"=>2, "name"=>"ben", "money"=>200.1]; //获取行返回数组 $val = $table->get("alice"); var_dump($val); //获取行返回对象 $val = $table["ben"]; var_dump($val); //共享内存表内字段值增加 $table->incr("alice", "money", 100); var_dump($table["alice"]); //共享内存表中字段值减少 $table->decr("ben", "money", 50); var_dump($table["ben"]); echo json_encode($table).PHP_EOL;方法解析
创建对象construct
创建共享内存表function Table->__construct(int $size, float $conflict_propertion = 0.2);
共享内存表实际上是一个开链法实现的哈希表,内存是由哈希键Key与具体数据组成的数组,如果哈希冲突即不同的键值对一个同一个哈希,那么就会从内存池pool中分配出一个元素作为数组元素的链表尾。 内存表占用的内存总数 = ( 结构体长度 + 哈希键长度64byte + 行尺寸$size ) * 预留作为哈希冲突的百分比 * 列尺寸 参数1:int $size $size表示创建共享内存表时设置的最大行数,必须是2的次方,如果不是底层会自动调整为接近的一个数字,若小于1024则默认为1024,即1024为最小值。若机器内存不足内存表会创建失败。 共享内存表底层是建立在共享内存之上的哈希表HashTable(数据结构),最大行数$size决定了哈希表的总行数。由于内存表是在共享内存之上,所以无法动态扩容,因此$size必须在创建前提前设置好。 参数2:float $conflict_propertion 哈希冲突率如果哈希冲突超过最大比例,共享内存表将不再允许添加新的行元素。共享内存表能存储的总数据行数取决于数据的哈希键冲突率,默认如果冲突率超过20%,预留的哈希hash冲突内存块容量不足,会报Unable to allocate memory无法分配内存的错误,并返回false表示存储失败。
<?php use Swoole\Table; //创建共享内存表对象 $size = 1024;//2的n次方 $table = new swoole_table($size); $table->column("data", Table::TYPE_STRING, 1); $table->create(); echo json_encode($table).PHP_EOL;// {"size":1024,"memorySize":117548}
添加列数据column
共享内存表新增一列 bool Table->column(string $name, int $type, int $size = 0)swoole_table->column(string $name, int $type, int $size = 0)
参数列表string $name 表示字段的名称
int $type 表示字段类型,可支持3种类型分别是Table::TYPE_INT、Table::TYPE_FLOAT、Table::TYPE_STRING。
int $size 表示字符串字段的最大长度单位字节,字符串类型Table::TYPE_STRING的字段必须指定$size。
字段类型Table::TYPE_INT 整型默认4字节,可设置1、2、4、8共四种长度。
Table::TYPE_STRING 字符型必须指定,设置的字符串不得超过设置值。
Table::TYPE_FLOAT 浮点型,占用8字节的内存。
整型溢出 由于Swoole底层使用有符号整型,如果传入的数值超过溢出边界就可能会发生溢出。 整数类型安全值的范围int8 1byte -127 ~127
int16 2byte -32767~327767
int32 4byte -2147483647 ~ 2147483647
int64 8byte 不会溢出
内存对齐 从Swoole4.3版本开始,底层对内存长度做了对齐处理。字符串长度必须是8的整数倍,如果长度为18字节会自动对其到24字节。注意非x86环境则内存对齐。创建共享内存表create
当定义好表的结构后执行create会向操作系统申请内存并创建共享内存表。需要注意的是,调用create创建之前不能使用set、get等数据读写操作,调用create创建之后不能使用column添加新字段。如果 系统内存不足则会申请失败此时create将返回false,若申请成功create将会返回true。 Table使用共享内存来保存数据,在创建子进程之前,务必要执行create执行创建。服务器中使用共享内存表执行create创建操作必须在服务器执行start启动方法之前。 当使用create方法创建共享内存表后,可以读取$table->memorySize属性来获取实际占用的内存尺寸,单位为字节。function Table->create():bool
设置行数据set
共享内存表使用键值对的方式存取数据Table->set(string $key, array $value):bool
参数列表 参数1:string $key 设置行数据对应的键名,相同的键名对应同一行数据,如果set设置了同一个键名则会覆盖上一次的数据。 参数2:array $value 设置行数据对应的键值,必须是一个数组,必须与字段定义的名称$name完全相同。 返回值 设置成功返回true,设置失败返回false,失败的原因可能是由于哈希冲突过多导致动态空间无法分配内存,可以调大构造方法的$conflict_propertion参数。 如果传入字符串的长度超过列定义的最大尺寸,底层会自动截断。获取行数据get
获取单行数据array Table->get(string $key, string $field = null)
参数列表中string $key表示查询数据行的键名,必须为字符串类型。 如果带查询的键名不存在,则get方法将会返回false。若成功则返回的结果为数组类型。检查键名是否存在exist
exist方法用于检查共享内存表中是否存在某个键名key,若存在则返回true,否则返回false。bool swoole_table->exists(string $key)
条目数量count
count方法用于获取共享内存表table中存在的条目数量int function Table->count()
删除键值对del
bool Table->del(string $keyy)
del方法用于删除共享内存表$table中指定键名$key的行,如果键名$key对应的是数据不存在则返回false,否则成功返回true。此处需要注意的是键名$key是非二进制安全的,所以必须为字符串类型,因此不得传入二进制数据。原子递增incr
function Table->incr(string $key, string $column. mixed $incrby = 1): int
incr表示原子递增操作,参数string $key表示指定数据的键名,如果键名对应的行不存在则默认值为0。int column 表示指定的列表即字段名,仅支持浮点型和整型字段。
int $incrby表示则增量默认为1,若列为整型则增量必须为整型,如果列为浮点型则增量必须为浮点型。
返回最终的结果数值原子自减decr
function Table->decr(string $key, string $column, mixed $decrby = 1) int | bool
参数列表string $key表示指定数据的键名,如果键名对应的行不存在,底层会首先将该行数据初始化为0。
string $column表示指定的列名,仅支持浮点型和整型字段。
mixed $decrby表示减量默认为1,如果列为整型则减量必须也为整型,同样如果列为浮点型则减量也必须为浮点型。
返回值将返回最终的结果数值,数值为0时递减会变成负数。 实例:创建swoole_table用于进程间数据共享<?php class Server { private $server; public function __construct($host="0.0.0.0", $port = 9501, $size = 1024) { //创建swoole_table用于进程间数据共享 $table = new swoole_table($size); $table->column("fd", swoole_table::TYPE_INT); $table->column("uid", swoole_table::TYPE_INT); $table->column("type", swoole_table::TYPE_STRING, 256); $table->column("data", swoole_table::TYPE_STRING, 256); $table->create(); //创建WebServer服务器对象 $this->server = new swoole_websocket_server($host, $port); $this->server->table = $table; //注册事件回调函数 $this->server->on("handShake", [$this, "onHandShake"]); $this->server->on("workerStart", [$this, "onWorkerStart"]); $this->server->on("open", [$this, "onOpen"]); $this->server->on("message", [$this, "onMessage"]); $this->server->on("close", [$this, "onClose"]); //开启服务器 $this->server->start(); } public function onHandShake($server) { echo "[handshake]".PHP_EOL; } public function onWorkerStart($server) { echo "[workerstart]".PHP_EOL; } public function onOpen($server) { echo "[open]".PHP_EOL; } public function onMessage($server) { echo "[message]".PHP_EOL; } public function onClose($message) { echo "[close]".PHP_EOL; } } $server = new Server();链接:https://www.jianshu.com/p/1dae870407bc
在多进程模式下进程之间的内存是相互隔离的,在一个工作进程中的全局变量和超全局变量,在另一个工作进程中是无法读取和操作的。
如果只有一个工作进程,则不存在进程隔离问题,可以使用全局变量和超全局变量。 要实现进程间共享数据,我们可以使用第三方的 Redis 内存数据库或 Swoole 内置的 Table 共享内存来实现。swoole 内存表Table 的优势
性能强悍,单线程每秒可读写 200 万次;
应用代码无需加锁,Table 内置行锁自旋锁,所有操作均是多线程 / 多进程安全。用户层完全不需要考虑数据同步问题;
支持多进程,Table 可以用于多进程之间共享数据;
使用行锁,而不是全局锁,仅当 2 个进程在同一 CPU 时间,并发读取同一条数据才会进行发生抢锁。
我们来用swoole的内存表实现类似memcache的缓存服务中间件,代码如下:
<?php // 实例化一个占用的共享内存大小为1024的内存表 $table = new Swoole\Table(1024); // 内存表增加3列 $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)); 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"); } elseif ($cmd[0] == 'set') { // 使用连接的文件描述符作为key写入内存表 $ret = $serv->table->set($fd, array('fd' => $fd, 'reactor_id' => $reactor_id, '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();