数十億のトラフィックがあるシステムで JVM を使用する方法

数十億のトラフィックがあるシステムで JVM を使用する方法

[[340168]]

この記事はWeChatの公開アカウント「Shooter Teacup」から転載したもので、著者はShooterです。記事を転載する場合はShooter Teacup公式アカウントまでご連絡ください。

長い間新しい記事を書いていなくてすみません。私は知識を広げるために勉強しており、仕事もとても忙しいです。長い間更新を遅らせていました。

私は友人に JVM シリーズを出版すると約束しました。記事はいくつかあるはずです。品質を確保しながら出力できるよう最善を尽くします。

それでは今日の話題に入りましょう

序文

JVM 関連の問題で困ったことはありませんか?

先月、ある会社の面接に行った友人が、JVM にイライラして泣いてしまったと言っていました。

面接官は品質に関する3つの質問から始めました。

高同時実行プロジェクトの経験はありますか?頻繁なGCを解決するにはどうすればいいですか? JVM チューニングを行ったことがありますか?

友人の会社はB2Bの方向で、システムトラフィックはそれほど大きくなく、勤続年数は2年だけです。彼は尋ねられたとき混乱した

戻ってきてから、高並行性システムで遊ぶ方法を尋ねました。作業の重複を避けるためにこの記事を書きました〜

1. 10億レベル交通システムの見直し

次に、確認してみましょう。

OTAプラットフォーム4億人のユーザー

ピーク時には100万件の注文

ピーク時には12時間で1億8000万回の訪問

1時間あたりのトラフィックは、1億8000万/12 = 1250万です。

1分あたりの流量は1250w / 60 = 20.8wです。

1秒あたりの流量は20.8w / 60 = 3472です。

32 台の 8C/16G マシンを備えた 2 つのクラスター

コアインターフェースクエリは平均5MBのメモリを消費します

毎秒、新世代ヒープメモリ空間の550MBがJVMによって占有されます。

2. システムJVMパラメータ

G1ガベージコレクターに基づく

ここでは、サービス実稼働環境の JVM パラメータをキャプチャしました。

  1. -Xmx12288m 初期ヒープ サイズ。
  2. -Xms12288m 最大ヒープサイズ
  3. -Xss256k 各スレッドのスタックメモリサイズ
  4. -XX:MetaspaceSize=256m メタスペースの初期サイズ
  5. -XX:MaxMetaspaceSize=1g メタスペースの最大サイズ
  6. -XX:MaxGCPauseMillis=200 各YGC/MixedGCの最大一時停止時間(予想される最大一時停止時間)
  7. -XX:+UseG1GC java8 は G1 ガベージコレクターの使用を指定します
  8. -XX:-OmitStackTraceInFastThrow は例外に対する最適化です。例外は非常に速くスローされますが、例外スタック情報は表示されません(参照のみ)
  9. -XX:MinHeapFreeRatio=30 GC 後の Java ヒープ内の空き領域の最小比率。この値より小さい場合、ヒープメモリが増加します。
  10. -XX:MaxHeapFreeRatio=50 GC 後の Java ヒープ内の空き領域の最大割合。この値より大きい場合、ヒープメモリが削減されます。
  11. -XX:CICompilerCount=4 比較的大きな設定にすると、JIT コンパイルの速度がある程度向上します。デフォルト値は2です
  12. -XX:SoftRefLRUPolicyMSPerMB=0 メモリ領域を解放するために、すべてのソフト参照オブジェクトが次の GC でできるだけ早く解放されます。
  13. -XX:+PrintGC GCログを出力する
  14. -XX:+PrintGCDetailsはGCの詳細なログを出力します
  15. -XX:+PrintGCDateStamps GCタイムスタンプを出力します(ベースタイムの形式で)
  16. -XX:+UseGCLogFileRotation は GC ログローテーション機能をオンまたはオフにします
  17. -XX:NumberOfGCLogFiles=5 ローリングログファイルの数を設定する
  18. -XX:GCLogFileSize=32M ローリング ログ ファイルのサイズを設定します。現在の書き込みログ ファイルのサイズがこのパラメータ値を超えると、ログは次のファイルに書き込まれます。
  19. -XX:+HeapDumpOnOutOfMemoryError JVMはOutOfMemoryErrorが発生するとヒープダンプのスナップショットを取得し、ファイルに保存します。

知らせ

-XX:SoftRefLRUPolicyMSPerMB=0 このパラメータにより、場合によってはメタスペース OOM が発生する可能性があります。一般的には 2000 / 5000 に設定するのが最適です。

0 は、この問題が発生しないことを確認するためのチューニング後にのみ使用されます。

OOM が発生する理由については、後の記事で説明します。

3. 高い同時実行性の下で JVM はどのように動作しますか?

ヒープ空間にメモリを割り当てるにはどうすればいいですか?

ヒープ スペースには 12 GB のメモリが割り当てられますが、新しい世代は最初は 12 GB すべてを占有するわけではありません。古い世代は依然としてその一部を占有しなければなりません。

新しい世代には、最初は古い世代に比例したスペースが割り当てられません。新しい世代では、最初はヒープ メモリ領域の 5% のみを割り当て、その後徐々に増加します。

これは、-XX:G1NewSizePercent を使用して新しい世代の初期比率を設定することによって実行できます。実際には、このデフォルト値を維持するだけで十分です。

旧世代にも同じことが言えます。 G1 はリージョン ロジックに基づいてパーティション分割されるため、最初は数 GB は割り当てられません。

新世代のYoungGC(ygc)はどのくらいの頻度で発動されるのでしょうか?

新世代のエデンエリアが満杯でなければ、YGCが発動すると言う人もいます。では、メモリが不足する前に、どのくらいの Eden 領域を使用すればよいのでしょうか?

デフォルト値が 60% のパラメーター -XX:G1MaxNewSizePercent があり、これにより新しい世代がヒープ メモリの最大 60% を占めるように制限されます。

つまり、12G * 60% = 7.2G となり、新しい世代はエデンと 2 人のサバイバーで構成されます。デフォルトの比率は8:1:1です。

7.2G * 0.8 = 5.76G は、エデンエリアが 5.7G に到達しようとすると、ygc が発動するという意味ですか?

いいえ、G1 には非常に重要なパラメーター -XX:MaxGCPauseMillis があります。このパラメータのデフォルト値は 200 です。

つまり、ガベージ コレクションが実行されるたびに、最長の一時停止時間は 200 ミリ秒を超えません。このため、G1 は、それが引き起こす STW は制御可能な一時停止であると主張しています。

大胆な仮定を立ててみましょう: G1 は 200 ミリ秒で 300 のリージョンを再利用することができます。

G1は論理的に旧世代と新世代を区別するため、ヒープ全体は2048の領域に分割され、12Gのヒープメモリの各領域の平均サイズは6MBである。

ただし、リージョンのサイズは2の累乗でなければならないため、各リージョンのサイズは8MBになります。

以前に計算したところ、このシステムによって 1 秒あたりに新しい世代に送信されるオブジェクトのサイズは 550 MB で、550 MB / 8 MB = 68 となり、平均して 1 秒あたり 68 の領域が占有されることになります。

300 個の領域をリサイクルするには 200 ミリ秒かかります (300 / 68 = 4.5 ミリ秒)。

YGC は約 4.5 ミリ秒ごとに実行され、1 分間に 13 個の YGC が実行され、各 YGC は 200 ミリ秒続きます。

この分析により、G1 のガベージ コレクションが実際には非常に動的かつ柔軟であることがわかります。予想される GC 一時停止時間に基づいてリサイクルを実行します。

G1 ではどのオブジェクトが古い世代に入りますか?

  1. オブジェクトは若い世代で 15 回のガベージ コレクションを生き延びており、回収するには古すぎます。古い世代に入りました。
  2. 大きなオブジェクトは古い世代に直接送信されます。パラメーター XX:PretenureSizeThreshold は、オブジェクトがどの程度の大きさであるとみなされるかを制御します。 XX:PretenureSizeThreshold=100000000 単位: ビット
  3. 動的年齢決定ルール: 新世代GCの後に、生き残ったオブジェクトがSurvivorの50%を超えることが判明した場合
  4. YGC 後、生き残ったオブジェクトが多すぎるため、Survivor エリアに保持できなくなります。これらのオブジェクトは古い世代に入ります。

このインターフェースが完了するまでには通常約 200 ミリ秒かかります。ただし、同時実行性が高い状況では、メモリ リソースが非常に不足し、CPU とスレッド リソースの負荷が高くなり、パフォーマンスのジッターが発生する可能性があります。

対応するパフォーマンスは、インターフェースの応答時間が長くなり、タイムアウトが発生する可能性さえあります。頻繁なfgcの場合:

  1. サバイバーエリアの一部のオブジェクトは、15 YGC 後に古い世代に昇格されます。
  2. 多くのインターフェースの応答時間が長くなり、動的な年齢判断ルールがトリガーされ、多数のオブジェクトが古い世代に昇格されます。

これだけのメモリがあれば、サバイバー領域も十分に広く、昇格ルールも比較的厳格になると思われます。ただし、同時実行性の高いシナリオでは、上記のプロセスを数回繰り返すだけで済みます。

古い世代ではオブジェクトがどんどん増えていく

混合GCとは何ですか?

G1 はリージョンに基づいているため、旧世代と新世代の間に厳密な区別はありません。

G1 には、XX:InitiatingHeapOccupancyPercent というパラメータがあり、そのデフォルト値は 45% です。つまり、古い世代がヒープ メモリ領域の 45% を占めている場合、新しい世代と古い世代の混合コレクションをトリガーしようとします。

Full GC(fgc)はいつ発生しますか?

異常事態

  1. 大きなオブジェクトが多すぎるため、すべてのオブジェクトが古い世代に移動されます。古い世代のメモリが不足すると、fgc がトリガーされます。 fgc メモリがまだ不足している場合は、再度メモリを申請すると OOM 例外がスローされ、このように fgc が繰り返されます。システムは直接ハングすることはありませんが、一時停止しているように見え、ユーザーエクスペリエンスが非常に悪くなります。
  2. メタスペースとダイレクトメモリ領域がほぼいっぱいになると、fgc がトリガーされます。

今後、ヒープ スペース、メタスペース、およびダイレクト メモリ (オフヒープ メモリ) OOM の実際の運用環境の事例が登場する予定ですので、ご期待ください。

通常の状況

  1. ご存知のとおり、FGC は非常に時間のかかる操作です。 G1 の通常の動作状態では、Full GC の概念はありません。古い世代のガベージの収集タスクは、Mixed GC によって完全に処理されます。
  2. ただし、混合コレクションを実行する場合、コピー アルゴリズムに基づいて若い世代と古い世代の両方がリサイクルされ、各リージョンの生き残ったオブジェクトを他のリージョンにコピーする必要があります。
  3. このとき、コピー処理中に残存オブジェクトを保持するための空き領域がないことが判明すると、失敗がトリガーされます。失敗すると、すぐにシステム プログラムを停止し、G1 外部の Serial Old GC に切り替えてヒープ全体 (Young、Old、Metaspace を含む) を収集します。これが本当のフルGCです(フルGCはG1の制御下にありません)
  4. G1 がこの状態に入ることは、パラメータ -XX:+UseSerialGC を使用した Full GC と同じです (その背後にあるコア ロジックは同じです)。次に、マーキング、クリーニング、圧縮に 1 つのスレッドが使用され、一連の領域が解放され、GC に 1 つのスレッドが使用されます。このプロセスは非常に遅いです。
  5. これは JVM チューニングの鍵でもあります。システムで Full GC がトリガーされないようにしてください。

補充する

-XX:MaxGCPauseMillis = 200 がデフォルト値です。 200msの一時停止はそれほど長くはありませんが、低遅延と高速応答を必要とする高同時実行システムでは、

この値は少し下げる必要がありますが、この値を下げることはまだ推奨されません。

多くの場合、200 ミリ秒の設定は実際には 20 ~ 80 ミリ秒のみです。これは、30 以上の本番環境で GC を観察して私が得た結論です。

私はパフォーマンス テストの専門家とこの件について話し合いました。G1 は、優れたパフォーマンスを備えた、動的で柔軟性のある自律型ガベージ コレクターです。

設定が小さすぎると、各 Mixed GC または ygc は領域のごく一部しか再利用できず、最終的にはプログラムによるメモリ割り当ての速度に追いつけなくなる可能性があります。

これにより Full GC がトリガーされるため、多くのシステムではこの値を 50 または 100 に変更しません。

設定が大きすぎると、G1 では新しいエージェントに新しいオブジェクトを継続的に割り当てることができ、大量のオブジェクトが蓄積され、一度に数百の領域がリサイクルされる可能性があります。

このとき、GC の一時停止時間は数百ミリ秒に達することがありますが、GC の頻度は非常に低くなります。

たとえば、新世代の GC は 30 分ごとに 1 回トリガーされますが、一時停止はそれぞれ 500 ミリ秒です。 500 ミリ秒は、高同時実行システムにとっては長すぎることは間違いありません。

4. JVM をチューニングするにはどうすればいいですか?

主に新世代で最適化

新世代の GC を最適化するにはどうすればよいでしょうか?

G1 の場合、まず JVM ヒープ領域全体に十分なメモリを割り当て、次に新しい世代に十分なメモリを割り当てて次の点を確保する必要があります。

  • オブジェクトが15回のガベージコレクションを通過して古い世代に入るのを防いでください
  • 生存者を小さくしすぎて動的年齢判定をトリガーしないようにし、また生存者が各ygcの後に生き残ったオブジェクトを置くことができるようにします。

計算の結果、毎分 550 MB のオブジェクトが新しい世代に入り、4.5 秒ごとに YGC が発生することになります。

1 分間に約 13 回の GC が行われ、各 GC にかかる時間は 200 ミリ秒未満です。

PS: G1 コレクションでは、初期マーキング フェーズと再マーキング フェーズのみが STW であり、他のフェーズは同時実行されます。

gc 200ms、実際のstw時間は数十から数百msに過ぎない可能性がある

しかし、YGC の頻度は 1 分間に 13 回で、1 回あたりの時間は 200 ミリ秒に近く、GC 効率が低すぎます。

新世代の最適化

メモリセット(RSet)の存在により、G1領域回復の効率が変わらない場合、最適化ポイントは

各リージョンのサイズを拡張します。つまり、ヒープ メモリのサイズを拡張します。つまり、マシンのメモリをアップグレードするか、クラスターを拡張してサーバーの数を増やすことを意味します。

現在、この業務システムには 8C と 16G のメモリを搭載したマシンが 32 台しかなく、ヒープ領域は 12G しかないため、数十億のトラフィックを処理するには不十分です。

現在の段階的な分析の結果、パフォーマンスのボトルネックはCPUではなく、メモリをアップグレードするだけでよいことが判明しました。

8C 32G にアップグレードすると、ヒープ用に 24 ~ 26G のスペースと 1G のメタスペースが提供され、マシンの数は変わりません。 16C 64G にアップグレードすると、ヒープ用に 58 ~ 60G のスペースと 1G のメタスペースが提供され、複数のマシンをダウンロードすることもできます。

混合 GC はなぜ発生するのでしょうか?

Mixed GC のトリガーについては、古い世代がヒープ メモリの 45% 以上を占めるとトリガーされることは誰もが知っています。

もう一度確認しましょう: 若い世代のオブジェクトが古い世代に入るためのいくつかの条件:

  1. 新世代GC以降、生存者エリアに配置できるオブジェクトが多すぎる
  2. 主題が古すぎる
  3. 動的年齢決定ルール

最も重要なのは、新世代の GC 後に Survivor 領域に配置できないほど多くのオブジェクトが生き残ることと、動的年齢決定ルールです。これら 2 つの条件により、多くのオブジェクトがすぐに古い世代に入る可能性があります。

古い世代がヒープメモリの 45% のしきい値に頻繁に達すると、Mixed GC が頻繁にトリガーされます。

私たちの目標は次のとおりです。

オブジェクトが古い世代に急速に移行することを避け、混合 GC が頻繁にトリガーされることを避けると、混合 GC を根本的に最適化できます。

混合GCの最適化のアイデア

混合GC最適化の核となるのは、依然としてパラメータ-XX:MaxGCPauseMillsである。

-XX:MaxGCPauseMills パラメータの値を大きな値に設定すると、システムが長時間実行されることが想像できます。

新しい世代はヒープメモリの70%を占有する可能性があり、この時点で新しい世代のGCがトリガーされます。

そうすると、生き残ったオブジェクトが多数存在する可能性があり、Survivor 領域はそれほど多くのオブジェクトを保持することができないため、古い世代に入ります。

または、新世代の GC の後、生き残るオブジェクトが多すぎるため、Survivor 領域に入った後に動的年齢決定ルールがトリガーされます。

サバイバーエリアの50%に到達すると、一部のオブジェクトがすぐに古い世代に入ります。

したがって、ここで重要なのは、パラメータ -XX:MaxGCPauseMills の値を調整することです。新世代の GC が頻繁に行われないようにする一方で、各 GC 後に残存するオブジェクトの数も考慮する必要があります。

生き残ったオブジェクトがすぐに古い世代に入り、混合GCが頻繁に発生するのを避ける

5. 実用的かつ効果的なチューニングパラメータ

1.-XX:MaxGCPauseMillis: システムの許容可能な応答時間と指標に基づいて、JVM 回復時間を変更します。単位: ミリ秒

小さすぎると、メモリ割り当ての速度に追いつくことができません。大きすぎると、GC 時間が長くなりすぎます。

2.-XX:ParallelGCThreads: stw フェーズで動作している GC スレッドの数。現在のマシンのCPUコア数に応じて設定できます。推奨値は -1 です。

-XX:ConcGCThreads: 非stwフェーズで動作するGCスレッドの数は、CPUリソースをめぐってユーザースレッドと競合するため、システムのスループットに影響します。

システムが計算集約型である場合、推奨される数は CPU コア数の 1/4 ~ 1/3 です。システムがIO集約型の場合、推奨数は1/2です。

3.-XX:G1ReservePercent: 割り当て保証のために G1 によって予約されているスペースの割合。つまり、新しい世代を昇格させるために古い世代が残すスペースの量です。デフォルトは10%

昇格が失敗すると、単一スレッドの古い GC がトリガーされ、非常に恐ろしいことになります。同時実行性の高いシステムでは、マシン メモリを増やし、このパラメータの比率を増やすことをお勧めします。

4.-XX:MaxMetaspaceSize: メタスペースの最大サイズ。同時実行性が高く、マシン メモリが十分な場合は、メタスペースのサイズを増やすことをお勧めします。

少し大きいシステムには多くの依存コンポーネントがあり、これらのコンポーネントは下部でリフレクションまたはバイトコード フレームワークを使用することがあり、名前が理解できないクラスがいくつか生成されます。

サードパーティのフレームワークに問題が発生すると、システムにも影響が出る可能性があります。

また、メタスペースを増やし、監視システムのアラーム メカニズムを設定し、システムにバッファ時間を与えることも必要です。

5.-XX:トレースクラスのロード -XX:トレースクラスのアンロード

クラスのロードとアンロードの追跡はTomcatのcatalina.outログファイルで行うことができます。

JVM でロードおよびアンロードされるクラスを出力します。

6.-XX:SoftRefLRUPolicyMSPerMB: JVM がソフト参照のリサイクルを許容できる期間

0の場合、ソフト参照はメモリを解放するたびにリサイクルされます。

あるケースでは、15 回の反射の後、反射の効率を向上させるためにいくつかのソフト参照クラスが動的に生成されます。 YGC が実行されると、これらのソフト参照はリサイクルされます。

ただし、クラス ローダーや奇妙な名前のクラスはメタスペースにまだ残っているため、次にこのリフレクション オブジェクトを使用するときには、それらを再作成する必要があります。

これにより、メタスペースが無限に拡大し、OOM が発生します。このパラメータは2000~5000(単位:ms)に設定することをお勧めします。

7.-XX:+DisableExplicitGC: フルGCと同様の操作をトリガーするSystem.gc()への明示的な呼び出しをオフにします。

開くか閉じるかは2つの状況がある

終了: チーム内の新しい天才がビジネス ロジックを記述し、System.gc() を呼び出してメモリを最適化することを防ぐため (私に聞かないでください、その天才は私です)

有効: プロジェクト内の Nio 関連の操作では、Java の DirecByteBuffer オブジェクトによって要求される直接メモリが使用されます。

特定の不合理な状況下では、この領域を制御する DirecByteBuffer が古い世代に昇格されます。

Nio がオフヒープ メモリ領域を不十分に要求すると、System.gc() を手動で呼び出して、DirecByteBuffer オフヒープ メモリを再利用します。

Nio を使用するシステムでこのパラメータをオフにすると、Direct バッファ メモリが失敗する可能性が高くなります。

オンにするかオフにするかは、システムと、プログラマがSystem.gc()を明示的に呼び出すのを防ぐためにコードレビューを行えるかどうかによって異なります。

8.-XX:G1MixedGCCountTarget: ガベージコレクションの混合コレクションフェーズを設定します。これは最大で複数のコレクションに分割できます。

G1 のガベージ コレクションは、初期マーキング、同時マーキング、最終マーキング、混合コレクションの各段階に分かれています。

混合リサイクルは同時に複数回繰り返すことができます。これの利点は、時間がかかりすぎるリサイクルのための単一の一時停止を回避できることです。

システムをしばらく停止し、一部の領域をリサイクルし、システムをしばらく実行してから、再びシステムをしばらく停止し、一部の領域を再びリサイクルします。

これにより、システムが長時間停止するのを防ぎ、また、複数のリサイクルの合間にシステムを実行することもできます。

ある程度、一部のインターフェースのタイムアウトを防ぐことができます。

VI.まとめ

これを読めば、高並行性システムのオブジェクトが JVM メモリを消費する方法と、頻繁な GC を解決する方法についてある程度理解できるはずです。

効果的な解決策は依然としてマシンを追加することですが、追加するマシンの数、マシンの追加方法、および JVM パラメータの設定方法がわかりました。

<<:  5つのオープンソース仮想化ツールで仮想マシンを管理する

>>:  Kubernetes リソースの管理: 留意すべき 5 つのポイント

推薦する

企業はどのようにしてブランドマーケティングの正しい方向性を見つけることができるのでしょうか?

一部の企業にとって、ブランドの概念は曖昧で、ブランドマーケティングの需要が手の届かないところにあるか...

ウェブサイトのおすすめ: 購入者主導の芸術的な電子商取引ウェブサイト、Ubokia

Ubokia: 購入者主導の芸術的な電子商取引ウェブサイトJD.comのようなB2Cであれ、Taob...

ゲームウェブサイトがオープンカジノに変身? 2年足らずで7億5600万の利益

キーボードを打つだけで大金を稼ぎ、2年足らずで7億5600万元を稼ぐオンラインゲーム仮想通貨は、オン...

新規消費者ブランドのためのKOLマーケティングの6つの成功原則

華西子、王宝宝、ベビーケア、ウブラス、ルルレモンなど、数多くの新しい消費者ブランドの台頭は、間違いな...

ブロックチェーンと暗号通貨は近い将来クラウドストレージの基盤になるかもしれない

これを基にブロックチェーンを使用してクラウドストレージを管理し、余剰ディスク容量とネットワーク容量を...

沈国軍: 電子商取引以前の時代の「見えざる主人」

【中国企業家ネットワーク】「見えざる達人」として、中国銀泰投資有限公司の沈国軍会長は、投資に対する深...

SEOには秘密はないが、コツはある

多くの人は、SEO はキーワード ランキングと同じだと考えています。実際、キーワード ランキングは ...

virtono-11.7 ユーロ/年払い/256 MB メモリ/20 GB SSD/2 CPU/100 MB 無制限

今年、virtono.com ではクリスマス プロモーションを実施しており、すべての VPS が 3...

BaiduのスパイダーがプレーンテキストのURLをクロールできることを示す証拠

以前、テキスト形式の URL は Web サイトの軽量化に効果的であると書きました。今日、プレーン ...

中国聯通とテンセントの戦略的協力の強化は実体経済のデジタル変革に貢献する

12月20日、中国聯通とテンセントは「2022年中国聯通パートナー会議」で新たな戦略協力協定に署名し...

A5 最適化チーム: 新しいサイトに適したフレンドリーリンクを交換する方法

ウェブサイトの重みが高ければ高いほど、交換されるフレンドリーリンクの品質が高くなります。これは、ウェ...

カフェのマンスリーカードが引き起こす「バタフライ効果」(前編)

月収10万元の起業の夢を実現するミニプログラム起業支援プラン前回の記事では、ケータリング業界でのマー...

ユーザーエクスペリエンスを無視すると、サイトは失敗する運命にある

今日のウェブマスター サークルは急速に発展しており、サークルに参加する人が増え、誰もがこの小さな領域...

クラウドコンピューティング、仮想化、コンテナ化について

クラウドコンピューティングとは何ですか? 1.1 クラウドコンピューティングの概念[[268482]...

製薬B2C業界の規模は3倍に拡大し、ベンチャーキャピタルはこれを最後のブルーオーシャンと呼んでいる

「2012年、中国の医薬品B2Cの年間規模は16億6500万元に達した。」中国オンライン薬局協議会が...