Laravel REST API 和高 CPU 负载

Posted

技术标签:

【中文标题】Laravel REST API 和高 CPU 负载【英文标题】:Laravel REST API and high CPU load 【发布时间】:2014-03-16 00:24:46 【问题描述】:

我正在使用 Laravel 4 开发一个简单的 RESTful API。 我设置了一个Route,它调用了我的Controller 的一个函数,它基本上是这样做的:

如果信息在数据库中,则将其打包到 JSON 对象中并返回响应 否则尝试下载它(html/xml 解析),存储它,最后打包 JSON 响应并发送它。

我注意到,在总共执行 1700 个请求(一次只有 2 个请求)时,CPU 负载上升到 70-90%。

我是一个完整的 php 和 laravel 初学者,我按照this tutorial 制作了 API,也许我做错了什么,或者这只是缺乏优化的概念证明。如何改进此代码? (启动函数为getGames) 你认为所有问题的根源是 Laravel 还是我应该得到相同的结果,即使改变框架/使用原始 PHP?

UPDATE1我也设置了一个文件缓存,但是CPU负载还是~50%。

UPDATE2 我将查询速率设置为每 500 毫秒两次,CPU 负载降低了 12%,所以我猜这段代码缺少队列处理或类似的东西。

class GameController extends BaseController
    private static $platforms=array(
        "Atari 2600",
        "Commodore 64",
        "Sega Dreamcast",
        "Sega Game Gear",
        "Nintendo Game Boy",
        "Nintendo Game Boy Color",
        "Nintendo Game Boy Advance",
        "Atari Lynx",
        "M.A.M.E.",
        "Sega Mega Drive",
        "Colecovision",
        "Nintendo 64",
        "Nintendo DS",
        "Nintendo Entertainment System (NES)",
        "Neo Geo Pocket",
        "Turbografx 16",
        "Sony PSP",
        "Sony PlayStation",
        "Sega Master System",
        "Super Nintendo (SNES)",
        "Nintendo Virtualboy",
        "Wonderswan");
    private function getDataTGDB($name,$platform)
        $url = 'http://thegamesdb.net/api/GetGame.php?';
        if(null==$name || null==$platform) return NULL;
        $url.='name='.urlencode($name);
        $xml = simplexml_load_file($url);
        $data=new Data;
        $data->query=$name;
        $resultPlatform = (string)$xml->Game->Platform;

        $data->platform=$platform;
        $data->save();
        foreach($xml->Game as $entry)
            $games = Game::where('gameid',(string)$entry->id)->get();
            if($games->count()==0)
                if(strcasecmp($platform , $entry->Platform)==0 || 
                (strcasecmp($platform ,"Sega Mega Drive")==0 && 
                ($entry->Platform=="Sega Genesis" || 
                $entry->Platform=="Sega 32X" || 
                $entry->Platform=="Sega CD")))
                    $game = new Game;
                    $game->gameid = (string)$entry->id;
                    $game->title = (string)$entry->GameTitle;
                    $game->releasedate = (string)$entry->ReleaseDate;
                    $genres='';
                    if(NULL!=$entry->Genres->genre)
                    foreach($entry->Genres->genre as $genre)
                        $genres.=$genre.',';
                    
                    $game->genres=$genres;
                    unset($genres);
                    $game->description = (string)$entry->Overview;
                    foreach($entry->Images->boxart as $boxart)
                        if($boxart["side"]=="front")
                            $game->bigcoverurl = (string)$boxart;
                            $game->coverurl = (string) $boxart["thumb"];
                         continue;
                    
                    $game->save();
                    $data->games()->attach($game->id);
                 
            
            else foreach($games as $game)
                $data->games()->attach($game->id);
            
        
        unset($xml);
        unset($url);
        return $this->printJsonArray($data);
    

    private function getArcadeHits($name)
        $url = "http://www.arcadehits.net/index.php?p=roms&jeu=";
        $url .=urlencode($name);

        $html = file_get_html($url);

        $data = new Data;
        $data->query=$name;
        $data->platform='M.A.M.E.';
        $data->save();
        $games = Game::where('title',$name)->get();
        if($games->count()==0)
            $game=new Game;
            $game->gameid = -1;
            $title = $html->find('h4',0)->plaintext;
            if("Derniers jeux commentés"==$title)
             
                unset($game);
                return Response::json(array('status'=>'404'),200);
            
            else
                $game->title=$title;
                $game->description="(No description.)";
                $game->releasedate=$html->find('a[href*=yearz]',0)->plaintext;
                $game->genres = $html->find('a[href*=genre]',0)->plaintext;
                $minithumb = $html->find('img.minithumb',0);
                $game->coverurl = $minithumb->src;
                $game->bigcoverurl = str_replace("/thumb/","/jpeg/",$minithumb->src);
                $game->save();
                $data->games()->attach($game->id);
            
        

        unset($html);
        unset($url);
        return $this->printJsonArray($data);
    

    private function printJsonArray($data)
        $games = $data->games()->get();
        $array_games = array();
        foreach($games as $game)
            $array_games[]=array(
                'GameTitle'=>$game->title,
                'ReleaseDate'=>$game->releasedate,
                'Genres'=>$game->genres,
                'Overview'=>$game->description,
                'CoverURL'=>$game->coverurl,
                'BigCoverURL'=>$game->bigcoverurl
            );
        
        $result = Response::json(array(
            'status'=>'200',
            'Game'=>$array_games
            ),200);
        $key = $data->query.$data->platform;
        if(!Cache::has($key))
            Cache::put($key,$result,1440);
        return $result;
    

    private static $baseImgUrl = "";
    public function getGames($apikey,$title,$platform)
            $key = $title.$platform;
            if(Cache::has($key)) return Cache::get($key);
        if(!in_array($platform,GameController::$platforms)) return Response::json(array("status"=>"403","message"=>"non valid platform"));
        $datas = Data::where('query',$title)
                ->where('platform',$platform)
                ->get();
        //If this query has already been done we return data,otherwise according to $platform
        //we call the proper parser.
        if($datas->count()==0)
            if("M.A.M.E."==$platform)
                return $this->getArcadeHits($title);
            
            else
                return $this->getDataTGDB($title,$platform);
            
         else
            else return $this->printJsonArray($datas->first());
        
    



?>

【问题讨论】:

... 我使用 Linode 平台,曾经遇到同样的问题。只需重新启动机器(虚拟服务器机器),一切都恢复正常。我认为一些“幽灵代码”可能在我不知道的情况下在后台运行!你应该试一试(如果你可以的话)而且我也在制作一个 RESTful API! @DennisBraga 不幸的是我无法重新启动,但是当我不使用 API 时,CPU 使用率为 0%,所以这是我的代码。我想我应该实现一些东西来防止请求泛滥或队列系统,这样只有 2-3 个请求一起完成,而其他客户端必须排队等待。 查看了您的代码。当您从其他服务器检索数据时,您正在拨打被“搁置”的电话。认为有你的问题。尝试解决这部分问题,我认为你会没事的。 P.S.:尝试阅读 Lavarel 代码指南。 codding 会有一些不错的提示。 @DennisBraga 问题是我应该设置可以同时处理的最大请求数,因为即使 API 返回所有缓存数据,CPU 负载也很高。我通过在每个请求之间添加 500 毫秒的暂停来测试它,并且 CPU 负载下降到 5-10%。解决方案是队列系统。例如,我可以创建一个“锁”并调用usleep,直到锁再次打开。 【参考方案1】:

我首先要做的是使用分析器找出哪些部分需要优化。例如,您可以使用:

http://xdebug.org/docs/profiler

您也没有指定它是什么类型的 CPU,您使用了多少个内核?是不是你的 cpu 使用率这么高的问题?

【讨论】:

我在共享主机上运行服务,该主机运行在 Xeon CPU (E3-1230v2) 上,我保留了 1 个核心,问题在于我的 CPU 使用率很高,因为只有我自己使用它。如果多人一起使用呢? 首先尝试分析您的代码,如果您无能为力,请升级您的托管计划。顺便问一下 1700 个请求需要多少时间? 我从 android 客户端发送请求,它们是按顺序处理的,但同时只有 2 个。 我将安装移植到我的本地环境,使用双 E5440 和 8GB Ram DDR2-ECC,在那里我安装了 xampp 并启用了 xdebug,因此我能够生成 cachegrind 文件。任务管理器说 httpd 在获取数据时使用 10-15% 的 CPU,在返回缓存数据时使用 35%。我对 KCachegrind 并不完全有信心,但我只看到有关函数完成所需时间的数据,并且除获取 html/xml 的函数之外的所有函数的时间都更短。 我认为这可能更像是一个评论而不是答案本身。它不能解决问题,所以...【参考方案2】:

您正在尝试从其他人的服务器检索数据。那就是让你的 CPU “暂停”,直到数据被完全检索到。这就是使您的代码如此“CPU 昂贵”(找不到适合此处的其他内容 =/) 的原因,因为您的脚本正在等待接收到数据然后释放脚本(CPU)的工作。

强烈建议您进行异步调用。这将释放您的 CPU 来处理代码,而系统的其他部分正在获取您需要的信息。

我希望这会有所帮助! =D

更新 举个例子,我不得不重新考虑你的代码(而且我很懒惰!)。但是,我可以肯定地告诉你:如果你把你的请求代码,那些调用其他站点的 XML 的人,放到一个队列中,你将获得大量的空闲 CPU 时间。每个请求都被重定向到一个队列。一旦它们准备好,您就可以按照自己的意愿对待它们。 Laravel 有一种处理队列的好方法。

【讨论】:

一些关于如何做到这一点的例子会有所帮助:) @Vektor88 添加了更新。很抱歉第一次没有更直接。 我对队列的缺失是我了解它们是如何工作的以及如何实现它们,但在所有示例中,fire 函数都存储处理后的结果,而不是将其返回给客户端。那是我找不到的。 我没有可能在本地运行队列,实际上我会使用iron.io服务 我终于决定托管 API 不是一个好主意,所以我没有为我的客户端应用程序制作 xml/html 到 json 包装器,而是让应用程序解析 xml/html从能够承担此类工作量的原始服务。但如果我必须采用这种策略,这将是明确的答案,我会将我的 API 移动到 VPS 或专用服务器,可能会与第二个用于工作人员的服务器配对。【参考方案3】:

例如,您应该使用 Laravel 的队列系统和 beanstalkd,然后使用 artisan queue:listen 监控队列(工作人员)

【讨论】:

以上是关于Laravel REST API 和高 CPU 负载的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5 REST API

简单的 Laravel 5 REST API

php 【Laravel】5.7 REST API设置

Laravel REST API - 无限循环

如何分析 laravel 的 REST API

Laravel 5.3 Rest API 登录