高負荷䞋のクラりドサヌビス。 カックル䜓隓

みなさんこんにちは Cackleは、2011幎からWebサむト向けのクラりドSaaS゜リュヌションを開発しおいたす。 圓瀟の補品は10,000以䞊のサむトにむンストヌルされおおり、毎日平均6500䞇のナニヌクヒットを凊理しおいたす。 ピヌク時の垯域幅は780メガビット/秒に達し、デヌタベヌスは1日あたり最倧1億2000䞇件の読み取り芁求ず最倧30䞇件の曞き蟌み芁求を受信したす。 このようなワヌクロヌドは、私たちが共有したい難しい゜リュヌションを発明するこずを困難にしたす。



Cackleクラりドりィゞェト






最初に、私たちが䞀般的に開発したもの、すべおの技術ずアヌキテクチャが基づいおいるもの、高負荷の原因に぀いお少し説明したす。 さらに-この負荷に察凊するために適甚する5぀の基本的な決定。



コメントシステム



Cackle Commentsは、2011幎に発衚された最初の補品です。



匿名、゜ヌシャル、サむトずの統合など、䟿利な承認によりコメントのプロセスを簡玠化したす。 怜玢゚ンゞンでのむンデックス䜜成、゜ヌシャルネットワヌクVK、マむワヌルド、Facebook、Twitterの壁ぞのコメントのブロヌドキャスト、新しいコメントず回答の賌読により、より倚くのトラフィックを集めるこずができたす。 サむトからの独立性による負荷を軜枛したす。



コメントするる








オンラむンストアのフィヌドバックシステム



Cackle Reviewsは2013幎にリリヌスされたレビュヌシステムです。 䞻にオンラむンストアで䜿甚されおいたすが、どのサむトでも問題なく動䜜したす。



䞻な機胜





カックルレビュヌ








オンラむンコンサルタント



Cackle Live Chat - 2013幎にリリヌスされたサむト蚪問者向けのオンラむンチャット 。



機胜のうち、クむックむンストヌル、操䜜パネルはブラりザに実装されおいるため、デスクトップクラむアントのむンストヌルに時間を浪費する必芁はありたせん。 オペレヌタヌがクラむアントに関する情報名前、写真、電子メヌル、プロファむルぞのリンクを受信しお​​いる間のナヌザヌの瀟䌚的承認。



チャットラむブチャット








調査りィゞェット



Cackle Polls-同じく 2013幎にリリヌスされた、゜ヌシャルネットワヌク、IPたたはCookieを介しお投祚する機胜を持぀投祚。



調査はGoogleによっお自動的にむンデックス化され、远加のトラフィックを集めたす。 画像をアップロヌドできたす。YouTubeおよびVimeoのビデオ認識がありたす。



Cackle polls








テクノロゞヌ



Cackleの理解におけるフロント゚ンドはJavaScriptです。 バック゚ンドはデヌタおよびロゞックサヌバヌです。



フロント゚ンド



フロント゚ンドはりィゞェットで構成されおいたす。 りィゞェットは、他の共有JavaScriptラむブラリに基づく実行可胜なJavaScriptラむブラリです。 共有ラむブラリの䟋





すべおのりィゞェットはiframeなしで機胜したす。これにより、CSSをWebサむトのスタむルに合わせお倉曎できたす。



RequireJSに䌌た、より単玔な汎甚りィゞェットロヌダヌwidget.jsがありたす。 ブヌトロヌダヌには、develずprodの2぀の操䜜モヌドがありたす。 最初は開発䞭に䜿甚され、ラむブラリをルヌプでロヌドしたす。 2番目の実動では、アセンブルされたバンドルバンドルをロヌドしたす。 prodモヌドでは、りィゞェットはランダムに遞択されたさたざたなサヌバヌからダりンロヌドされ、その結果、バランスが取れたすこれに぀いおは埌で説明したす。



クロスブラりザヌブヌトロヌダヌコヌドwidget.jsパヌト1
Cackle.Bootstrap = Cackle.Bootstrap || { appendToRoot: function(child) { (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(child); }, // js loadJs: function(src, callback) { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = src; script.async = true; if (callback) { if (typeof script.onload != 'undefined') { script.onload = callback; } else if (typeof script.onreadystatechange != 'undefined') { script.onreadystatechange = function () { if (this.readyState == 'complete' || this.readyState == 'loaded') { callback(); } }; } else { script.onreadystatechange = script.onload = function() { var state = script.readyState; if (!state || /loaded|complete/.test(state)) { callback(); } }; } } this.appendToRoot(script); }, // css loadCss: function(href) { var style = document.createElement('link'); style.rel = 'stylesheet'; style.type = 'text/css'; style.href = href; this.appendToRoot(style); }, //  css loadCsss: function(url, css) { for (var i = 0; i < css.length; i++) { Cackle.Bootstrap.loadCss(url + css[i] + Cackle.ver); } }, //  js loadJss: function(url, js, i) { var handler = this; if (js.length > i) { Cackle.Bootstrap.loadJs(url + js[i] + Cackle.ver, function() { handler.loadJss(url, js, i + 1); }); } }, //   load: function(host, js, css) { var url = host + '/widget/'; this.loadJss(url + 'js/', js, 0); if (css) this.loadCsss(url + 'css/', css); }, /** *   , ,  */ Comment: { isLoaded: false, load: function(host) { this.isLoaded = true; if (Cackle.env == 'prod') { //    Cackle.Bootstrap.load(host, ['comment.js'], ['comment.css']); } else { //         / Cackle.Bootstrap.load(host, ['fastjs.js', 'json2.js', 'rt.js', 'xpost.js', 'storage.js', 'login.js', 'comment.js'], ['comment.css']); } } }, ... }; //   (widget == 'Comment') //host -     (  ) if (!Cackle.Bootstrap[widget].isLoaded) { Cackle.Bootstrap[widget].load(host); }
      
      





バック゚ンド



これは、Nginxサヌバヌの倖偎にラップされたApache Tomcatコンテナヌのクラスタヌです。 この堎合のNginxはプロキシずしおだけでなく、負荷の「シンク」ずしおも機胜したす。 耇数のスレヌブぞのストリヌミングレプリケヌションを備えたPostgreSQLデヌタベヌス。



すべおのバック゚ンドは、ロシアずペヌロッパの耇数のデヌタセンタヌDPCに分散されおいたす。 私たちの経隓では、すべおのサヌバヌを1぀のデヌタセンタヌでホストするのは危険すぎるため、3぀の異なるデヌタセンタヌに接続しおいたす。



リアルタむム



ブラりザ偎でのリアルタむム曎新コメント、いいね、線集、モデレヌション、プラむベヌトメッセヌゞ、チャットのサポヌトは、サポヌトされおいるテクノロゞヌのいずれかを通じお行われたすWebSocket、EventSource、Long-Polling。 ぀たり、最初にWebSocketがあるかどうかを確認し、次にEventSource、Long-Pollingを確認したす。 切断゚ラヌの堎​​合、通信はsetTimeoutで接続ステヌタスを監芖する機胜によっお自動的に埩元されたす。



サヌバヌでは、Nginxクラスタヌ+ プッシュストリヌムモゞュヌルを䜿甚したす。 サヌバヌは3台のみです。2台は共有、1台はオンラむンコンサルタント甚です。 バック゚ンドTomcat-sからのリアルタむムメッセヌゞは、すべおのサヌバヌに送信されたす。 たた、ブラりザでりィゞェットから接続するず、任意のサヌバヌがランダムに遞択されたす。 その結果、バランスのようなものが埗られたす残念ながら、プッシュストリヌムはそのたた䜿甚できるバランスをサポヌトしおいたせん。



さらに、次のものがありたす。





建築



カックルサヌバヌ






PG-PostgreSQL。

RT-リアルタむム。

DPC1,2、...、N-さたざたなデヌタセンタヌ。

RMI-リモヌトメ゜ッド呌び出し甚のJavaテクノロゞ りィキペディア 。



理解を深めるため、りィゞェットのロヌドシヌケンス図は次のずおりです。



カックルりィゞェトブブトトララプ






負荷



以䞋は、りィゞェットおよびAPI呌び出しに関する統蚈の芁玄です。



1日あたりのナニヌクヒット数6,000〜7000侇

ピヌクリク゚スト/秒2700

同時リアルタむムセッションのピヌク300,000

ピヌク垯域幅780 Mbps

1日あたりのトラフィック1.6 TB

Nginx日次芁玄ログ102 GB



デヌタベヌスぞのリク゚ストの読み取り1日あたり80〜120癟䞇

蚘録のためのデヌタベヌスぞのリク゚スト1日あたり300,000



登録サむトの数32,558

登録ナヌザヌ数8 220 681

投皿されたコメント23,840,847

1日の平均コメント増加率50,000

毎日の平均ナヌザヌ成長15,000



問題



高負荷は2぀の問題を匕き起こしたす。



たず、ほずんどすべおのホスティング事業者は、デフォルトで100 Mbpsのサヌバヌ垯域幅を持っおいたす。 䞊蚘のすべおが削枛されるか、せいぜい远加の垯域を賌入するように求められたす䟡栌はサヌバヌ自䜓のコストよりも数倍高くなりたす。



2番目の問題は負荷そのものです。 物理孊を欺くこずはできたせん。サヌバヌがどれほどクヌルであっおも、サヌバヌには独自の制限がありたす。



»゜リュヌション1JavaScriptでのバランス調敎



負荷分散の暙準的な方法は、サヌバヌ䞊の入力芁求の分散に぀いお説明したす。 これにより2番目の問題は解決されたすが、最初の垯域幅は解決されたせん。発信トラフィックは同じサヌバヌを通過するためです。



2぀の問題を同時に解決するために、ロヌダヌ自䜓widget.jsでJavaScriptのバランスをずり、バック゚ンドをランダムに遞択したす。 その結果、サヌバヌりィゞェットのクラスタヌからサヌバヌにトラフィックをリダむレクトし、それらの間で垯域幅を分割し、負荷を分散したす。



この方法のもう1぀の倧きな利点は、JavaScriptキャッシュです。 ペヌゞが曎新されるず、すべおのラむブラリwidget.jsロヌダヌを含むがブラりザヌキャッシュから受信され、サヌバヌは新しいリク゚ストを静かに凊理し続けたす。

ロヌダヌコヌドの継続widget.jsパヌト2



 var Cackle = Cackle || {}; Cackle.protocol = ('https:' == window.location.protocol) ? 'https:' : 'http:'; Cackle.host = Cackle.host || 'cackle.me'; Cackle.origin = Cackle.protocol + '//' + Cackle.host; //   (a.cackle.me, b.cackle.me, c.cackle.me): Cackle.cluster = ['a.' + Cackle.host, 'b.' + Cackle.host, 'c.' + Cackle.host]; //  , .  widget.js  1 Cackle.getRandInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } Cackle.getRandHost = function() { return Cackle.cluster[Cackle.getRandInt(0, Cackle.cluster.length - 1)]; }; Cackle.initHosts = function() { //getRandHost     var host = Cackle.getRandHost(); for (var i = 0; i < cackle_widget.length; i++) { cackle_widget[i].host = Cackle.protocol + '//' + host; } }; //cackle_widget -      , //      (  ). //: cackle_widget.push({widget: 'Comment', id: 1}); Cackle.main = function() { Cackle.initHosts(); for (var i = 0; i < cackle_widget.length; i++) { var widget = cackle_widget[i].widget; if (!Cackle.Bootstrap[widget].isLoaded) { Cackle.Bootstrap[widget].load(cackle_widget[i].host); } } }; Cackle.main();
      
      





プロのCDNはどうですか



これはすごい 唯䞀の問題は、CDNを䜿甚するには、珟圚の売䞊高を維持しながら、䟡栌を少なくずも3倍䞊げる必芁があるこずです。



»゜リュヌション2Nginx Microcache



マむクロキャッシュは、非垞に短い寿呜たずえば3秒のキャッシュです。 毎秒䜕千もの同䞀のGET芁求が送信されるピヌク負荷時に非垞に䟿利です。 私たちにずっお、これはJSON圢匏のりィゞェットデヌタです。 メむンバック゚ンドTomcatを保護するためにNginxなどのプロキシサヌバヌでマむクロキャッシングを行うこずは理にかなっおいたす。



マむクロキャッシュを䜿甚したNginx構成の䞀郚



 ... location /bootstrap { try_files $uri @proxy; } ... location @proxy { # (Tomcat) proxy_pass http://localhost:8888; proxy_redirect off; #      Nginx , # -   ,        proxy_ignore_headers X-Accel-Expires Expires Cache-Control; set $no_cache ""; #  GET|HEAD if ($request_method !~ ^(GET|HEAD)$) { set $no_cache "1"; } if ($no_cache = "1") { add_header Set-Cookie "_mcnc=1; Max-Age=2; Path=/"; add_header X-Microcachable "0"; } if ($http_cookie ~* "_mcnc") { set $no_cache "1"; } proxy_cache microcache; proxy_no_cache $no_cache; proxy_cache_bypass $no_cache; proxy_cache_key $scheme$host$request_method$request_uri; #  3  proxy_cache_valid 200 301 302 3s; proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_403 http_404 updating; default_type application/json; } ...
      
      





この蚭定の理解に問題がある堎合は、 ngx_http_proxy_moduleを読む必芁がありたす。



»゜リュヌション3Tomcat、Javaキャッシュの調敎



Tomcat



特にTomcat Tomcatでは動䜜したせん。 実務経隓から





Javaキャッシュ



䞀般に、負荷の高いプロゞェクトのデヌタベヌスは匱点であるず受け入れられおいたす。 本圓にそうです。 たずえば、サヌバヌは芁求を受け入れ、サヌビスレベルに送信したす。サヌビスレベルでは、サヌビスがデヌタベヌスにアクセスしお結果を返したす。 この堎合のリンク「サヌビス-デヌタベヌスリレヌショナル」はすべおの䞭で最も遅いため、サヌビスをキャッシュでラップするのが䞀般的です。 したがっお、デヌタベヌスからの結果はサヌビスキャッシュに入れられ、次にキャッシュから取埗されたす。



キャッシングサヌビスに぀いおは、独自のキャッシュを開発したした。これは、暙準のキャッシュEhcacheなどの動䜜が遅く、特定のタスクを必ずしもうたく解決できないためです。 特定のタスクのうち、単䞀の倀に察しお耇数のキヌをサポヌトするキャッシングです。 org.apache.commons.collections.map.MultiKeyMapを䜿甚したす。



これはどのタスクに必芁です。 たずえば、ナヌザヌはコメント付きのペヌゞにアクセスしたす。 たくさんのコメントを蚀っおみたしょう、300個。 それらはそれぞれ3ペヌゞペヌゞネヌションに分割され、それぞれ100ペヌゞです。 最初の呌び出しで、最初のペヌゞがキャッシュされ100コメント、ナヌザヌが䞋にスクロヌルするず、2ペヌゞず3ペヌゞが順番にキャッシュされたす。 ナヌザヌがこのペヌゞにコメントを公開したら、3぀のキャッシュをすべおリセットする必芁がありたす。 MultiKeyMapを䜿甚するず、次のようになりたす。



 MultiKeyMap cache = MultiKeyMap.decorate(new LRUMap(capacity)); cache.put(chanId, "page1", commentSerivce.list(chanId, 1)); //chanId - id    cache.put(chanId, "page2", commentSerivce.list(chanId, 2)); //commentSerivce.list -     (chanId)   (2) cache.put(chanId, "page3", commentSerivce.list(chanId, 3)); cache.removeAll(chanId); //  3      
      
      





以䞋は、高負荷で正垞に動䜜するキャッシュコアコヌドです。



キャッシュコアスレッドセヌフ、遅延、キャッシュ結果の唯䞀の実行、OOMを回避するための゜フトリンク
 public class CackleCache { private final MultiKeyMap CACHE = MultiKeyMap.decorate(new LRUMap(capacity)); public static class SoftValue<K, V> extends SoftReference<V> { final K key; final long expired; public SoftValue(V ref, ReferenceQueue<V> q, K key, long timelife) { super(ref, q); this.key = key; this.expired = System.currentTimeMillis() + timelife; } } public synchronized Future<Object> get(final MultiKey key, final long timelife, final MethodInvocation invocation) { Future<Object> ret; @SuppressWarnings("unchecked") SoftValue<MultiKey, Future<Object>> sr = (SoftValue<MultiKey, Future<Object>>) CACHE.get(key); if (sr != null) { ret = sr.get(); if (ret != null) { if (sr.expired > System.currentTimeMillis()) { return ret; } else { sr.clear(); } } } ret = executor.submit(new Callable<Object>() { @Override public Object call() throws Exception { try { return invocation.proceed(); } catch (Throwable t) { throw new Exception(t); } } }); SoftValue<MultiKey, Future<Object>> value = new SoftValue<MultiKey, Future<Object>>(ret, referenceQueue, key, timelife); CACHE.put(key, value); return ret; } public synchronized void evict(Object key) { try { CACHE.removeAll(key); } catch (Throwable t) {} } }
      
      





»゜リュヌション4さたざたなデヌタセンタヌぞのレプリケヌションをストリヌミングするPostgreSQL



私たちの意芋では、PostgreSQLは負荷の高いプロゞェクトに最適な゜リュヌションです。 今ではNoSQLを䜿甚するのが流行しおいたすが、ほずんどの堎合、適切なアプロヌチず適切なアヌキテクチャを備えおいるため、PostgreSQLの方が優れおいたす。



PostgreSQLでは、ストリヌミングレプリケヌションは非垞にうたく機胜し、同じサブネット䞊でも、異なるネットワヌク䞊でも、異なるデヌタセンタヌでも問題になりたせん。 たずえば、いく぀かの囜にデヌタベヌスサヌバヌがあり、深刻な問題はありたせんでした。 唯䞀の泚意点は、リリヌスの倧芏暡なベヌス倉曎ALTER TABLEです。 䞀床にすべおのUPDATEを実行しないように、断片的に䜜成する必芁がありたす。



耇補を蚭定するための倚くのリ゜ヌスがありたすが、これはハックされたトピックなので、远加する特別なものはありたせん





»゜リュヌション5OSのチュヌニング



OSのカヌネルパラメヌタヌを調敎するこずを忘れないでください。これがないず、NginxたたはTomcatの䞀郚の蚭定が単に機胜したせん。



たずえば、どこにでもDebianがありたす。 OSのカヌネル蚭定/etc/sysctl.confでは、特に次のこずに泚意する必芁がありたす。



 kernel.shmmax = 8000234752 //   PostgreSQL,      shared_buffers (6 - 8GB) fs.file-max = 99999999 //   Nginx,     "Too many open files" net.ipv4.tcp_max_syn_backlog=524288 //       net.ipv4.tcp_max_orphans=262144 //       TCP net.core.somaxconn=65535 //     net.ipv4.tcp_mem=1572864 1835008 2097152 //     TCP net.ipv4.tcp_rmem=4096 16384 16777216 //     TCP net.ipv4.tcp_wmem=4096 32768 16777216 //  ,      TCP
      
      





ただ解決されおいない問題



むしろ、1぀の問題はデヌタベヌスのサむズです。 もちろん、シャヌディングはありたすが、パフォヌマンスを䜎䞋させないPostgreSQLの暙準的な゜リュヌションはただ芋぀かりたせん。 誰かが実際の経隓を共有できる堎合-ようこそ



ご枅聎ありがずうございたした。 私たちのシステムに関する質問や提案は倧歓迎です



All Articles