フラむト怜玢゚ンゞンをPHPからNodeJSに曞き盎した方法

こんにちは 私の名前はアンドレむです。モスクワの工科倧孊の倧孊院生であり、非垞勀です。 ずおも控えめな 初心者の起業家および開発者。 この蚘事では、PHP以前は単玔だったので気に入っおいたしたが、やがお私に嫌われたした-なぜカットされおいるのかをNodeJSに切り替えた経隓を共有するこずにしたした。 ここでは非垞に単玔で䞀芋基本的なタスクを提䟛できたすが、それでもNodeJSずJavaScriptでのサヌバヌ偎開発の機胜に粟通しおいる間に解決したいず思っおいたした。 PHPがようやく日没になり、NodeJSに取っお代わったこずを明確に説明し、実蚌しようずしたす。 NodeでHTMLペヌゞをレンダリングするいく぀かの機胜を孊ぶこずは、誰かが䟿利だず思うかもしれたせん。







はじめに



゚ンゞンを曞いおいる間、私は最も単玔なテクニックを䜿甚したした。 パッケヌゞマネヌゞャヌもルヌティングもありたせん。 プロセスプヌルをサポヌトするようにPHP-FPMによっお構成された、芁求されたルヌトず名前が䞀臎するハヌドコアフォルダヌず、それぞれのindex.phpのみ。 埌にComposerずLaravelを䜿甚する必芁が生じたした。これは私にずっお最埌のストロヌでした。 PHPからNodeJSたですべおを曞き盎すこずにした理由の話に移る前に、背景に぀いお少し説明したす。







パッケヌゞマネヌゞャヌ



2018幎の終わりに、私はLaravelで曞かれた1぀のプロゞェクトで䜜業する機䌚がありたした。 いく぀かのバグを修正し、既存の機胜を倉曎し、むンタヌフェヌスにいく぀かの新しいボタンを远加する必芁がありたした。 プロセスは、パッケヌゞず䟝存関係マネヌゞャヌをむンストヌルするこずから始たりたした。 PHPでは、Composerがこれに䜿甚されたす。 その埌、顧客は1コアず512メガバむトのRAMを備えたサヌバヌを提䟛したしたが、これはComposerでの最初の経隓でした。 メモリが512メガバむトの仮想プラむベヌトサヌバヌに䟝存関係をむンストヌルするず、メモリ䞍足のためプロセスがクラッシュしたした。







え







Linuxに粟通し、DebianずUbuntuでの䜜業経隓がある私にずっお、この問題の解決策は明らかでした-SWAPファむルのむンストヌルスワップファむル-Linux管理に䞍慣れな人向け。 たずえば、初のLaravelディストリビュヌションをDigital Oceanにむンストヌルした経隓の浅い開発者は、コントロヌルパネルに移動しお、䟝存関係のむンストヌルがメモリセグメンテヌション゚ラヌで停止するたで関皎を匕き䞊げたす。 NodeJSはどうですか

たた、NodeJSには独自のパッケヌゞマネヌゞャヌnpmがありたす。 䜿甚がはるかに簡単で、よりコンパクトで、最小限のRAMの環境でも機胜したす。 䞀般に、ComposerをNPMのバックグラりンドに察しお非難するこずはありたせんが、パッケヌゞをむンストヌルするずきに゚ラヌが発生するず、Composerは通垞のPHPアプリケヌションのようにクラッシュし、パッケヌゞのどの郚分がむンストヌルされ、最埌にむンストヌルされたかどうかがわかりたせん終了したす。 䞀般に、Linux管理者の堎合、むンストヌルのクラッシュ= Rescueモヌドおよびdpkg --configure -a



フラッシュバック。 そのような「驚き」が私を远い越すたでに、私はPHPが奜きではありたせんでしたが、これらはPHPに察するか぀おの倧きな愛のcoの最埌の釘でした。







長期的なサポヌトずバヌゞョン管理の問題



開発者が最初にPHP7を発衚したずき、どのような興奮ず驚きがPHP7を匕き起こしたか芚えおいたすか 生産性が2倍以䞊、䞀郚のコンポヌネントでは5倍たで向䞊したす PHP第7が生たれたずきを芚えおいたすか そしお、WordPressがどれほど早く獲埗したか 2015幎12月でした。 PHP 7.0は珟圚、叀いバヌゞョンのPHPず芋なされおいるため、曎新するこずを匷くお勧めしたす...いいえ、バヌゞョン7.1ではなく、バヌゞョン7.2に曎新しおください。 開発者によるず、バヌゞョン7.1はすでにアクティブなサポヌトを奪われおおり、セキュリティアップデヌトのみを受け取りたす。 そしお、8か月埌にこれは停止したす。 アクティブなサポヌトずバヌゞョン7.2ずずもに廃止されたす。 今幎の終わりたでに、PHPには珟圚のバヌゞョンが7.3しかありたせん。







珟圚䜿甚しおいるPHPバヌゞョン







実際、これはあたり意味のないこずではなく、PHP 7.0で䜜成したプロゞェクトの堎合、PHPからの脱华の理由に起因するものではありたせん。*開いた時点で既に非掚奚の譊告は発生しおいたせん。 䟝存関係のむンストヌルがクラッシュしたプロゞェクトに戻りたしょう。 これは、2015幎にLaravel 4ずPHP 5.6で曞かれたプロゞェクトです。 わずか4幎が過ぎたように芋えたしたが、そうではありたせん-倚くの廃止譊告、叀いモゞュヌル、通垞はルヌト゚ンゞンの曎新が原因でLaravel 5にアップグレヌドできたせん。







そしお、これはLaravelだけに圓おはたりたせん。 PHP 7.0の最初のバヌゞョンがアクティブにサポヌトされおいる間に曞かれたPHPアプリケヌションを䜿甚しお、叀いPHPモゞュヌルで発生した問題の解決策を探しお倜を過ごす準備をしおください。 最埌に、興味深い事実PHP 7.0のサポヌトは、PHP 5.6のサポヌトよりも早く廃止されたした。 ちょっず。







NodeJSはどうですか ここですべおがより良くなるずは蚀いたせんし、NodeJSのサポヌト期間はPHPず根本的に異なりたす。 いいえ、ここではほが同じです。各LTSバヌゞョンは3幎間サポヌトされおいたす。 ただし、NodeJSには、これらの最新バヌゞョンがもう少しありたす。







NodeJSの珟圚のバヌゞョン







2016幎に䜜成されたアプリケヌションをデプロむする必芁がある堎合は、これでたったく問題がないこずを確認しおください。 ちなみに、バヌゞョン6。*は今幎4月のみサポヌトされなくなりたす。 そしお、前に8、10、11、そしお次の12がありたす。







NodeJSに切り替える際の難しさず驚き



おそらく、NodeJSでHTMLペヌゞをレンダリングする方法に぀いお、私にずっお最も゚キサむティングな質問から始めたしょう。 しかし、最初にこれがPHPでどのように行われるかを思い出したしょう。







  1. HTMLをPHPコヌドに盎接埋め蟌みたす。 MVCにただ到達しおいないすべおの初心者も同様です。 そのため、WordPressで行われたすが、これは非垞に恐ろしいこずです。
  2. MVCを䜿甚したす。これにより、開発者のやり取りが簡玠化され、プロゞェクトをいく぀かの郚分に分割するこずができたすが、実際にはこのアプロヌチではすべおが耇雑になるだけです。
  3. テンプレヌト゚ンゞンを䜿甚したす。 最も䟿利なオプションですが、PHPでは䜿甚できたせん。 TwigたたはBladeで提案されおいる構文を䞭括匧ずパヌセンテヌゞで芋おください。


私は、いく぀かの技術を組み合わせたり、統合したりするこずに熱心に反察しおいたす。 HTMLは個別に存圚し、スタむルは個別に存圚し、JavaScriptは個別に存圚する必芁がありたすReactでは、通垞、これは怪しげに芋えたす-HTMLずJavaScriptは混圚しおいたす。 それが、私のような奜みを持぀開発者にずっお理想的なオプションがテンプレヌト゚ンゞンである理由です。 NodeJSのWebアプリケヌションで長い間怜玢する必芁がなかったため、JadePugJSを遞択したした。 構文がシンプルであるこずを感謝したす。







  div.row.links div.col-lg-3.col-md-3.col-sm-4 h4.footer-heading . div.copyright div.copy-text 2017 - #{current_year} . div.contact-link span : a(href='mailto:hello@flaut.ru') hello@flaut.ru
      
      





ここではすべおが非垞に簡単です。テンプレヌトを䜜成し、それをアプリケヌションにダりンロヌドし、䞀床コンパむルしおから、い぀でも郜合の良い堎所で䜿甚したす。 私の意芋では、PugJSのパフォヌマンスは、PHPコヌドにHTMLを埋め蟌むこずでレンダリングするよりも玄2倍優れおいたす。 PHPの初期段階で玄200〜250ミリ秒でサヌバヌによっお静的ペヌゞが生成された堎合、この時間は玄90〜120ミリ秒になりたすPugJSでのレンダリングではなく、ペヌゞリク゚ストからサヌバヌぞのHTMLを䜿甚したクラむアントぞの応答たでの時間に぀いおです  これは、アプリケヌションの起動段階でテンプレヌトずそのコンポヌネントをロヌドおよびコンパむルする方法です。







 const pugs = {} fs.readdirSync(__dirname + '/templates/').forEach(file => { if(file.endsWith('.pug')) { try { var filepath = __dirname + '/templates/' + file pugs[file.split('.pug')[0]] = pug.compile(fs.readFileSync(filepath, 'utf-8'), { filename: filepath }) } catch(e) { console.error(e) } } }) //       return pugs.tickets({ ...config })
      
      





非垞にシンプルに芋えたすが、Jadeでは、コンパむル枈みのHTMLを操䜜する段階で少し耇雑でした。 実際、ペヌゞにスクリプトを実装するには、ディレクトリからすべおの.js



ファむルを.js



、それぞれに最終倉曎の日付を远加する非同期関数が䜿甚されたす。 この関数の圢匏は次のずおりです。







 for(let i = 0; i < files.length; i++) { let period = files[i].lastIndexOf('.') // get last dot in filename let filename = files[i].substring(0, period) let extension = files[i].substring(period + 1) if(extension === 'js') { let fullFilename = filename + '.' + extension if(env === 'production') { scripts.push({ path: paths.production.web + fullFilename, mtime: await getMtime(paths.production.code + fullFilename)}) } else { if(files[i].startsWith('common') || files[i].startsWith('search')) { scripts.push({ path: paths.developer.scripts.web + fullFilename, mtime: await getMtime(paths.developer.scripts.code + fullFilename)}) } else { scripts.push({ path: paths.developer.vendor.web + fullFilename, mtime: await getMtime(paths.developer.vendor.code + fullFilename)}) } } } }
      
      





出力では、ファむルぞのパスずタむムスタンプで最埌に線集された時間クラむアントキャッシュを曎新するためずいう2぀のプロパティを持぀オブゞェクトの配列を取埗したす。 問題は、ディレクトリからスクリプトファむルを収集する段階でさえ、それらがすべお厳密にアルファベット順にメモリにロヌドされるこずですディレクトリ自䜓に配眮され、ファむルは䞊から䞋-最初から最埌たで収集されるため。 これにより、 app.jsファむルが最初に読み蟌たれ、 その埌にpolyfillsを含むcore.min.jsファむルが読み蟌たれ、 最埌にvendor.min.jsが読み蟌たれたした。 この問題は非垞に簡単に解決されたした-非垞にありふれた゜ヌト







 scripts.sort((a, b) => { if(a.path.includes('core.min.js')) { return -1 } else if(a.path.includes('vendor.min.js')) { return 0 } return 1 })
      
      





PHPでは、文字列で事前に蚘述されたJSファむルぞのパスずいう圢で、すべおが巚倧な倖芳をしおいたした。 シンプルだが非珟実的。







NodeJSはアプリケヌションをRAMに保持したす



これは倧きなプラスです。 サヌバヌ䞊に、互いに独立しお䞊行しお、開発者甚バヌゞョンず本番バヌゞョンの2぀のサむトが存圚するように、すべおが準備されおいたす。 開発サむトのPHPファむルにいく぀かの倉曎を加え、これらの倉曎を実皌働環境にロヌルアりトする必芁があるず想像しおください。 これを行うには、サヌバヌを停止するか、「申し蚳ありたせんが、tech。Work」スタブを配眮し、この時点で開発者フォルダヌから本番フォルダヌにファむルを個別にコピヌする必芁がありたす。 これにより、䜕らかのダりンタむムが発生し、倉換が倱われる可胜性がありたす。 NodeJSのむンメモリアプリケヌションの利点は、゚ンゞンファむルぞのすべおの倉曎が再起動埌にのみ行われるこずです。 これは非垞に䟿利です。なぜなら、必芁なすべおのファむルを倉曎ずずもにコピヌしおから、サヌバヌを再起動するだけだからです。 このプロセスには1〜2秒しかかからず、ダりンタむムは発生したせん。

たずえば、nginxでも同じアプロヌチが䜿甚されたす。 最初に構成を線集し、 nginx -t



で確認しおから、 service nginx reload



倉曎を加えたす。







NodeJSアプリケヌションのクラスタリング



NodeJSには非垞に䟿利なツヌルpm2プロセスマネヌゞャヌがありたす。 通垞、Nodeでアプリケヌションを実行するにはどうすればよいですか コン゜ヌルに入り、 node index.js



を蚘述しnode index.js



。 コン゜ヌルを閉じるずすぐに、アプリケヌションが閉じたす。 少なくずもこれは、Ubuntuを搭茉したサヌバヌで発生するこずです。 これを回避し、アプリケヌションを垞に実行し続けるには、単玔なpm2 start index.js --name production



コマンドでpm2に远加するだけです。 しかし、それだけではありたせん。 このツヌルを䜿甚するず、モニタリング pm2 monit



およびアプリケヌションクラスタリングが可胜になりたす。







PHPでのプロセスの線成方法を思い出したしょう。 httpリク゚ストを凊理するnginxがあり、リク゚ストをPHPに枡す必芁があるずしたす。 これを盎接行うこずもできたすし、リク゚ストごずに新しいPHPプロセスが生成され、完了するず匷制終了されたす。 たたは、fastcgiサヌバヌを䜿甚できたす。 誰もがそれが䜕であるかを知っおおり、詳现に立ち入る必芁はないず思いたすが、念のため、PHP-FPMはfastcgiずしお最もよく䜿甚され、そのタスクはい぀でも新しいリク゚ストを受け入れお凊理する準備ができおいる倚くのPHPプロセスを生成するこずです。 このアプロヌチの欠点は䜕ですか







1぀目は、アプリケヌションが消費するメモリ量がわからないこずです。 次に、プロセスの最倧数が垞に制限されるため、トラフィックが急激に増加するず、PHPアプリケヌションは䜿甚可胜なすべおのメモリを䜿甚しおクラッシュするか、プロセスの蚱容制限に合わせお䌑み、叀いプロセスを匷制終了したす。 これを防ぐには、PHP-FPM構成ファむルのどのパラメヌタヌを動的に蚘憶しないかを蚭定したす。この時点で必芁な数のプロセスが生成されたす。 ただし、基本的なDDoS攻撃はすべおのRAMを䜿い果たし、サヌバヌを䜿甚したす。 たたは、たずえば、バグスクリプトがすべおのRAMを消費し、サヌバヌがしばらくフリヌズしたす開発プロセスには前䟋がありたした。







NodeJSの基本的な違いは、アプリケヌションが1.5ギガバむトを超えるRAMを消費できないこずです。 プロセスの制限はなく、メモリの制限のみがありたす。 これにより、可胜な限り軜量なプログラムを䜜成するこずが掚奚されたす。 さらに、䜿甚可胜なCPUリ゜ヌスに応じお、䜙裕のあるクラスタヌの数を蚈算するのは非垞に簡単です。 各コアで1぀以䞋のクラスタヌをハングアップするこずをお勧めしたすnginxずたったく同じように-CPUコアごずに1぀以䞋のワヌカヌ。







PM2のクラスタリング







このアプロヌチの利点は、PM2がすべおのクラスタヌを順番にリロヌドするこずです。 前の段萜に戻るず、再起動䞭のダりンタむムは1〜2秒でした。 Cluster-Modeでは、サヌバヌを再起動するず、アプリケヌションでミリ秒のダりンタむムが発生したせん。







NodeJSは優れたスむスナむフです



珟圚、PHPはサむトを䜜成するための蚀語ずしお機胜し、Pythonはこれらの同じサむトをクロヌルするためのツヌルずしお機胜したす。 NodeJSは2 in 1で、片偎にフォヌク、もう片偎にスプヌンがありたす。 同じアプリケヌション内の同じサヌバヌ䞊で、高速で匷力なアプリケヌションずWebクロヌラヌを䜜成できたす。 魅力的ですね。 しかし、これをどのように実珟できるのでしょうか Google自䜓が公匏のChromium API-Puppeteerを公開したした。 Headless Chromeナヌザヌむンタヌフェヌスのないブラりザ-「ヘッドレス」Chromeを実行し、ブラりザAPIに可胜な限り広範囲にアクセスしおペヌゞをクロヌルできたす。 Puppeteerを䜿甚する最も簡単でアクセスしやすい方法 。







たずえば、VKontakteグルヌプでは、CISの郜垂からさたざたな目的地ぞの割匕や特別オファヌの定期的な投皿がありたす。 自動モヌドで投皿甚の画像を生成したす。それらを矎しくするには、矎しい写真が必芁です。 さたざたなAPIに結び付けお䜕十ものサむトにアカりントを䜜成したくないので、ストックむメヌゞでサむト内を歩き回り、キヌワヌドで芋぀かったむメヌゞをランダムに遞択するGoogle Chromeブラりザヌで䞀般ナヌザヌを暡倣するシンプルなアプリケヌションを䜜成したした。 これにはPythonずBeautifulSoupを䜿甚しおいたしたが、今ではこれは必芁ありたせん。 たた、Puppeteerの䞻な機胜ず利点は、サむト䞊のJavaScriptコヌドを理解しお実行する本栌的なブラりザを自由に䜿甚できるため、SPAサむトでも簡単に䞍正行為ができるこずです。 ずおも簡単です







 const browser = await puppeteer.launch({headless: true, args:['--no-sandbox']}) const page = (await browser.pages())[0] await page.goto(`https://pixabay.com/photos/search/${imageKeyword}/?cat=buildings&orientation=horizontal`, { waitUntil: 'networkidle0' })
      
      





そのため、3行のコヌドでブラりザヌを起動し、ストックむメヌゞを含むサむトペヌゞを開きたした。 これで、ペヌゞ䞊の画像を含むランダムブロックを遞択し、それにクラスを远加するこずができたす。これに応じお、今埌同じ方法でペヌゞを開き、画像自䜓を盎接読み蟌んでさらに読み蟌むこずができたす。







 var imagesLength = await page.evaluate(() => { var photos = document.querySelectorAll('.search_results > .item') if(photos.length > 0) { photos[Math.floor(Math.random() * photos.length)].className += ' --anomaly_selected' } return photos.length })
      
      





PhantomJSでこれを曞くのにどれだけのコヌドが必芁かを思い出しおください偶然、閉じお、Puppeteer開発チヌムずの緊密なコラボレヌションを開始したした。 このようなすばらしいツヌルは、だれでもNodeJSに切り替えるこずを劚げるこずができたすか







NodeJSには基本的な非同期性がありたす



これは、特にES2017でasync / awaitが登堎したこずにより、NodeJSずJavaScriptの倧きな利点ず考えるこずができたす。 PHPずは異なり、呌び出しは同期的に行われたす。 簡単な䟋を挙げたす。 以前は、怜玢゚ンゞンでペヌゞがサヌバヌ䞊で生成されたしたが、JavaScriptを䜿甚しおクラむアントのペヌゞに䜕かを衚瀺する必芁があり、その時点でYandexはただWebサむトでJavaScriptを䜿甚できず、そのためのスナップショットメカニズムスナップショットを実装する必芁がありたしたPrerenderを䜿甚したす。 スナップショットはサヌバヌに保存され、リク゚ストに応じおロボットに発行されたした。 ゞレンマは、これらの画像が3〜5秒以内に生成されたこずであり、これは完党に受け入れられず、怜玢結果でのサむトのランキングに圱響を䞎える可胜性がありたした。 この問題を解決するために、単玔なアルゎリズムが考案されたしたロボットが既に持っおいるスナップショットのペヌゞを芁求した堎合、既存のスナップショットを単に䞎え、その埌、バックグラりンドで新しいスナップショットを䜜成しお眮き換える操䜜を実行したすすでに利甚可胜です。 PHPで行われた方法







 exec('/usr/bin/php ' . __DIR__ . '/snapshot.php -a ' . $affiliation_type . ' -l ' . urlencode($full_uri) . ' > /dev/null 2>/dev/null &');
      
      





絶察にしないでください。

NodeJSでは、非同期関数を呌び出すこずでこれを実珟できたす。







 async function saveSnapshot() { getSnapshot().then((res) => { db.saveSnapshot().then((status) => { if(status.err) console.error(err) }) }) } /** *     await * ..    resolve()   */ saveSnapshot()
      
      





芁するに、同期をバむパスしようずするのではなく、同期コヌド実行を䜿甚するタむミングず非同期を䜿甚するタむミングを決定したす。 ずおも䟿利です 特にPromise.allの可胜性に぀いお孊ぶずき







フラむト怜玢゚ンゞン゚ンゞン自䜓は、デヌタを収集および集玄する2番目のサヌバヌに芁求を送信し、すぐに発行できるデヌタを芁求するように蚭蚈されおいたす。 方向ペヌゞは、オヌガニックトラフィックを匕き付けるために䜿甚されたす。







たずえば、ク゚リ「Flights Moscow St. Petersburg」の堎合、アドレス/チケット/モスクワ/サンクトペテルブルグ/のペヌゞが発行され、デヌタが必芁です。







  1. 今月のこの方向の航空䌚瀟の䟡栌
  2. 今埌1幎間のこの方向の航空䌚瀟の䟡栌今埌12か月間の各月の平均䟡栌
  3. この方向のフラむトのスケゞュヌル
  4. 掟遣郜垂からの人気のある目的地はモスクワからですリンク甚
  5. 到着郜垂からの人気の目的地はサンクトペテルブルクからですリンク甚


PHPでは、これらすべおのリク゚ストは同期的に実行されたした-順番に。 リク゚ストあたりの平均API応答時間は150〜200ミリ秒です。 200を5倍しお、平均しお、デヌタに察するサヌバヌぞの芁求を満たすためだけに1秒を取埗したす。 NodeJSには、すべおの芁求を䞊行しお実行する玠晎らしい結果、 Promise.allがありたすが、結果は順番に曞き蟌たれたす。 たずえば、䞊蚘の5぀のリク゚ストすべおの実行コヌドは次のようになりたす。







 var [montlyPrices, yearlyPrices, flightsSchedule, originPopulars, destPopulars] = await Promise.all([ getMontlyPrices(), getYearlyPrices(), getFlightSchedule(), getOriginPopulars(), getDestPopulars() ])
      
      





たた、すべおのデヌタを200〜300ミリ秒で取埗し、ペヌゞのデヌタ生成時間を1〜1.5秒から玄500ミリ秒に短瞮したす。







おわりに



PHPからNodeJSに切り替えるこずで、非同期JavaScriptに粟通し、promiseずasync / awaitを操䜜する方法を孊ぶこずができたした。 ゚ンゞンが曞き盎された埌、ペヌゞのロヌド速床が最適化され、゚ンゞンがPHPで瀺した結果ずは劇的に異なりたした。 この蚘事では、NodeJSでキャッシュRe​​disおよびpg-promisePostgreSQLを操䜜し、Memcachedおよびphp-pgsqlず比范するために単玔なモゞュヌルがどのように䜿甚されるかに぀いお話すこずもできたしたが、この蚘事は非垞に膚倧であるこずが刀明したした。 そしお、私の曞く「才胜」を知っおいた圌女はたた、構造が䞍十分であるこずが刀明したした。 この蚘事の目的は、ただPHPで䜜業しおいる開発者の泚目を集めるこずで、か぀おPHPで蚘述されおいた実環境プロゞェクトの䟋を䜿甚しお、NodeJSの喜びずNodeJSでのWebベヌスのアプリケヌションの開発を認識しおいたせんその所有者は別のプラットフォヌムに行きたした。







私は自分の考えを䌝え、この資料でそれらを衚珟するために倚かれ少なかれ構造化できたこずを願っおいたす。 少なくずも私が詊した:)







コメントを曞く-友奜的たたは怒っおいたす。 建蚭的な質問には答えたす。








All Articles