ホームページ  >  記事  >  ウェブフロントエンド  >  NodeJSによるフロントエンドとバックエンド分離の思考と実践(6) Nginx Node.js Javaソフトウェアスタック導入実践_node.js

NodeJSによるフロントエンドとバックエンド分離の思考と実践(6) Nginx Node.js Javaソフトウェアスタック導入実践_node.js

WBOY
WBOYオリジナル
2016-05-16 16:35:162244ブラウズ

淘宝オンライン アプリケーションの従来のソフトウェア スタック構造は、Nginx Velocity Java、つまり次のとおりです。

このシステムでは、Nginx がリクエストを Java アプリケーションに転送し、Java アプリケーションがトランザクションを処理し、Velocity テンプレートを使用してデータを最終ページにレンダリングします。

Node.js を導入した後は、次の問題に直面することになります:

科学的かつ合理的になるように、テクノロジースタックのトポロジーはどのように設計され、展開方法はどのように選択されるべきですか?プロジェクト完了後、運用とメンテナンスを便利かつ迅速に行うために、トラフィックをどのように分割するか?オンラインで問題が発生した場合、できるだけ早く危険を解決し、より大きな損失を回避するにはどうすればよいでしょうか?アプリケーションの健全性を確保し、負荷分散スケジュール レベルで管理するにはどうすればよいでしょうか?システムトポロジの継承

フロントエンドとバックエンドの分離に関する考え方と実践 (2) - フロントエンドとバックエンドの分離に基づくテンプレート探索によると、この構造は次のようになります。 :

これはもちろん理想的な目標です。しかし、従来のスタックに初めて Node.js レイヤーを導入するのは、やはり新しい試みです。安全のため、お気に入りのアイテム コレクション ページ (shoucang.taabao.com/item_collect.htm) でのみ新しいテクノロジーを有効にすることを決定し、他のページでは引き続き従来のソリューションを使用します。つまり、Nginx は要求されたページの種類を判断し、要求を Node.js と Java のどちらに転送するかを決定します。したがって、最終的な構造は次のようになりました:

導入計画

上記の構造には問題がないように見えますが、実際には、この先に新たな問題が待っています。従来の構造では、Nginx と Java は同じサーバー上にデプロイされ、Nginx はポート 80 で待機し、上位ポート 7001 で待機する Java と通信します。 Node.js が導入されたので、ポートをリッスンする新しいプロセスを実行する必要があります。Node.js と Nginx Java を同じマシンにデプロイする必要がありますか、それとも別のクラスターに Node.js をデプロイする必要がありますか?
2 つの方法の特徴を比較してみましょう:

淘宝網のお気に入りは、毎日数千万の PV を持つアプリケーションであり、安定性に対する非常に高い要件があります (実際、どの製品でもオンラインでの不安定性は許容できません)。同じクラスター展開ソリューションを採用する場合、リリースを完了するには、1 回のファイル配布と 2 回のアプリケーションの再起動のみが必要です。ロールバックが必要な場合は、ベースライン パッケージを 1 回操作するだけで済みます。パフォーマンスの点では、同じクラスターに導入することには理論上の利点がいくつかあります (ただし、イントラネットのスイッチ帯域幅と遅延は非常に楽観的です)。 1対多または多対1の関係については、理論的にはサーバーを最大限に活用することが可能ですが、安定性要件に比べれば、それほど緊急性の高い点ではなく、解決する必要があります。したがって、お気に入りの変換では、同じクラスター展開ソリューションを選択しました。

グレースケールモード

最大限の安定性を確保するために、この変換では Velocity コードを直接完全には削除しませんでした。アプリケーション クラスターには 100 台近くのサーバーがあり、トラフィックを徐々に導入する粒度としてサーバーを使用します。つまり、すべてのサーバーが Java Node.js プロセスを実行していますが、Nginx に対応する転送ルールがあるかどうかによって、このサーバー上の宝物コレクションのリクエストが Node.js によって処理されるかどうかが決まります。 Nginx の構成は次のとおりです:

location = "/item_collect.htm" {
  proxy_pass http://127.0.0.1:6001; # Node.js 进程监听的端口
}

只有添加了这条 Nginx 规则的服务器,才会让 Node.js 来处理相应请求。通过 Nginx 配置,可以非常方便快捷地进行灰度流量的增加与减少,成本很低。如果遇到问题,可以直接将 Nginx 配置进行回滚,瞬间回到传统技术栈结构,解除险情。

第一次发布时,我们只有两台服务器上启用了这条规则,也就是说大致有不到 2% 的线上流量是走 Node.js 处理的,其余的流量的请求仍然由 Velocity 渲染。以后视情况逐步增加流量,最后在第三周,全部服务器都启用了。至此,生产环境 100% 流量的商品收藏页面都是经 Node.js 渲染出来的(可以查看源代码搜索 Node.js 关键字)。

灰度过程并不是一帆风顺的。在全量切流量之前,遇到了一些或大或小的问题。大部分与具体业务有关,值得借鉴的是一个技术细节相关的陷阱。

健康检查

在传统的架构中,负载均衡调度系统每隔一秒钟会对每台服务器 80 端口的特定 URL 发起一次 <font face="NSimsun">get</font> 请求,根据返回的 HTTP Status Code 是否为 <font face="NSimsun">200</font> 来判断该服务器是否正常工作。如果请求 1s 后超时或者 HTTP Status Code 不为 <font face="NSimsun">200</font>,则不将任何流量引入该服务器,避免线上问题。

这个请求的路径是 Nginx -> Java -> Nginx,这意味着,只要返回了 <font face="NSimsun">200</font>,那这台服务器的 Nginx 与 Java 都处于健康状态。引入 Node.js 后,这个路径变成了 Nginx -> Node.js -> Java -> Node.js -> Nginx。相应的代码为:

  var http = require('http');
  app.get('/status.taobao', function(req, res) {
    http.get({
      host: '127.1',
      port: 7001,
      path: '/status.taobao'
    }, function(res) {
      res.send(res.statusCode);
    }).on('error', function(err) {
      logger.error(err);
      res.send(404);
    });
  });

但是在测试过程中,发现 Node.js 在转发这类请求的时候,每六七次就有一次会耗时几秒甚至十几秒才能得到 Java 端的返回。这样会导致负载均衡调度系统认为该服务器发生异常,随即切断流量,但实际上这台服务器是能够正常工作的。这显然是一个不小的问题。

排查一番发现,默认情况下, Node.js 会使用 <font face="NSimsun">HTTP Agent</font> 这个类来创建 HTTP 连接,这个类实现了 socket 连接池,每个主机+端口对的连接数默认上限是 5。同时 <font face="NSimsun">HTTP Agent</font> 类发起的请求中默认带上了 <font face="NSimsun">Connection: Keep-Alive</font>,导致已返回的连接没有及时释放,后面发起的请求只能排队。

最后的解决办法有三种:

禁用 <font face="NSimsun">HTTP Agent</font>,即在在调用 <font face="NSimsun">get</font> 方法时额外添加参数 <font face="NSimsun">agent: false</font>,最后的代码为:

  var http = require('http');
  app.get('/status.taobao', function(req, res) {
    http.get({
      host: '127.1',
      port: 7001,
      agent: false,
      path: '/status.taobao'
    }, function(res) {
      res.send(res.statusCode);
    }).on('error', function(err) {
      logger.error(err);
      res.send(404);
    });
  });

设置 <font face="NSimsun">http</font> 对象的全局 socket 数量上限:

 http.globalAgent.maxSockets = 1000;

在请求返回的时候及时主动断开连接:

http.get(options, function(res) {
  }).on("socket", function (socket) {
  socket.emit("agentRemove"); // 监听 socket 事件,在回调中派发 agentRemove 事件
});

实践上我们选择第一种方法。这么调整之后,健康检查就没有再发现其它问题了。

Node.js 与传统业务场景结合的实践才刚刚起步,仍然有大量值得深入挖掘的优化点。比比如,让 Java 应用彻底中心化后,是否可以考分集群部署,以提高服务器利用率。或者,发布与回滚的方式是否能更加灵活可控。等等细节,都值得再进一步研究。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。