ビッグデータの一般的なインタビューの質問のカフカ

記事ディレクトリ

1. kafkaと従来のメッセージキューの違い

  • まず第一に、Kafkaは受信したメッセージを分割し、各トピックのメッセージは異なるパーティションを持つため、一方ではメッセージのストレージは単一のサーバーのストレージスペースによって制限されず、もう一方ではメッセージの処理によって制限されます。複数のサーバーで並行して実行することもできます
  • 次に、高可用性を確保するために、各パーティションには一定数のレプリカがあります。そのため、一部のサーバーが利用できない場合は、レプリカが配置されているサーバーが引き継ぎ、アプリケーションの継続性を確保します
  • さらに、Kafkaはパーティション内のメッセージの順序正しい消費を保証します
  • Kafkaにはコンシューマグループの概念もあります。各パーティションは、同じグループの1つのコンシューマだけが使用できますが、複数のグループが使用できます。

RabbitMQと比較:

  • 1.アーキテクチャモデル
  • RabbitMQはAMQPプロトコルに従い、RabbitMQのbrokerExchange、Binding、およびキューが構成されます。ここで、交換とバインディングがメッセージのルーティングキーを形成します。クライアントプロデューサーは接続チャネルを介してサーバーと通信し、コンシューマーは消費のためにキューからメッセージを取得します(長い接続、キューにはメッセージがあります)これはコンシューマーにプッシュされ、コンシューマーはループで入力ストリームからデータを読み取ります)rabbitMQはブローカー中心です:メッセージの確認メカニズム
  • Kafkaは一般的なMQ構造に従います。プロデューサー、ブローカー、およびコンシューマーは、メッセージ確認メカニズムなしで、消費ポイントに従ってブローカーからバッチでデータをプルします
  • 2.スループット
  • Kafkaはスループットが高く、内部でメッセージバッチ処理、ゼロコピーメカニズムを使用し、データの保存と取得はローカルディスクの順次バッチ操作であり、O(1)の複雑さと高いメッセージ処理効率を備えています。
  • RabbitMQは、スループットの点でkafkaよりも少し劣っています。開始点は異なります。RabbitMQは、メッセージの信頼できる配信をサポートし、トランザクションをサポートし、バッチ操作をサポートしていません。ストレージの信頼性要件に基づくストレージは、メモリまたはハードディスクを使用できます
  • 3.可用性
  • rabbitMQはミラーのキューをサポートし、メインキューは失敗し、ミラーキューが引き継ぎます
  • Kafkaのブローカーはアクティブモードとスタンバイモードをサポートします
  • 4.クラスターの負荷分散
  • Kafkaは、zookeeperを使用してクラスター内のブローカーとコンシューマーを管理し、トピックをzookeeperに登録できます。zookeeperの調整メカニズムにより、プロデューサーは対応するトピックのブローカー情報を保存します。ブローカーにランダムまたはポーリングで送信でき、セマンティクスに基づいて指定できます。シャーディング、メッセージはブローカーの特定のシャードに送信されます

2. Kafkaアプリケーションのシナリオ

  • Kafkaは、コンシューマースケールのWebサイトのすべてのアクションフローデータを処理できる高スループットの分散パブリッシュ/サブスクライブメッセージングシステムです。簡単に言えば、Kafkaはメールボックスのようなものであり、プロデューサーはメールを送信する人、コンシューマーは人はメールを受信する人であり、カフカは物事を保存するために使用されますが、メールを処理するためのいくつかのメカニズムを提供します。
  • ログ収集:企業はKafkaを使用してさまざまなサービスのログを収集し、Kafkaを介した統合インターフェースサービスでさまざまな消費者にそれを開くことができます。
  • メッセージシステム:プロデューサーとコンシューマーの分離、メッセージのキャッシュなど
  • ユーザーアクティビティの追跡:Kafkaは、Webの閲覧、検索、クリック、その他のアクティビティなど、Webユーザーまたはアプリユーザーのさまざまなアクティビティを記録するためによく使用されます。これらのアクティビティ情報は、さまざまなサーバーによってKafkaトピックに公開され、消費者はこれらのトピックにサブスクライブしますリアルタイムの監視と分析のために、データベースに保存することもできます
  • 運用インジケーター:Kafkaは、さまざまな分散アプリケーションからのデータの収集、アラームやレポートなどのさまざまな操作の集中型フィードバックの生成など、運用監視データの記録にもよく使用されます
  • ストリーミング:スパークストリーミングやストームなど

Three。同時実行性が高い場合に、Kafkaがメッセージの損失とメッセージの重複をどのように回避できるか

1.メッセージ損失ソリューション

  • 1)カフカの速度制限
  • 2)再試行メカニズムを有効にし、再試行間隔を長く設定します
  • 3)Kafakはacks = allを設定します。つまり、ISR内の対応するすべてのパーティションがメッセージの受信を確認した後、送信は成功したと見なされます

2.メッセージ複製ソリューション

  • 1)メッセージは一意のIDで識別できます
  • 2)プロデューサー(ack = allは、少なくとも1つの正常な送信を表します)
  • 3)コンシューマー(オフセットは手動で送信され、オフセットはビジネスロジックが正常に処理された後に送信されます)
  • 4)テーブルの削除(データの重複を避けるための主キーまたは一意のインデックス)
  • ビジネスロジックの処理(Redisまたはmongdbに格納する一意の主キーを選択し、最初に存在するかどうかを照会し、存在する場合は処理しません。存在しない場合は、RedisまたはMongdbを最初に挿入して、ビジネスロジック処理を実行します)

4. Kafkaがスパークストリーミングをどのようにしてデータの整合性を確保し、データが繰り返し消費されないようにするのですか?

データが失われないようにする(少なくとも)

  • spark rddの内部メカニズムは、少なくともセマンティクスのデータを保証できます
  • レシーバーモード。WAL(ログ先行書き込み)をオンにし、Kafkaから受信したデータをログファイルに書き込みます。これにより、すべてのデータを障害から回復できます。

直接法

  • a。チェックポイントメカニズムに依存して、
  • b。データが繰り返されないようにする、つまり、Exactly onceセマンティクス
  • べき等演算:繰り返し実行しても問題は発生しません。データが繰り返されないようにするために追加の作業は必要ありません
  • ビジネスコードはトランザクション操作を追加します。つまり、各パーティションのデータに対して、uniqueldが生成されます。このパーティションのすべてのデータが完全に消費された場合にのみ、それは成功と見なされます。それ以外の場合は無効と見なされ、ロールバックする必要があります。次にこのuniqueldが繰り返される場合、正常に実行されました。スキップしてください

5. Kafkaコンシューマーの高レベルAPIと低レベルAPIの違い

  • Kafkaは、高レベルのコンシューマーAPIとSimpleConsumer APIの2つのコンシューマーAPIを提供します。高レベルのコンシューマーAPIは、Kafkaからの消費データの高レベルの抽象化を提供し、SimpleConsumer APIは、開発者が詳細に注意を払う必要があります。

1.高レベルのコンシューマーAPI

  • 高レベルのコンシューマーAPIは、コンシューマーグループのセマンティクスを提供します。メッセージはグループ内の1つのコンシューマーだけが消費でき、コンシューマーはメッセージを消費するときにオフセットに注意を払いません。最後のオフセットは動物園管理者によって保存されます
  • 高レベルのコンシューマAPIを使用すると、マルチスレッドアプリケーションになる可能性があります。次の点に注意してください。
  • 1)コンシューマースレッドの数がパーティションの数より大きい場合、一部のスレッドはメッセージを受信できません。
  • 2)パーティションの数がスレッドの数より大きい場合、一部のスレッドは複数のパーティションからメッセージを受信します
  • 3)スレッドが複数のパーティションを消費する場合、受信するメッセージの順序は保証されず、パーティション内のメッセージが順序付けされます

2. SimpleConsumer API

  • パーティションをさらに制御したい場合は、次のようなSimpleConsumer APIを使用する必要があります。
  • 1)メッセージを複数回読む
  • 2)パーティション内のメッセージの一部のみを消費します
  • 3)トランザクションを使用して、メッセージが一度だけ消費されるようにします
  • ただし、このAPIを使用すると、パーティションオフセットブローカーリーダーなどは透過的ではなくなり、自分で管理する必要があり、多くの追加作業を行う必要があります。
  • アプリケーションでオフセットを追跡して、次に消費するメッセージを決定する必要があります
  • アプリケーションは、プログラムを通じて各パーティションのリーダーが誰であるかを知る必要があります
  • リーダーの変更に対処する必要がある

6. Kafkaはどのようにしてデータが1回だけ消費されることを保証しますか?

  • べき等プロデューサー:単一パーティションのメッセージは1度だけ送信され、重複メッセージはないことが保証されています
  • トランザクション:複数のパーティションへのアトミックな書き込みを保証するには、つまり、複数のパーティションに書き込まれたメッセージがすべて成功するか、すべてロールバックされるか

7. Kafkaはデータの一貫性と信頼性を保証します

1.データの一貫性の保証

  • 整合性の定義:メッセージがクライアントに表示される場合、リーダーが死亡しても、新しいリーダーでデータを読み取ることができます
  • HW-HighWaterMark:最大オフセットが外部から見える場合でも、クライアントがリーダーから読み取ることができる最大メッセージオフセット、HW = max(replica.offset)
  • リーダーが受信した新しいメッセージの場合、クライアントはメッセージをすぐには消費できません。リーダーは、ISR内のすべてのレプリカによってメッセージが同期されるのを待って、HWを更新します。この時点でメッセージを消費できます。これにより、リーダーが失敗した場合でもメッセージを消費できます。新しく選出されたリーダーから入手する
  • 内部ブローカーからの読み取りリクエストの場合、HW制限はありません。同時に、followerは独自のHWのコピーを維持します、Follower.HW = min(Leader.HW、Follower.offset)

2.データの信頼性保証

  • プロデューサーがリーダーにデータを送信するとき、データの信頼性のレベルは、acksパラメーターを介して設定できます
  • 0:書き込みが成功したかどうかに関係なく、サーバーはプロデューサーに応答を送信する必要はありません。例外が発生した場合、サーバーは接続を終了し、プロデューサーをトリガーしてメタデータを更新します
  • 1:リーダーが正常に書き込みを行った後、応答が送信されます。この場合、リーダーが失敗すると、データが失われます
  • -1:プロデューサーに応答を送信する前に、すべてのISRがメッセージを受信するのを待ちます。これは最も強力な保証です

8. Sparkリアルタイムジョブがダウンしました。kafkaによって指定されたトピックデータが蓄積された場合はどうすればよいですか?

  • 1.spark.streaming.concurrentJobs = 10:同時実行ジョブの数を増やします。このパラメータは実際にはスレッドプールのコアスレッドの数を指定していることがソースコードからわかります。指定しない場合、デフォルトは1です。
  • 2.spark.streaming.kafka.maxRatePerPartition = 2000:1秒あたりのパーティションごとに取得されるログの最大数を設定し、処理されるデータの量を制御して、均一なデータ処理を保証します
  • 3.spark.streaming.kafka.maxRetries = 50:トピックパーティションリーダーとそのオフセットを取得する場合、再試行回数を増やします
  • 4.アプリケーションレベルで再試行を設定します。spark.yarn.maxAttemps= 5 不能超过hadoop集群中yarn.resourcemanager.am.max-attempts
  • 5.失敗した試行の有効期間の設定。spark.yarn.am.attempFailuresValidtyInterval= 1h

Nine.kafkaの読み取りおよび書き込みプロセス

1.書き込みプロセス

  • 1)zkクラスターに接続し、対応するトピックのパーティション情報とパーティションのリーダーをzkから取得します
  • 2)対応するリーダーに対応するブローカーに接続します
  • 3)パーティションのリーダーにメッセージを送信します
  • 4)他のフォロワーはリーダーからデータをコピーします
  • 5)順番に確認に戻る
  • 6)ISRのすべてのデータが書き込まれ、書き込みプロセス全体が終了するまで、送信は完了しません。
  • 書き込みプロセスを説明しているため、レプリカとzk間のハートビート通信は表現されていません。ハートビート通信は、kafkaの高可用性を確保するためのものです。リーダーがハングアップするか、フォロワーの同期タイムアウトまたは同期が遅すぎると、ハートビート情報を通じてzkに報告され、zk選挙リーダーまたはフォロワーをISRからOSRに移動

2.読み取りプロセス

  • 1)zkクラスターに接続し、対応するトピックのパーティション情報とパーティションのリーダーをzkから取得します
  • 2)対応するリーダーに対応するブローカーに接続します
  • 3)消費者は保存したオフセットをリーダーに送信します
  • 4)リーダーは、オフセットやその他の情報に従ってセグメント(インデックスファイルとログファイル)を見つけます。
  • 5)インデックスファイルの内容に従って、ログファイルでオフセットに対応する開始位置を特定し、対応する長さのデータを読み取って、コンシューマーに返します。

10. Kafkaがリーダーに読み取りと書き込みのみを許可する理由

  • Kafkaのリーダーは読み取りと書き込みのみを担当し、フォロワーはバックアップのみを担当します。リーダーがダウンした場合、KafkaはISRと呼ばれる同期レプリカのセット(同期レプリカのセット)を動的に維持し、ISRにはf + 1ノードがあります。 、fノードがダウンしているときにメッセージを失わずにサービスを正常に提供することが許可されます。ISRのメンバーは動的です。ノードが削除された場合、ノードが再び「同期」状態になると、 ISRに再参加します。リーダーがダウンした場合は、ISRからフォロワーを選択してください。
  • Kafkaがレプリケーションを導入すると、同じパーティションに複数のレプリカが存在する可能性があります。現時点では、これらのレプリケーション間でリーダーを選択する必要があります。プロデューサーとコンシューマーはこのリーダーとのみ対話し、他のレプリカはリーダーからフォロワーとしてデータをコピーします。同じパーティションの複数のレプリカ間のデータの整合性を確保する必要があります(そのうちの1つがダウンした後、他のレプリカは引き続き機能し、データの複製もデータの損失も引き起こさないようにする必要があります)リーダーがない場合、すべてのレプリカは同時にデータを読み書きするには、複数のレプリカが互いにデータを同期することを確認する必要があります(n * nパス)。データの一貫性と順序を確認することは非常に難しく、レプリケーションの実装の複雑さが大幅に増加します。リーダーの導入後は、リーダーのみがデータの読み取りと書き込みを担当し、フォロワーはデータ(nチャネル)のみを順次リーダーにフェッチするため、システムはよりシンプルで効率的になります。

11.ディスクがいっぱいになるのを防ぐために、Kafkaは定期的に古いメッセージを削除します。

  • Kafkaには2つの保持戦略があります
  • 1つはメッセージの保存期間に基づいており、メッセージが指定した期間よりも長くKafkaに保存されると、削除できます
  • もう1つは、トピックに格納されているデータのサイズに基づいています。トピックが占めるログファイルのサイズがしきい値を超えると、最も古いメッセージの削除を開始できます
  • Kafkaはバックグラウンドスレッドを開始して、削除できるメッセージがあるかどうかを定期的にチェックします
  • 保持ポリシーの構成は非常に柔軟で、グローバル構成にすることも、グローバル構成をオーバーライドするトピック用に構成することもできます

12. Kafkaデータの高可用性の原則

1.データ保存形式

  • Kafkaの高い信頼性保証は、その堅牢なレプリケーション戦略に由来します。トピックは複数のパーティションに分割でき、パーティションは物理的に複数のセグメントで構成されます
  • セグメントは、インデックスファイルとデータファイルの2つの部分に分かれています。インデックスファイルは、メタデータを保存し、メッセージのオフセットをデータファイルに記録します。メッセージは、正しい読み取り長を確保するために固定された物理構造を持っています
  • セグメントファイルの利点:期限切れのファイルのクリーニングを容易にします。期限切れのセグメント全体を削除するだけで済みます。追加の方法でメッセージを書き込み、ディスクに順次書き込むことで、効率が大幅に向上します
  • 特定のオフセットメッセージを読み取る手順は次のようになります。バイナリ検索でオフセットが配置されているセグメントを見つけます。セグメントのインデックスファイルから、オフセットが置かれているデータファイルの物理オフセットを見つけ、データを読み取ります。

2.レプリカの複製と同期

  • 外部から見ると、パーティションは、メッセージを拡大して格納する配列に似ています。各パーティションには、データの書き込みを記録するためのMySQL binlogに似たファイルがあります。HW(HighWatermark)という2つの新しい用語があり、現在のコンシューマーはパーティションを表示できます。オフセット位置LEO(LogEndOffset)は、現在のパーティションの最新のメッセージのオフセットを表し、各コピーは個別に維持されます。メッセージの信頼性を向上させるために、パーティションにはn個のコピーがあります
  • n個のレプリカのうち、1個のリーダーと残りのn-1個のフォロワーがあります。Kafka書き込み操作はリーダーレプリカでのみ実行されます。通常、この種類のレプリカを書き込む方法は2つあります
  • 1)リーダーがログファイルを正常に書き込むと、成功を返します。このように、データが同期される前にフォロワーがダウンすると、データが失われます。この方法により、効率が向上します
  • 2)リーダーは、フォロワーがログを正常に書き込み、返されたACKを受信して​​から成功を返すまで待機します。このようにして、リーダーはダウンします。再選されたリーダーはダウンリーダーのデータと一致し、データは失われません。ただし、フォロワーが戻るのを待機する必要があるため、効率は遅くなります一般に、少数派が過半数に従う選挙方式が採用されます。f個のレプリカのダウンタイムに対処する場合は、少なくとも2f + 1個のレプリカが必要で、それらのf + 1個を正常に書き込みます。Kafkaは上記のメカニズムを使用しません。ISR(In -Sync Replication)メカニズム

13. Kafkaのオフセットはどこに保存されますか?なぜですか?

  • kafka-0.9のバージョン以降、コンシューマグループとkafkaのオフセット情報はzookeeperに格納されず、ブローカーサーバーに格納されるため、コンシューマのコンシューマグループ名(group.id)を指定すると次に、コンシューマーが開始されると、コンシューマーグループ名と、消費するトピックのオフセット情報がブローカーサーバーに記録されます。

1。概要

  • Kafkaバージョン[0.10.1.1]は、デフォルトで消費オフセットをKafkaの__consumer_offsetsというトピックに移動しました。実際には、バージョン0.8.2.2から、トピックに消費オフセットを格納することがサポートされていますが、その時点ではデフォルトでは、消費されたオフセットをzookeeperクラスターに格納します。公式のデフォルトでは、消費されたオフセットをkafkaのトピックに格納します。同時に、offsets.storageプロパティで設定されるzookeeperに格納されたインターフェースも保持します。

2.コンテンツ

  • 実際、このような公式の推奨も妥当です。以前のバージョンでは、Kafkaには実際には比較的大きな隠れた危険がありました。これは、zookeeperを使用して各コンシューマー/グループの消費の進捗状況を保存および記録することです。ただし、使用の過程で、JVMはいくつかの最適化が行われましたが、消費者は頻繁に動物園係と対話する必要があり、zkClientのAPIを使用して動物園係を頻繁に操作すること自体は比較的非効率的なアクションであり、後の水平拡張の頭痛の種でもあります。クラスターが変更され、kafkaクラスターのスループットも影響を受けます。その後、当局は実際にカフカへの移行の概念を非常に早く提案しましたが、以前はデフォルトでzookeeperクラスターに格納されており、手動で設定する必要があります。 Kafkaの使用に慣れていない場合は、デフォルトのストレージを受け入れました。Kafkaの新しいバージョン以降では、Kafkaによって消費されるオフセットは、デフォルトでKafkaクラスターの__consumer_offsetsというトピックに格納されます。

14. Kafkaメッセージの順序を確認する方法

  • Kafkaは、特定のコンシューマーによってコンシュームされたときに、パーティション内のメッセージが正しい順序であることが保証されるだけです。実際、トピックの観点から、複数のパーティションがある場合でも、メッセージはグローバルに順序付けされていません。

15.カフカパーティションの数

  • パーティションの数は良くありません。一般に、パーティションの数はクラスターマシンの数を超えないようにしてください。パーティションが多いほど、メモリの占有量(ISRなど)が多くなります。ノードが集中している場合、ノードが集中していると、システムが停止しているときに、システムのパーティションが多くなります。より大きな影響
  • パーティションの数は通常、3〜10に設定されています。

16. Kafkaパーティション割り当て戦略

  • Kafkaには、2つのデフォルトのパーティション割り当て戦略があります。RangeとRoundRobinです。

1.範囲

  • デフォルトの戦略、範囲は各トピック(つまり、各トピックの分割)です。最初に、同じトピックのパーティションがシリアル番号でソートされ、コンシューマーがアルファベット順にソートされます。次に、パーティションのパーティションを使用します数値をコンシューマースレッドの総数で割り、各コンシューマースレッドが消費するパーティションの数を決定します。分割されていない場合、以前のコンシューマースレッドはもう1つのパーティションを消費します
  • 例:10個のパーティション、2つのコンシューマー(C1、C2)、3つのコンシューマースレッド、10/3 = 3、無尽蔵
  • C1-0は0、1、2、3パーティションを消費します
  • C2-0は4、5、および6パーティションを消費します
  • C2-1は7、8、および9パーティションを消費します

2.ラウンドロビン

  • 前提条件:同じコンシューマグループ内のすべてのコンシューマのnum.streams(コンシューマ消費スレッドの数)は等しく、各コンシューマがサブスクライブするトピックは同じである必要があります
  • すべてのトピックパーティションをTopicAndPartitionリストに作成し、TopicAndPartitionリストをhashCodeに従ってソートし、最後にポーリング方式で各コンシューマスレッドに送信します。

Seventeen.kafkaデータ量の計算

  • 1日あたりの総データ量は100g、1億のログが毎日生成され、1億/ 24/60/60 = 1150レコード/秒
  • 1秒あたりの平均:1150
  • 毎秒低い谷:400バー
  • ピーク/秒:1150 *(2-20回)= 2300-23000
  • 各ログサイズ:0.5-2k
  • 1秒あたりのデータ量:2.3M〜20M

18. Kafkaメッセージデータバックログ、Kafkaの不十分な消費容量への対処方法

  • Kafkaの消費容量が不十分な場合は、トピックパーティションの数を増やし、同時にコンシューマーグループのコンシューマーの数を増やすことを検討できます。コンシューマーの数=パーティションの数
  • ダウンストリームのデータ処理がタイムリーでない場合は、各バッチのプル数を増やし、バッチの数が少なすぎる(プルデータ/処理時間<本番速度)ので、処理されたデータを本番データよりも小さくします。これもデータの原因になりますやり残し

19.カフカハイスループットの実現

1.順次読み取りおよび書き込み

  • Kafkaメッセージはファイルに継続的に追加されるため、Kafkaはディスクのシーケンシャルな読み取りおよび書き込みパフォーマンスを最大限に活用できます。シーケンシャルな読み取りと書き込みは、ハードディスクヘッドのシーク時間を必要とせず、わずかなセクター回転時間しか必要としないため、速度はランダムな読み取りと書き込みよりもはるかに高速です。

2.ゼロコピー

  • Linuxカーネル2.2以降、「ゼロコピー」と呼ばれるシステムコールメカニズムが登場しました。これは、「ユーザーバッファー」のコピーをスキップし、ディスク領域とメモリ間の直接マッピングを確立します。データはコピーされなくなります「ユーザーモードバッファ」。ゼロコピーはコピーを必要としませんが、不要なコピーの数を減らします。通常は、IO読み取りおよび書き込みプロセスです。「ゼロコピーテクノロジー」は、ディスクファイルのデータをページキャッシュに一度コピーして、ページキャッシュからネットワークに直接送信するだけで済みます。

3.パーティション

  • Kafkaのキュートピックは複数のパーティションに分割され、各パーティションは複数のセグメントに分割されるため、キュー内のメッセージは実際にはセグメント化された方法でN個の複数のフラグメントファイルに格納されます。これらはすべて小さなファイルでの操作であり、移植性が高く、並列処理機能も向上します。

4.バッチ送信

  • Kafkaでは、メッセージをバッチで送信できます。メッセージはまずメモリにキャッシュされ、次に1つの要求でバッ​​チで送信されます。たとえば、キャッシュされたメッセージが特定の量に達したときに送信するか、100メッセージなどの一定期間後に送信するかを指定できます。メッセージが送信されるか、5秒ごとに送信されます。この戦略により、サーバー上のI / Oの数が大幅に削減されます。

5.データ圧縮

  • Kafkaは、メッセージコレクションの圧縮もサポートしています。Producerは、メッセージコレクションをGZIPまたはSnappy形式で圧縮できます。利点は、送信されるデータの量を減らし、ネットワーク送信の負荷を減らすことです。

6.コンシューマーロードバランシング

  • コンシューマーがグループに参加またはグループから離脱すると、パーティションのバランスがトリガーされます。バランスの最終的な目標は、トピックの同時消費容量を改善することです。

おすすめ

転載: blog.csdn.net/sun_0128/article/details/108069129