Laravel 如何 Swoole\Table共享内存实现多进程数据共享

235 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
由于 PHP 语言不支持多线程,因此 Swoole 使用多进程模式,在多进程模式下存在进程内存隔离,在工作进程内修改 global 全局变量和超全局变量时,在其他进程是无效的。
Swoole中实现多进程间数据共享有三种方法实现:

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

虽然有这三种方法实现,但会带来新的问题。如多进程同时操作一个文件,以数据库为列,多个进程同时操作一条数据,这样会导致最终结果与所预期的不一致,此时可以通过锁来解决。 Swoole\Table是一个基于共享内存和锁实现的高性能并发数据结构,可用于解决多进程/多线程数据共享和同步加锁问题,有如下优势:

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

通过Swoole\Table实现小案例

<?php


// 初始化一个内存容量为1024的内存表
$table = new \Swoole\Table(1024);

// 内存表中新增 id 列
$table->column('id',\Swoole\Table::TYPE_INT);
// 内存表中新增 name 列
$table->column('name', \Swoole\Table::TYPE_STRING, 10);
// 内存表中新增中 score 列
$table->column('score', \Swoole\Table::TYPE_FLOAT);

// 创建内存表
$table->create();


// 设置数据
$table->set('stu-1',['id'=>1, 'name'=>'lisi', 'score'=>60]);
$table->set('stu-2',['id'=>2, 'name'=>'wangwu','score'=>90 ]);

// 若key存在则打印其值
if ($table->exist('stu-1')) {
   echo "stu-" . $table->get('stu-1', 'id') . ':' . $table->get('stu-1', 'name').":".
       $table->get('stu-1', 'score') . "\n";
}

// 自增
$table->incr('stu-2', 'score', 5);
// 自减
$table->decr('stu-2', 'score', 5);


// 获取表中总数量
$count = $table->count();

// 删除指定表
$table->del('stu-1');

**Laravel8中使用Swoole\Table **

如何在Laravel8中实现Swoole\Table,这里还是基于先前安装的LaravelS作为案例。

本篇文章基于 WebSocket实现,沿用之前的代码。若还没有配置WebSocket请参考 Laravel8使用laravel-s实现WebSocket服务器

第一步:配置laravels.php

文件位置:config/laravels.php配置swoole_tables(该配置默认为空数组)

'swoole_tables' => [
   // ws为表名前缀,完整表为wsTable
   'ws'    =>  [
       // 表容量
       'size'  =>  102400,
       // 表字段,字段名为value
       'column'    =>  [
           ['name'=>'value', 'type'=>\Swoole\Table::TYPE_INT, 'size'=>8]
       ]
   ]
   // 可以配置多个表
],

第二步:修改WebSocketService.php

文件位置:app/Services/WebSocketService.php
只修改 onOpen与onMessage方法,没有变动的使用省略号替代

<?php
 
// *** 省略命名空间
   
class WebSocketService implements WebSocketHandlerInterface
{
 // ***
 
  public function onOpen(Server $server, Request $request)
  {
      Log::info('WebSocket 建立连接' . $request->fd);
      // 设置表中的数据
      app('swoole')->wsTable->set('fd:' . $request->fd, ['value' => $request->fd]);
      // 发送给客户端的数据
      $server->push($request->fd, 'Welcome to LaravelS,I am WebSocket');
  }
   
  public function onMessage(Server $server, Frame $frame)
  {
      // 遍历表中的数据
       foreach (app('swoole')->wsTable as $key => $row) {
           if (strpos($key, 'fd:') === 0 && $server->exist($row['value'])) {
               Log::info('Receive message from client: ' . $row['value']);
               // 调用 push 方法向客户端推送数据
               $server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s'));
           }
       }
  }
   
 // ***
}

第三步:运行html文件

我这里将html文件放在了windows上

第四步:查看laravel日志文件

tail -f laravel.log
   
[2021-04-22 16:36:29] local.INFO: WebSocket 建立连接1  
[2021-04-22 16:36:33] local.INFO: Receive message from client: 1  
[2021-04-22 16:36:41] local.INFO: Receive message from client: 1

如果看到日志信息,那么此案例就完成了。