記事ディレクトリ
建築デザイン
なぜ技術的なフレームワークを設計するのか
- モジュール機能: プログラムをモジュール化します。つまり、内部集約が高く、モジュール間の結合が低くなります。
- 開発効率の向上: 開発者は 1 つの点 (ビューの表示、ビジネス ロジック、データ処理) に集中するだけで済みます。
- テスト効率の向上: 後のテストでは、エラー フィードバックに基づいて問題の場所を迅速に特定できます。
6 つの設計原則
6 つのデザイン原則はデザイン パターンの理論であり、デザイン パターンはデザイン原則の実践です。
1. 単一責任の原則
クラスは 1 つの責任に対してのみ責任を負います。この用語を 1 つの変化原因と呼びます。クラスは、関連性の高い関数とデータのセットをカプセル化したものである必要があります。
第二に、開閉の原理
ソフトウェア エンティティは、拡張に対してオープンにし、変更に対してクローズする必要があります。
クラスを開発したら、新しい機能を順次追加し、このクラスを変更するのではなく、継承またはインターフェイス実装によって新しいクラスを追加することによって実装することをお勧めします。
3. 依存関係逆転の原則
抽象化は詳細に依存すべきではなく、詳細は抽象化に依存する必要があります。言い換えれば、実装ではなくインターフェースに対してプログラムするということです。
つまり、2 つのモジュール間の通信はインターフェイスを介して実現される必要があります。
4. 界面分離の原理
単一の汎用インターフェイスの代わりに複数の専用インターフェイスを使用します。つまり、クライアントは必要のないインターフェイスに依存すべきではありません。これは、呼び出し元が依存するインターフェイスをできるだけ小さくすることであり、インターフェイスの分離は単一責任の原則に似ています。
5. ディミットの法則 (最低限の知識の原則とも呼ばれます)
ソフトウェア エンティティは、他のエンティティとの対話をできるだけ少なくする必要があります。言い換えれば、クラスは呼び出す必要があるクラスについてはほとんど知りません。クラスの内部は呼び出し先 ( とも呼ばれます) とは何の関係もありません迪米特隔离
。
たとえば、Thread クラスで run メソッドを使用すると、Dimit 原則に従って run を分離し、userClass が使用する Runnable インターフェイスを構築できるため、呼び出し側 userClass と Thread の間の対話が最小限になります。
6. リスコフ置換原理
基本クラス (親クラス) を参照するすべての場所は、そのサブクラスのオブジェクトを透過的に使用できなければなりません。つまり、ソフトウェア システムでは、特定のクラスが使用されているすべての場所がそのサブクラスに置き換えられたとしても、システムは依然として正常に動作するはずです。この原則は、オブジェクト指向の継承とポリモーフィズムに依存しています。
事例解釈
- 以下に従って
单一职责原则
クラスまたはインターフェイスを構築します 开闭原则
クラスの継承またはインターフェイスの実装に基づいて新しいクラスを構築します。- 原則に基づいて
里式替换
、親クラスが使用されているすべての場所をサブクラスに置き換えることができます。 依赖倒置
他のクラスと対話する場合は、原則としてインターフェイス通信を使用します。- インターフェイスを設計するときは、
接口隔离
原則に基づいて、特定の機能を具体的に実装する複数のインターフェイスを設計する必要があります - これに基づいて
迪米特原则
、教師クラスが生徒の点呼を実現したい場合は、生徒と対話するために使用されるモニターを介して階層的に実装する必要があります。
一般的なデザインパターン
固定観念
シングルトンパターン
ハングリー シングルトン: 初期化中に静的インスタンス オブジェクトを直接作成します。これは本質的にスレッド セーフです。
遅延シングルトン: 実際に必要なときに作成され、スレッド同期メカニズムを使用する必要があります。これを記述するには次の 3 つの方法があります。
- 同期コード ブロック: コンストラクターをプライベート化し、インスタンス メンバーを静的にし、シングルトンを取得する静的メソッドを公開します。インスタンスが作成されていないことが検出された場合は、静的メソッドであるため、synchronized を使用して同期コード ブロックを構築します。クラス(クラス名.クラス)オブジェクトのバイトコードを同期ロックオブジェクトとして使用します。
class SingleInstance {
private SingleInstance() {
}
private static SingleInstance singleInstance;
public static SingleInstance getInstance() {
if (singleInstance == null) {
synchronized (SingleInstance.class) {
if (singleInstance == null) {
singleInstance = new SingleInstance();
}
}
}
return singleInstance;
}
}
- 同期メソッドを使用して、シングルトン全体を取得する静的メソッドを直接ロックします。
class Single {
private Single(){
}
private static Single single;
public static synchronized Single GetInstance() {
if (single == null) {
single = new Single();
}
return single;
}
}
- 静的内部クラス + 最終内部メンバー
静的内部クラスのメンバーは、初めて呼び出されたときにのみ初期化されます。これは、遅延シングルトンの方法と非常に一致しています。
class SingleByInner{
private SingleByInner() {
}
static class Inner {
private static final SingleByInner INSTANCE = new SingleByInner();
}
public static SingleByInner getInstance() {
return Inner.INSTANCE;
}
}
class SingleByInner{
private SingleByInner() {
}
static class Inner {
private static final SingleByInner INSTANCE = new SingleByInner();
}
public static SingleByInner getInstance() {
return Inner.INSTANCE;
}
}
工場パターン
シンプルな工場
- シンプル ファクトリ パターンは、オブジェクトの作成時に内部の詳細を顧客に公開せず、オブジェクトを作成するための共通のインターフェイスのみを提供することを実現するために提案されています。(ディミットの法則/最小知識原則に基づく)
- シンプル ファクトリはインスタンス化操作をクラスのみに組み込み、このクラスはシンプル ファクトリ クラスになり、アプリケーションをインスタンス化する特定のサブクラスをシンプル ファクトリ クラスが決定できるようになります。
- これにより、クライアント クラスと特定のサブクラスの実装が分離される可能性があります。ビジネスでは複数のクライアント クラスが存在することがよくあります。クライアント クラスがすべてのサブクラスの詳細を知る必要がある場合、サブクラスが変更されると、すべてのクライアント クラスを改訂する必要があります。ファクトリ モードを使用する場合は、ファクトリ クラスを変更するだけでよく、サブクラスが必要なクライアント クラスを使用する場合はインターフェイス パラメータを変更するだけです。
ファクトリーメソッド
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到了子类
- 単純なファクトリでは、オブジェクトを作成するのはファクトリ クラスであり、ファクトリ メソッドでは、オブジェクトを作成するのはファクトリ クラスのサブクラスです。
ジェネレータパターン
封装一个对象的构造过程,并允许按步骤构造
行動的
リスナー(オブザーバー)パターン
リスナーは、それ自体が関心のあるイベントを監視し、それ自体が関心のあるイベントを受信したときにカスタム操作を実行するために使用されます。
リスナーモードはAndroidで広く使われているモードなので、皆さんも違和感はないと思います。Android 開発では、Button コントロールのクリック イベントがリスナー モードの最も一般的な例です。
ボタンをクリックすると、OnClickListener.onClick が実行されます。アクティビティは、ボタンに対して独自に実装された OnClickListener を設定し、onClick メソッドをオーバーライドしてカスタム操作を実行します。
メディエーターパターン
中間オブジェクトを使用して一連のオブジェクトの対話をカプセル化します。中間オブジェクトにより、疎結合の目的を達成するために、各オブジェクトが表示せずに相互に対話できるようになります。
以下の部分で構成されています
- メディエーター 抽象的な仲介者 - さまざまな同僚間のやり取りを調整するために使用されます。
- 具体的な調停者 具体的な調停者の役割 - 各同僚のクラスによって異なります
- 同僚の役割 (カプセル化されるオブジェクト)
各同僚の役割はメディエーターの役割を認識していますが、他の同僚と対話することはなく、メディエーターを通じてスケジュールされます。
- 利点は、クラス間の依存関係を減らし、元の 1 対多の依存関係を 1 対 1 の依存関係に変更することです。カップリングを減らします。
- 欠点は、仲介者が非常に大規模になり、ロジックが複雑になることです。
プロキシモード
このオブジェクトへのアクセスを制御するために、他のオブジェクトにプロキシを提供します。委任モードとも呼ばれます。
主な役割は次の 3 つです。
- サブジェクトの抽象的なサブジェクトの役割。これは抽象クラスまたはインターフェイスにすることができ、特別な要件のない最も一般的なビジネス タイプ定義です。
- RealSubject 固有のサブジェクトの役割。委任された役割、委任された役割とも呼ばれます。特定の業務の執行者です。
- プロキシ プロキシ テーマの役割。委任クラス、プロキシ クラスとも呼ばれます。実際のキャラクターのアプリケーションを担当し、事前操作や事後処理などのカスタム操作を追加できます。
アドバンテージ:
- 明確な責任 - 実際の役割は、実際のビジネス ロジックの実装のみを担当し、他の業務には関与しません。プロキシはさらに多くのことができます。
- 高度な拡張性 - インターフェイスが実装されている限り、プロキシを変更せずに具体的なサブジェクトの役割の実装を大幅に変更できます。
責任連鎖モデル
責任連鎖パターンは、次のような行動設計パターンです。これは、複数のリクエスト プロセッサ オブジェクトをチェーンに接続するために使用され、リクエスト プロセッサが処理されるまでリクエストをチェーンに沿って継続的に渡すことができ、効率的かつ柔軟なリクエストの処理と配信を実現します。
関与する主な役割は次の 3 つです。
- 抽象プロセッサ (ハンドラ): リクエストを処理するためのインターフェイスを定義し、後続のプロセッサ オブジェクトを維持します。
- 具象ハンドラー (ConcreteHandler): リクエストを処理するためのインターフェイスを実装し、リクエストを処理できるかどうかを決定します。処理できない場合、リクエストは後続のプロセッサに転送されます。
- クライアント: リクエスト ハンドラー オブジェクトを作成し、それを責任のチェーンに追加します。
アドバンテージ:
- 結合度を下げる: 責任連鎖モードでは、リクエストの送信者と受信者が分離されます。リクエストはチェーンの最初から最後まで渡されます。この期間中、各ノードは自分のノードに集中するだけで済みます。独自の処理ロジックを使用するため、ノードの数が減り、ノード間の結合度が減ります。
- 強化された柔軟性と優れたスケーラビリティ: 責任連鎖モデルでは、ノード オブジェクトを動的に追加または削除したり、チェーン内のノード オブジェクトの呼び出し順序を変更したり、システム プロセスを柔軟に変更または拡張したりできます。
欠点:
- チェーンが長すぎると、システムのパフォーマンスと効率が低下します。
- リクエストは処理されない可能性があります。リクエストはチェーンの最後に到達する可能性がありますが、リクエストを処理する適切な処理ノードがまだありません。この場合、この状況を処理するには特別な処理メカニズムが必要です。
- デバッグの難しさ: チェーン内の特定のノードの呼び出しに問題がある場合、チェーン全体のリクエストが処理されない可能性があり、デバッグ中に 1 つずつ確認する必要があります。
構造的
アダプター (ラッパー) パターン
クラスのインターフェイスを、クライアント ロックが予期する別のインターフェイスに変換します。これにより、インターフェイスの不一致により連携できない 2 つのクラスが連携できるようになります。
アドバンテージ:
- 無関係なクラスを一緒に実行できるようにする
- クラスの透明性の向上
- クラスの再利用性の向上
分散理論
キャップ
CAPとは、Consistency(一貫性)、Availability(可用性)、Partition Tolerance(パーティション耐障害性)の頭文字を組み合わせたものです。
CAP 定理 (CAP 定理) は、分散システムの場合、読み取りおよび書き込み操作を設計するときに、次の 3 つの点のうち 2 つだけを同時に満たすことができることを指摘しています。
- C: 一貫性: すべてのノードがデータの同じ最新コピーにアクセスします。
- A: 可用性: 障害のないノードは、適切な時間内に適切な応答 (エラーやタイムアウト応答ではない) を返します。
- P: パーティション トレランス (パーティション トレランス): 分散システムにネットワーク パーティションがある場合でも、外部サービスを提供できます。
ネットワークパーティションとは何ですか?
分散システムでは、複数のノード間のネットワークはもともと接続されていますが、何らかの障害(一部のノードのネットワーク障害など)により、一部のノードが接続されず、ネットワーク全体がいくつかのエリアに分割されることをネットワーク分割といいます。
この法則を説明するとき、多くの人は単に「整合性、可用性、分割耐性は、同時に 2 つしか達成できず、同時に達成することは不可能です。」と単純に表現します。実はこれは非常に誤解を招く記述であり、CAP理論誕生から12年後の2012年にCAPの父も前回の論文を書き直している。
ネットワークの分断が発生した場合、サービスを継続したい場合、強整合性と可用性は 2 つのうち 1 つしか選択できません。つまり、ネットワーク分割後は P が前提となり、P が決定されて初めて C と A の選択が可能となります。つまり、分割耐性(Partition tolerance)を実現する必要がある。
つまり、CAP 理論では、パーティションの耐障害性 P が満たされなければならず、これに基づいて、可用性 A または一貫性 C のみを満たすことができます。
したがって、分散システムが CA アーキテクチャを選択することは理論的に不可能であり、CP または AP アーキテクチャのみを選択する必要があります。たとえば、ZooKeeper と HBase は CP アーキテクチャであり、Cassandra と Eureka は AP アーキテクチャであり、Nacos は CP アーキテクチャだけでなく AP アーキテクチャもサポートしています。
CA アーキテクチャを選択できないのはなぜですか?
例: システムに「パーティション」がある場合、システム内の特定のノードが書き込み操作を実行しています。Cを確保するには、他のノードの読み書き操作を禁止する必要があり、Aと矛盾します。A を保証するために、他のノードの読み取りおよび書き込み操作が正常である場合、C との競合が発生します。
CP か AP を選択するかどうかは、現在のビジネス シナリオに依存しており、結論はありませんが、たとえば銀行など、強整合性を確保する必要があるシナリオでは、一般的に CP を保証することが選択されます。
さらに、追加する必要がある点が 1 つあります。ネットワーク分割が正常 (システムがほとんどの場合にある状態) の場合、つまり P を保証する必要がない場合、C と A は次のとおりです。も同時に保証されます。
コンセンサス合意
2フェーズコミット
3フェーズコミット
ハイパフォーマンス
負荷分散
負荷分散とは、システム全体の同時処理能力と信頼性を向上させるために、ユーザー要求を異なるサーバーに分散して処理することを指します。負荷分散サービスは専用のソフトウェアまたはハードウェアで実現できますが、一般にハードウェアの方が性能が良く、ソフトウェアの価格が安くなります。
最も一般的なのは、レイヤー 4 とレイヤー 7 の負荷分散です。
- 4 層のロード バランシングは、OSI モデルの 4 番目の層であるトランスポート層で機能します。この層の主なプロトコルは TCP/UDP です。ロード バランサは、データ パケット内の送信元ポート アドレスと宛先ポート アドレスを確認できます。この情報に基づいて、データ パケットは特定の負荷分散アルゴリズムを通じてバックエンドの実サーバーに転送されます。言い換えれば、レイヤー 4 ロード バランシングの中核は、特定のパケット コンテンツを含まない IP+ポート レベルでのロード バランシングです。
- 7 層の負荷分散は、OSI モデルの 7 番目の層であるアプリケーション層で機能し、この層の主要なプロトコルは HTTP です。この層での負荷分散は、4 層の負荷分散ルーティング ネットワーク リクエストよりも複雑で、メッセージのデータ部分 (メッセージの HTTP 部分など) を読み取り、読み取ったデータの内容に従って、 URL、Cookie など) を使用して負荷分散を決定します。つまり、レイヤー 7 ロード バランサーの中核は、メッセージ コンテンツ (URL、Cookie など) レベルでの負荷分散であり、レイヤー 7 負荷分散を実行するデバイスは通常、リバース プロキシ サーバーと呼ばれます。。
レイヤ 7 ロード バランシングはレイヤ 4 ロード バランシングよりも多くのパフォーマンスを消費しますが、比較的柔軟性が高く、ネットワーク リクエストをよりインテリジェントにルーティングできます。たとえば、コンテンツの内容に応じてキャッシュ、圧縮、暗号化などを最適化できます。リクエスト。
簡単に言うと、4 層の負荷分散パフォーマンスが強化され、7 層の負荷分散機能が強化されます。ただし、ほとんどのビジネス シナリオでは、レイヤー 4 の負荷分散とレイヤー 7 の負荷分散のパフォーマンスの違いは基本的に無視できます。
仕事では通常、Nginx を使用して 7 層の負荷分散を実行し、LVS (Linux Virtual Server 仮想サーバー、Linux カーネルの 4 層負荷分散) を使用して 4 層の負荷分散を実行します。
一般的な負荷分散アルゴリズム
- ランダム方式: 重みが設定されていない場合、すべてのサーバーが同じ確率でアクセスされます。重みが設定されている場合、重みが高いサーバーがアクセスされる可能性が高くなります。
- ラウンド ロビン: ラウンド ロビン アルゴリズムは、各サーバーが同じ負荷を運ぶ、同様のパフォーマンスを持つサーバーのクラスターに適しています。重み付きラウンドロビン アルゴリズムは、サーバーのパフォーマンスが異なるクラスターに適しており、重みの存在によりリクエストの割り当てがより合理的になります。
- 最小接続方法: 新しいリクエストが表示されたら、サーバー ノードのリストを調べて、現在のリクエストに応答するアクティブな接続の数が最も少ないサーバーを選択します。アクティブな接続の数は、現在処理されているリクエストの数として理解できます。ただし、この方法は実装が最も複雑でもあり、各サーバーが処理するリクエスト接続の数を監視する必要があります。
- 一貫したハッシュ法:
ハッシュ関数の出力空間をリング ドメインとして想像してください。
さまざまなオブジェクト データがハッシュによってリングにマッピングされます。
同じハッシュ マップを使用してサーバーの一意の識別コードを入力し、マシンをこのリングにマッピングします。オブジェクト
とマシンは同じハッシュ空間にあるため、時計回りに回転します。m1 は t3 に格納され、m3 と m4 は t3 に格納されます。 t2 、m2 は t1 に格納されます。
マシン t4 を追加する必要がある場合: m4->t2 を
m4->t4に変更するだけで済み
ます。 データの移動は t2 と t4 の間でのみ発生し、他のマシン上のデータは影響を受けません。
マシン t1 を削除する場合、m2->t1 を m2->t3 に変更する必要があり、データ移行は t1 と t3 の間でのみ発生します。
既存の問題
-
問題 1. マシン ノードの数が少ないとデータ ドメインが偏る
クラスタ内のノードの数が少ないと、ハッシュ空間内のノードの分布が偏るという問題が発生する可能性があります。下図に示すように、図中のノード A、B、C の分布が比較的集中しているため、ハッシュ リングが傾きます。データ 1、2、3、4、および 6 はすべてノード A に保存され、データ 5 のみがノード B に保存され、データはノード C には保存されません。3 台のマシン A、B、C の負荷は非常に不均衡です。
-
問題 2 データ移行による負荷の不均衡
極端な場合、ノード A に障害が発生すると、A に保存されているすべてのデータを B に転送する必要があります。大量のデータによりノード B がクラッシュし、ノード A と B が障害を起こす可能性があります。すべてのデータがノード C に移行されるため、ノード C もクラッシュし、クラスター全体がダウンします。この状況は雪崩効果として知られています。
これらの問題はどちらも仮想ノードで解決できます。
仮想ノード
各実機(ノード)には多数の仮想ノードが割り当てられており、実ノードと仮想ノード間のマッピングが記録されている限り、多数の仮想ノードによってハッシュ入力リング領域のバランスが保たれます。
マシンを増減すると、多数の仮想ノードも増減し、仮想ノード上でデータ移行を完了することができます。
同時に、実機メモリのサイズに応じて異なる数の仮想ノードを割り当てることができ(各仮想ノードの負荷容量が一定であることが前提)、負荷管理に使用できます。