Web システムの毎日のアクセス数が 10 万から 1,000 万、さらには 1 億を超えると、その過程で Web システムへの負荷が増大します。これらのパフォーマンスのプレッシャーによって引き起こされる問題を解決するには、Web システム アーキテクチャ レベルで複数のレベルのキャッシュ メカニズムを構築する必要があります。さまざまなプレッシャーの段階で、さまざまな問題に遭遇し、さまざまなサービスやアーキテクチャを構築することで問題を解決します。
Web 負荷分散 (ロード バランシング) は、簡単に言えば、「作業タスク」をサーバー クラスターに割り当てることであり、適切な割り当て方法を使用することは、バックエンド Web サーバーを保護するために非常に重要です。
負荷分散戦略は多数ありますが、簡単な戦略から始めましょう。
1.HTTPリダイレクト
ユーザーがリクエストを送信すると、Web サーバーは HTTP 応答ヘッダーの Location タグを変更して新しい URL を返し、ブラウザは引き続き新しい URL をリクエストします。これは実際にはページのリダイレクトです。リダイレクトを通じて、「負荷分散」の目標が達成されます。たとえば、PHP ソース コード パッケージをダウンロードしてダウンロード リンクをクリックすると、さまざまな国や地域でのダウンロード速度の問題を解決するために、最も近いダウンロード アドレスが返されます。以下に示すように、リダイレクトされた HTTP リターン コードは 302 です:
PHPコードを使用してこの機能を実装する場合、方法は次のとおりです:
このリダイレクトは実装が非常に簡単で、さまざまな戦略でカスタマイズできます。ただし、大規模なトラフィックの下ではパフォーマンスが低下します。さらに、実際のリクエストがリダイレクトされるため、ユーザー エクスペリエンスが低下し、ネットワーク遅延が増加します。
2. リバースプロキシ負荷分散
リバース プロキシ サービスの中核的な仕事は主に HTTP リクエストを転送することであり、ブラウザとバックエンド Web サーバー間の中継の役割を果たします。 7層のネットワーク構造の7層目であるHTTP層(アプリケーション層)で動作するため、「7層負荷分散」とも呼ばれます。リバース プロキシとして使用できるソフトウェアは数多くありますが、最も一般的なものの 1 つは Nginx です。
Nginx は、転送戦略を自由にカスタマイズしたり、サーバー トラフィックの重みを割り当てたりできる、非常に柔軟なリバース プロキシ ソフトウェアです。リバース プロキシでは、一般的な負荷分散戦略ではリクエストがランダムに割り当てられるため、一般的な問題は Web サーバーによって保存されるセッション データです。同じログインユーザーからのリクエストが同じ Web マシンに割り当てられる保証はなく、セッションが見つからないという問題が発生します。
主な解決策は 2 つあります:
リバース プロキシ サービスでもキャッシュを有効にすることができます。有効にすると、リバース プロキシの負荷が増加するため、注意して使用する必要があります。この負荷分散戦略は実装と展開が非常に簡単で、パフォーマンスも比較的良好です。ただし、ハングすると大きな問題が発生する「単一点障害」という問題があります。また、後段では Web サーバの数が増加し続けるため、Web サーバ自体がシステムのボトルネックになる可能性があります。
3. IP ロードバランシング
IP 負荷分散サービスは、ネットワーク層 (IP の変更) とトランスポート層 (ポートの変更、レイヤー 4) で動作し、そのパフォーマンスはアプリケーション層 (レイヤー 7) で動作するよりもはるかに高くなります。原理は、IP 層パケットの IP アドレスとポート情報を変更して負荷分散を実現することです。この方法は「4 層負荷分散」とも呼ばれます。一般的な負荷分散方法は LVS (Linux 仮想サーバー、Linux 仮想サービス) で、これは IPVS (IP 仮想サーバー、IP 仮想サービス) を通じて実装されます。
負荷分散サーバーがクライアントの IP パケットを受信すると、IP パケットのターゲット IP アドレスまたはポートを変更して、そのまま内部ネットワークに配信し、データ パケットは実際の Web サーバーに流れ込みます。実際のサーバー処理が完了すると、データ パケットは負荷分散サーバーに返送され、その後、ターゲット IP アドレスがユーザー IP アドレスに変更されて、最終的にクライアントに返されます。
上記の方法は LVS-NAT と呼ばれます。さらに、LVS-RD (ダイレクト ルーティング) と LVS-TUN (IP トンネル) がありますが、これら 3 つは長さの問題による違いがあります。詳細に進みます。
IP ロード バランシングのパフォーマンスは、Nginx のリバース プロキシのパフォーマンスよりもはるかに高く、データ パケットをさらにグループ化せずにトランスポート層まで処理するだけで、実際のサーバーに直接転送します。ただし、その構成と構造は比較的複雑です。
4. DNS 負荷分散
DNS (ドメイン ネーム システム) は、ドメイン名解決サービスを担当します。ドメイン名の URL は、実際には IP アドレスです。解決プロセスでは、DNS がドメイン名から IP へのマッピングを完了します。ドメイン名は複数の IP に対応するように構成できます。したがって、DNS は負荷分散サービスとしても使用できます。
この負荷分散戦略は構成が簡単で、優れたパフォーマンスを発揮します。しかし、ルールを自由に定義することができず、マッピングしたIPの変更やマシンの故障時に手間がかかり、DNSの反映が遅れるという問題もありました。
5. DNS/GSLB ロードバランシング
私たちが一般的に使用している CDN (Content Delivery Network、コンテンツ配信ネットワーク) の実装方法は、実際には同じドメイン名を複数の IP にマッピングすることに基づいてさらに一歩進んで、GSLB (Global Server Load) を通じて指定されたルールに従ってドメイン名をマッピングします。バランス、グローバル負荷分散) IP。通常の状況では、地理的位置に応じてユーザーに最も近い IP がユーザーに返されるため、ネットワーク伝送におけるルーティング ノード間のホッピングのコストが削減されます。
図の「検索」の実際のプロセスは、LDNS (ローカル DNS) が最初にルート ネーム サーバーから最上位のルート ネーム サーバー (.com など) を取得し、次に指定されたドメインの認可された DNS を取得します。名前を入力してから、実際のサーバー IP を取得します。
CDN は通常、大規模な静的リソース (html/Js/Css/images など) の読み込みの問題を解決するために Web システムで使用され、ネットワーク ダウンロードに依存するこれらのコンテンツを可能な限りユーザーの近くに作成します。 。
たとえば、imgcache.gtimg.cn (Tencent が独自に構築した CDN、qq.com ドメイン名を使用しない理由は、HTTP リクエストに余分な Cookie 情報が含まれないようにするため) 上の画像にアクセスしたとき、 IP は 183.60.217.90 です。
この方法は、以前の DNS 負荷分散と同様に、パフォーマンスが優れているだけでなく、複数の戦略の構成もサポートしています。ただし、セットアップとメンテナンスのコストは非常に高くなります。第一線のインターネット企業は独自の CDN サービスを構築しますが、中小企業は通常、サードパーティが提供する CDN を使用します。
Web システムの外部ネットワーク環境についての説明は終わりました。ここで、Web システム自体のパフォーマンスの問題に注目し始めます。 Web サイトへのアクセス数が増加するにつれて、これらの問題を解決することはマシンを拡張するだけでなく、適切なキャッシュ メカニズムを確立して使用することが重要になります。
当初、私たちの Web システム アーキテクチャは次のようになります。各リンクには 1 台のマシンしかありません。
最も基本的なデータストレージから始めましょう。
1. MySQL データベースの内部キャッシュの使用
MySQL のキャッシュ メカニズムは MySQL 内から始まります。次の内容は、最も一般的な InnoDB ストレージ エンジンに焦点を当てます。
1. 適切なインデックスを作成します
最も簡単なのはインデックスを作成することです。テーブルのデータが比較的大きい場合、インデックスはデータを迅速に取得する役割を果たしますが、コストもかかります。まず、結合されたインデックスはある程度のディスク領域を占有しますが、生成されるインデックスはソース データよりも大きくなる可能性があるため、注意して使用する必要があります。次に、インデックス作成後のデータの挿入/更新/削除などの操作では、元のインデックスを更新する必要があるため、時間がかかります。もちろん、実際には、システム全体が選択クエリ操作によって支配されているため、インデックスを使用するとシステムのパフォーマンスが大幅に向上します。
2. データベース接続スレッドプールキャッシュ
すべてのデータベース操作リクエストで接続の作成と破棄が必要な場合、間違いなくデータベースにとって大きなオーバーヘッドになります。このタイプのオーバーヘッドを軽減するために、MySQL で thread_cache_size を構成して、再利用のために予約されているスレッドの数を示すことができます。スレッドが足りない場合は再度作成され、アイドル状態のスレッドが多すぎる場合は破棄されます。
実際には、pconnect (データベースロング接続) を使用する、より根本的なアプローチがあり、一度スレッドが作成されると、それは長期間維持されます。ただし、アクセス量が比較的多く、マシン数が多い場合、この使用法では接続が確立され再利用されず、最終的に max_connections (最大接続数) に達してしまうため、「データベース接続数が枯渇する」可能性があります。データベースの接続)。したがって、長い接続を使用するには、通常、CGI マシンによって「盲目的に」作成される接続の数を制御するために、CGI と MySQL の間に「接続プール」サービスを実装する必要があります。
データベース接続プール サービスを確立するには多くの方法があります。PHP の場合は、swoole (PHP のネットワーク通信拡張機能) を使用して実装することをお勧めします。
3. Innodb キャッシュ設定 (innodb_buffer_pool_size)
innodb_buffer_pool_size これは、インデックスとデータを保存するために使用されるメモリ キャッシュ領域です。マシンが MySQL 専用である場合、通常はマシンの物理メモリの 80% にすることが推奨されます。テーブル データをフェッチするシナリオでは、ディスク IO を削減できます。一般に、この値を大きく設定するほどキャッシュ ヒット率は高くなります。
4. サブライブラリ/テーブル/パーティション。
MySQL データベース テーブルは通常、数百万のデータ量に耐えることができます。そのため、データ量がこのレベルを超えることが予測される場合は、データベース/パーティションを分割することをお勧めします。他の操作。最善のアプローチは、最初からサービスをサブデータベースおよびサブテーブルのストレージ モデルに設計し、中期以降の段階でのリスクを根本的に排除することです。ただし、リストベースのクエリなどの一部の利便性が犠牲になり、同時にメンテナンスの複雑さが増加します。しかし、データの量が数千万以上になると、それらすべてに価値があることがわかります。
2. 複数の MySQL データベース サービスのセットアップ
1 MySQL マシンは、ダウンすると Web サービスが利用できなくなるため、実際には高リスクの単一ポイントです。さらに、Web システムへのアクセス数が増加し続けると、ある日、ついに 1 台の MySQL サーバーではサポートできないことがわかり、さらに多くの MySQL マシンを使用する必要が生じました。複数の MySQL マシンを導入すると、多くの新たな問題が発生します。
1. スレーブデータベースをバックアップとして、MySQL マスターとスレーブを確立します
このアプローチは純粋に「単一障害点」の問題を解決するためのもので、メイン データベースに障害が発生した場合にスレーブ データベースに切り替えます。ただし、スレーブ ライブラリは実際にはアイドル状態であるため、このアプローチは実際には少しリソースの無駄になります。
2. MySQL は読み取りと書き込みを分離し、メイン データベースへの書き込みとスレーブ データベースからの読み取りを行います。
2 つのデータベースは読み取りと書き込みを分離します。メイン データベースは書き込み操作を担当し、スレーブ データベースは読み取り操作を担当します。さらに、メイン データベースに障害が発生しても、読み取り操作には影響しません。同時に、すべての読み取りと書き込みを一時的にスレーブ データベースに切り替えることができます (トラフィックに注意する必要があります。スレーブ データベースがダウンする可能性があります)。交通量が多すぎるため)。
3. マスターとマスターはお互いを養います。
2 つの MySQL サーバーは、お互いのスレーブ データベースであると同時にマスター データベースでもあります。このソリューションは、トラフィックのプレッシャーを回避するだけでなく、「単一障害点」の問題も解決します。いずれかのユニットに障害が発生した場合でも、別のサービス セットが利用可能です。
ただし、このソリューションは 2 台のマシンを使用するシナリオでのみ使用できます。ビジネスが依然として急速に拡大している場合は、ビジネスを分離し、複数のマスター間サービスおよび相互バックアップ サービスを確立することを選択できます。
3. MySQL データベースマシン間のデータ同期
問題を解決するたびに、古い解決策から新しい問題が必然的に発生します。複数の MySQL サーバーがある場合、ビジネスのピーク時には、2 つのデータベース間でデータに遅延が発生する可能性があります。また、ネットワークやマシンの負荷などもデータ同期の遅延に影響します。かつて、1 日のアクセス数が 1 億に近い特殊なシナリオに遭遇しました。スレーブ データベースのデータがマスター データベースのデータに追いつくまでに何日もかかりました。このシナリオでは、スレーブ ライブラリは基本的にその有効性を失います。
それで、次に注力する必要があるのは、同期の問題の解決です。
1. MySQL にはマルチスレッド同期が付属しています
MySQL 5.6 は、マルチスレッドを使用したメイン データベースとスレーブ データベース間のデータ同期のサポートを開始します。ただし、この制限も比較的明らかであり、ライブラリにのみ基づくことができます。 MySQL のデータ同期は、binlog ログを介して行われます。メイン データベースによって binlog ログに書き込まれる操作は、特に SQL 操作にテーブル構造への変更が含まれる場合、後続の SQL ステートメント操作に影響します。したがって、データベースからのデータの同期は 1 つのプロセスを経る必要があります。
2. binlog 解析とマルチスレッド書き込みを自分で実装します。
データベース テーブルを 1 つの単位として使用して、複数の binlog テーブルを解析し、同時にデータ同期を実行します。これにより、確かにデータ同期の効率が向上しますが、テーブル間に構造的な関係やデータの依存関係がある場合は、書き込み順序の問題も発生します。この方法は、比較的安定した、比較的独立したデータ テーブルに使用できます。
国内の一流インターネット企業のほとんどは、データ同期効率を高速化するためにこの方法を使用しています。もっと抜本的なアプローチは、バイナリログを直接解析し、テーブルを単位として無視し、直接書き込むことです。ただし、このアプローチは実装が複雑で、使用範囲はさらに制限されており、特殊なシナリオ (テーブル構造の変更がない、テーブル間のデータ依存関係がないなど) の一部のデータベースでのみ使用できます。
4. Webサーバーとデータベースの間にキャッシュを確立します
実際、大量のトラフィック量の問題を解決するには、データベース レベルだけに焦点を当てることはできません。 「80/20 ルール」によると、リクエストの 80% はホット データの 20% のみに焦点を当てています。したがって、Web サーバーとデータベースの間にキャッシュ メカニズムを確立する必要があります。このメカニズムでは、ディスクをキャッシュまたはメモリ キャッシュとして使用できます。これらにより、ほとんどのホット データ クエリはデータベースの前でブロックされます。
1. 固定ページ
ユーザーがウェブサイト上の特定のページにアクセスすると、ページ上のコンテンツのほとんどは長期間変更されない可能性があります。たとえば、ニュースレポートは、一度公開されるとほとんど変更されません。この場合、CGI によって生成された静的 HTML ページは、Web サーバーのディスク上にローカルにキャッシュされます。動的 CGI クエリ データベースを通じて取得される初回を除き、ローカル ディスク ファイルはユーザーに直接返されます。
Web システムの規模が比較的小さかったときは、このアプローチは完璧に見えました。しかし、Webサーバーが100台になるなど、Webシステムの規模が大きくなると。この方法では、これらのディスク ファイルのコピーが 100 個存在することになりますが、これはリソースの無駄であり、保守が困難です。この時点で、サーバーを一元管理して保存できるのではないかと考える人もいるかもしれません。次のキャッシュ方法を検討してみてはいかがでしょうか。
2. シングルメモリキャッシュ
ページの静的化の例を通して、Web マシン自体で「キャッシュ」を維持するのは難しく、より多くの問題を引き起こすことがわかります (実際、PHP の apc 拡張により、Web の Key/Value を通じて操作できます)サーバーのネイティブ メモリ)。したがって、構築することを選択したメモリ キャッシュ サービスも独立したサービスである必要があります。
メモリキャッシュの選択には主にredis/memcacheが含まれます。パフォーマンスの点では、機能の豊富さの点では、両者に大きな違いはありません。
3. メモリキャッシュクラスター
単一のメモリ キャッシュを構築すると、単一障害点の問題に直面するため、それをクラスターに変換する必要があります。簡単な方法は、スレーブをバックアップ マシンとして追加することです。しかし、実際にリクエストが多く、キャッシュ ヒット率が高くなく、より多くのマシン メモリが必要であることが判明した場合はどうなるでしょうか?したがって、クラスターとして構成することをお勧めします。たとえば、redis クラスターに似ています。
Redis クラスター内の Redis は複数のマスター/スレーブ グループであり、各ノードがリクエストを受け入れることができるため、クラスターを拡張するときに便利です。クライアントは任意のノードにリクエストを送信でき、それが「担当する」コンテンツであれば、そのコンテンツが直接返されます。それ以外の場合は、実際に責任のある Redis ノードを見つけて、クライアントにアドレスを通知し、クライアントは再度要求します。
これは、キャッシュ サービスを使用するクライアントに対してすべて透過的です。
メモリキャッシュサービスを切り替える際には、一定のリスクが伴います。クラスタ A からクラスタ B に切り替えるプロセスでは、クラスタ B が事前に「ウォームアップ」されていることを確認する必要があります (クラスタ B のメモリ内のホット データは可能な限りクラスタ A のメモリと同じである必要があります)そうしないと、切り替え時に大量のコンテンツ要求が要求され、クラスター B のメモリ キャッシュでコンテンツが見つかりません。トラフィックはバックエンド データベース サービスに直接影響を及ぼし、データベースのダウンタイムが発生する可能性があります。
4. データベースの「書き込み」を削減します
上記のメカニズムはいずれもデータベースの「読み取り」操作を軽減しますが、書き込み操作も大きな負担となります。書き込み操作を減らすことはできませんが、リクエストをマージすることで負荷を軽減できます。このとき、メモリ キャッシュ クラスタとデータベース クラスタの間で変更の同期メカニズムを確立する必要があります。
まず、外部クエリが正常に表示できるように変更リクエストをキャッシュに入れてから、これらの SQL 変更をキューに入れて保存します。キューがいっぱいになったとき、または時々、それらは 1 つにマージされます。リクエストがデータベースに送信され、データベースが更新されます。
上記のシステム アーキテクチャを変更することで書き込みパフォーマンスを向上させることに加えて、MySQL 自体もパラメーター innodb_flush_log_at_trx_commit を構成することでディスク書き込み戦略を調整できます。マシンのコストが許せば、ハードウェア レベルから問題を解決するために、古い RAID (独立したディスクの冗長アレイ、ディスク アレイ) または新しい SSD (ソリッド ステート ドライブ、ソリッド ステート ドライブ) を選択できます。
5. NoSQL ストレージ
データベースの読み取りまたは書き込みに関係なく、トラフィックがさらに増加すると、最終的には「マンパワーが制限されている場合」のシナリオに到達します。マシンを追加するコストは比較的高く、実際には問題を解決できない可能性があります。現時点では、一部のコア データに NoSQL データベースの使用を検討できます。ほとんどの NoSQL ストレージはキー値方式を使用します。Redis 自体がメモリ キャッシュであり、ディスクにデータを直接保存できるため、Redis を使用することをお勧めします。
この場合、データベース内で頻繁に読み書きされるデータをいくつか分離し、新しく構築した Redis ストレージ クラスターに配置します。これにより、元の MySQL データベースへの負荷がさらに軽減されます。これは、Redis 自体が .メモリレベルキャッシュを使用すると、読み取りと書き込みのパフォーマンスが大幅に向上します。
国内の一流インターネット企業は、アーキテクチャの点で上記のソリューションと同様のソリューションを多く使用していますが、使用されるキャッシュ サービスは必ずしも Redis であるとは限りません。また、独自のビジネス特性に基づいて独自の NoSQL を開発することもあります。仕える。
6. 空のノードのクエリの問題
上記のサービスをすべて構築し、Web システムがすでに非常に強力であると考えたとき。私たちは今でも同じことを言っていますが、新たな問題は依然として発生します。空のノード クエリは、データベースにまったく存在しないデータ リクエストを指します。たとえば、存在しない個人情報のクエリをリクエストすると、システムはすべてのレベルで段階的にキャッシュを検索し、最終的にデータベース自体を見つけ、見つからないという結論を導き出し、結果を返します。フロントエンドまで。すべてのレベルのキャッシュが無効であるため、このリクエストは大量のシステム リソースを消費し、空のノード クエリが多数行われると、システム サービスに影響を与える可能性があります。
以前の仕事の経験では、私はそれにひどく苦しみました。したがって、Web システムの安定性を維持するには、適切な空ノード フィルタリング機構を設計することが非常に必要です。
その時に採用した方法は、単純なレコードマッピングテーブルを設計することでした。既存のレコードを保存し、メモリ キャッシュに置きます。この場合、空のノード クエリがまだ存在する場合、それらはキャッシュ レベルでブロックされます。
上記のアーキテクチャ構築が完了した後、私たちのシステムは十分に強力ですか?答えはもちろんノーです。最適化には制限がありません。 Web システムは表面的にはより強力であるように見えますが、ユーザーに提供されるエクスペリエンスは必ずしも最高であるとは限りません。中国東北部出身の同級生が深センのウェブサイトサービスを利用しているため、ネットワークの距離がまだ遅いと感じています。現時点では、Web システムをユーザーに近づけるためにオフサイトに展開する必要があります。
1. コアの集中とノードの分散化
大規模なオンライン ゲームをプレイしたことのある学生は、オンライン ゲームには多くのエリアがあり、通常は広東地域や北京地域などの地域ごとに分かれていることをご存知でしょう。広東省の選手が北京地域にプレーするために行くと、広東省にいるときよりも明らかに行き詰まりを感じるだろう。実際、これらの地域の名前には、サーバーの所在地がすでに示されているため、広東省のプレイヤーが北京のサーバーに接続すると、当然ながらネットワークは遅くなります。
システムとサービスが十分に大規模になったら、オフサイト展開の問題を検討し始める必要があります。サービスをできるだけユーザーに近づけます。 Web の静的リソースを CDN に保存し、DNS/GSLB を通じて「全国に」配布できることを以前に述べました。ただし、CDN は静的リソースの問題を解決するだけで、固定都市にのみ集中する巨大なバックエンド システム サービスの問題は解決しません。
この時点で、オフサイト展開が始まります。オフサイト展開は通常、コアが集中化され、ノードが分散されます。
たとえば、上海をコア ノードとしてデプロイし、北京、深セン、武漢、上海を分散ノードとしてデプロイすることを選択します (上海自体も分散ノードです)。私たちのサービス構造は以下の通りです:
上の図では、上海ノードとコア ノードは同じコンピュータ ルームにあり、他の分散ノードには別のコンピュータ ルームがあることを付け加えておく必要があります。
中国には大規模なオンラインゲームが多数あり、一般的に上記の構造に従っています。少量のデータを含むユーザーのコア アカウントはコア ノードに配置され、機器、タスク、その他のデータやサービスなどのほとんどのオンライン ゲーム データはリージョナル ノードに配置されます。もちろん、コア ノードとリージョナル ノードの間にはキャッシュ メカニズムもあります。
2. ノードの災害復旧と過負荷保護
ノードの災害復旧とは、ノードに障害が発生した場合でも、サービスが引き続き利用できることを保証するメカニズムを確立する必要があることを意味します。ここでのより一般的な災害復旧方法は、近くの都市ノードに切り替えることであることに疑いの余地はありません。システムの天津ノードに障害が発生した場合は、ネットワーク トラフィックを近くの北京ノードに切り替えます。負荷分散を考慮すると、トラフィックを地理的に近い複数のノードに同時に切り替えることが必要になる場合があります。一方、コアノード自体に障害が発生すると、国家サービスに影響が及びます。
過負荷保護とは、ノードが最大容量に達し、それ以上のリクエストを受け入れ続けることができないことを意味します。システムには保護メカニズムが必要です。サービスがすでに完全にロードされており、新しいリクエストを受け入れ続ける場合、結果としてダウンタイムが発生する可能性があり、少なくともほとんどのユーザーが正常に使用できるようにするには、過負荷保護が必要です。
過負荷保護を解決するには、一般に 2 つの方向があります:
アクセスの規模が増加するにつれて、Web システムは、需要に対応できる 1 台のサーバーから「巨大な」大規模クラスターまで徐々に成長します。 Web システムが大きくなる過程は、実は問題を解決する過程でもあります。さまざまな段階でさまざまな問題が解決され、古い解決策の上に新しい問題が生まれます。
システムの最適化には限界がありません。新しいソリューションは古い問題を解決すると同時に、新しい課題ももたらします。