GaussDB テクノロジーの解釈丨高度な圧縮

この記事の著者|Huawei クラウドデータベース GaussDB チーフアーキテクト Feng Ke

【バックグラウンド】

データ圧縮とリレーショナル データベースの組み合わせはもはや新しいトピックではなく、さまざまなデータベース圧縮製品やソリューションが登場しています。GaussDB にとって、今日のデータ圧縮の導入と、それが顧客にどのような異なる価値をもたらすことができるかは、私たちが過去しばらく考えてきた問題です。

この質問に答えるために、私たちはまず、最高のパフォーマンスを備えた LZ4/Snappy から、パフォーマンスと圧縮率のバランスが取れた Zstd/Zlib、そして圧縮率を重視した LZMA/BZip まで、さまざまな一般的な圧縮アルゴリズムについて広範なテストを実施しました。最良の圧縮アルゴリズムであっても、オンライン データベースのパフォーマンスに大きな影響を与えずに圧縮することはできないことがわかりました。また、データベース分野でのさまざまな符号化手法も調査しており、その中には、近年学術コミュニティによって発表された予測と線形フィッティングに基づくいくつかの符号化手法も含まれます。研究によって発表されたテスト結果と実測によると、データベース符号化は、特定の数値分布の圧縮率問題を解決するために使用されています。圧縮アルゴリズムの成熟度と比較して、実際のデータセットのほとんどのシナリオで安定した圧縮率を提供できる一般的なデータベース符号化手法は現在存在しません。

これがデータベース圧縮の分野における当社の基本的な技術的判断です。過去の製品実践でもこの点は検証されています。多くの商用データベースとオープン ソース データベースが圧縮をサポートしていることがわかりました。ほとんどの場合、特定のテーブルで圧縮を有効にするかどうかは顧客に委ねられています。圧縮をオンにすることはスペースを節約することを意味しますが、同時にパフォーマンスの低下を意味します。この一見単純な選択は、まさにお客様にとって最も難しい選択です。これが、非常に多くのデータベース圧縮製品が存在する理由ですが、データ圧縮がデータベース オンライン ビジネスで実際に広く使用されている根本的な理由はほとんど見つかりません。

これは私たちにさらなるインスピレーションを与えてくれます。私たちは、圧縮率とビジネスへの影響のバランスをとることができる、真に適用可能なデータベース圧縮テクノロジーを選択する必要があると考えています。つまり、テクノロジーに基づいてデータの温度を判断でき、この判断に基づいて、ビジネス内の比較的コールドなデータを、それらの比較的ホットなデータには触れずに選択的に圧縮できます。

このような技術的な選択は、すべてのビジネス シナリオを満たすことができないことを意味しており、ビジネスのデータ温度分布が 80-20 分布ルールを満たす必要があります。つまり、ストレージ要件の 80% を占めるが、コンピューティング要件の 20% のみを占めるコールド データは圧縮し、ストレージ要件の 20% しか占めないが、コンピューティング要件の 80% を占めるホット データには触れません。幸いなことに、容量制御を必要とするほとんどのビジネスがそのような特性を持っていることがわかりました。

【シーンとターゲットの選択】

多数のビジネスシナリオを分析した結果、データベース圧縮技術に対するビジネスニーズは多様化しており、オンライントランザクションビジネス(OLTP)のストレージ圧縮シナリオ、分析ビジネス(OLAP)のストレージ圧縮シナリオ、履歴ビジネスのストレージ圧縮シナリオ、ディザスタリカバリビジネスの送信圧縮シナリオなど、多様化していることがわかりました。さまざまなシナリオでは、圧縮パフォーマンス、圧縮率、解凍パフォーマンスの 3 次元指標、およびビジネス侵入に対する耐性の観点から、圧縮テクノロジの要件はまったく異なります。

これは、フル シナリオの GaussDB の高度な圧縮機能を作成したい場合は、さまざまなシナリオのニーズを満たすために、さまざまなテクノロジーの組み合わせとアプリケーションを通じて、さまざまな圧縮アルゴリズム、さまざまなホットおよびコールドの判断モデルと方法、さまざまなデータ ストレージ組織などを含む複数のテクノロジーを組み合わせる必要があることを意味します。

これは、さまざまな圧縮アプリケーション シナリオのサポートにおいて優先順位のトレードオフが必要であることも意味します。私たちの答えは、OLTP ストレージ圧縮シナリオのサポートを優先することを選択することです。これは、データベース圧縮テクノロジが最も価値があると当社が信じているビジネス領域であり、もちろん、最大の技術的課題がある領域でもあります。

シナリオを決定したら、次のステップは技術目標を決定することですが、このシナリオでどのような核となる競争力を構築するかは、典型的な顧客シナリオの分析によって決まります。私たちは 2 つの典型的な顧客シナリオを特定しました。

シナリオ A: お客様のビジネスは、データベース容量が 50TB の単一データベースを備えた IBM ミニコンピューターを使用していましたが、オープン プラットフォームに移行した後、過剰な容量と長い運用およびメンテナンス期間の問題に直面しました。データベースの解体という選択は分散変換を意味し、長年安定的に運営してきたストックキービジネスにとってこの技術を選択するリスクは高すぎます。圧縮を選択すると、容量のリスクを大幅に軽減できますが、ビジネスの初期設計ではホットとコールドの分離 (時間次元に基づいてパーティションを確立するなど) が考慮されていませんでした。ゼロ侵入圧縮テクノロジのサポートが必要であると同時に、ビジネス パフォーマンスへの影響は十分に低いです。

シナリオ B: お客様のビジネスは分散クラスターに基づいて展開されており、単一クラスターの容量は 1PB を超え、依然として急速に成長しているため、定期的な拡張が必要です。圧縮を選択すると、容量拡張の頻度が減り、ビジネス ソフトウェアとハ​​ードウェアのコストが大幅に削減され、変更のリスクが軽減されます。しかし、ビジネスのデータ分散設計は、ホットとコールドの分離を考慮せず、スケーラビリティを重視したもの(ユーザー ディメンションに基づいたパーティションの作成など)を指向しているため、パフォーマンスへの影響が十分に低いゼロ侵入圧縮テクノロジのサポートも必要です。

典型的な顧客シナリオの要件を整理することにより、GaussDB OLTP ストレージ圧縮の基本的な設計目標を決定しました: 1) ホットとコールドの決定はビジネスへの侵入をゼロにし、ビジネスの既存のデータ分散とロジック モデルに依存すべきではありません; 2) ビジネスへの影響は十分に低くなければならず、目標を 10% 未満と定義し、課題を 5% と定義します; 3) 合理的な圧縮率を提供し、目標を 2:1 以上と定義します。基本的な設計目標を定義すると、特定のシナリオごとにその後のテクノロジーの選択を決定論的な問題に変えることができます。

【温冷判定】

設計目標を決定した後、プロジェクトの実装を開始しました。解決すべき問題は 3 つあります: 1) ホット データとコールド データの決定をどのように実現するか、2) 圧縮データのストレージ構成をどのように実現するか、3) 競争力のある圧縮アルゴリズムを実現する方法。

ホット判定とコールド判定の場合、まず判定の粒度を決定する必要があります。データのホットおよびコールド判定は、行レベル、ブロック レベル、テーブル/パーティション レベルなどのさまざまな粒度に基づいて実装できます。粒度が粗いほど、実装の複雑さは低くなりますが、ビジネスへの侵入は大きくなります。設計目標からすると、業務データの分散に最も依存しないソリューションである行単位のホット・コールド判定を選択するのは当然ですが、ホット・コールド判定の導入コストをどう抑えるかが課題となります。

私たちは、GaussDB ストレージ エンジンの既存のメカニズムを使用して、この問題を巧みに解決しました。具体的には、GaussDB ストレージ エンジンは、データの各行のメタデータにその行を最後に変更したトランザクション ID (XID) を記録し、この情報を使用してトランザクションの可視性の判断をサポートし、マルチバージョン同時実行制御 (MVCC) を実装します。特定の行について、その XID が現在アクティブなすべてのトランザクションに表示されるほど「古い」場合、現時点では XID の特定の値に実際には注意を払いません。特定のフラグ ビット (FLG) を導入することでこれを記録でき、元の XID に埋められた値は物理時間で置き換えることができます。この物理時間は、その行が属する行の最終変更時刻 (LMT、最終変更時刻) の上限を表します。明らかに、LMT を使用してホット判定とコールド判定をサポートできます (詳細は図 1 を参照)。

cke_114.png

図 1: 行レベルのホットとコールドの決定

上記のソリューションの利点は、LMT の導入によって追加のオーバーヘッドが追加されず、ビジネス ロジック モデルに依存しないことです。要件が特に厳密でない場合、ほとんどの場合、企業は次のようなホットおよびコールドの判断を達成するための単純なルールを定義できます。

3 か月間何も変更を加えなかった場合

この時点で、システムはターゲット テーブルをスキャンし、現在の時刻から 3 か月を超える LMT を差し引いた時刻を満たすすべての行を圧縮します。

上記のスキームでは、実際には行の書き込みホットスポットのみが特定されており、行の読み取りホットスポットは特定されていないことに注意してください。条件を満たす行が 3 か月以内に更新されていないことがわかっているだけで、これらの行が 3 か月以内に頻繁に読み取られたかどうかは確認できません。現在、行の読み取りホットスポットを維持するための低コストの技術的ソリューションはありません。注文詳細などのパイプライン サービスの場合、データの読み取りと書き込みが同じ温度特性を示し、未変更時間が増加するにつれてアクセス頻度が低下し続けるため、このソリューションはうまく機能します。しかし、携帯電話のフォト アルバムなどのコレクション サービスの場合、非常に早い段階で確立されたコレクション関係は依然として頻繁にアクセスされる可能性があるため、識別して書き込むだけでは十分ではない可能性があります。

これは、システムがホットとコールドの決定を行ったとしても、企業が圧縮データにアクセスする可能性があるシナリオを最適化する必要があることを意味します。この問題はストレージ構成と圧縮アルゴリズムに任せます。圧縮アルゴリズムについては、その解凍パフォーマンスにより注意を払います。

もう 1 つの問題は、シナリオによっては、デフォルトのホットおよびコールド判定を使用するだけでは不十分な場合があることです。たとえば、一部の種類のトランザクションでは、生成された注文の詳細は 3 か月以内は変更されない場合がありますが、特定のトリガー条件 (保護されたトランザクションの凍結解除など) に達すると更新されます。このシナリオは実際のビジネスでは一般的ではありませんが、ビジネスがパフォーマンスを本当に重視している場合は、デフォルトのホット判定ルールとコールド判定ルールに加えて、次のようなルールをカスタマイズできるようにサポートします。

3 か月間変更がなかった後 (order_status = "finished")

現時点では、3 か月間変更がなく、注文ステータスが完了しているデータのみが圧縮されます。

現在、サポートされているカスタム ルールは、任意の正当な行式です。企業は、データのホット判定ルールとコールド判定ルールを表す任意の複雑な式を作成できますが、式内で参照されるフィールドは、ターゲット テーブルの正当なフィールドのみにすることができます。このデフォルト ルールとカスタム ルールの組み合わせにより、企業に十分に低い使用しきい値と優れた柔軟性を提供します。

【保管組織】

ホット判定条件とコールド判定条件を満たす行を圧縮する場合、圧縮データをどのように格納するかを決定する必要がありますが、設計目標に基づいて、最も業務への影響が少ないストレージ構成実装であるブロック内圧縮を選択します。

リレーショナル データベースのストレージ構成は固定長ブロックに基づいていることがわかっています。GaussDB データベースでは、一般的なデータ ブロック サイズは 8 KB です。より大きなデータ ブロックを選択すると、明らかに圧縮に有利ですが、ビジネス パフォーマンスへの影響も大きくなります。いわゆるブロック内圧縮とは、1) ホット・コールド判定条件を満たす単一ブロック内の行をすべて圧縮する、2) 圧縮後のデータを現在のデータブロックに格納する、その格納領域は通常ブロックの最後にあるBCA(Block Compressed Area)と呼ばれます。

ブロック内圧縮の設計は、他のデータ ブロックにアクセスせず、現在のブロックにのみ依存してデータを解凍することを意味します。圧縮率の観点から見ると、この設計は最もフレンドリーではありませんが、ビジネスへの影響を制御するのに非常に有益です。なお、前回の議論では、ビジネスがホット・コールドの判定条件を定義したとしても、一定の確率で圧縮データにアクセスする可能性があるため、このアクセスコストには確定的な上限を設けることが望ましいと考えています。

図 2 は、ブロック内圧縮の詳細なプロセスを示しています。まず、圧縮がトリガーされると、システムはデータ ブロック内のすべての行をスキャンし、指定されたホットまたはコールド条件に従って R1 と R3 がコールド データであることを認識します (図 2(a))。次に、システムは R1 と R3 を全体として圧縮し、圧縮データをデータ ブロックの BCA に格納します (図 2(b))、R1 が削除された (図 2(c))、最後にシステムがデータ ブロック上により多くのスペースが必要な場合は、BCA 内の R1 に属するスペースを再利用できます (図 2(d))。

cke_115.png

図 2: ブロック内圧縮

設計全体で注意すべき点は 2 つあります: 1) 実際にはユーザー データ Data のみを圧縮し、トランザクションの可視性をサポートするために通常使用される対応するメタデータ Meta は圧縮しません; 2) ホットとコールドの誤った判断による影響を排除するために、コールド データを再度ホット データに変更することをサポートします。同様に、圧縮率の観点から見ると、このような設計は最も使いやすいとは言えませんが、ビジネスへの侵入は大幅に軽減されます。簡単に言うと、圧縮データへのビジネス アクセスは、機能の制限がなく、通常のデータとまったく同じであり、トランザクション セマンティクスにも違いはありません。これは非常に重要な原則です。当社の OLTP ストレージ圧縮はビジネスに対して完全に透過的です。これは、この現在の機能とその後のすべての GaussDB 高度な圧縮シリーズの機能が従う基本原則です。

【圧縮アルゴリズム】

設計目標に基づいて、圧縮率、圧縮性能、伸長性能という3次元の指標を見たときに、実際に必要なのは、適度な圧縮率、適度な圧縮性能、そして究極の伸長性能を実現できる圧縮アルゴリズムである、これが圧縮アルゴリズム設計の基本です。

まず、圧縮に LZ4 を直接使用することをテストしました. LZ4 は、現在最も圧縮性能と伸張性能が優れているオープンソースの 3 部構成ライブラリとして知られています. 実際の測定結果から、LZ4 の圧縮率は比較的低いです。私たちはそのアルゴリズム原理を注意深く分析しました。LZ4 は LZ77 アルゴリズムに基づいた実装です。LZ77 アルゴリズムの考え方は非常にシンプルです。つまり、圧縮されるデータをバイト ストリームと見なします。アルゴリズムはバイト ストリームの現在位置から開始し、現在位置と同じ一致する文字列を前方検索し、一致した文字列の長さと現在位置からのオフセットを使用して一致した文字列を表現し、圧縮効果を実現します。アルゴリズム原理的には、LZ77アルゴリズムは長文に対しては圧縮効果が高いものの、短文や構造化データの数値型が多い場合には圧縮効果が限定的であり、実際のテストでもこの点が確認されています。

次に、圧縮アルゴリズムを 2 つの層に分割しました。最初の層では、いくつかの数値型を列ごとにエンコードし、他のフィールドの値に依存せずに特定のフィールドを解凍できるほど軽量な単純な差分エンコードを選択しました。2 番目の層では、LZ4 を呼び出してエンコードされたデータを圧縮します。最初の層では、実際には列ごとにエンコードし、行ごとに保存しますが、これは業界の一般的な実装 (列ごとにエンコードして保存する) とは大きく異なることに注意してください。列ごとに保存すると圧縮率が向上しますが、列ごとに保存すると、同じ行のデータが BCA の異なる領域に分散されることになります。この従来の設計では、後で達成したいと考えている部分的な圧縮解除をサポートできません。この問題については、結論でさらに詳しく説明します。

実際の測定により、この列エンコーディング + 一般圧縮の実装により、圧縮率が効果的に向上し、同時にビジネスへの影響の明らかな増加を抑制できることがわかりました。ただし、2 層の実装は疎結合であるため、多くの追加オーバーヘッドが発生します。したがって、慎重に検討した結果、LZ4 を放棄し、完全に LZ77 アルゴリズムに基づいた密結合圧縮アルゴリズムを再実装することにしました。

これは当時非常に危険な試みであるように見えましたが、実際、私たちの前には、一般的な圧縮アルゴリズムを単独で実装することを選択するデータベース カーネル チームは存在しませんでした。しかし、最終的なメリットから判断すると、私たちは実際に全く新しい扉を開いたのです。列エンコーディングと LZ77 アルゴリズムの間の境界が壊れた場合、一連の最適化の革新を導入します。スペースを考慮すると、技術的な詳細をすべて示すことはできません。ここでは、2 つの小さな最適化のみを紹介します。

最初の最適化は、組み込みの行境界です。システムが 2 層圧縮アルゴリズムを採用する場合、LZ77 アルゴリズムが解凍された後に各行の境界を見つける必要があるため、データの各行のエンコードされた長さを追加で保存する必要があることがわかりました。これは小さなオーバーヘッドではありません。このオーバーヘッドを除去するために、LZ77 符号化形式に行境界マークを埋め込むことを選択しましたが、このマークは 1 ビットしか占有せず、既存の方式と比較してオーバーヘッドが大幅に削減されます。もちろん、このマーカー ビットが占有されると、LZ77 前方検索の最大ウィンドウ長は半分になりますが、このシナリオでは、典型的なページ長はわずか 8KB であるため、これは問題にはなりません。

2 番目の最適化は、2 バイトの短いエンコードです。元の LZ4 実装では、圧縮パフォーマンスを向上させるために、システムは 3 バイトのエンコードを使用して一致を記述します。これは、システムが識別できる最短の一致は 4 バイトであることを意味します。ただし、構造化データでは 3 バイトの一致が非常に一般的です。次の例を参照してください。

A = 1 … B = 2

このうち、A と B は同じデータ行にある 2 つの整数フィールドであり、その値はそれぞれ 1 と 2 です。現在のバイト順序に基づいて、メモリ内のデータ行の実際の格納形式は次のようになります。

01 00 00 00 … 02 00 00 00

上図の赤枠部分に注目してください、当然3バイト一致していますが、LZ4では認識できません。

この問題は、LZ77 アルゴリズムに 2 バイトのショート コードを追加することで解決され、2 バイトのショート コードにより最小 3 バイトの一致を識別できるため、LZ4 と比較して圧縮率が向上します。もちろん、ショート コードの導入には追加のオーバーヘッドが発生します: 1) 2 つの独立した HASH テーブルを確立する必要があるため、圧縮パフォーマンスはある程度低下します。幸いなことに、このシナリオでは、極端な圧縮パフォーマンスは目標ではありません。2) 2 バイト コードは、一致する文字列と一致する文字列の間の距離を表すビット幅を減少させます。つまり、3 バイトの一致が認識されるためには、より近くなければなりません。

【効果評価】

標準の TPCC テストを使用して、OLTP ストレージ圧縮機能を有効にした場合のビジネスへの影響を評価します。TPCC モデルには合計 9 つのテーブルが含まれており、そのうち 3 つのフロー テーブルのスペースは動的に増加します。これら 3 つのテーブルのうち、注文詳細テーブル (Orderline テーブル) は他のテーブルに比べてスペースの増加が桁違いに大きいため、このテーブルで圧縮を有効にすることを選択します。TPCC のビジネス セマンティクスに基づいて、各注文の配送が完了すると、注文ステータスは完了状態になり、完了した注文は変更されませんが、一定の確率でクエリが実行されます。このセマンティクスに基づいて、完了した注文のみを圧縮するホットおよびコールドの判断原則を選択します。

圧縮なしと圧縮ありのシステムのパフォーマンス値をテストしました。結果を図 3 に示します。

cke_116.png

図 3: ビジネスへの影響評価

テスト結果は次のことを示しています。TPCC テスト シナリオでは、圧縮を有効にすると、圧縮が有効でない場合に比べてシステム パフォーマンスが約 1.5% 低下します。これは非常に良好な結果であり、システムが 100 万 tpmC を超えるピークのビジネス シナリオでも圧縮を有効にできることを意味します。これ以前に、業界の他のデータベース製品がこのレベルに到達できたかどうかはわかりません。

Orderline テーブルの圧縮率をテストしました。より豊富なデータ セットとして、TPCH モデル内の 4 つのテーブル (Lineitem、Orders、Customer、および Part テーブル) もテスト用に選択しました。比較のため、データセットごとにLZ4、ZLIBと当社の圧縮アルゴリズムの圧縮率性能を同時にテストしましたが、ZLIBは圧縮・伸長性能と圧縮率のバランスを重視したアルゴリズムであり、圧縮・伸長性能はLZ4に比べて5~10倍低くなります。最終結果を図 4 に示します。

cke_117.png

図 4: 圧縮率の評価

テスト結果は期待どおりで、数値フィールドが多い場合、圧縮アルゴリズムの圧縮率は一般的な圧縮アルゴリズムよりも高くなりますが、テキスト フィールドが多い場合、圧縮アルゴリズムの圧縮率は、LZ と LZ + ハフマンの組み合わせの圧縮アルゴリズムの間になります。

【運用・保守のヒント】

この圧縮スキームは実際にはオフラインであることに注意してください。つまり、データは最初に生成されたときにホット データである必要があり、圧縮はトリガーされず、これらのデータへのビジネス アクセスのパフォーマンスには何の影響もありません。時間の経過とともに、これらのデータの温度は徐々に低下し、最終的には独立した圧縮タスクによってコールド データとして認識され、圧縮されます。

これらの圧縮タスクを業務時間のローピーク時に実行することを選択し、そのリソース消費を制御することは、運用および保守側が注意を払う必要がある問題です。この領域では、運用および保守のウィンドウ、圧縮タスクの並列処理、各圧縮タスクの圧縮データ量の指定など、豊富な運用および保守方法が提供されます。ほとんどの企業では、実際には単位時間あたりに新たに追加されるデータの量が比較的限られているため、3 か月前に追加されたコールド データの圧縮を毎月 1 日の午前 2 時から午前 4 時までに完了するなど、特定の時間を選択して圧縮タスクを集中的に完了することもできます。

企業は、圧縮を有効にすることを決定する前に、圧縮を有効にした後の利点を理解し、その利点の大きさに基づいて決定を下したい場合があります。この目的を達成するために、対象テーブルのデータをサンプリングし、実際の圧縮プロセスと同じアルゴリズムを使用してサンプリングされたデータを圧縮し、圧縮率を計算できる圧縮率評価ツールを提供します。ただし、実際に BCA を生成したり、データを変更したりすることはありません。

ビジネスが圧縮データを別のテーブルに移行すると、すべてのデータが圧縮データから非圧縮データに変更され、その結果、スペースが拡張される可能性があります。これは当社のソリューションでは導入されていませんが、すべての圧縮ソリューションが解決する必要がある問題です。ホットおよびコールドの判断ルールが非常に確実である場合、企業は圧縮タスクを手動で実行して圧縮を即座に有効にすることができますが、長時間かかる大容量の圧縮テーブルの移行の場合は、自動圧縮タスクを定期的に開始して完了することも選択できます。

最後に、圧縮を有効または無効にするための最も詳細な制御を提供します。共通テーブル内の単一パーティションか共通パーティション テーブル、あるいはセカンダリ パーティション内の単一パーティションやサブパーティションかどうかに関係なく、企業は圧縮を個別に有効または無効にできます。これにより、ビジネス自体がホット データとコールド データを区別しているシナリオでも (たとえば、時間分割に基づいて)、圧縮機能と適切に連携することが可能になります。

【結論】

OLTPテーブル圧縮機能では、最新の圧縮アルゴリズム、きめ細かな自動ホット/コールド判定、ブロック内圧縮サポートなど一連の技術革新を導入し、適度な圧縮率を実現しながらビジネスへの影響を大幅に軽減することができ、主要オンラインサービスの容量制御をサポートする重要な役割を果たすことが期待されています。

次に、圧縮、部分解凍機能、OLTP インデックス圧縮の導入によるビジネスへの影響を軽減するための革新と反復を継続し、関連する問題を解決し、ビジネスにより大きな価値を生み出すための画期的な技術的ブレークスルーを実現したいと考えています。

 

クリックしてフォローして、Huawei Cloudの最新テクノロジーについて初めて学びましょう~

 

RustDesk 1.2: Flutterを使用してデスクトップ版を書き換え、 deepinで告発されたWaylandをサポート V23は2023年に最も需要の多いWSL 8プログラミング言語への適応に成功: PHPは好調、C/C++需要は鈍化 ReactはAngular.jsの瞬間を経験している? CentOS プロジェクトは「誰にでもオープン」であると主張 MySQL 8.1 および MySQL 8.0.34 が正式にリリース Rust 1.71.0 安定版 リリース
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4526289/blog/10089459