前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >震惊!北京一男子竟然用swoole做了这种事!

震惊!北京一男子竟然用swoole做了这种事!

作者头像
老李秀
发布2019-11-13 16:23:41
1.1K0
发布2019-11-13 16:23:41
举报

自己挖的坑自己填吧,今天咱就简单地利用swoole(实际上用我撸的那个沙雕一样的ti-rpc,上手会快一些)去实现这种【大量耗时数据导出】需求。但是,我还是偷了两点儿懒:

  • 我实在懒得实现【数据库查询并生成csv或excel】这个功能了,这个地方我用一个sleep函数去模拟了一下
  • 没有写网页而是用curl模拟了网页,模拟了点击【导出】和等待ajax轮训结果的用户行为

作为PM,我来说下大概的需求是怎样的:我们前段时间做的那个【搞附近】项目成功了,骗到了融资小目标:一个亿。现在是我们的运营需要一个网页能导出所有用户资料为excel文件的功能。因为用户量十分巨大,所以导出工作不可以使用PHP-FPM来实现,所以柱子在衡量了一下后决定采用swoole这种具备常驻内存特性的玩意来实现数据导出工作(老李去旅长那里背黑锅去了)。

在跟老赵报告了一下技术可行性后,柱子做的PPT里展示的具体技术流程是这样shai儿的:

  1. 当运营在网页上点击了【导出】按钮后,会向服务器发送一个ajax请求,请求中会带上参数:比如文件id。然后自此之后开始ajax轮训(依然需要带上文件id参数)文件状态,一秒钟一次
  2. 服务器收到该指令后,立马向网页客户端返回消息(此处一定要注意,要立马向网页返回消息,比如【开始处理】,此处利用了swoole异步特性)告诉运营已经开始处理了
  3. 然后紧接着第2步,服务器会向redis中写入一个文件处理状态标记,表示这个id的文件正在【处理中】
  4. 从数据库中读取数据,然后生成文件。具体演示里,此处柱子偷懒直接用sleep代替了整个处理过程。知道这叫什么吗?这就叫业务模型抽象能力...
  5. 文件处理完毕后,修改redis中文件处理状态标记为【处理完毕】,并开始将文件的下载链接拼接好(这个看你们把文件存哪儿了),把【文件下载链接】和【文件处理状态标记】一并返回给网页客户端
  6. 因为网页客户端还在保持一秒钟一次的ajax轮训,所以当它发现服务器返回了【处理完毕】状态,所以它就取【文件下载链接】的值并同时告诉运营:您要的文件已经O jb K,点击下载吧

完美

在正式开始贴上可供大家复制粘贴的代码前,请你准备好下列物料:

  • linux环境,ubuntu、centos都行...
  • swoole 1.9.22(http://pecl.php.net/get/swoole-1.9.22.tgz)
  • MySQL(虽然用sleep抽象了,但是看看swoole里怎么用mysql吧)
  • Redis(其实有洁癖的人可以用swoole-table来代替redis)
  • Ti-RPC(https://github.com/elarity/ti-rpc)

CV BOY们,项目代码github地址如下:

https://github.com/elarity/wechat-official-accounts-demo-code

运行方式:

  • 服务端:git clone下项目后,进入到ti-rpc根目录,然后php index.php start(PS:记得配置你的MySQL数据库账号密码,在System->Library->Mysql.php的第59行,不然MySQL可能会连接不上)
  • 网页客户端:进入到ti-rpc根目录中,再进入到example目录中,执行php http_client.php

但是!请看这边!代码还是要讲解、分析的


服务端业务代码分析

代码语言:javascript
复制
<?php
namespace Application\Controller;
use Application\Model as Model;
class Account{
/*  
   * @desc : param['file_id'] : 文件的唯一id
             在实际业务里,你可以用[文件id+uid]保证唯一
   */
public function mysql2excel( $param ){
    // file_id参数验证,必须d得有这个参数
    if ( empty( $param['param']['file_id'] ) ) { 
      return []; 
    }   
    $s_file_id = $param['param']['file_id'];
    // 获取服务容器并从中取出redis操作句柄
    $o_di    = \System\Component\Di::getInstance();
    $o_redis = $o_di->get('redis');
    // 根据客户端/网页中传来的file_id参数设置内存标记
    // 我就偷懒了昂,直接用redis来记录文件状态 
    $s_file_export_state = $o_redis->get( $s_file_id );
    // 如果存在这个标记,表示文件正在【处理中】或者【已完成】
    if ( false !== $s_file_export_state ) { 
      // 默认给一个空下载链接,如果已经处理完毕,你按照你的具体文件存放路径规律可以直接将下载地址拼接出来
      $s_download_link = 'done' == $s_file_export_state ? 'http://www.baidu.com/1.zip' : '' ;
      return array(
      'state' => $s_file_export_state,
      'data'  => $s_download_link,
      );
    }
    // 如果不存在这个标记,就直接进入到导出处理逻辑中 
    else {

      $s_file_export_state = 'processing';
       // 向redis中写入文件【处理中】标记
      $b_set_ret = $o_redis->set( $s_file_id, $s_file_export_state );
      if ( !$b_set_ret ) {
        return array(
          'code'    => -1,
          'message' => '写入redis文件标记失败',
        );
      }

      // 从服务容器中获取mysql资源句柄
      // 模拟30秒钟文件处理过程
      // 你可以在下面这里处理你的数据查询逻辑,以及查询完毕后如果生成为csv或者excel文件的逻辑
      // 这个数据库查询没啥用,就是顶多演示一下swoole里怎么搞MySQL数据查询
      $o_mysql    = $o_di->get('mysql');
      $a_user_ret = $o_mysql->query( "select * from bilibili_user_info limit 10" );
      sleep( 30 );

       // 处理完毕后,改写redis中文件标记为【已完成】
      $s_file_export_state = 'done';
      $o_redis->set( $s_file_id, $s_file_export_state );
      return array(
        'code' => 0,
      );

    }
    return [];
  }

}

当客户端执行开始执行代码后,我们注意下服务端的demo代码会打印如下log:

你们这是什么意思吗?注意看第一个进程PID为5561的进程自从第一次出现后,就再也没有出现过,其他PID则是轮流重复出现,为什么?因为5561就是正在处理【数据导出为文件】任务的进程,作为业务为同步阻塞模型的代码,此时该进程不会相应其他任何请求的。所以我们这个demo的一个缺陷就是:如果所有进程都在处理【数据导出为文件】任务了,那么就会出现网页客户端ajax轮训无法查询到状态的情况。


CURL模拟的网页端代码

代码语言:javascript
复制
<?php
/*
@desc : 利用curl封装的简单对http的客户端演示案例
 */
// 封装curl方法
function curl_init_param( $curl, $json_data ) {
  curl_setopt_array( $curl, array(
    CURLOPT_PORT      => 6666,
    CURLOPT_URL       => "http://127.0.0.1:6666/",
    CURLOPT_ENCODING  => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT   => 30,
    CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST  => "POST",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS     => $json_data,
    CURLOPT_HTTPHEADER     => array(
      "cache-control: no-cache",
      "postman-token: efe52804-aa89-8c4d-01ae-5e9a27012312"
    ),
  ) );
}
// 文件名称
$s_file_id = '123456789123456';
// 模拟网页点击【导出】按钮后,发出【导出】命令请求...
// 构造请求参数.
$sn = array(
  // ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
  // type=SN的意思就是:请求触发后,服务器立马返回消息,客户端不会被阻塞一直等待结果
  // 在本次业务就是满足运营点击完【导出】按钮后的情境
  // ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
  'type' => 'SN',
  'requestId' => time(),
   // 这里就是构建的具体参数,我们会调用Account的mysql2excel方法,参数是file_id
  'param' => array(
  'model' => 'Account',
  'method' => 'mysql2excel',
  'param' => array(
    'file_id'  => $s_file_id,
   ),
  ),
);
$json_data = json_encode( $sn );
$curl      = curl_init();
curl_init_param( $curl, $json_data );
$response  = curl_exec( $curl );
$err       = curl_error( $curl );
if ( $err ) {
  echo "cURL Error #:" . $err.PHP_EOL;
  exit();
}
print_r( json_decode( $response, true ) );

// 点击完毕【导出】按钮后,开始模拟ajax轮训状态,一秒钟一次...
while ( 1 ) {
  sleep( 1 );
  // 构造请求参数.
  $sw = array(
    // ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
    // type=SW的意思就是:请求触发后,服务器不会马上返回请求,而是一直到处理完毕数据后才返回给客户端
    // 此处就是ajax轮训文件处理状态,这个是要等服务器从redis里取出状态后,才能返回给网页客户端的,所以
    // 必须阻塞等待。其实这里就是和传统的php-fpm是一回事。
    // ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
    'type' => 'SW',
    'requestId' => time(),
    'param' => array(
      'model' => 'Account',
      'method' => 'mysql2excel',
      'param' => array(
        'file_id'  => $s_file_id,
       ),
     ),
  );
  $json_data = json_encode( $sw );
  curl_init_param( $curl, $json_data );
  $response  = curl_exec( $curl );
  $err       = curl_error( $curl );
  if ( $err ) {
    echo "cURL Error #:" . $err.PHP_EOL;
  } else {
    print_r( json_decode( $response, true ) );
  }
}

php http_client.php执行了网页客户端后,我们等待30秒钟会看到如下结果,就相当于网页上【处理中】按钮变成【已完成,请点击下载】按钮:

自此,我们算实现了下面这个技术流程图:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能API社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档