2022面试200题目和答案分布式+微服务+MYSQL+Redis+JVM+Spring

200题目和答案分布式+微服务+MYSQL+Redis+JVM+Spring等等

带图MD在资源https://download.csdn.net/download/m0_47987937/86509554

Java面向对象有哪些特征,如何应用

​ 面向对象编程是利用类和对象编程的一种思想。万物可归类,类是对于世界事物的高度抽象 ,不同的事物之间有不同的关系 ,一个类自身与外界的封装关系,一个父类和子类的继承关系, 一个类和多个类的多态关系。万物皆对象,对象是具体的世界事物,面向对象的三大特征封装,继承,多态。封装,封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;继承是父类和子类的关系,多态说的是类与类的关系。

​ 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。属性的封装:使用者只能通过事先定制好的方法来访问数据,可以方便地加入逻辑控制,限制对属性的 不合理操作;方法的封装:使用者按照既定的方式调用方法,不必关心方法的内部实现,便于使用; 便于修改,增强 代码的可维护性;

継承とは既存のクラスから新しいクラスを派生させることであり、新しいクラスは既存のクラスのデータ属性や動作を吸収し、新しい機能を拡張できます。要は特殊-一般の関係、よく言われるis-aの関係です。サブクラスは親クラスを継承します。これは、サブクラスが特別な親クラスであり、親クラスにはない属性またはメソッドがあることを示します。基底クラスは、さまざまな実装クラスから抽象化され、さまざまな実装クラスに共通の性質を持ち、実装クラスが extends キーワードで基底クラス(親クラス)を継承すると、同様の性質を持ちます。継承されたクラスをサブクラス(派生クラスまたはスーパークラス)といい、継承されたクラスを親クラス(または基本クラス)といいます。たとえば、猫、犬、トラと共通の特徴 (食べる、走る、吠えるなど) を持つ動物クラスを猫、犬、トラから抽象化できます。Java では extends キーワードによる継承が実装されており、親クラスで private で定義された変数やメソッドは継承されず、親クラスで private で定義された変数やメソッドをサブクラスで直接操作することはできません。継承により、一般クラスと特殊クラスの共通機能の繰り返しの記述が回避されます。継承により、それぞれの共通機能が適用される概念の範囲が明確に表現されます。一般クラスで定義された属性や操作は、クラス自体にも適用され、その下の各特殊クラスのすべてのオブジェクト。継承の原則を使用すると、システム モデルがより簡潔かつ明確になります。

カプセル化と継承と比較すると、Java ポリモーフィズムは 3 つの主要な機能の中で最も難しいものの 1 つです。カプセル化と継承は最終的にポリモーフィズムに起因します。ポリモーフィズムとは、クラスとクラス間の関係を指します。2 つのクラスには継承関係があります。メソッドがあります。書き換えると、呼び出し時に親クラス参照がサブクラス オブジェクトを指すことができるようになります。ポリモーフィズムには、継承、書き換え、サブクラス オブジェクトを指す親クラス参照という 3 つの重要な要素があります。

HashMap の原理と jdk1.7 と 1.8 の違いは何ですか

HashMap はキーの hashCode 値に従ってデータを格納します。ほとんどの場合、その値は直接特定できるため、アクセス速度は高速ですが、走査順序は不確かです。HashMap では、最大でも 1 つのレコードのキーのみが null になることが許可され、複数のレコードの値が null になることが許可されます。HashMap はスレッドセーフではありません。つまり、複数のスレッドがいつでも同時に HashMap に書き込むことができ、データの不整合が生じる可能性があります。スレッド セーフを満たす必要がある場合は、Collections の synchronizedMap メソッドを使用して HashMap をスレッド セーフにするか、ConcurrentHashMap を使用します。以下の写真を使って紹介します

ハッシュマップの構造。

JAVA7の実装

画像の説明を追加してください

一般的な方向では、HashMap は配列であり、配列内の各要素は一方向リンク リストです。上の図では、それぞれの緑

エンティティはネストされたクラス Entry のインスタンスであり、Entry にはキー、値、ハッシュ値、一方向リンク リストの next という 4 つの属性が含まれます。

  1. 容量: 現在の配列容量 (常に 2^n) を拡張でき、拡張後の配列サイズは現在のサイズの 2 倍になります。

  2. loadFactor: 負荷係数、デフォルトは 0.75 です。

  3. しきい値: 拡張のしきい値、容量 * 負荷係数に等しい

**JAVA8 の実装**

Java8 では HashMap にいくつかの変更が加えられていますが、最大の違いは赤黒ツリーを使用することで、配列 + リンク リスト + 赤黒ツリーで構成されます。

Java7 HashMap の導入によると、検索時にハッシュ値に従って配列の特定の添え字をすばやく見つけることができますが、その後、リンクされたリストに沿って 1 つずつ比較して必要なものを見つける必要があることがわかりました。時間計算量は次のものに依存します。

リンクされたリストの長さにより、O(n) になります。この部分のオーバーヘッドを軽減するために、Java8ではリンクリストの要素数が8を超える場合、リンクリストを赤黒ツリーに変換し、計算量をO(logN)に削減することができます。これらの位置で検索する場合。

画像の説明を追加してください

ArrayList と LinkedList の違いは何ですか

ArrayList と LinkedList は両方とも List インターフェイスを実装していますが、次のような違いがあります。
ArrayList はインデックスベースのデータ インターフェイスであり、その最下層は配列です。O(1) の時間計算量で要素へのランダム アクセスを実行できます。これに対応して、LinkedList はデータを要素のリストの形式で格納し、各要素は前後の要素とリンクされます。この場合、要素を見つける時間計算量は O( n) です。
ArrayList と比較して、LinkedList の挿入、追加、削除操作は高速です。これは、要素がコレクション内の任意の位置に追加されるときに、配列のようにサイズを再計算したりインデックスを更新したりする必要がないためです。
LinkedList は各ノードに対して 2 つの参照 (前の要素を指し、もう 1 つは次の要素を指す) を格納するため、LinkedList は ArrayList よりも多くのメモリを消費します。
ArrayList と LinkedList も参照してください。

  1. 配列はインデックスベースのデータ構造であるため、インデックスを使用した配列内のデータの検索と読み取りは非常に高速です。データを取得する配列の時間計算量は O(1) ですが、データを削除するには、配列内のすべてのデータを再配置する必要があるため、非常にコストがかかります。

  2. ArrayList と比較して、LinkedList の挿入は高速です。LinkedList は ArrayList とは異なるため、配列のサイズを変更する必要はなく、配列がいっぱいになったときにすべてのデータを新しい配列に再ロードする必要もありません。これは ArrayList の最悪のケースであり、時間がかかります。複雑さは O(n) ですが、LinkedList への挿入または削除の時間計算量はわずか O(1) です。ArrayList は、データの挿入時に (配列の末尾の挿入に加えて) インデックスを更新する必要もあります。

  3. データの挿入と同様に、データを削除する場合も ArrayList より LinkedList の方が優れています。

  4. ArrayList の各インデックスの位置が実際のデータであり、LinkedList の各ノードには実際のデータと前後のノードの位置が格納されるため、LinkedList はより多くのメモリを必要とします (LinkedList インスタンスには、Node first と Node last の 2 つの値が格納されます)。はそれぞれ実際のノードとリンク リストの終了ノードを表し、各 Node インスタンスには 3 つの値 (E item、Node next、Node pre) が格納されます。

ArrayList の代わりに LinkedList を使用する方が適切なシナリオはどれですか

  1. アプリケーションはデータにランダムにアクセスしません。LinkedList の n 番目の要素が必要な場合、最初の要素から n 番目のデータまで順番にカウントしてデータを読み取る必要があるためです。

  2. アプリケーションはより多くの要素を挿入および削除し、より少ないデータを読み取ります。要素の挿入と削除にはデータの並べ替えが含まれないため、ArrayList よりも高速です。

つまり、ArrayList の実装は配列を使用し、LinkedList はリンク リストに基づいており、ArrayList は検索に適しており、LinkedList は追加と削除に適しています。

以上がArrayListとLinkedListの違いです。非同期のインデックスベースのデータ アクセスが必要な場合は、必ず ArrayList を使用してください。ArrayList は高速で使いやすいです。ただし、適切な初期サイズを指定し、変更された配列のサイズをできるだけ小さくすることを忘れないでください。

高い同時実行性のコレクションの問題は何ですか

**第一世代のスレッドセーフなコレクション クラス**

ベクトル、ハッシュテーブル

スレッドのスケジューリングを確実に行う方法: 同期変更メソッドを使用する*

短所: 非効率的

第 2 世代のスレッドアンセーフなコレクション クラス

配列リスト、ハッシュマップ

スレッドは安全ではありませんが、パフォーマンスは良好で、Vector、Hashtable の置き換えに使用されます

ArrayList と HashMap を使用するときにスレッド セーフが必要な場合はどうすればよいですか?

コレクションを使用します。synchronizedList (リスト); コレクション。同期マップ(m);

最下位層は同期コード ブロック ロックを使用してすべてのコードをロックしていますが、ロックはメソッド内にあり、メソッド外のパフォーマンスはわずかに向上していることがわかります。結局のところ、メソッド自体がリソースを割り当てる必要があります

第 3 世代のスレッドセーフなコレクション クラス

多数の同時実行の場合にコレクションの効率と安全性を向上させるにはどうすればよいでしょうか?

java.util.concurrent.*

同時ハッシュマップ:

CopyOnWriteArrayList :

CopyOnWriteArraySet: CopyOnWriteHashSet* ではないことに注意してください。

最下層のほとんどは Lock ロックを使用し (ConcurrentHashMap 1.8 は Lock ロックを使用しません)、セキュリティを確保しながら高いパフォーマンスを保証します。

jdk1.8の新機能は何ですか

1. インターフェースのデフォルトメソッド

Java 8 では、default キーワードを使用するだけで、非抽象メソッド実装をインターフェイスに追加できます。この機能は拡張メソッドとも呼ばれます。例は次のとおりです。

コードは以下のように表示されます。

インターフェイス式 { double Calculate(int a);

デフォルトの double sqrt(int a) { return Math.sqrt(a); } }

Formula インタフェースは、calculate メソッドに加えて sqrt メソッドを定義します。Formula インタフェースを実装するサブクラスは、calculate メソッドのみを実装する必要があります。デフォルトのメソッド sqrt は、サブクラスで直接使用できます。

コードは以下のように表示されます。

Formula Formula = new Formula() { @Override public double Calculate(int a) { return sqrt(a * 100); } };

式.計算(100); // 100.0 Formula.sqrt(16); // 4.0

この記事の数式は匿名クラスのインスタンスとして実装されており、非常にわかりやすいコードでsqrt(a * 100)の計算が6行のコードで実現されています。次のセクションでは、単一メソッドのインターフェイスを実装する簡単な方法を見ていきます。

翻訳者注: Java には単一継承しかありません。クラスに新しい機能を追加したい場合は、通常、それを実装するインターフェイスを使用します。C++ では多重継承がサポートされており、サブクラスが複数の親クラス インターフェイスと関数を持つことができます。他の言語では、クラスに他の再利用可能なコードを同時に持たせる方法をミックスインと呼びます。新しい Java 8 のこの機能は、コンパイラー実装の観点から見ると Scala の特徴に近いものです。C# には拡張メソッドと呼ばれる概念もあり、メソッドを既存の型に拡張できますが、これは意味的に Java 8 とは異なります。

2. ラムダ式

まず、古いバージョンの Java で文字列がどのように配置されるかを見てください。

コードは以下のように表示されます。

リスト名 = Arrays.asList(“peterF”, “anna”, “mike”, “xenia”);

Collections.sort(names, new Comparator() { @Override public int Compare(String a, String b) { return b.compareTo(a); } });

List オブジェクトとコンパレーターを静的メソッド Collections.sort に渡すだけで、指定された順序で並べ替えられます。通常は、匿名のコンパレータ オブジェクトを作成して、sort メソッドに渡します。

Java 8 では、この従来の匿名オブジェクト メソッドを使用する必要はなく、より簡潔な構文であるラムダ式が提供されます。

コードは以下のように表示されます。

Collections.sort(names, (文字列 a, 文字列 b) -> { return b.compareTo(a); });

コードは短くなり読みやすくなりますが、実際にはさらに短く書くこともできます。

コードは以下のように表示されます。

Collections.sort(names, (文字列 a, 文字列 b) -> b.compareTo(a));

コードが 1 行のみの関数本体の場合は、中括弧 {} と return キーワードを削除できますが、より短く記述することもできます。

コードは以下のように表示されます。

Collections.sort(names, (a, b) -> b.compareTo(a));

Java コンパイラーはパラメーターの型を自動的に推測できるため、型を再度記述する必要はありません。次に、ラムダ式でさらに便利なことを見てみましょう。

3. 機能インターフェイス

ラムダ式は Java の型システムでどのように表現されるのでしょうか? すべてのラムダ式は型 (通常はインターフェイス型) に対応します。また、「関数型インターフェイス」とは、抽象メソッドを 1 つだけ含むインターフェイスを指し、このタイプのすべてのラムダ式はこの抽象メソッドに一致します。デフォルト メソッドは抽象メソッドとみなされないため、関数インターフェイスにデフォルト メソッドを追加することもできます。

ラムダ式は、抽象メソッドを 1 つだけ含む任意のインターフェイス型として扱うことができます。インターフェイスがこの要件を満たしていることを確認するには、インターフェイスに @FunctionalInterface アノテーションを追加するだけです。インターフェイスが次のようにマークされていることがコンパイラによって検出された場合、このアノテーション 複数の抽象メソッドがある場合、エラーが報告されます。

例は次のとおりです。

コードは以下のように表示されます。

@FunctionalInterface インターフェイス Converter<F, T> { T Convert(F from); Converter<String, Integer> コンバータ = (from) -> Integer.valueOf(from); 変換された整数 = Converter.convert(“123”); System.out.println(変換済み); // 123

@FunctionalInterface が指定されていない場合、上記のコードも正しいことに注意してください。

翻訳者のメモでは、ラムダ式を単一メソッド インターフェイスにマップしています。このアプローチは、Rhino JavaScript インタープリタなど、Java 8 より前の他の言語で実装されています。関数パラメータが単一メソッド インターフェイスを受け取り、それを渡す場合は関数です、Rhino インタープリタは、単一のインターフェイス インスタンスから関数へのアダプタを自動的に作成します。典型的なアプリケーション シナリオには、org.w3c.dom.events.EventTarget の addEventListener の 2 番目のパラメータ EventListener が含まれます。

4 番目、メソッドとコンストラクターのリファレンス

前のセクションのコードは、静的メソッド参照によって表すこともできます。

コードは以下のように表示されます。

Converter<String, Integer> コンバータ = Integer::valueOf; 変換された整数 = Converter.convert(“123”); System.out.println(変換済み); // 123

Java 8 では、:: キーワードを使用してメソッドまたはコンストラクターの参照を渡すことができます。上記のコードは静的メソッドを参照する方法を示しています。また、オブジェクト メソッドを参照することもできます。

コードは以下のように表示されます。

コンバータ = 何か::startsWith; 文字列変換 = Converter.convert(“Java”); System.out.println(変換済み); // 「J」

次に、:: キーワードを使用してコンストラクターがどのように参照されるかを見てみましょう。まず、複数のコンストラクターを含む単純なクラスを定義します。

コードは以下のように表示されます。

クラス パーソン { String firstName; 文字列姓;

人() {}

Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = 姓; } }

次に、人物オブジェクトを作成するためのオブジェクト ファクトリ インターフェイスを指定します。

コードは以下のように表示されます。

インターフェース パーソンファクトリー

{ P create(String firstName, String lastName); }

ここでは、完全なファクトリーを実装する代わりに、コンストラクター参照を使用してそれらをリンクします。

コードは以下のように表示されます。

パーソンファクトリー personFactory = パーソン::新規; 人 人 = personFactory.create(“ピーター”, “パーカー”);

Person::new を使用して Person クラス コンストラクターの参照を取得するだけで済みます。Java コンパイラーは、PersonFactory.create メソッドのシグネチャに従って適切なコンストラクターを自動的に選択します。

5. ラムダスコープ

ラムダ式での外部スコープへのアクセスは、古い匿名オブジェクトと非常によく似ています。Final とマークされた外部ローカル変数、またはインスタンス フィールドや静的変数に直接アクセスできます。

6. ローカル変数にアクセスする

ラムダ式で外部ローカル変数に直接アクセスできます。

コードは以下のように表示されます。

最終的な整数 = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);

stringConverter.convert(2); // 3

ただし、匿名オブジェクトとは異なり、ここでの変数 num は Final として宣言する必要はなく、コードも正しいです。

コードは以下のように表示されます。

int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);

stringConverter.convert(2); // 3

ただし、ここでの num は次のコードによって変更してはなりません (つまり、暗黙的な最終セマンティクスがあります)。たとえば、次のコードはコンパイルできません。

コードは以下のように表示されます。

int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); 数値 = 3;

ラムダ式の num を変更しようとすることも許可されていません。

7. オブジェクトフィールドと静的変数にアクセスする

ローカル変数とは異なり、ラムダのフィールドと静的変数は両方とも読み取り可能であり、書き込み可能です。この動作は匿名オブジェクトと一致しています。

コードは以下のように表示されます。

クラス Lambda4 { static int innerStaticNum; int externalNum;

void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { innerNum = 23; String.valueOf(from) を返します。};

Converter<Integer, String> stringConverter2 = (from) -> {outerStaticNum = 72; String.valueOf(from) を返します。}; } }

8、デフォルトのアクセスインターフェース方法

最初のセクションの式の例を思い出してください。Formula インターフェイスは、匿名オブジェクトを含む Formula のインスタンスから直接アクセスできるデフォルトのメソッド sqrt を定義しますが、これはラムダ式では実行できません。ラムダ式ではデフォルトのメソッドにアクセスできないため、次のコードはコンパイルできません。

コードは以下のように表示されます。

計算式 = (a) -> sqrt( a * 100); 組み込みの機能インターフェイス

JDK 1.8 API には、古い Java で一般的に使用される Comparator インターフェイスや Runnable インターフェイスなど、多くの組み込み関数インターフェイスが含まれています。これらのインターフェイスには、ラムダで使用できるように @FunctionalInterface アノテーションが追加されています。Java 8 API は、作業をより便利にするための多くの新しい関数インターフェイスも提供します。一部のインターフェイスは Google Guava ライブラリからのものです。これらに精通している場合でも、使用中にこれらがどのようにラムダに拡張されるかを確認する必要があります。

述語****インターフェイス

Predicate インターフェイスにはパラメータが 1 つだけあり、ブール型を返します。このインターフェイスには、述語を他の複雑なロジック (and、or、not など) に組み合わせるためのさまざまなデフォルト メソッドが含まれています。

コードは以下のように表示されます。

述語 predicate = (s) -> s.length() > 0;

predicate.test(“foo”); // true predicate.negate().test(“foo”); // 間違い

述語 nonNull = オブジェクト::nonNull; 述語 isNull = オブジェクト::isNull;

述語 isEmpty = String::isEmpty; 述語 isNotEmpty = isEmpty.negate();

関数 インターフェース

Function インターフェイスは 1 つのパラメータを受け取り、他の関数と組み合わせることができるいくつかのデフォルト メソッド (compose andThen) を使用して結果を返します。

コードは以下のように表示されます。

Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123"); // "123"

Supplier インターフェイスSupplier インターフェイスは任意の型の値を返します。Function インターフェイスとは異なり、このインターフェイスにはパラメータがありません。

コードは以下のように表示されます。

サプライヤー personSupplier = person::new; personSupplier.get(); // 新しい人

Consumer インターフェイスConsumer インターフェイスは、単一のパラメータに対して実行される操作を表します。

コードは以下のように表示されます。

コンシューマ グリータ = § -> System.out.println("Hello, " + p.firstName); グリーター.accept(新しい人物(“ルーク”, “スカイウォーカー”));

Comparator インターフェイスComparator は古い Java の古典的なインターフェイスですが、Java 8 ではそれにさまざまなデフォルト メソッドが追加されています。

コードは以下のように表示されます。

コンパレータ comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

人物 p1 = 新しい人物(“ジョン”, “ドウ”); 人物 p2 = 新しい人物(「アリス」、「ワンダーランド」);

コンパレータ.比較(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0

オプションの インターフェース

Optional は関数ではなくインターフェイスです。これは NullPointerException を防ぐために使用される補助型です。これは次のセッションで使用される重要な概念です。次に、このインターフェイスで何ができるかを簡単に見てみましょう:

Optional は、値が null であってもなくてもよい単純なコンテナとして定義されます。Java 8 より前では、関数は通常、null 以外のオブジェクトを返す必要がありますが、null を返す場合もあります。Java 8 では、null を返し、Optional を返すことは推奨されません。

コードは以下のように表示されます。

オプション オプション = Optional.of(“bam”);

オプション.isPresent(); // true オプション.get(); // “bam”Optional.orElse(“fallback”); // 「バム」

Optional.ifPresent((s) -> System.out.println(s.charAt(0))); // 「b」

ストリーム インターフェース

java.util.Stream は、要素のセットに一度に 1 つずつ適用できる一連の操作を表します。ストリーム操作は中間操作と最終操作に分かれており、最終操作は特定の種類の計算結果を返し、中間操作はストリームそのものを返すため、複数の操作を連続して連鎖させることができます。Stream の作成には、java.util.Collection、List、Set のサブクラスなどのデータ ソースを指定する必要がありますが、Map ではサポートされていません。ストリーム操作はシリアルまたはパラレルで実行できます。

まず、Stream がどのように使用されるかを見てみましょう。まず、コード例で使用するデータ List を作成します。

コードは以下のように表示されます。

リスト stringCollection = new ArrayList<>(); stringCollection.add(“ddd2”); stringCollection.add(“aaa2”); stringCollection.add(“bbb1”); stringCollection.add(“aaa1”); stringCollection.add(“bbb3”); stringCollection.add(“ccc”); stringCollection.add(“bbb2”); stringCollection.add(“ddd1”);

Java 8 はコレクション クラスを拡張し、Collection.stream() または Collection.ParallelStream() を通じてストリームを作成できます。次のセクションでは、一般的に使用されるストリーム操作について詳しく説明します。

フィルター _

フィルタリングでは、述語インターフェイスを使用して、適格な要素のみをフィルタリングして保持します。この操作は中間操作であるため、フィルタリングされた結果に他の Stream 操作 (forEach など) を適用できます。forEach には、フィルターされた要素に対して順番に実行する関数が必要です。forEach は最後の操作であるため、forEach の後に他の Stream 操作を実行することはできません。

コードは以下のように表示されます。

stringCollection .stream() .filter((s) -> s.startsWith(“a”)) .forEach(System.out::println);

// “aaa2”、“aaa1”

並べ替え _

並べ替えは、並べ替えられたストリームを返す中間操作です。カスタム コンパレータを指定しない場合は、デフォルトの並べ替えが使用されます。

コードは以下のように表示されます。

stringCollection .stream() .sorted() .filter((s) -> s.startsWith(“a”)) .forEach(System.out::println);

// “aaa1”、“aaa2”

並べ替えでは、元のデータ ソースに影響を与えることなく、並べ替えられたストリームのみが作成されることに注意してください。並べ替え後、元のデータの stringCollection は変更されません。

コードは以下のように表示されます。

System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

マップ マッピング中間操作マップは、指定された Function インターフェイスに従って要素を他のオブジェクトに順番に変換します。次の例は、文字列から大文字の文字列への変換を示しています。また、map を使用してオブジェクトを他の型に変換することもできます。map によって返される Stream 型は、マップに渡される関数の戻り値によって決まります。

コードは以下のように表示されます。

stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);

// 「DDD2」、「DDD1」、「CCC」、「BBB3」、「BBB2」、「AAA2」、「AAA1」

マッチ マッチ

Stream は、指定された Predicate が Stream 全体と一致するかどうかを確認できるさまざまな一致操作を提供します。すべての一致操作は最終的なものであり、ブール値を返します。

コードは以下のように表示されます。

boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith(“a”));

System.out.println(anyStartsWithA); // 真実

boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith(“a”));

System.out.println(allStartsWithA); // 間違い

boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith(“z”));

System.out.println(noneStartsWithZ); // 真実

Count Counting は、Stream 内の要素の数を返す最終操作であり、戻り値の型は long です。

コードは以下のように表示されます。

longstartsWithB = stringCollection .stream() .filter((s) -> s.startsWith(“b”)) .count();

System.out.println(startsWithB); // 3

プロトコルを減らす

これは、指定された関数を通じてストリーム内の複数の要素を 1 つの要素に削減できるようにする最後の操作であり、削減後の結果は Optional インターフェイスを通じて表現されます。

コードは以下のように表示されます。

オプションのreduce = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + “#” + s2);

Reduce.ifPresent(System.out::println); // “aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2”

並列****ストリーム

前述したように、Stream にはシリアルとパラレルの 2 種類があり、シリアル Stream は 1 つのスレッドで順次処理が完了しますが、パラレル Stream は複数のスレッドで同時に実行されます。

次の例は、ストリームを並列化することでパフォーマンスを向上させる方法を示しています。

まず、繰り返し要素のない大きなテーブルを作成します。

コードは以下のように表示されます。

int max = 1000000; リスト値 = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); 値.add(uuid.toString()); }

次に、ストリームのソート (シリアルソート) にかかる時間を計算します。

コードは以下のように表示されます。

長い t0 = System.nanoTime();

長いカウント = 値.stream().sorted().count(); System.out.println(count);

出荷 t1 = System.nanoTime();

長いミリス = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format(“順次ソートにかかった時間: %d ミリ秒”, ミリ秒));

// シリアル時間: 899 ミリ秒 並列ソート:

コードは以下のように表示されます。

長い t0 = System.nanoTime();

長いカウント = 値.ParallelStream().sorted().count(); System.out.println(count);

出荷 t1 = System.nanoTime();

長いミリス = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format(“並列ソートにかかった時間: %d ミリ秒”, ミリ秒));

// 並列ソート時間: 472 ミリ秒 上記 2 つのコードはほぼ同じですが、並列バージョンの方が 50% も高速です。必要な唯一の変更は、stream() をParallelStream() に変更することです。

地図

前述したように、Map タイプはストリームをサポートしていませんが、Map は日常的なタスクを処理するための新しくて便利なメソッドをいくつか提供します。

コードは以下のように表示されます。

Map<Integer, String> マップ = new HashMap<>();

for (int i = 0; i < 10; i++) {map.putIfAbsent(i, “val” + i); }

map.forEach((id, val) -> System.out.println(val)); 上記のコードは理解しやすく、putIfAbsent では追加の存在チェックを行う必要がなく、forEach は Operate を処理するための Consumer インターフェイスを受け取ります。各キーと値のペアについて。

次の例は、マップ上の他の便利な関数を示しています。

コードは以下のように表示されます。

map.computeIfPresent(3, (num, val) -> val + num); マップ.get(3); // val33

map.computeIfPresent(9, (num, val) -> null); マップ.containsKey(9); // 間違い

map.computeIfAbsent(23, num -> “val” + num); マップ.containsKey(23); // 真実

map.computeIfAbsent(3, num -> “bam”); マップ.get(3); // val33

次に、マップ内でキーと値がすべて一致する項目を削除する方法を示します。

コードは以下のように表示されます。

map.remove(3, “val3”); マップ.get(3); // val33

map.remove(3, “val33”); マップ.get(3); // ヌル

もう 1 つの便利な方法:

コードは以下のように表示されます。

map.getOrDefault(42、「見つかりません」); // 見つかりません

マップの要素を結合することも簡単です。

コードは以下のように表示されます。

map.merge(9, “val9”, (value, newValue) -> value.concat(newValue)); マップ.get(9); // val9

map.merge(9, “concat”, (value, newValue) -> value.concat(newValue)); マップ.get(9); // val9concat

Merge は、キー名が存在しない場合は挿入し、存在しない場合は元のキーに対応する値をマージしてマップに再挿入します。

九、日付API

Java 8 には、パッケージ java.time の下に日付と時刻 API の新しいセットが含まれています。新しい日付 API は、オープン ソースの Joda-Time ライブラリに似ていますが、まったく同じではありません。次の例は、この新しい API の最も重要な部分のいくつかを示しています。

クロック クロック

Clock クラスは、現在の日付と時刻にアクセスするためのメソッドを提供します。Clock はタイム ゾーンに依存するため、System.currentTimeMillis() を置き換えて現在のマイクロ秒数を取得するために使用できます。特定の時点は、古い java.util.Date オブジェクトの作成にも使用できる Instant クラスを使用して表すこともできます。

コードは以下のように表示されます。

クロック クロック = Clock.systemDefaultZone(); 長いミリス = Clock.millis();

インスタント インスタント = Clock.instant(); 日付legacyDate = Date.from(インスタント); // レガシー java.util.Date

タイムゾーンタイム ゾーン

新しい API では、タイムゾーンは ZoneId で表されます。タイムゾーンは、 の静的メソッドを使用して簡単に取得できます。タイム ゾーンは、UTS 時間との時差を定義します。これは、インスタント タイム ポイント オブジェクトとローカル日付オブジェクトの間で変換する場合に非常に重要です。

コードは以下のように表示されます。

System.out.println(ZoneId.getAvailableZoneIds()); // 利用可能なすべてのタイムゾーン ID を出力します

ZoneIdzone1 = ZoneId.of(“ヨーロッパ/ベルリン”); ZoneIdzone2 = ZoneId.of(“ブラジル/東部”); System.out.println(zone1.getRules()); System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]

LocalTime 現地時間

LocalTime は、午後 10 時や 17:30:15 など、タイムゾーン情報を含まない時刻を定義します。次の例では、前のコードで作成したタイムゾーンを使用して 2 つの現地時間を作成します。次に、時間を比較し、2 つの時間の差を時と分で計算します。

コードは以下のように表示されます。

LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2)); // 間違い

長い時間の間 = ChronoUnit.HOURS.between(now1, now2); 長い分の間 = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween); // -3 System.out.println( minutesBetween); // -239

LocalTime には、時刻文字列の解析など、オブジェクトの作成を簡素化するためのいくつかのファクトリ メソッドが用意されています。

コードは以下のように表示されます。

LocalTime 遅れ = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59

DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse(“13:37”, germanFormatter); System.out.println(leetTime); // 13:37

LocalDate ローカル日付

LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。

代码如下:

LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();

System.out.println(dayOfWeek); // FRIDAY 从字符串解析一个LocalDate类型和解析LocalTime一样简单:

代码如下:

DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse(“24.12.2014”, germanFormatter); System.out.println(xmas); // 2014-12-24

LocalDateTime 本地日期时间

LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。

代码如下:

LocalDateTime シルベスター = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // 水曜日

月 月 = sylvester.getMonth(); System.out.println(月); // 12月

長い分OfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println( minutesOfDay); // 1439

タイムゾーン情報が添付されていれば、特定の時点の Instant オブジェクトに変換できます。また、Instant の特定時点のオブジェクトは、昔ながらの java.util.Date に簡単に変換できます。

コードは以下のように表示されます。

インスタント インスタント = シルベスター .atZone(ZoneId.systemDefault()) .toInstant();

日付legacyDate = Date.from(インスタント); System.out.println(legacyDate); // 2014 年 12 月 31 日水曜日 23:59:59 CET

LocalDateTime の書式設定は、時刻と日付の書式設定と同じです。定義済みの書式を使用することに加えて、独自に書式を定義することもできます。

コードは以下のように表示されます。

DateTimeFormatter フォーマッタ = DateTimeFormatter .ofPattern(“MMM dd, yyyy - HH:mm”);

LocalDateTime 解析済み = LocalDateTime.parse(“2014 年 11 月 3 日 - 07:13”、フォーマッタ); 文字列 string = formatter.format(parsed); System.out.println(文字列); // 2014 年 11 月 3 日 - 07:13

java.text.NumberFormat とは異なり、新しい DateTimeFormatter は不変であるため、スレッドセーフです。

10. 注釈メモ

Java 8 では複数のアノテーションがサポートされています。その意味を理解するために例を見てみましょう。まず、ラッパー クラスのヒント アノテーションを定義して、特定のヒント アノテーションのセットを配置します。

コードは以下のように表示されます。

@interface ヒント { ヒント [] value(); }

@Repeatable(Hints.class) @interface Hint { String value(); }

Java 8 では、同じタイプの注釈を複数回使用できます。注釈を @Repeatable でマークするだけです。

例 1: ラッパー クラスをコンテナとして使用して複数のアノテーションを格納する (古い方法)

コードは以下のように表示されます。

@Hints({@Hint(“hint1”), @Hint(“hint2”)}) クラス パーソン {}

例 2: 複数のアノテーションの使用 (新しい方法)

コードは以下のように表示されます。

@Hint(“ヒント1”) @Hint(“ヒント2”) クラス人 {}

2 番目の例では、Java コンパイラーが @Hints アノテーションを暗黙的に定義します。これを知っておくと、リフレクションを使用してこの情報を取得するのに役立ちます。

コードは以下のように表示されます。

ヒントヒント = Person.class.getAnnotation(Hint.class); System.out.println(ヒント); // ヌル

ヒントヒント 1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2

即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。 另外Java 8的注解还增加到两种新的target上了:

代码如下:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}

关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如Arrays.parallelSort, StampedLock和CompletableFuture等等。

Java中重写和重载有哪些区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态

性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为

重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方

法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

方法重载的规则:

1.方法名一致,参数列表中参数的顺序,类型,个数不同。

2.重载与方法的返回值无关,存在于父类和子类,同类中。

3.可以抛出不同的异常,可以有不同修饰符

方法重写的规则:

1.参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致。

2.构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次

声明。

3. アクセス権は、親クラスでオーバーライドされたメソッドのアクセス権より低くすることはできません。

4. オーバーライドされたメソッドは、オーバーライドされたメソッドに関係なく、未チェックの例外 (UncheckedException、非実行時例外とも呼ばれる) をスローできます。

例外はスローされません。ただし、オーバーライドされたメソッドは、新しい必須例外、またはオーバーライドされたメソッドで宣言された例外よりも広範囲の必須例外をスローすることはできません。また、その逆も同様です。

できる。

インターフェースと抽象クラスの違いは何ですか

違う:

抽象クラス:

1. コンストラクターは抽象クラスで定義できます。

2. 抽象的なメソッドと具体的なメソッドが存在する可能性があります

3. インターフェース内のすべてのメンバーはパブリックです

4. メンバ変数は抽象クラスで定義可能

5. 抽象メソッドを持つクラスは抽象クラスとして宣言する必要があり、抽象クラスは必ずしも抽象メソッドを持つ必要はありません。

6. 抽象クラスには静的メソッドを含めることができます

7. クラスは 1 つの抽象クラスのみを継承できます。

インターフェース:

1. インターフェース内にコンストラクターを定義することはできません

2. すべてのメソッドは抽象メソッドです

3. 抽象クラスのメンバーは、プライベート、デフォルト、プロテクト、パブリックにすることができます。

4. インターフェースで定義されたメンバー変数は実際には定数です

5. インターフェース内に静的メソッドを含めることはできません

6. クラスは複数のインターフェースを実装できます

同じ:

1.インスタンス化できない

2. 抽象クラスとインターフェイス型を参照型として使用できる

3. クラスが特定の抽象クラスを継承するか、特定のインターフェイスを実装する場合、そのクラス内のすべての抽象メソッドを実装する必要があります。そうでない場合、クラスは引き続き必要です

抽象クラスとして宣言されている

クラスが継承されないことを宣言する方法と、そのクラスがどのようなシナリオで使用されるか

クラスが最終的に変更される場合、このクラスはサブクラスを持つことができず、他のクラスに継承することもできません。1 つのクラス内のすべてのメソッドを書き換える必要がなく、現在のクラスにサブクラスがない場合は、最終的に変更されたクラスを使用できます。

Javaの==とequalsの違いは何ですか

等しいと == の最大の違いは、一方がメソッドであり、もう一方が演算子であることです。

==: 比較対象のオブジェクトが基本データ型の場合は値が等しいかどうか比較され、比較対象のオブジェクトが参照データ型の場合はオブジェクトが比較されます。

アドレス値は等しいです。

equals(): 2 つのオブジェクトの内容が等しいかどうかを比較するために使用されます。

注: 基本データ型の変数に対しては、equals メソッドを使用することはできません。equals メソッドを書き換えない場合、比較は参照型の変数に対して行われます。

ボリュームが指すオブジェクトのアドレス。

String、StringBuffer、StringBuilderの違いと使用シナリオ

Java プラットフォームには、String と StringBuffer/StringBuilder という 2 種類の文字列が用意されており、どちらも文字列を保存および操作できます。

次のように。

1) String は読み取り専用の文字列です。つまり、String によって参照される文字列の内容は変更できません。初心者は次のような誤解をするかもしれません。

String str = “abc”;
str = “bcd”;

上記のように、文字列 str は明らかに変更できます。実際、str は単なる参照オブジェクトであり、文字列オブジェクト「abc」を指します。いいえ。

2 行のコードの意味は、str が新しい文字列「bcd」オブジェクトを指すようにすることです。「abc」オブジェクトは変更されていませんが、オブジェクトは変更されています。

手の届かない物体になってしまいました。

2) StringBuffer/StringBuilder で表される文字列オブジェクトは直接変更できます。

3) StringBuilder は Java5 で導入されました。StringBuffer とまったく同じですが、違いはシングルスレッド環境で使用されることです。

すべてのメソッドが synchronized によって変更されないため、理論的には StringBuffer よりも効率が高くなります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-xsAqGin6-1662355491057)(images/StringBuilder.png)]

Javaエージェントのいくつかの実現方法

最初のタイプ: 静的プロキシ。特定のクラスまたは特定のメソッドのみを静的にプロキシできます。推奨されません。機能は比較的弱いですが、コーディングは簡単です。

2 番目のタイプ: 動的プロキシ (プロキシ プロキシと CGLIB 動的プロキシを含む)

プロキシ プロキシは、JDK に組み込まれた動的プロキシです。

特徴: インターフェイス指向、サードパーティ依存の動的プロキシをインポートする必要がなく、複数の異なるインターフェイスを強化でき、リフレクションを通じて注釈を読み取る場合、インターフェイス上の注釈のみを読み取ることができます。

原則: インターフェイス指向、実装インターフェイス内の実装クラスによって定義されたメソッドのみを拡張できます。

インターフェースと実装を定義する

package com.proxy;

public interface UserService {
    public String getName(int id);

    public Integer getAge(int id);
}
package com.proxy;

public class UserServiceImpl implements UserService {
    @Override
    public String getName(int id) {
        System.out.println("------getName------");
        return "riemann";
    }

    @Override
    public Integer getAge(int id) {
        System.out.println("------getAge------");
        return 26;
    }
}
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    public Object target;

    MyInvocationHandler() {
        super();
    }

    MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getName".equals(method.getName())) {
            System.out.println("++++++before " + method.getName() + "++++++");
            Object result = method.invoke(target, args);
            System.out.println("++++++after " + method.getName() + "++++++");
            return result;
        } else {
            Object result = method.invoke(target, args);
            return result;
        }
    }
}
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main1 {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        InvocationHandler invocationHandler = new MyInvocationHandler(userService);
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),invocationHandler);
        System.out.println(userServiceProxy.getName(1));
        System.out.println(userServiceProxy.getAge(1));
    }
}

CGLIB 動的プロキシ

機能: 親クラスの動的プロキシ、サードパーティの依存関係をインポートする必要がある

原則: 親クラスに面し、最下層は親クラスを継承するサブクラスとメソッドの書き換えという形で機能拡張を実装します。

プロキシと CGLIB は非常に重要なプロキシ モードであり、Spring AOP の基礎となる実装の主な 2 つの方法です。

CGLIB のコア クラス:
net.sf.cglib.proxy.Enhancer – 主要な拡張クラス
net.sf.cglib.proxy.MethodInterceptor – 主要なメソッド インターセプト クラス。これは Callback インターフェイスのサブインターフェイスであり、ユーザーを必要とします。 net.sfを実装するためのcglib.proxy.MethodProxy – JDK の java.lang.reflect.Method クラスのプロキシ クラスで、次のようなソース オブジェクトメソッド
の呼び出しを簡単に実装できます。
(proxy, args);//最初の One パラメータはプロキシされたオブジェクトですが、無限ループの問題は発生しません。

net.sf.cglib.proxy.MethodInterceptor インターフェイスは、最も一般的なタイプのコールバック (コールバック) であり、メソッド呼び出しをインターセプト (インターセプト) するためにプロキシベースの AOP によってよく使用されます。このインターフェイスは、
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable を 1 つだけ定義します。

最初のパラメータはプロキシ オブジェクトで、2 番目と 3 番目のパラメータはそれぞれインターセプトされたメソッドとメソッド パラメータです。元のメソッドは、java.lang.reflect.Method オブジェクトまたは net.sf.cglib.proxy.MethodProxy オブジェクトを使用した通常のリフレクションを通じて呼び出すことができます。net.sf.cglib.proxy.MethodProxy の方が高速であるため、通常は net.sf.cglib.proxy.MethodProxy を使用することが好まれます。

package com.proxy.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
 
public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        System.out.println(method.getName());
        Object o1 = methodProxy.invokeSuper(o, args);
        System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
        return o1;
    }
}

package com.proxy.cglib;
 
import com.test3.service.UserService;
import com.test3.service.impl.UserServiceImpl;
import net.sf.cglib.proxy.Enhancer;
 
public class Main2 {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(cglibProxy);
 
        UserService o = (UserService)enhancer.create();
        o.getName(1);
        o.getAge(1);
    }
}

ハッシュコードとイコールの使い方

equals() は java.lang.Object から派生し、2 つのオブジェクトが等しいことを単純に検証するために使用されます。Object クラスで定義されたデフォルトの実装は、2 つのオブジェクトのオブジェクト参照をチェックして、それらが等しいことを検証するだけです。このメソッドをオーバーライドすることで、オブジェクトが等しいことを検証するための新しいルールをカスタマイズできます。ORM を使用して一部のオブジェクトを処理する場合は、メンバーを直接参照するのではなく、hashCode() オブジェクトと equals() オブジェクトでゲッターとセッターを必ず使用する必要があります。変数。

hashCode() は java.lang.Object から派生し、このメソッドは指定されたオブジェクトの一意の整数 (ハッシュ コード) を取得するために使用されます。このオブジェクトをハッシュ テーブルなどのデータ構造に保存する必要がある場合、この整数を使用してバケットの場所が決定されます。デフォルトでは、オブジェクトの hashCode() メソッドは、オブジェクトが存在するメモリ アドレスの整数表現を返します。hashCode() は、HashTable、HashMap、および HashSet によって使用されます。デフォルトでは、Object クラスの hashCode() メソッドは、このオブジェクトが格納されているメモリ アドレスの番号を返します。

ハッシュ ハッシュ アルゴリズムでは、ハッシュ テーブル内のレコードを見つけるのに O(1) がかかります。各レコードには独自のハッシュコードがあり、ハッシュ アルゴリズムは、ハッシュコードに従って適切な位置にレコードを配置します。レコードを検索するときは、最初にパスを渡します。ハッシュコードはレコードの場所をすばやく特定し、equals を使用してそれらが等しいかどうかを比較します。ハッシュコードが見つからない場合は等価ではなく、その要素はハッシュ テーブルに存在しません。見つかった場合でも、同じハッシュコードを持つ複数の要素の等価を実行するだけでよく、等しくない場合は、ハッシュテーブルにはまだ存在しません。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-7MwOJV0v-1662355491058)(images/HashMap1.7hashcodequals.png)]

HashMap と HashTable の違いと基礎となる実装

HashMap と HashTable の比較

  1. HashTable スレッド同期、HashMap 非スレッド同期。
  2. HashTable では <key, value> に null 値を含めることはできませんが、HashMap では <key, value> に null 値を含めることができます。
  3. HashTable は Enumeration を使用し、HashMap は Iterator を使用します。
  4. HashTable のハッシュ配列のデフォルト サイズは 11 で、増加方法は old*2+1 です。HashMap のハッシュ配列のデフォルト サイズは 16 で、増加方法は 2 の指数倍です。

5. jdk1.8 リスト + リンク リスト以降の HashMap、jdk1.8 リスト + リンク リスト、リンク リストの長さが 8 に達すると、赤黒ツリーに変換されます

6. HashMap リンク リストへのノードの挿入方法 Java 1.7 では、リンク リスト ノードの挿入にはヘッダー挿入方法が使用されます。Java 1.8では末尾挿入方式となります

7. Java1.8 の hash() では、ハッシュ値の上位ビット (最初の 16 ビット) がモジュロ計算に関与するため、計算結果の不確実性が増大し、ハッシュ衝突の確率が減少します。

HashMap 拡張の最適化:

展開後、1.7 は要素に対して再ハッシュ アルゴリズムを実行し、展開後のハッシュ テーブル内の各要素の位置を計算します。1.8 2 重展開メカニズムの助けにより、要素は位置を再計算する必要はありません。

JDK 1.8 では、容量拡張時に JDK 1.7 のように各要素のハッシュ値を再計算するのではなく、高次演算 **(e.hash & oldCap)** を使用して要素の移動が必要かどうかを判断します。 key1 は次のとおりです。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-3GI9glGx-1662355491058)(images/1621414916379-1621752756248.png)]

e.hash & oldCap を使用して得られた結果、上位ビットは 0、結果が 0 の場合、展開中に要素の位置が変更されないことを意味し、キー 2 の情報は次のとおりです

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-bi1yJyfi-1662355491059)(images/1621414931120-1621752756248.png)]

上位ビットは 1 です。結果が 1 の場合、拡張中に要素の位置が変更され、新しい添字の位置は元の添字の位置 + 元の配列の長さ **ハッシュマップ** に等しいことを意味します。1.7のようにすべての位置を再計算する必要はありません。

ハッシュマップが 2 倍大きいのはなぜですか?

ソースコードを表示する

要素を格納する際、要素の位置に (n-1)&hash というアルゴリズムがあり、ここでは & ビット演算子である hash&(newCap-1) が使用されます。

HashMapの容量が16、バイナリ値が10000、(n-1)のバイナリ値が01111の場合、ハッシュ値の計算結果は以下のようになります。

HashMapの容量が2のn乗でない場合を見てみましょう。容量が10の場合、バイナリ値は01010、(n-1)のバイナリ値は01001となります。同じ要素をその結果は

同じ結果を得るために 3 つの異なる要素が & 操作に入力されており、深刻なハッシュ衝突が発生していることがわかります。

n の値が 2 の N 乗である場合にのみ、& ビット演算を実行するときに、すべての計算を行わずに最後の数桁のみを確認できます。

ハッシュマップスレッド安全な方法?

HashMap はスレッド セーフではなく、プログラムを作成するときにいくつかのメソッドで回避する必要があることがよくありますが、実際、JDK は HashMap をスレッド セーフにサポートするための 2 つのメソッドをネイティブに提供しています。

方法 1: Collections.synchronizedMap() を通じて新しい Map を返します (スレッドセーフです)。返されるのは HashMap ではなく Map 実装であるため、全員がインターフェイス ベースのプログラミングに慣れる必要があります。

方法 2: HashMap を書き換えた詳細は java.util.concurrent.ConcurrentHashMap を参照してください。この方法は方法 1 に比べて大幅に改良されました。

方法 1 の特徴:

Collections.synchronizedMap() を使用して、すべての安全でない HashMap メソッドをカプセル化します。toString と hashCode もカプセル化されます。カプセル化の重要なポイントは 2 つあります。1) 相互排他のために従来の同期を使用します。2) プロキシ モードで新しいクラスが作成されました。これは Map インターフェースも実装します ハッシュマップでは、オブジェクトを同期ロックするため、最初にロックを適用するスレッドがブロックに入り、起動するのを待ちます 利点: コード実装 非常にシンプルで理解しやすいです短所: ロックの観点から見ると、このメソッドは基本的に可能な最大のコード ブロックをロックするロック メソッドを直接使用するため、パフォーマンスが比較的低くなります。

メソッド 2 の特徴:

HashMap は書き直されており、主な変更点は次のとおりです. 新しいロック メカニズムを使用して HashMap を複数の独立したブロックに分割し、同時実行性が高い場合のロック競合の可能性を低減します おそらく、NonfairSync が使用されます. この機能は、原子性と相互排除を保証する CAS 命令複数のスレッドが同じセグメント上で動作する場合、1 つのスレッドのみが実行されます。

利点: 相互排除を必要とするコード セグメントが少なくなり、パフォーマンスが向上します。ConcurrentHashMap はマップ全体を複数のブロックに分割し、ロックの衝突の可能性が大幅に減少し、パフォーマンスが向上します。欠点: コードが煩雑になります。

Java例外処理メソッド

Java はオブジェクト指向メソッドを通じて例外を処理します。メソッドが例外をスローすると、システムは例外オブジェクトに基づいて例外を処理する適切な例外ハンドラー (例外ハンドラー) を自動的に見つけ、さまざまな例外を分類し、適切なインターフェースを提供します。Java では、すべての例外はペアです

オブジェクト。Throwable クラスまたはそのサブクラスのインスタンスです。メソッドで例外が発生すると、例外情報を含む例外オブジェクトがスローされ、このオブジェクトを呼び出すメソッドは例外をキャッチして処理できます。Java の例外処理は、try、catch、throw、throws、finally の 5 つのキーワードによって実装されます。

Java アプリケーションの例外処理メカニズムは、例外の宣言、例外のスロー、および例外のキャッチに分かれています。

throw和throws的区别:
(1)位置不同:
throw:方法内部
throws: 方法的签名处,方法的声明处

(2)内容不同:
throw+异常对象(检查异常,运行时异常)
throws+异常的类型(可以多个类型,用,拼接)

(3)作用不同:
throw:异常出现的源头,制造异常。
throws:在方法的声明处,告诉方法的调用者,这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理:
要么自己处理要么再继续向外抛出异常

1.throws声明异常

通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下

去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。注意

非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。

​ 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误

2.throw抛出异常

如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。 throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。

3.trycatch捕获异常

程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。如何选择异常类型

可以根据下图来选择是捕获异常,声明异常还是抛出异常

自定义异常在生产中如何应用

Java には豊富な例外処理クラスが用意されていますが、プロジェクトではカスタム例外が使用されることが多く、その主な理由は、Java が提供する例外クラスでは実際のニーズを満たせない場合があるためです。たとえば、次のような状況が考えられます。
1. システム内の一部のエラーは Java 構文に準拠していますが、ビジネス ロジックには準拠していません。

2. 階層化されたソフトウェア構造では、システムの他のレベルでの例外は通常、プレゼンテーション層でキャプチャされ、処理されます。

IOC コンテナを実装するにはどうすればよいですか?

IOC (Inversion of Control) とは、制御の反転を意味しますが、技術ではなく設計思想です。IOC とは、従来のオブジェクト内部の直接制御の代わりに、設計したオブジェクトをコンテナ コントロールに引き渡すことを意味します。

従来のプログラミングでは、オブジェクト内の new を通じてオブジェクトを直接作成し、プログラムが依存オブジェクトを積極的に作成しますが、IOC にはオブジェクトを作成するための特別なコンテナがあります。つまり、IOC コンテナがオブジェクトの作成を制御します。

従来のアプリケーションでは、オブジェクトをアクティブに制御して依存オブジェクトを直接取得します。これが順方向ローテーションであり、その逆はコンテナが依存オブジェクトの作成と注入を支援します。このプロセス中、コンテナは依存オブジェクトの注入レベルを見つけるのに役立ちます。オブジェクトの場合、オブジェクトは依存オブジェクトを受動的に受け入れるだけです。

1. まず、後続のプロセスで特定のオブジェクトを保存しやすくするために、マップ構造のコレクションを含む基本的なコンテナ オブジェクトを準備します。

2. 設定ファイルを読み取るか、アノテーションを分析し、作成する必要がある Bean オブジェクトを BeanDefinition オブジェクトにカプセル化し、コンテナに格納します。

3. コンテナは、リフレクションを通じてカプセル化された BeanDefinition オブジェクトをインスタンス化し、オブジェクトのインスタンス化を完了します。

4. オブジェクトの初期化操作を実行します。つまり、クラスに対応する属性値を設定します。つまり、依存性注入を実行し、オブジェクト全体の作成を完了し、マップ構造に格納される完全な Bean オブジェクトになります。コンテナの

5. コンテナオブジェクトを通じてオブジェクトを取得し、オブジェクトの取得と論理処理を実行します。

6. 破棄操作を提供します。オブジェクトが使用されない場合、またはコンテナが閉じられている場合、不要なオブジェクトは破棄されます。

春についてのあなたの理解について教えてください。

公式サイトアドレス:https://spring.io/projects/spring-framework#overview

圧縮パッケージのダウンロードアドレス: https://repo.spring.io/release/org/springframework/spring/

ソースアドレス: https://github.com/spring-projects/spring-framework

Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs. As of Spring Framework 5.1, Spring requires JDK 8+ (Java SE 8+) and provides out-of-the-box support for JDK 11 LTS. Java SE 8 update 60 is suggested as the minimum patch release for Java 8, but it is generally recommended to use a recent patch release.

Spring supports a wide range of application scenarios. In a large enterprise, applications often exist for a long time and have to run on a JDK and application server whose upgrade cycle is beyond developer control. Others may run as a single jar with the server embedded, possibly in a cloud environment. Yet others may be standalone applications (such as batch or integration workloads) that do not need a server.

Spring is open source. It has a large and active community that provides continuous feedback based on a diverse range of real-world use cases. This has helped Spring to successfully evolve over a very long time.

Spring 使创建 Java 企业应用程序变得更加容易。它提供了在企业环境中接受 Java 语言所需的一切,,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并可根据应用程序的需要灵活地创建多种体系结构。 从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8+),并且已经为 JDK 9 提供了现成的支持。

Spring支持各种应用场景, 在大型企业中, 应用程序通常需要运行很长时间,而且必须运行在 jdk 和应用服务器上,这种场景开发人员无法控制其升级周期。 其他可能作为一个单独的jar嵌入到服务器去运行,也有可能在云环境中。还有一些可能是不需要服务器的独立应用程序(如批处理或集成的工作任务)。

Spring 是开源的。它拥有一个庞大而且活跃的社区,提供不同范围的,真实用户的持续反馈。这也帮助Spring不断地改进,不断发展。

春の核心は何だと思いますか?

Spring はオープンソースのフレームワークです。

Spring は、エンタープライズ開発を簡素化し、開発をよりエレガントかつ簡潔にするために生まれました。

Spring はIOCおよびAOPコンテナ フレームワークです。

IOC: 制御の反転

AOP: アスペクト指向プログラミング

コンテナ: 水の入ったバケツを使用するのと同じように、アプリケーション オブジェクトのライフ サイクルを格納して管理します。春はバケツ、オブジェクトは水です

スプリングを使用するメリットについて教えてください。

1. Spring は、DI、AOP、定型コードの排除を通じてエンタープライズレベルの Java 開発を簡素化します

2. Spring フレームワークに加えて、コア フレームワーク上に構築された巨大なエコシステムがあり、Spring を Web サービス、REST、モバイル開発、NoSQL などのさまざまな分野に拡張します。

3. 低侵入設計、コード汚染が極めて低い

4. さまざまなアプリケーション サーバーから独立して、Spring フレームワークに基づくアプリケーションは、Write Once、Run Anywhere の約束を真に実現できます。

5. Spring の IoC コンテナは、ビジネス オブジェクトの置換の複雑さを軽減し、コンポーネント間の分離を改善します。

6. Spring の AOP サポートにより、セキュリティ、トランザクション、ログなどのいくつかの一般的なタスクの集中処理が可能になり、再利用性が向上します。

7. Spring の ORM および DAO は、サードパーティの永続層フレームワークとの適切な統合を提供し、基盤となるデータベース アクセスを簡素化します。

8. Spring の高度なオープン性は、アプリケーションが Spring に完全に依存することを強制するものではなく、開発者は Spring フレームワークの一部またはすべてを自由に選択できます。

Spring はどのようにして開発を簡素化するのでしょうか?

POJOベースの軽量かつ低侵襲プログラミング

依存関係の注入とインターフェイス指向による疎結合

側面と規約に基づいた宣言型プログラミング

アスペクトとテンプレートを通じて定型コードを削減

Aop についてのあなたの理解について教えてください。

AOP の正式名称はアスペクト指向プログラミングです。デカップリングのために生まれました。デカップリングは、プログラマーがコーディングと開発のプロセスで追求してきた状態です。AOP は、ビジネス クラスの分離でデカップリングを確実に実現します。そこには、いくつかの核となる概念があります。

  • アスペクト: 複数のオブジェクトにまたがる可能性のある、懸念事項のモジュール化を指します。トランザクション管理は、エンタープライズ Java アプリケーションにおける横断的な問題の一例です。Spring AOP では、一般的なクラスベースのスキーマ (スキーマベースのアプローチ) または通常のクラスのアノテーション@Aspect(@AspectJ アノテーション) を使用してアスペクトを実装できます。

  • 結合ポイント: メソッドが呼び出されるときや例外が処理されるときなど、プログラムの実行における特定のポイント。Spring AOP では、ジョインポイントは常にメソッドの実行を表します。

  • アドバイス: アスペクトの特定の結合ポイントで実行されるアクション。通知には、「前後」、「前」、「後」など、いくつかの種類があります。通知の種類については後の章で説明します。Spring を含む多くの AOP フレームワークは、通知モデルとしてインターセプターを使用し、参加ポイントを中心としたインターセプターのチェーンを維持します。

  • ポイントカット: 結合ポイントに一致するアサーション。アドバイスはポイントカット式に関連付けられており、このポイントカットを満たす結合ポイントで動作します (たとえば、特定の名前のメソッドが実行されるとき)。ポイントカット式が結合ポイントとどのように一致するかが AOP の中心です。Spring はデフォルトで AspectJ ポイントカット セマンティクスを使用します。

  • はじめに: 追加のメソッドまたは型のフィールドを宣言します。Spring では、推奨されたオブジェクトに新しいインターフェイス (および対応する実装) を導入できます。たとえば、インポートを使用して、キャッシュ メカニズムを簡素化するために Bean にIsModifiedインターフェイス(AspectJ コミュニティでは、インポートは内部型宣言 (inter) とも呼ばれます)。

  • ターゲット オブジェクト: 1 つ以上の側面によってアドバイスされるオブジェクト。推奨オブジェクトとも呼ばれます。Spring AOP はランタイム プロキシを通じて実装されるため、このオブジェクトは常にプロキシされたオブジェクトになります。

  • AOP プロキシ (AOP プロキシ): AOP フレームワークによって作成されたオブジェクトは、アスペクト コントラクト (通知メソッドの実行などの機能を含む) を実装するために使用されます。Spring では、AOP プロキシは JDK 動的プロキシまたは CGLIB プロキシになります。

  • ウィービング: アスペクトを他のアプリケーション タイプまたはオブジェクトに接続し、アドバイスされるオブジェクトを作成するプロセス。このプロセスは、コンパイル時 (AspectJ コンパイラーなどを使用)、クラスロード時、または実行時に実行できます。Spring は、他の純粋な Java AOP フレームワークと同様に、実行時に織り込まれます。

    これらの概念はあまりにも学術的ですが、もっと簡単に説明すると、実際は非常に簡単です。

    システムはさまざまなコンポーネントで構成されており、各コンポーネントは特定の機能を担当しますが、ログ、トランザクション、権限などのコア サービス コンポーネントなど、ビジネスに関係のないコンポーネントも多数存在します。サービス コンポーネントは多くの場合、特定のビジネス ロジックに統合されます。特定のビジネス ロジック操作ごとにそのようなコードを追加すると、コードが冗長すぎることは明らかなので、これらの共通のコード ロジックをアスペクトに抽象化してから注入する必要があります。それらを対象オブジェクト(特定業務)に組み込む、AOPはこのような考え方に基づいて実装されており、動的プロキシによりアスペクトに注入する必要のあるオブジェクトをプロキシし、呼び出し時に公開ロジックを直接追加するというものです。元のビジネスのロジック コードを変更する必要はなく、元のビジネス ロジックに基づいていくつかの機能拡張を行うだけで済みます。

IOCについてのあなたの理解について教えてください。

	IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
	IOC与大家熟知的依赖注入同理,. 这是一个通过依赖注入对象的过程 也就是说,它们所使用的对象,是通过构造函数参数,工厂方法的参数或这是从工厂方法的构造函数或返回值的对象实例设置的属性,然后容器在创建bean时注入这些需要的依赖。 这个过程相对普通创建对象的过程是反向的(因此称之为IoC),bean本身通过直接构造类来控制依赖关系的实例化或位置,或提供诸如服务定位器模式之类的机制。

このプロセスがわかりにくい場合は、結婚相談所と自分で彼女を見つけるプロセスを想像してください。このプロセスが理解できれば、上記の質問に答えることができます。

1、谁控制谁:在之前的编码过程中,都是需要什么对象自己去创建什么对象,有程序员自己来控制对象,而有了IOC容器之后,就会变成由IOC容器来控制对象,
2、控制什么:在实现过程中所需要的对象及需要依赖的对象
3、什么是反转:在没有IOC容器之前我们都是在对象中主动去创建依赖的对象,这是正转的,而有了IOC之后,依赖的对象直接由IOC容器创建后注入到对象中,由主动创建变成了被动接受,这是反转
4、哪些方面被反转:依赖的对象

BeanFactory と ApplicationContext の違いは何ですか

同じ:

  • Spring は 2 つの異なる IOC コンテナーを提供します。1 つは BeanFactory で、もう 1 つは ApplicationContext で、どちらも Java インターフェースです。ApplicationContext は BeanFactory から継承します (ApplicationContext は ListableBeanFactory から継承します)。
  • どちらも XML 属性の構成に使用でき、属性の自動挿入もサポートしています。
  • ListableBeanFactory は BeanFactory を継承し、BeanFactory と ApplicationContext はどちらも getBean("bean name") を使用して Bean を取得する方法を提供します。

違う:

  • getBean() メソッドを呼び出すと、BeanFactory は Bean をインスタンス化するだけですが、ApplicationContext はコンテナの起動時にシングルトン Bean をインスタンス化し、getBean() メソッドがインスタンス化されるのを待ちません。
  • BeanFactory は国際化、つまり i18n をサポートしていませんが、ApplicationContext は国際化をサポートします。
  • BeanFactory と ApplicationContext のもう 1 つの違いは、リスナーとして登録された Bean にイベントを発行できることです。
  • BeanFactoryのコア実装はXMLBeanFactory、ApplicationContextのコア実装はClassPathXmlApplicationContextで、WebApplicationContextを使用し、Webコンテナの環境にgetServletContextメソッドを追加します。
  • 自動インジェクションを使用してBeanFactoryを使用する場合はAutoWiredBeanPostProcessorをAPIに登録する必要がありますが、ApplicationContextを使用する場合はXMLを使用して設定できます。
  • つまり、BeanFactory は基本的な IOC および DI 機能を提供し、ApplicationContext は高度な機能を提供します。BeanFactory はテストおよび非運用用途に使用できますが、ApplicationContext はより豊富なコンテナ実装であり、BeanFactory よりも優れているはずです。

春豆のライフサイクルを簡単に説明してください。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-N4JDWJyP-1662355491059)(images/bean lifecycle.png)]

1. Bean オブジェクトをインスタンス化する

オブジェクトはリフレクションによって作成されますが、このときはヒープ領域にスペースを適用するだけの作成であり、属性はすべてデフォルト値です。

2. オブジェクトのプロパティを設定する

オブジェクトの属性の値を設定します

3. Aware 関連のインターフェイスを確認し、関連する依存関係を設定します。

オブジェクトがコンテナ内のオブジェクトを参照する必要がある場合は、認識インターフェースのサブクラスメソッドを呼び出して統一的な設定を行う必要があります

4. BeanPostProcessorの前処理

生成されたBeanオブジェクトに対して前処理作業を実行します

5. InitializingBean のサブクラスかどうかを確認して、afterPropertiesSet メソッドを呼び出すかどうかを決定します

現在の Bean オブジェクトが InitializingBean インターフェイスで設定されているかどうかを確認し、プロパティ設定などの基本的な作業を実行します

6. カスタムの init-method が設定されているかどうかを確認します。

現在の Bean オブジェクトが初期化メソッドを定義している場合は、ここで初期化メソッドを呼び出します

7. BeanPostProcessor の後処理

生成されたBeanオブジェクトに対して後処理作業を実行します

8. 必要な破壊関連のコールバック インターフェイスを登録します。

オブジェクトの破棄を容易にするために、ここでキャンセル用のコールバック インターフェイスが呼び出され、オブジェクトの破棄が容易になります。

9. Bean オブジェクトを取得して使用する

コンテナを通じてオブジェクトを取得して使用する

10. DisposableBeanインターフェースを実装するかどうか

DisposableBean インターフェースが実装されているかどうかを判断し、特定のメソッドを呼び出してオブジェクトを破棄します

11. カスタムの破棄メソッドが設定されていますか?

現在の Bean オブジェクトが破棄メソッドを定義している場合は、ここで破棄メソッドを呼び出します

Spring でサポートされる Bean スコープは何ですか?

①シングルトン

この属性を使用して Bean を定義する場合、IOC コンテナは Bean インスタンスを 1 つだけ作成し、IOC コンテナは毎回同じ Bean インスタンスを返します。

②試作

この属性を使用して Bean を定義すると、IOC コンテナは複数の Bean インスタンスを作成し、それぞれが新しいインスタンスを返すことができます。

③リクエスト

この属性は HTTP リクエストでのみ機能します。この属性を使用して Bean を定義すると、HTTP リクエストごとに WebApplicationContext 環境に適した新しい Bean が作成されます。

④セッション

この属性は HTTP セッションにのみ使用され、同じセッションが Bean インスタンスを共有します。異なるセッションは異なるインスタンスを使用します。

⑤ グローバルセッション

この属性は HTTP セッションにのみ使用され、セッション スコープとは異なり、すべてのセッションは Bean インスタンスを共有します。

Spring フレームワークのシングルトン Bean はスレッドセーフですか?

Spring の Bean オブジェクトはデフォルトでシングルトンであり、フレームワークは Bean のマルチスレッド カプセル化を実行しません。

Bean がステートフルである場合、開発者はスレッドの安全性を確保する必要があります。最も簡単な方法は、Bean オブジェクトの各リクエストが新しいオブジェクトの作成と同等になるように、Bean のスコープを変更し、シングルトンをプロトタイプに変更することです。スレッドの安全性を確保するために

ステートフルとはデータを保存する機能です

ステートレスというのはデータを保存しないという意味ですが、よく考えてみるとコントローラー、サービス、daoはスレッドセーフではなく、内部のメソッドを呼び出しているだけで、複数のスレッドでインスタンスを呼び出すメソッドはコピーされてしまいます。これは独自のスレッドの作業メモリであり、最も安全です。

したがって、使用する場合は、Bean 内でステートフルなインスタンス変数やクラス変数を宣言しないでください。必要な場合は、ThreadLocal を使用して変数をスレッドに対してプライベートにすることも推奨します。インスタンス変数やクラス変数の場合は、 Bean は複数に存在する必要があります。複数のスレッド間で共有されている場合、スレッド同期を実現するには、synchronized、lock、cas およびその他のメソッドのみを使用できます。

Spring フレームワークで使用されるデザイン パターンとアプリケーション シナリオ

1. ファクトリ モード。さまざまな BeanFactory および ApplicationContext の作成で使用されます。

2. テンプレート モードは、さまざまな BeanFactory および ApplicationContext の実装でも使用されます。

3. プロキシ モード、Spring AOP は AspectJ AOP を使用して実現します! AspectJ AOP の最下層は動的プロキシを使用します

4. ストラテジー モード、さまざまなメソッドを使用してリソース ファイルをロードする方法: ClassPathResourcece、FileSystemResource、ServletContextResource、UrlResource ですが、それらにはすべて共通の言い訳があります Resource; Aop の実装では、2 つの異なるメソッドが使用されます。プロキシと CGLIB プロキシ

5. シングルトン モード (Bean 作成時など)。

6. オブザーバーモード、SpringのApplicationEvent、ApplicationListener、ApplicationEventPublisher

7. アダプター モード、MethodBeforeAdviceAdapter、ThrowsAdviceAdapter、AfterReturningAdapter

8. Decorator モード、ソースコード内に Wrapper または Decorator を含むタイプはすべて

春取引の実施原理は何ですか?

Spring フレームワークを使用する場合、トランザクションを実装するには 2 つの方法があります。1 つはユーザーがコードを通じてトランザクションの処理ロジックを制御するプログラムによるトランザクションで、もう 1 つは @Transactional アノテーションによって実装される宣言型トランザクションです。

本当はトランザクションの動作はデータベースによって制御されるべきですが、ユーザーがビジネスロジックを操作しやすくするために、Springではトランザクション機能の実装を拡張しました。一般にプログラムによるトランザクションなどはほとんど使用しません。 @Transactional アノテーションの追加は実装に使用されます。このアノテーションが追加されると、トランザクションの自動機能が閉じられ、それを制御するための Spring フレームワークが存在します。

実際、トランザクション操作は AOP の核となる実装です。メソッドに @Transactional アノテーションが付けられると、Spring はこのクラスに基づいてプロキシ オブジェクトを生成し、このプロキシ オブジェクトを Bean として使用します。このプロキシ オブジェクトのメソッドを使用するとき、トランザクション処理は、最初にトランザクションがリレーションシップに自動的に投入され、その後、特定のビジネス ロジックが実行されます。実行ロジックに異常がなければ、プロキシ ロジックが直接投入されます。もちろん、どの例外をロールバックするかはユーザーが制御できます。

トランザクションインターセプター

Spring トランザクションの分離レベルは何ですか?

Spring のトランザクション分離レベルはデータベースの分離レベルであり、次のタイプがあります。

コミットされていない読み取り

コミットされた読み取り

反復可能な読み取り

シリアル化可能

設定時に、データベースと Spring コードの分離レベルが異なる場合は、Spring の設定がメインになります。

Spring のトランザクション伝播メカニズムとは何ですか?

複数のトランザクション メソッドが相互に呼び出した場合、これらのメソッド間でトランザクションがどのように伝播するかについて、Spring はトランザクションの正常な実行を保証する 7 つの異なる伝播機能を提供します。

必須: デフォルトの伝播機能。現在のトランザクションがない場合は新しいトランザクションを作成し、現在のトランザクションがある場合はこのトランザクションに参加します。

サポート: 現在トランザクションがある場合は現在のトランザクションに参加し、現在のトランザクションがない場合は非トランザクションで実行します。

必須: 現在トランザクションが存在する場合は、現在のトランザクションに参加し、現在のトランザクションが存在しない場合は、例外をスローします。

REQUIRED_NEW: 新しいトランザクションを作成します。現在のトランザクションがある場合は、トランザクションを一時停止します。

NOT_SUPPORTED: 非トランザクション方式で実行します。現在のトランザクションがある場合は、現在のトランザクションを一時停止します。

NEVER: トランザクションを使用しません。現在のトランザクションが存在する場合、例外がスローされます。

NESTED: 現在のトランザクションが存在する場合は、ネストされたトランザクションで実行します。それ以外の場合、REQUIRED の操作は同じです。

NESTED と REQUIRED_NEW の違い:

REQUIRED_NEW は新しいトランザクションを作成し、新しく開始されたトランザクションは元のトランザクションとは何の関係もありませんが、NESTED は現在のトランザクションがある場合にネストされたトランザクションを開始します。NESTED の場合、親トランザクションがロールバックすると、子トランザクションもロールバックされ、REQUIRED_NEW の場合、新しく開かれたトランザクションに影響を与えることなく、元のトランザクションがロールバックされます。

NESTED と REQUIRED の違い:

REQUIREDの場合、呼び出し元にトランザクションがある場合、呼び出し先と呼び出し元は同じトランザクションを使用し、呼び出し先に例外が発生した場合、同じトランザクションが共有されるため、例外の有無に関係なくトランザクションはロールバックされます。 NESTED では、呼び出し先で例外が発生した場合、呼び出し元が例外をキャッチできるため、サブトランザクションのみがロールバックされ、親トランザクションはロールバックされない場合があります。

Spring トランザクションはいつ失敗しますか?

1. Bean オブジェクトは Spring コンテナーによって管理されません

2. メソッドのアクセス修飾子がパブリックではありません

3. 自己呼び出し問題

4. データ ソースがトランザクション マネージャーで構成されていません。

5. データベースはトランザクションをサポートしていません

6. 例外がキャッチされる

7. 例外タイプエラーまたは設定エラー

Bean の自動配線とは何ですか?またそのメソッドは何ですか?

Bean の自動アセンブリとは、注入時に特定のルールとメソッドを通じてコン​​テナ内で Bean の属性値が検索され、特定のオブジェクト属性に設定されることを意味します。主なメソッドは次の 5 つです。

no – デフォルトでは、自動構成は「ref」属性を通じて手動で設定されます。プロジェクトで最も一般的に使用される
byName – プロパティ名に基づく自動アセンブリ。Bean の名前が別の Bean のプロパティ名と同じ場合、その Bean は自己接続されます。
byType – データ型によるオートワイヤー Bean のデータ型が他の Bean プロパティのデータ型である場合、互換性があり、自動的にアセンブルされます。
コンストラクター – コンストラクター パラメーターの byType メソッド内。
autodetect – デフォルトのコンストラクターが見つかった場合は、「自動配線用のコンストラクター」を使用し、それ以外の場合は、「タイプによる自動配線」を使用します。

spring、springmvc、springboot の違いは何ですか?

spring和springMvc:

  1. Spring はワンストップの軽量 Java 開発フレームワークであり、コアは制御の反転 (IOC) とアスペクト指向 (AOP) であり、WEB 層 (springMvc)、ビジネス層 (Ioc)、永続層 (jdbcTemplate) の開発に使用されます。など、さまざまな構成ソリューションを提供します。

  2. springMvc は Spring をベースにした MVC フレームワークで、主に Web 開発のパス マッピングとビューのレンダリングを扱い、Spring フレームワークの WEB 層開発の部分に属します。

springMvc と springBoot:

1. springMvc はエンタープライズ WEB 開発用の MVC フレームワークに属し、フロントエンド ビューの開発、ファイル構成、バックグラウンド インターフェイス ロジックの開発などをカバーします。XML、構成、その他の構成は比較的面倒で複雑です。

2. springMvc フレームワークと比較して、springBoot フレームワークは、フロントエンド ビューの開発ではなく、マイクロサービスのバックグラウンド インターフェイスの開発に重点を置いていると同時に、デフォルトの構成に従い、プラグインの構成プロセスを簡素化します。 XML を設定する必要があり、springmvc プロセスと比較して設定が大幅に簡素化されます。

要約:

1. Spring フレームワークはファミリーのようなもので、boot、security、jpa などの多くの派生製品があります。しかし、それらの基盤はすべて Spring の ioc、aop などです。ioc は依存性注入コンテナを提供し、aop はセクション指向のプログラミングを解決し、その 2 つをベースにして他の拡張製品の高度な機能を実現します。

2. springMvc は主に WEB 開発の問題を解決する、Servlet ベースの MVC フレームワークであり、XML 構成を通じて、フロントエンド ビューとバックエンド ロジックが統合された方法で開発されます。

3. Spring の設定は非常に複雑であるため、さまざまな XML、JavaConfig、サーブレットの扱いが面倒ですが、開発者の使用を簡素化するために、springBoot フレームワークが独創的に起動されます。 springMvc の構成プロセス; しかし、その違い springMvc の場合、springBoot は単一のマイクロサービス インターフェイスの開発に焦点を当て、フロントエンドから分離します。springBoot は springMvc のフロントエンドおよびバックエンドと一緒に開発することもできますが、これはオリジナルと少し矛盾します。 springBoot フレームワークの意図。

springmvc ワークフローとは何ですか?

リクエストが開始されると、リクエストはフロントエンド コントローラーによってインターセプトされ、リクエスト パラメーターに従ってプロキシ リクエストが生成され、リクエストに対応する実際のコントローラーが見つかり、コントローラーはリクエストを処理し、データ モデルを作成します。データベースにアクセスし、モデルを中央コントローラーに応答します。コントローラーはモデルとビューを使用してビューの結果をレンダリングし、結果を中央コントローラーに返し、結果をリクエスターに返します。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-iqBbFVyG-1662355491059)(images/springmvc running process.jpg)]

1. DispatcherServlet はフロント コントローラーを表し、SpringMVC 全体のコントロール センターです。ユーザーがリクエストを行うと、DispatcherServlet がリクエストを受信して​​インターセプトします。
2. HandlerMapping はプロセッサ マッピングです。DispatcherServlet は HandlerMapping を呼び出し、HandlerMapping はリクエスト URL に従って Handler を探します。
3. プロセッサ実行チェーンに戻り、URL に従ってコントローラを見つけ、解析された情報を DispatcherServlet に渡します。
4. HandlerAdapter はプロセッサ アダプタを表し、特定のルールに従ってハンドラを実行します。
5. ハンドラーを実行して特定のプロセッサを見つけます。
6. コントローラーは、ModelAndView などの特定の実行情報を HandlerAdapter に返します。
7. HandlerAdapter は、ビューの論理名またはモデルを DispatcherServlet に渡します。
8. DispatcherServlet は、ビュー リゾルバー (ViewResolver) を呼び出して、HandlerAdapter によって渡された論理ビュー名を解決します。
9. ビュー パーサーは、解析された論理ビュー名を DispatcherServlet に渡します。
10. DispatcherServlet は、ビュー リゾルバーによって解析されたビュー結果に従って特定のビューを呼び出し、レンダリングを試みます
11. 応答データをクライアントに返します

springmvc の 9 つのコンポーネントとは何ですか?

1.HandlerMappingは
リクエストに従って対応するプロセッサを見つけます。ハンドラー (コントローラー) には 2 つの形式があるため、1 つはクラスベースのハンドラー、もう 1 つはメソッドベースのハンドラー (これが一般的に使用されるもの) です。

2. HandlerAdapter は
ハンドラーのアダプターを呼び出します。Handler(Controller)をツールとみなすと、HandlerAdapterはワーカーに相当します。

3.HandlerExceptionResolverの
例外処理

4. ViewResolver は
、String 型のビュー名と Locale を View 型のビューに解決するために使用されます。

5. RequestToViewNameTranslator のハンドラー (コントローラー) は、
リクエストから viewName を取得する必要がある void メソッドなど、処理後の戻り値の型を設定しません。

6.LocaleResolver は
リクエストからロケールを解決します。Locale は、zh-cn などの領域を表し、さまざまな領域のユーザーに対して異なる結果を表示します。これは i18n です (SpringMVC には特定のインターセプタ LocaleChangeInterceptor があります)。

7. ThemeResolver
テーマ分析。携帯電話のテーマ、異なる UI、CSS などを変更するのと似ています。

8. MultipartResolver は
アップロード要求を処理し、通常の要求を MultipartHttpServletRequest にカプセル化します。

9. FlashMapManager は
FlashMap の管理に使用され、FlashMap はリダイレクト リダイレクトでパラメーターを渡すために使用されます。

SpringBoot の自動構成の原理は何ですか?

前回の講座では、springboot の起動プロセスについて説明しましたが、実際、面接プロセスで最もよく聞かれる質問は自動アセンブリの原理であり、自動アセンブリは起動プロセス中に完了しますが、最初は選択的にスキップします。自動組み立てのプロセスについては、以下で詳しく説明します。

1. springboot の起動プロセスにコンテキストを作成するステップがありますが、覚えていない場合は、次のコードを参照してください。

public ConfigurableApplicationContext run(String... args) {
    
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
    
    
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] {
    
     ConfigurableApplicationContext.class }, context);
            //此处完成自动装配的过程
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
    
    
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
    
    
			listeners.running(context);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

2. prepareContext メソッドでロード メソッドを見つけ、レイヤーごとに内側にクリックして、最終的なロード メソッドを見つけます。

//prepareContext方法
	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    
    
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
    
    
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
    
    
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
    
    
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
    
    
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
        //load方法完成该功能
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}


	/**
	 * Load beans into the application context.
	 * @param context the context to load beans into
	 * @param sources the sources to load
	 * 加载bean对象到context中
	 */
	protected void load(ApplicationContext context, Object[] sources) {
    
    
		if (logger.isDebugEnabled()) {
    
    
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
        //获取bean对象定义的加载器
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
    
    
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
    
    
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
    
    
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

	/**
	 * Load the sources into the reader.
	 * @return the number of loaded beans
	 */
	int load() {
    
    
		int count = 0;
		for (Object source : this.sources) {
    
    
			count += load(source);
		}
		return count;
	}

3. 次に示すように、実際にロードを実行するのは BeanDefinitionLoader のロード メソッドです。

	//实际记载bean的方法
	private int load(Object source) {
    
    
		Assert.notNull(source, "Source must not be null");
        //如果是class类型,启用注解类型
		if (source instanceof Class<?>) {
    
    
			return load((Class<?>) source);
		}
        //如果是resource类型,启动xml解析
		if (source instanceof Resource) {
    
    
			return load((Resource) source);
		}
        //如果是package类型,启用扫描包,例如@ComponentScan
		if (source instanceof Package) {
    
    
			return load((Package) source);
		}
        //如果是字符串类型,直接加载
		if (source instanceof CharSequence) {
    
    
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

4. 次のメソッドは、リソースのタイプが groovy によってロードされるかアノテーションによってロードされるかを決定するために使用されます。

	private int load(Class<?> source) {
    
    
        //判断使用groovy脚本
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
    
    
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			load(loader);
		}
        //使用注解加载
		if (isComponent(source)) {
    
    
			this.annotatedReader.register(source);
			return 1;
		}
		return 0;
	}

5. 次のメソッドは、スタートアップ クラスに @Component アノテーションが含まれているかどうかを判断しますが、スタートアップ クラスにそのようなアノテーションが存在しないことが魔法のようにわかります。引き続き MergedAnnotations クラスがパラメータ SearchStrategy.TYPE_HIERARCHY に渡されていることを確認します。このアノテーションを含めて、@SpringBootApplication–>@SpringBootConfiguration–>@Configuration–>@Component が継承関係にあるかどうかを確認します。 @Component アノテーションが見つかった場合、オブジェクトは AnnotatedBeanDefinitionReader オブジェクトに登録されます

private boolean isComponent(Class<?> type) {
    
    
   // This has to be a bit of a guess. The only way to be sure that this type is
   // eligible is to make a bean definition out of it and try to instantiate it.
   if (MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class)) {
    
    
      return true;
   }
   // Nested anonymous classes are not eligible for registration, nor are groovy
   // closures
   return !type.getName().matches(".*\\$_.*closure.*") && !type.isAnonymousClass()
         && type.getConstructors() != null && type.getConstructors().length != 0;
}

	/**
	 * Register a bean from the given bean class, deriving its metadata from
	 * class-declared annotations.
	 * 从给定的bean class中注册一个bean对象,从注解中找到相关的元数据
	 */
	private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {
    
    

		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
    
    
			return;
		}

		abd.setInstanceSupplier(supplier);
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		abd.setScope(scopeMetadata.getScopeName());
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		if (qualifiers != null) {
    
    
			for (Class<? extends Annotation> qualifier : qualifiers) {
    
    
				if (Primary.class == qualifier) {
    
    
					abd.setPrimary(true);
				}
				else if (Lazy.class == qualifier) {
    
    
					abd.setLazyInit(true);
				}
				else {
    
    
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		if (customizers != null) {
    
    
			for (BeanDefinitionCustomizer customizer : customizers) {
    
    
				customizer.customize(abd);
			}
		}

		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}

	/**
	 * Register the given bean definition with the given bean factory.
	 * 注册主类,如果有别名可以设置别名
	 */
	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
    
    

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
    
    
			for (String alias : aliases) {
    
    
				registry.registerAlias(beanName, alias);
			}
		}
	}

//@SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
    
     @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
    }

//@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    
    }

//@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    
    }

上記のコードを読んだ時点ではスタートアップオブジェクトの注入が完了しているだけで、自動アセンブリはまだ開始されていませんので、自動アセンブリに入りましょう。

6. コンテナの更新から始めてエントリを自動的に組み立てます

@Override
	public void refresh() throws BeansException, IllegalStateException {
    
    
		synchronized (this.startupShutdownMonitor) {
    
    
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
    
    
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
                // 此处是自动装配的入口
				invokeBeanFactoryPostProcessors(beanFactory);
            }

7. invokeBeanFactoryPostProcessors メソッドで Bean のインスタンス化と実行を完了します。

/**
	 * Instantiate and invoke all registered BeanFactoryPostProcessor beans,
	 * respecting explicit order if given.
	 * <p>Must be called before singleton instantiation.
	 */
	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    
    
        //开始执行beanFactoryPostProcessor对应实现类,需要知道的是beanFactoryPostProcessor是spring的扩展接口,在刷新容器之前,该接口可以用来修改bean元数据信息
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

		// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
		// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
		if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
    
    
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}
	}

8. invokeBeanFactoryPostProcessors の具体的な実行メソッドを表示する

	public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    
    

		// Invoke BeanDefinitionRegistryPostProcessors first, if any.
		Set<String> processedBeans = new HashSet<>();

		if (beanFactory instanceof BeanDefinitionRegistry) {
    
    
			BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
			//开始遍历三个内部类,如果属于BeanDefinitionRegistryPostProcessor子类,加入到bean注册的集合,否则加入到regularPostProcessors
			for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
    
    
				if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
    
    
					BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
					registryProcessors.add(registryProcessor);
				}
				else {
    
    
					regularPostProcessors.add(postProcessor);
				}
			}

			// Do not initialize FactoryBeans here: We need to leave all regular beans
			// uninitialized to let the bean factory post-processors apply to them!
			// Separate between BeanDefinitionRegistryPostProcessors that implement
			// PriorityOrdered, Ordered, and the rest.
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
            //通过BeanDefinitionRegistryPostProcessor获取到对应的处理类“org.springframework.context.annotation.internalConfigurationAnnotationProcessor”,但是需要注意的是这个类在springboot中搜索不到,这个类的完全限定名在AnnotationConfigEmbeddedWebApplicationContext中,在进行初始化的时候会装配几个类,在创建AnnotatedBeanDefinitionReader对象的时候会将该类注册到bean对象中,此处可以看到internalConfigurationAnnotationProcessor为bean名称,容器中真正的类是ConfigurationClassPostProcessor
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            //首先执行类型为PriorityOrdered的BeanDefinitionRegistryPostProcessor
            //PriorityOrdered类型表明为优先执行
			for (String ppName : postProcessorNames) {
    
    
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    
    
                    //获取对应的bean
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    //用来存储已经执行过的BeanDefinitionRegistryPostProcessor
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
            //开始执行装配逻辑
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

			// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
            //其次执行类型为Ordered的BeanDefinitionRegistryPostProcessor
            //Ordered表明按顺序执行
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
    
    
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
    
    
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

			// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
            //循环中执行类型不为PriorityOrdered,Ordered类型的BeanDefinitionRegistryPostProcessor
			boolean reiterate = true;
			while (reiterate) {
    
    
				reiterate = false;
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
				for (String ppName : postProcessorNames) {
    
    
					if (!processedBeans.contains(ppName)) {
    
    
						currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
						processedBeans.add(ppName);
						reiterate = true;
					}
				}
				sortPostProcessors(currentRegistryProcessors, beanFactory);
				registryProcessors.addAll(currentRegistryProcessors);
				invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
				currentRegistryProcessors.clear();
			}

			// Now, invoke the postProcessBeanFactory callback of all processors handled so far.	
            //执行父类方法,优先执行注册处理类
			invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
            //执行有规则处理类
			invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
		}

		else {
    
    
			// Invoke factory processors registered with the context instance.
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let the bean factory post-processors apply to them!
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

		// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
		// Ordered, and the rest.
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
    
    
			if (processedBeans.contains(ppName)) {
    
    
				// skip - already processed in first phase above
			}
			else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    
    
				priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
			}
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    
    
				orderedPostProcessorNames.add(ppName);
			}
			else {
    
    
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

		// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String postProcessorName : orderedPostProcessorNames) {
    
    
			orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

		// Finally, invoke all other BeanFactoryPostProcessors.
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String postProcessorName : nonOrderedPostProcessorNames) {
    
    
			nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

		// Clear cached merged bean definitions since the post-processors might have
		// modified the original metadata, e.g. replacing placeholders in values...
		beanFactory.clearMetadataCache();
	}

9. 自動構成ロジック (デフォルト構成ではなく、スタートアップ クラスで指定された構成) の実行を開始します。デバッグを通じて層ごとに内部を検索すると、最終的には ConfigurationClassParser クラスにあることがわかります。すべての構成クラスの解析クラス、すべての解析ロジックは parser.parse(candidates) にあります

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    
    
		for (BeanDefinitionHolder holder : configCandidates) {
    
    
			BeanDefinition bd = holder.getBeanDefinition();
			try {
    
    
                //是否是注解类
				if (bd instanceof AnnotatedBeanDefinition) {
    
    
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    
    
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
    
    
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
    
    
				throw ex;
			}
			catch (Throwable ex) {
    
    
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
    	//执行配置类
		this.deferredImportSelectorHandler.process();
	}
-------------------
    	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    
    
		processConfigurationClass(new ConfigurationClass(metadata, beanName));
	}
-------------------
    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    
    
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    
    
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
    
    
			if (configClass.isImported()) {
    
    
				if (existingClass.isImported()) {
    
    
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
    
    
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
    
    
            //循环处理bean,如果有父类,则处理父类,直至结束
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}

10. doProcessConfigurationClass メソッドのフォローアップを続けます。これは、アノテーション構成をサポートするコア ロジックです。

/**
	 * Apply processing and build a complete {@link ConfigurationClass} by reading the
	 * annotations, members and methods from the source class. This method can be called
	 * multiple times as relevant sources are discovered.
	 * @param configClass the configuration class being build
	 * @param sourceClass a source class
	 * @return the superclass, or {@code null} if none found or previously processed
	 */
	@Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
    
    

        //处理内部类逻辑,由于传来的参数是启动类,并不包含内部类,所以跳过
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    
    
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);
		}

		// Process any @PropertySource annotations
        //针对属性配置的解析
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
    
    
			if (this.environment instanceof ConfigurableEnvironment) {
    
    
				processPropertySource(propertySource);
			}
			else {
    
    
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
        // 这里是根据启动类@ComponentScan注解来扫描项目中的bean
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    
    
            
			for (AnnotationAttributes componentScan : componentScans) {
    
    
				// The config class is annotated with @ComponentScan -> perform the scan immediately
                //遍历项目中的bean,如果是注解定义的bean,则进一步解析
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    
    
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
    
    
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    
    
                        //递归解析,所有的bean,如果有注解,会进一步解析注解中包含的bean
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
        //递归解析,获取导入的配置类,很多情况下,导入的配置类中会同样包含导入类注解
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
        //解析@ImportResource配置类
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
    
    
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
    
    
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
        //处理@Bean注解修饰的类
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
    
    
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
        // 处理接口中的默认方法
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
        //如果该类有父类,则继续返回,上层方法判断不为空,则继续递归执行
		if (sourceClass.getMetadata().hasSuperClass()) {
    
    
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
    
    
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

11. 構成クラスを取得するロジックを表示する

processImports(configClass, sourceClass, getImports(sourceClass), true);

	/**
	 * Returns {@code @Import} class, considering all meta-annotations.
	 */
	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    
    
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}
------------------
    	/**
	 * Recursively collect all declared {@code @Import} values. Unlike most
	 * meta-annotations it is valid to have several {@code @Import}s declared with
	 * different values; the usual process of returning values from the first
	 * meta-annotation on a class is not sufficient.
	 * <p>For example, it is common for a {@code @Configuration} class to declare direct
	 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
	 * annotation.
	 * 看到所有的bean都以导入的方式被加载进去
	 */
	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {
    
    

		if (visited.add(sourceClass)) {
    
    
			for (SourceClass annotation : sourceClass.getAnnotations()) {
    
    
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
    
    
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

12. ConfigurationClassParser の parse メソッドの最後の行に戻り、メソッドのフォローアップを続けます。

this.deferredImportSelectorHandler.process()
-------------
public void process() {
    
    
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
    
    
				if (deferredImports != null) {
    
    
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
    
    
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
---------------
  public void processGroupImports() {
    
    
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    
    
				grouping.getImports().forEach(entry -> {
    
    
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
    
    
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
    
    
						throw ex;
					}
					catch (Throwable ex) {
    
    
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}
------------
    /**
		 * Return the imports defined by the group.
		 * @return each import with its associated configuration class
		 */
		public Iterable<Group.Entry> getImports() {
    
    
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    
    
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}
	}
------------
    public DeferredImportSelector getImportSelector() {
    
    
			return this.importSelector;
		}
------------
    @Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    
    
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    
    
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

springboot のスターターを理解するにはどうすればよいですか?

spring+springmvc フレームワークを使用して開発する場合、mybatis フレームワークを導入する必要がある場合、必要な Bean オブジェクトを xml で定義する必要があります。このプロセスは明らかに非常に面倒です。追加で他のコンポーネントを導入する必要がある場合は、それも必要になります。設定が複雑なので、springboot でスターターを導入します

スターターは jar パッケージであり、 @Configuration 構成クラスを記述し、その中でこれらの Bean を定義し、スターター パッケージの META-INF/spring.factories に構成クラスを記述すると、springboot プログラムが起動時に続きます。設定クラスをロードするための規約

開発者は、Bean オブジェクトを個別に設定することなく、アプリケーションに対応するスターター パッケージを利用し、関連する属性を設定してコードを開発するだけで済みます。

組み込みサーバーとは何ですか?また、組み込みサーバーを使用する理由は何ですか?

Springboot フレームワークでは、Tomcat が埋め込まれていることがわかります。以前の開発プロセスでは、コードを記述するたびに、プロジェクトを追加の Web サーバーにデプロイする必要がありました。この方法でのみプロジェクトを実行できます。これは、これは明らかに大変な作業ですが、springboot を使用すると、プロジェクトを開始するときに、追加の環境サポートや Tomcat サーバーを必要とせずに、Java アプリケーションの方法でプロジェクトを直接開始できることがわかります。 -in tomcat.jar は、main メソッドを通じてコン​​テナーを起動するために使用され、追加の操作を行わずにワンクリックで開発とデプロイメントを実現します。

マイバティスのメリットとデメリットは何ですか?

1. マイベイトのメリット:

(1) 学びやすく、使いやすい (Hibernate と比較して) SQL ベースのプログラミング、
(2) JDBC と比較してコード量が 50% 以上削減され、JDBC の多数の冗長コードが削除され、手動で接続を切り替える必要はありません
( 3) さまざまなデータベースとの互換性が非常に優れています (MyBatis はデータベースへの接続に JDBC を使用するため、JDBC をサポートするデータベース MyBatis がサポートし、JDBC がスケーラビリティを提供する限り、データベースには Java 用の jar パッケージがあり、MyBatis と互換性があるため、開発者はデータベースの違いを考慮する必要はありません。
(4) 多くのサードパーティ プラグイン (ページング プラグイン/リバース エンジニアリング) を提供します;
(5) Spring と適切に統合できます;
(6) MyBatis は非常に柔軟で、既存の設計に影響を与えません。 SQL は XML で記述され、プログラム コードから完全に分離されており、SQL とプログラム コードの結合が分離されているため、統合管理と最適化に便利で、再利用することができます。
(7) 動的 SQL ステートメントの作成をサポートする XML タグを提供します。
(8) オブジェクトとデータベース間の ORM フィールド関係マッピングをサポートするマッピング タグを提供します。
(9) オブジェクト リレーショナルの作成と保守をサポートするオブジェクト リレーショナル マッピング タグを提供します。
2. MyBatis フレームワークの欠点:

(1) SQL ステートメントを作成する作業負荷は、特に多くのフィールドと多数の関連テーブルがある場合に比較的大きく、開発者には SQL ステートメントを作成するための特定のスキルが必要です。
(2) SQL文がデータベースに依存しているため、データベースの移植性が悪く、データベースを自由に置き換えることができません。

mybatis と hibernate の違いは何ですか?

休止状態の利点:

1. Hibernate は完全に自動です Hibernate はオブジェクト リレーショナル モデルを通じてデータベースの操作を完全に実現でき、JavaBean オブジェクトとデータベースの間の完全なマッピング構造を備えて SQL を自動的に生成します。

2. 強力な機能、優れたデータベース独立性、強力な O/R マッピング機能、記述するコードが少なく、開発速度が速い。

3. より優れた二次キャッシュ メカニズムがあり、サードパーティのキャッシュを使用できます。

4. データベースの移植性が良い。

5. Hibernate には完全なログ システムがあります Hibernate のログ システムは非常に健全であり、SQL レコード、関係例外、最適化警告、キャッシュ プロンプト、ダーティ データ警告などを含む幅広い問題が含まれます。

休止状態の欠点:

1. 学習の敷居が高く、習熟の敷居が高い プログラマは、O/R マッピングをどのように設計するか、パフォーマンスとオブジェクト モデルのバランスをどう取るか、Hibernate を使いこなすには、豊富な経験と能力が必要です。

2. 多くの Hibernate SQL は自動的に生成され、SQL を直接維持することはできません。HQL クエリはありますが、機能は依然として SQL ほど強力ではありません。Hibernate はネイティブ SQL クエリもサポートしていますが、その開発モードは orm とは異なります。考え方を変える必要があるので、少し使いにくいです。つまり、Hibernate は SQL を書く際に mybatis ほど柔軟ではありません。

マイバティスの利点:

1. 使いやすく、使いこなすのが簡単です。データベース クエリの自動オブジェクト バインディング機能を提供し、SQL を使用する際の優れたエクスペリエンスを継続します。オブジェクト モデルの要件がそれほど高くないプロジェクトには非常に最適です。

2. SQL は XML で記述されており、統合管理と最適化、および SQL とプログラム コードの分離に便利です。

3. マッピング タグを提供し、オブジェクトとデータベース間の orm フィールド関係マッピングをサポートします。

4. オブジェクト リレーショナルの作成と保守をサポートするオブジェクト リレーショナル マッピング タグを提供する

5. XML タグを提供し、動的 ​​SQL の記述をサポートします。

6. Hibernateよりも速度が速い

マイバティスのデメリット:

1. 関連するテーブルとフィールドが多数ある場合、SQL ワークロードが重くなります。

2. SQL はデータベースに依存しているため、データベースの移植性が低くなります。

3. XML 内のタグ ID は一意である必要があるため、DAO のメソッドはメソッドのオーバーロードをサポートしていません。

4. オブジェクト リレーショナル マッピング ラベルとフィールド マッピング ラベルはマッピング関係の説明にすぎず、具体的な実装は依然として SQL に依存します。

5. DAO 層が単純すぎるため、オブジェクトのアセンブリの作業負荷が比較的大きくなります。

6. カスケード更新およびカスケード削除はサポートされていません。

7. Mybatis ログの基本的な記録機能に加えて、その他の機能は非常に貧弱です。

8. 動的 SQL を作成する場合、特にロジックが複雑な場合はデバッグが不便です。

9. 動的SQLを書くために提供されているxmlタグ機能はシンプルですが、動的SQLの記述にはまだ制限があり、可読性が低いです。

mybatis の #{} と ${} の違いは何ですか?

a. #{} はプリコンパイル処理です。KaTeX解析エラー: 'EOF' が予期され、位置 24 で '#' を取得しました: .... b. Mybatis が #̲{} を処理するとき、 SQL 内の #{... {} の場合、${} を変数の値に置き換えます。
d. #{} を使用すると、SQL インジェクションを効果的に防止し、システムのセキュリティを向上させることができます

mybatisプラグインの動作原理と開発プロセスを簡単に説明してください。

Mybatis は、ParameterHandler、ResultSetHandler、StatementHandler、および Executor の 4 つのインターフェイスのプラグインのみをサポートします。Mybatis は、jdk の動的プロキシを使用して、インターフェイス メソッドのインターセプトを実装するためにインターセプトする必要があるインターフェイスのプロキシ オブジェクトを生成します。これら 4 つのインターフェイスのメソッドがサポートされるたびに、オブジェクトが実行されると、インターセプト メソッド、具体的には InvocationHandler の呼び出しメソッドに入り、インターセプトするように指定したメソッドをインターセプトします。

プラグインを作成します。Mybatis の Interceptor インターフェイスを実装し、インターセプト メソッドを書き換えます。次に、プラグインのアノテーションを作成し、どのインターフェイスのどのメソッドをインターセプトするかを指定し、作成されたプラグインを構成ファイルに設定します。

@Intercepts({
    
    @Signature(type = StatementHandler.class,method = "parameterize",args = Statement.class)})

インデックス作成の基礎

1. なぜインデックスがあるのですか?
一般的なアプリケーション システムでは、読み取りと書き込みの比率は約 10:1 であり、挿入操作や一般的な更新操作ではパフォーマンスの問題が発生することはほとんどありません。問題は依然として複雑なクエリ操作にあるため、クエリ ステートメントの最適化が最優先であることは明らかです。高速化されたクエリと言えば、インデックスについて触れなければなりません。
2. インデックスとは何ですか?
インデックスは MySQL では「キー」とも呼ばれ、ストレージ エンジンがレコードを迅速に検索するために使用するデータ構造です。インデックスは良好なパフォーマンスにとって重要であり
、特にテーブル内のデータ量がますます増大し、パフォーマンスに対するインデックスの影響がますます重要になる場合には重要です。
インデックスの最適化は、クエリのパフォーマンスを最適化する最も効果的な手段です。インデックスを使用すると、クエリのパフォーマンスを簡単に桁違いに向上させることができます。
索引は辞書のふりがな表に相当するもので、単語を調べたい場合、ふりがな表を使わないと何百ページもあるページから1ページずつ調べる必要があります。

3. インデックス作成の原理

インデックス作成の目的は、クエリの効率を向上させることです。これは、書籍を参照するときに使用する目次と同じです。最初に章を見つけ、次にその章の下のサブセクションを見つけ、次にページ数を見つけます。同様の例としては、辞書の検索、電車の番号の検索、飛行機の便の検索などが挙げられます。

本質は、取得するデータの範囲を継続的に絞り込んで最終的に望ましい結果を除外すると同時に、ランダムなイベントを連続したイベントに変換することによって、つまり、このインデックス作成メカニズムを使用すると、常に同じ検索方法でデータをロックします。

数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段…这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?按照搜索树的模型,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。

4、索引的数据结构

MySQL主要用到两种结构:B+ Tree索引和Hash索引
Inodb存储引擎 默认是 B+Tree索引
Memory 存储引擎 默认 Hash索引;
MySQL中,只有Memory(Memory表只存在内存中,断电会消失,适用于临时表)存储引擎显示支持Hash索引,是Memory表的默认索引类型,尽管Memory表也可以使用B+Tree索引。Hash索引把数据以hash形式组织起来,因此当查找某一条记录的时候,速度非常快。但是因为hash结构,每个键只对应一个值,而且是散列的方式分布。所以它并不支持范围查找和排序等功能。
B+Tree是mysql使用最频繁的一个索引数据结构,是InnoDB和MyISAM存储引擎模式的索引类型。相对Hash索引,B+Tree在查找单条记录的速度比不上Hash索引,但是因为更适合排序等操作,所以它更受欢迎。毕竟不可能只对数据库进行单条记录的操作。
对比:
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)

mysql聚簇和非聚簇索引的区别是什么?

​ mysql的索引类型跟存储引擎是相关的,innodb存储引擎数据文件跟索引文件全部放在ibd文件中,而myisam的数据文件放在myd文件中,索引放在myi文件中,其实区分聚簇索引和非聚簇索引非常简单,只要判断数据跟索引是否存储在一起就可以了。

innodb ストレージ エンジンがデータを挿入するとき、データはインデックスとともにまとめられる必要があります。主キーがある場合は、主キーが使用されます。主キーがない場合は、一意のキーが使用されます。一意のキーがない場合は、一意のキーが使用されます。キーには6バイトのROWIDが使用されるため、データにバインドされます。一緒にクラスタ化インデックスがあり、データの冗長な格納を避けるために、クラスタ化インデックスのキー値はリーフに格納されます。他のインデックスのノードなので、InnoDB にはクラスター化インデックスと非クラスター化インデックスの両方があり、myisam には非クラスター化インデックスのみがあります。

mysql インデックス構造とは何ですか?また、その長所と短所は何ですか?

インデックスのデータ構造は、特定のストレージ エンジンの実装に関連しています。MySQL でよく使用されるインデックスには、ハッシュ インデックスと B+​​ ツリー インデックスが含まれます。innodb のインデックスは B+ ツリーとして実現され、メモリ ストレージ エンジンはハッシュ インデックスです。

B+ ツリーはバランスの取れたマルチフォーク ツリーであり、ルート ノードから各リーフ ノードまでの高さの差は 1 を超えず、同じレベルの 2 つのノード間にはポインタ関連の接続があります。 , ルートノードからリーフノードまでの検索効率は基本的に同じで大きな変動はありません. また、インデックスベースの逐次スキャンでは、双方向ポインタを使用して左右に高速に移動することもできます。非常に効率的です。なぜなら、B+ ツリー インデックスはデータベース、ファイル システム、その他のシナリオで広く使用されているからです。

ハッシュインデックスは、特定のハッシュアルゴリズムを使用してキー値を新しいハッシュ値に変換します。検索の際、B+ツリーのようにルートノードからリーフノードまで段階的に検索する必要はなく、必要なハッシュアルゴリズムは1つだけです。すぐに検索 対応する位置への移動速度が非常に速いです。

それが同等のクエリである場合、ハッシュ インデックスには明らかに絶対的な利点があります。キー値が一意であれば、対応するキー値を見つけるために 1 つのアルゴリズムを通過するだけで済むからです。キーの値が一意でない場合は、まずキーの場所を見つけてから、対応するデータが見つかるまでリンク リストに従って逆方向にスキャンする必要があります。

範囲クエリの取得の場合、現時点では Haxu インデックスは役に立ちません。元の順序のキー値がハッシュ アルゴリズム後に不連続になる可能性があり、範囲クエリの取得を完了するためにインデックスを使用する方法がないためです。

並べ替えを完了するためにインデックスを使用する方法はなく、次のようなあいまいなクエリもあります。

ハッシュ インデックスは、複数列結合インデックスの左端の一致ルールもサポートしていません。

B+ツリーインデクスのキーワード検索効率は変動の大きいB-treeと異なり比較的平均的ですが、重複キー値が多い場合にはハッシュインデクスの検索効率も非常に低くなるため、ハッシュ衝突の問題。

インデックスの設計原則は何ですか?

インデックスを設計するときは、インデックス フィールドが占有するスペースができるだけ小さくなるようにする必要があります。これは単なる一般的な方向であり、注意が必要な詳細がいくつかあります。

1. インデックス付けに適した列は、where 句に出現する列、または接続句で指定された列です。

2. カーディナリティが小さいテーブルはインデックスの効果が低いため、インデックスを作成する必要はありません。

3. インデックス列を選択する場合、短いほど良いです。一部の列の一部を指定できます。すべてのフィールドの値を使用する必要はありません

4. テーブル内のすべてのフィールドにインデックスを作成しないでください。インデックスが多ければ多いほど良いというわけではありません。

5. 外部キーを定義するデータ列にはインデックスを作成する必要があります

6. 頻繁に更新されるフィールドのインデックスを持たない

7. インデックスに列を作成しすぎないでください。複合インデックスを作成できますが、複合インデックスの列の数が多すぎることはお勧めできません。

8. 大きなテキストや大きなオブジェクトのインデックスを作成しないでください。

mysql ロックにはどのような種類がありますか?

ロックベースの属性分類: 共有ロック、排他的ロック。

ロックベースの詳細な分類: 行レベルのロック (innodb)、テーブルレベルのロック (innodb、myisam)、ページレベルのロック (innodb エンジン)、レコード ロック、ギャップ ロック、およびキー ロック。

ロックベースのステータス分類: インテント共有ロック、インテント排他ロック。

共有ロック (共有ロック): 共有ロックは読み取りロックとも呼ばれ、S ロックとも呼ばれます。トランザクションがデータに読み取りロックを追加すると、他のトランザクションはデータに読み取りロックのみを追加できますが、書き込みロックを追加することはできません。読み取りロックが解放されると、他のトランザクションがそのデータに対する書き込みロックを保持できるようになります。共有ロックの機能は、主にデータの同時読み取りをサポートすることであり、繰り返し読み取りの問題を回避するために、データ読み取り時の変更はサポートしません。

排他的ロック: 排他的ロックは、書き込みロック、または略して X ロックとも呼ばれます。トランザクションがデータに書き込みロックを追加すると、ロックが解放されるまで、他のリクエストはデータにロックを追加できなくなります。データがロックされています。排他ロックの目的は、ダーティ データやダーティ リードの問題を回避するために、他の人が同時にデータを変更したり、他の人がデータを読み取れないようにすることです。

テーブル ロック (テーブル ロック): テーブル ロックとは、ロックがロックされるとテーブル全体がロックされることを意味します。次のトランザクションがテーブルにアクセスするときは、前のトランザクションがロックを解放するのを待ってからテーブルにアクセスする必要があります。特徴: 大粒度、ロックは単純で競合しやすい。

行ロック: 行ロックとは、ロックする際にテーブル内の 1 行以上のレコードをロックすることを意味します。他のトランザクションが同じテーブルにアクセスする場合、ロックされたレコードのみアクセスできなくなり、他のレコードは通常どおりアクセスできます。特徴: 粒度小さい、ロックはテーブル ロックよりも面倒、競合しにくい、テーブル ロックによってサポートされる同時実行性が高い

レコード ロック: レコード ロックも行ロックの一種ですが、レコード ロックの範囲はテーブル内の特定のレコードのみです。レコード ロックとは、ロック後にトランザクションがテーブル内の特定のレコードのみをロックすることを意味します。レコード ロックが追加された後、データは、クエリ中にデータが変更されるという繰り返し読み取りの問題を回避でき、また、変更されたトランザクションがコミットされる前に他のトランザクションによって読み取られるダーティ リードの問題も回避できます。

ページ ロック: ページ レベル ロックは MysQL のロックの一種で、そのロック粒度は行レベル ロックとテーブル レベル ロックの間です。テーブルレベルのロックは高速ですが、競合が多く、行レベルのロックは競合が少ないですが、遅くなります。したがって、隣接するレコードのグループを一度にロックするために、妥協したページ レベルが採用されます。特徴: オーバーヘッドとロック時間はテーブル ロックと行ロックの間であり、デッドロックが発生します。ロックの粒度はテーブル ロックと行ロックの間で、同時実行性は平均的です。

ギャップ ロック: 行ロックの一種です。トランザクションがロックされた後、ギャップ ロックはテーブル レコードの一定範囲をロックします。テーブルの隣接する ID 間にギャップがある場合、開いたままの状態に続く範囲が形成されます。右クローズ原則。範囲クエリとクエリはレコードにヒットせず、クエリ条件はインデックスにヒットする必要があり、ギャップ ロックは REPEATABLE_READ (反復読み取り) のトランザクション レベルでのみ表示されます。

Next-Key ロック: これも行ロックの一種であり、INNODB の行ロックのデフォルト アルゴリズムです。要約すると、レコード ロックとギャップ ロックの組み合わせです。Next-Key ロックはレコード ロックをクエリし、同時に、範囲クエリ内のすべてのギャップ スペースもロックし、次に隣接する間隔もロックします。

mysqlの実行計画を確認するにはどうすればよいですか?

エンタープライズ アプリケーション シナリオでは、SQL ステートメントの実行を最適化する方法を知るために、SQL ステートメントの実行効率を高速化するために SQL ステートメントの特定の実行プロセスを確認する必要があります。

Explain+SQL ステートメントを使用して、SQL クエリ ステートメントを実行するオプティマイザーをシミュレートし、mysql が SQL ステートメントをどのように処理するかを知ることができます。

公式Webサイトアドレス:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

1. 実行計画に含まれる情報

意味
ID 識別子SELECT_
選択タイプ 種類SELECT_
テーブル 出力行のテーブル
パーティション 一致するパーティション
タイプ 結合タイプ
possible_keys 選択可能なインデックス
実際に選ばれたインデックス
key_len 選択したキーの長さ
参照 インデックスと比較される列
検査される行の見積もり
フィルタリングされた テーブル条件によってフィルタリングされた行の割合
追加 追加情報

ID

選択クエリのシリアル番号 (一連の数字を含む)。クエリ内で選択句または操作テーブルが実行される順序を示します。

ID 番号は次の 3 つの状況に分けられます。

1. IDが同じ場合、実行順序は上から下へ

explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal;

2. idが異なる場合、サブクエリの場合はidの通し番号がインクリメントされ、idの値が大きいほど優先度が高く、より早く実行されます。

explain select * from emp e where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');

3. 同じ ID と異なる ID が同時に存在する: 同じものを 1 つのグループとしてみなし、上から下へ順番に実行されます。すべてのグループの中で、ID 値が大きいほど優先順位が高く、より早く実行されます。処刑

explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');

選択タイプ

主に、クエリの種類(通常のクエリ、結合クエリ、サブクエリ)を区別するために使用されます。

select_type価値 意味
単純 単純な SELECT (UNION やサブクエリを使用しない)
主要な 最外側のSELECT
連合 UNION 内の 2 番目以降の SELECT ステートメント
従属組合 UNION 内の 2 番目以降の SELECT ステートメント (外部クエリに依存)
組合結果 UNION の結果。
サブクエリ サブクエリ内の最初の SELECT
従属サブクエリ サブクエリ内の最初の SELECT (外部クエリに依存)
派生 派生テーブル
キャッシュ不能なサブクエリ 結果をキャッシュできず、外部クエリの行ごとに再評価する必要があるサブクエリ
キャッシュできないユニオン キャッシュ不可能なサブクエリに属する​​ UNION 内の 2 番目以降の選択 (UNCACHEABLE SUBQUERY を参照)
--sample:简单的查询,不包含子查询和union
explain select * from emp;

--primary:查询中若包含任何复杂的子查询,最外层查询则被标记为Primary
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;

--union:若第二个select出现在union之后,则被标记为union
explain select * from emp where deptno = 10 union select * from emp where sal >2000;

--dependent union:跟union类似,此处的depentent表示union或union all联合而成的结果会受外部表影响
explain select * from emp e where e.empno  in ( select empno from emp where deptno = 10 union select empno from emp where sal >2000)

--union result:从union表获取结果的select
explain select * from emp where deptno = 10 union select * from emp where sal >2000;

--subquery:在select或者where列表中包含子查询
explain select * from emp where sal > (select avg(sal) from emp) ;

--dependent subquery:subquery的子查询要受到外部表查询的影响
explain select * from emp e where e.deptno in (select distinct deptno from dept);

--DERIVED: from子句中出现的子查询,也叫做派生类,
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;

--UNCACHEABLE SUBQUERY:表示使用子查询的结果不能被缓存
 explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
 
--uncacheable union:表示union的查询结果不能被缓存:sql语句未验证

テーブル

対応する行、テーブル名、または別名によってアクセスされているテーブル。一時テーブルまたはユニオン結合結果
セットである可能性があります。

2. テーブル名は、derivedN の形式です。これは、ID が N のクエリによって生成された派生テーブルが使用されることを意味します。

3. ユニオン結果がある場合、テーブル名はユニオン n1、n2 などの形式になり、n1 と n2 はユニオンに参加している ID を表します

タイプ

type はアクセス タイプを示します。データにどのようにアクセスするかを示します。最も簡単に考えられるのは、フル テーブル スキャンです。テーブルを直接走査して必要なデータを見つけます。効率は非常に低く、アクセスには多くのタイプがあります。 、効率が最高から最低の順に:

システム > const > eq_ref > ref > フルテキスト > ref_or_null > インデックスマージ > ユニークサブクエリ > インデックスサブクエリ > 範囲 > インデックス > ALL

一般に、クエリが少なくとも範囲レベルに到達することを確認する必要があり、参照に到達することが最善です。

--all:全表扫描,一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。
explain select * from emp;

--index:全索引扫描这个比all的效率要好,主要有两种情况,一种是当前的查询时覆盖索引,即我们需要的数据在索引中就可以索取,或者是使用了索引进行排序,这样就避免数据的重排序
explain  select empno from emp;

--range:表示利用索引查询的时候限制了范围,在指定范围内进行查询,这样避免了index的全索引扫描,适用的操作符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN() 
explain select * from emp where empno between 7000 and 7500;

--index_subquery:利用索引来关联子查询,不再扫描全表
explain select * from emp where emp.job in (select job from t_job);

--unique_subquery:该连接类型类似与index_subquery,使用的是唯一索引
 explain select * from emp e where e.deptno in (select distinct deptno from dept);
 
--index_merge:在查询过程中需要多个索引组合使用,没有模拟出来
explain select * from rental where rental_date like '2005-05-26 07:12:2%' and inventory_id=3926 and customer_id=321\G

--ref_or_null:对于某个字段即需要关联条件,也需要null值的情况下,查询优化器会选择这种访问方式
explain select * from emp e where  e.mgr is null or e.mgr=7369;

--ref:使用了非唯一性索引进行数据的查找
 create index idx_3 on emp(deptno);
 explain select * from emp e,dept d where e.deptno =d.deptno;

--eq_ref :使用唯一性索引进行数据查找
explain select * from emp,emp2 where emp.empno = emp2.empno;

--const:这个表至多有一个匹配行,
explain select * from emp where empno = 7369;
 
--system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现

possible_keys

​ 显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

key

​ 实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠。

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

key_len

表示索引中使用的字节数,可以通过key_len计算查询中使用的索引长度,在不损失精度的情况下长度越短越好。

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

rows

根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的sql找了多少数据,在完成目的的情况下越少越好

explain select * from emp;

extra

包含额外的信息。

--using filesort:说明mysql无法利用索引进行排序,只能利用排序算法进行排序,会消耗额外的位置
explain select * from emp order by sal;

--using temporary:建立临时表来保存中间结果,查询完成之后把临时表删除
explain select ename,count(*) from emp where deptno = 10 group by ename;

--using index:这个表示当前的查询时覆盖索引的,直接从索引中读取数据,而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找,如果没有,表面索引被用来读取数据,而不是真的查找
explain select deptno,count(*) from emp group by deptno limit 10;

--using where:使用where进行条件过滤
explain select * from t_user where id = 1;

--using join buffer:使用连接缓存,情况没有模拟出来

--impossible where:where语句的结果总是false
explain select * from emp where empno = 7469;

事务的基本特性是什么?

事务四大特征:原子性,一致性,隔离性和持久性。

  1. 原子性(Atomicity)
    一个原子事务要么完整执行,要么干脆不执行。这意味着,工作单元中的每项任务都必须正确执行。如果有任一任务执行失败,则整个工作单元或事务就会被终止。即此前对数据所作的任何修改都将被撤销。如果所有任务都被成功执行,事务就会被提交,即对数据所作的修改将会是永久性的。
  2. 一致性(Consistency)
    一致性代表了底层数据存储的完整性。它必须由事务系统和应用开发人员共同来保证。事务系统通过保证事务的原子性,隔离性和持久性来满足这一要求; 应用开发人员则需要保证数据库有适当的约束(主键,引用完整性等),并且工作单元中所实现的业务逻辑不会导致数据的不一致(即,数据预期所表达的现实业务情况不相一致)。例如,在一次转账过程中,从某一账户中扣除的金额必须与另一账户中存入的金额相等。支付宝账号100 你读到余额要取,有人向你转100 但是事物没提交(这时候你读到的余额应该是100,而不是200) 这种就是一致性
  3. 分離 分離とは、
    トランザクションが他のプロセスやトランザクションを妨げずに独立して実行される必要があることを意味します。つまり、トランザクションまたは作業単位によってアクセスされるデータは、トランザクションまたは作業単位の実行が完了するまで、システムの他の部分から影響を受けることはありません。
  4. 永続性 (耐久性)
    永続性とは、トランザクションの実行中に、トランザクションが正常に終了する前に、データに加えられたすべての変更を何らかの物理ストレージ デバイスに保存する必要があることを意味します。これにより、システム障害が発生した場合でも、行われた変更が失われることがなくなります。

MySQL の分離レベルは何ですか?

MySQL では、トランザクションの内外でどの変更が表示され、どの変更が表示されないかを制限するために使用される、いくつかの特定のルールを含む 4 つの分離レベルが定義されています。一般に、分離レベルが低いと、より高い同時処理がサポートされ、オーバーヘッドが低くなります。
REPEATABLE READ は、
MySQL データベースのデフォルトの分離レベルを繰り返すことができます。このレベルは、READ UNCOMMITTED 分離レベルによって引き起こされる問題を解決します。これにより、同じトランザクションの複数のインスタンスがトランザクションを同時に読み取るときに、それらのインスタンスが「同じ行を参照する」ことが保証されます。しかし、これは「ファントム・リーディング」という別のやっかいな問題を引き起こすことになる。InnoDB および Falcon ストレージ エンジンは、マルチバージョン同時実行制御メカニズムを通じてファントム読み取りの問題を解決します。
READ COMMITTED
ほとんどのデータベース システム (MySQL ではない) のデフォルトの分離レベルは、前述の単純な分離の定義を満たします。トランザクションが開始されると、トランザクションはコミットされたトランザクションによって行われた変更のみを「参照」でき、トランザクションによって最初から行われたデータ変更はすべて表示されます。コミットへの変更は、コミットされない限り表示されません。この分離レベルは、いわゆる「反復不可能読み取り」もサポートします。これは、ユーザーが同じステートメントを 2 回実行すると、異なる結果が表示されることを意味します。
READ UNCOMMITTED コミットされていないコンテンツの読み取り
この分離レベルでは、すべてのトランザクションがコミットされていないトランザクションの実行結果を「確認」できます。このレベルでは、ユーザーが自分が何をしているのかを本当に理解し、それを選択する十分な理由がない限り、多くの問題が発生する可能性があります。この分離レベルは、そのパフォーマンスが他のパフォーマンスよりはるかに優れている必要はなく、他のレベルには別の利点があるため、実際のアプリケーションではほとんど使用されません。コミットされていないデータの読み取り (「ダーティ リード」とも呼ばれます)
SERIALIZABLE 直列化可能
このレベルは最高レベルの分離です。トランザクションが互いに競合しないようにトランザクションの順序を強制することで、ファントム読み取りを解決します。つまり、SERIALIZABLE は読み取られたデータの各行をロックします。このレベルでは、多数のタイムアウト、タイムアウトおよびロック競合ロック競合現象が発生する可能性があります。このレベルは実際のアプリケーションではほとんど使用されませんが、ユーザーのアプリケーションがデータの安定性のために同時実行性の削減を強制する必要がある場合、この分離も可能です。選択クラスになります。

  1. ダーティリード

ダーティ リードとは、コミットされていないトランザクションの実行中にトランザクションがデータを読み取ることを意味します。
トランザクションの操作でデータが複数回変更される場合、トランザクションがコミットされる前に、別の同時トランザクションがデータを読み取るため、読み取られたデータは最後の永続化後のデータではなくなります。このデータはダーティ リード データです。

  1. 反復不可能な読み取り

非反復読み取りとは、データベース内の特定のデータに対して、トランザクションの実行中に複数のクエリが異なるクエリ結果を返すことを意味します。これは、データがトランザクションの実行中に他のトランザクションによって送信および変更されることを意味します。
ノンリピータブル リードとダーティ リードの違いは、ダーティ リードは、あるトランザクションが別の未完了のトランザクション実行プロセスでデータを読み取ることを意味するのに対し、ノンリピータブル リードは、トランザクションの実行中に別のトランザクションが現在のトランザクションをコミットして変更することを意味することです。読まれている。

  1. 幻の読書

ファントムリードとは、トランザクションが独立して実行されていない場合に発生する現象で、例えばトランザクションT1が列の値が1から2のテーブルのデータを一括変更しますが、このときトランザクションT2がこのテーブルに行を挿入します。カラム値が 1 のデータを入力して送信します。このとき、トランザクション T1 が操作を完了したデータを確認し、列値が 1 で変更されていないデータがまだ存在することを発見し、このデータが実際に送信され、挿入されます。先ほどのT2、これは幻の読み物です。
ファントム読み取りと非反復読み取りはどちらも、コミットされた別のトランザクションを読み取ります (これはダーティ読み取りとは異なります)。違いは、非反復読み取りは同じデータ項目をクエリするのに対し、ファントム読み取りはバッチを対象とするデータ全体 (データ数など)。

MySQL で遅いクエリにどう対処するか?

1. スロークエリログをオンにして、どの SQL ステートメントに問題があるかを正確に特定します

2. SQL ステートメントを分析して、追加のデータがロードされているかどうかを確認します。余分な行がクエリされて破棄されている可能性があります。結果に不要な多くの列がロードされ、ステートメントが分析されて書き換えられている可能性があります。

3. ステートメントの実行計画を分析し、インデックスの使用状況を取得し、ステートメントが可能な限りインデックスにヒットするようにステートメントを変更するか、インデックスを変更します。

4. ステートメントの最適化が不可能になった場合は、テーブル内のデータ量が大きすぎるかどうかを検討し、大きすぎる場合はテーブルを水平または垂直に分割できます。

ACID は何によって保証されますか?

原子性は、ロールバックが必要なログ情報を記録するアンドゥログ ログによって保証され、トランザクションがロールバックされると、正常に実行された SQL が元に戻されます。

他の 3 つの主要な機能によって一貫性が保証されており、プログラム コードはビジネスの一貫性を確保する必要があります。

分離はMVCCによって保証されています

redolog により永続性が保証されており、mysql がデータを変更すると、redolog にログが記録され、データの保存に失敗した場合でも、ログの保存に成功していればデータは失われません。

MVCCとは何ですか?

1、MVCC

MVCC、正式名称はMulti-Version Concurrency Control、つまり複数バージョンの同時実行制御です。MVCCは同時実行制御の手法であり、一般にデータベース管理システムではデータベースへの同時アクセスを実現し、トランザクションメモリをプログラミング言語で実現します。

	MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

2. 現在読んでいます

共有モードでの選択ロック (共有ロック)、更新用の選択、更新、挿入、削除 (排他的ロック) などの操作は、一種の電流読み取りですが、なぜ電流読み取りと呼ばれるのでしょうか? つまり、レコードの最新バージョンを読み取ります。読み取り時には、他の同時トランザクションが現在のレコードを変更できないようにする必要があり、読み取りレコードはロックされます。

3. スナップショット読み取り (データベースの同時クエリ機能の向上)

ロックされていない選択操作はスナップショット読み取り、つまりロックのない非ブロッキング読み取りです。スナップショット読み取りの前提は、分離レベルがシリアル レベルではなく、シリアル レベルでのスナップショット読み取りは現在の読み取りに縮退することです。スナップショットが使用される理由読み取りの場合は、同時実行性能の向上を考慮したものである スナップショット読み取りの実装は、マルチバージョン同時実行制御、つまり MVCC に基づいている MVCC は行ロックの一種であると考えることができ、ただし、多くの場合、ロック操作が回避され、オーバーヘッドが削減されます。これは複数のバージョンに基づいているため、つまり、スナップショットの読み取りでは必ずしも最新バージョンのデータが読み取られるとは限りませんが、以前の履歴バージョンである可能性があります。

4. 現在の読み取り、スナップショットの読み取り、MVCC の関係

MVCC マルチバージョン同時実行制御とは、読み取りおよび書き込み操作に競合がないように、データの複数のバージョンを維持することを指します。スナップショット読み取りは、MVCC を実装するための MySQL のノンブロッキング読み取り機能です。MySQL での MVCC モジュールの具体的な実装は、3 つの暗黙的なフィールド、アンドゥ ログ、および読み取りビューによって実現されます。

MVCC はどのような問題を解決しますか?

データベースの同時実行シナリオには、次の 3 つがあります。

1. 読み取り: 問題はなく、同時実行制御は必要ありません。

2. 読み取りと書き込み: スレッドの安全性の問題があり、トランザクション分離の問題が発生する可能性があり、ダーティ読み取り、ファントム読み取り、反復不能読み取りが発生する可能性があります。

3. 書き込み: スレッドの安全性の問題があり、更新が失われるという問題が発生する可能性があります。

MVCC は、読み取り/書き込みの競合を解決するために使用されるロックフリーの同時実行制御です。つまり、単一項目の増加タイムスタンプをトランザクションに割り当て、変更ごとにバージョンを保存します。バージョンはトランザクションのタイムスタンプに関連付けられ、読み取り-only トランザクションの開始 以前のデータベースのスナップショットなので、MVCC はデータベースに関する次の問題を解決できます。

1. データベースの読み取りと書き込みを同時に行う場合、読み取り操作中に書き込み操作がブロックされることを回避でき、書き込み操作が読み取り操作をブロックする必要がないため、データベースの同時読み取りと書き込みのパフォーマンスが向上します。

2. ダーティ読み取り、ファントム読み取り、非反復読み取りなどのトランザクション分離の問題は解決しますが、更新の消失の問題は解決できません。

MVCC実装の原理は何ですか?

mvcc の実現原理は主に、レコード、アンドゥログ、読み取りビューの 3 つの隠しフィールドに依存します。

隠しフィールド

カスタム フィールドに加えて、レコードの各行には DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID、およびデータベースによって暗黙的に定義されたその他のフィールドもあります。

DB_TRX_ID

6 バイト、最近変更されたトランザクション ID、このレコードを作成したトランザクション ID、またはこのレコードを最後に変更したトランザクション ID を記録します

DB_ROLL_PTR

7バイト、ロールバックポインタ、このレコードの前のバージョンを指します、undologと連携するために使用され、前の古いバージョンを指します

DB_ROW_JD

6 バイト、非表示の主キー。データテーブルに主キーがない場合、innodb は 6 バイトの row_id を自動的に生成します。

記録は次の図に示されています。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-XiEa49zi-1662355491060)(images/data case.png)]

上の図では、DB_ROW_ID はデフォルトでこの行レコードに対してデータベースによって生成される一意の暗黙的な主キー、DB_TRX_ID はこのレコードで現在動作しているトランザクション ID、DB_ROLL_PTR は元に戻す操作と連携するために使用されるロールバック ポインターです。ログを作成し、以前の古いバージョンをポイントする

元に戻すログ

undolog はロールバック ログと呼ばれ、挿入、削除、更新操作中に生成される便利なロールバック ログを意味します。

挿入操作を実行する場合、生成されたアンドゥログはトランザクションがロールバックされる場合にのみ必要となり、トランザクションがコミットされた直後に破棄できます。

更新および削除操作を実行する場合、生成されたアンドゥログは、トランザクションがロールバックされるときだけでなく、スナップショットが読み取られるときにも必要となるため、ログがスナップショットの読み取りまたはトランザクションに関与していない場合にのみ、気軽に削除することはできません。対応するログは、パージ スレッドによって均一にクリアされます (データが更新および削除されるとき、古いレコードの delete_bit が設定されるだけで、古いレコードは実際には削除されません。これは、innodb が特別なパージを行うためです。ディスク領域保存スレッドは、deleted_bit が true であるレコードをクリアします。レコードの delete_id が true で、パージ スレッドの読み取りビューに対して DB_TRX_ID が表示されている場合、このレコードは確実にクリアできます)

undolog によって生成されたレコード チェーンを見てみましょう

1. トランザクション番号 1 のトランザクションがテーブルにレコードを挿入すると、この時点の行データの状態は次のようになります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-oFHZSS9K-1662355491061)(images/1.png)]

2. レコードの名前を変更して lisi に変更する 2 番目のトランザクション番号 2 があるとします。

トランザクション 2 が行レコード データを変更すると、データベースは行に排他的ロックを追加します。

次に、データの行を古いレコードとしてアンドゥログにコピーします。つまり、アンドゥログには現在の行のコピーが存在します。

コピーが完了したら、行名を lisi に変更し、隠しフィールドのトランザクション ID を現在のトランザクション 2 の ID に変更します。ロールバック ポインターは、アンドゥログにコピーされたコピー レコードを指します。

トランザクションがコミットされた後、ロックが解放されます

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-fbFRtagP-1662355491061)(images/2.png)]

3. レコードの経過時間を変更して 32 に変更する 3 番目のトランザクション番号 3 があるとします。

トランザクション 3 が行データを変更すると、データベースは行に排他的ロックを追加します。

次に、データ行を古いレコードとしてアンドゥログにコピーし、その行レコードにすでにアンドゥログがあることがわかります。その後、最新の古いデータがリンク リストのヘッダーとして使用され、アンドゥログの先頭に挿入されます。行レコードの

​ 修改该行age为32岁,并且修改隐藏字段的事务id为当前事务3的id,回滚指针指向刚刚拷贝的undolog的副本记录

​ 事务提交,释放锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCKTX4Ii-1662355491061)(images/3.png)]

​ 从上述的一系列图中,大家可以发现,不同事务或者相同事务的对同一记录的修改,会导致该记录的undolog生成一条记录版本线性表,即链表,undolog的链首就是最新的旧记录,链尾就是最早的旧记录。

Read View

​ 上面的流程如果看明白了,那么大家需要再深入理解下read view的概念了。

​ Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。

​ 其实Read View的最大作用是用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,也有可能读取的是当前行记录的undolog中某个版本的数据

​ Read View遵循的可见性算法主要是将要被修改的数据的最新记录中的DB_TRX_ID(当前事务id)取出来,与系统当前其他活跃事务的id去对比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过DB_ROLL_PTR回滚指针去取出undolog中的DB_TRX_ID做比较,即遍历链表中的DB_TRX_ID,直到找到满足条件的DB_TRX_ID,这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本数据。

​ Read View的可见性规则如下所示:

​ 首先要知道Read View中的三个全局属性:

trx_list: 値のリスト。読み取りビューの生成時にシステムがアクティブであるトランザクション ID (1、2、3) を維持するために使用されます。

up_limit_id: trx_list リスト内の最小のトランザクション ID を持つ ID を記録します (1)

low_limit_id: Read Viewの生成時にシステムによって割り当てられていない次のトランザクションID (4)

具体的な比較ルールは次のとおりです。

1. まず DB_TRX_ID < up_limit_id を比較し、それより小さい場合、現在のトランザクションは DB_TRX_ID が配置されているレコードを参照でき、それ以上の場合、次の判定に進みます

2. 次に、DB_TRX_ID >= low_limit_id であるかどうかを判断し、それ以上であれば、DB_TRX_ID が配置されているレコードは Read View が生成された後にのみ表示されることを意味するため、現在のトランザクションには確実に表示されません。より小さい場合、次の判断ステップに入る

3. DB_TRX_ID がアクティブなトランザクション内にあるかどうかを確認します。アクティブなトランザクション内にある場合は、Read View が生成された時点でトランザクションがまだアクティブであることを意味します。コミットはなく、変更されたデータは現在のトランザクションでは見ることができません。そうでない場合は、このトランザクションは読み取りビューが生成される前にコミットが開始されているため、変更された結果が表示されることを意味します。

7. MVCCの全体的な処理の流れ

次の図に示すように、4 つのトランザクションが同時に実行されているとします。

トランザクション1 トランザクション2 ビジネス3 トランザクション 4
事業開始 事業開始 事業開始 事業開始
変更して送信しました
進行中 スナップショットの読み取り 進行中

上の表から、トランザクション 2 がデータ行のスナップショット読み取りを実行すると、データベースはデータ行の読み取りビュー ビューを生成し、トランザクション 1 とトランザクション 3 がまだアクティブであることがわかります。トランザクション 4 はトランザクション 2 内にあります。更新はスナップショット読み取りの直前に送信されたため、システムの現在アクティブなトランザクション 1 と 3 は読み取りビューに記録され、リストで維持されます。同時に、次の図に示すように、up_limit_id の値が 1、low_limit_id の値が 5 であることがわかります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-hLrGo8a7-1662355491062)(images/image-20210520143604440.png)]

上記の例では、トランザクション 2 がスナップショットを読み取る前に、トランザクション 4 のみが行レコードを変更してトランザクションを送信したため、行内の現在のデータのアンドゥログは次のようになります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-m8w7oCbk-1662355491062)(images/image-20210520143717928.png)]

トランザクション 2 がスナップショット内の行レコードを読み取るとき、行レコードの DB_TRX_ID を取得して up_limit_id、 lower_limit_id、およびアクティブなトランザクション リストと比較し、トランザクション 2 を解釈して行レコードのバージョンを確認します。

具体的な処理は以下の通りです。 まず、この行に記録されているトランザクションID(4)とRead Viewのup_limit_idを比較して小さいかどうかを判定し、比較した値より小さくないので、条件を満たしている、引き続き4がlow_limit_id以上であるか判定し、合格 比較の結果、以下であるため条件を満たさない trx_listリストでトランザクション4が処理されるかどうかを判定し、再度リストにないことが判明したため、可視性条件が満たされ、トランザクション 4 が変更された後に送信された最新の結果がトランザクション 2 のスナップショットに表示されます。したがって、トランザクション 2 によって読み取られた最新のデータ レコードは、トランザクション 4 によって送信されたバージョンであり、トランザクション 4 によって送信されたバージョンもグローバルな観点からは最新バージョンです。以下に示すように:

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-i5sElp8N-1662355491062)(images/image-20210520143742317.png)]

上記の内容を理解すれば、これらの中心概念間の関係を誰もが理解できるはずです。次に、異なる分離レベルでのスナップショット読み取りの違いについて説明します。

8. RC レベルと RR レベルでの InnoDB スナップショット読み取りの違いは何ですか?

Read View生成のタイミングが異なるため、RCレベルとRRレベルでのスナップショット読み取り結果が異なります

1. RR レベルの特定のトランザクションによる特定のレコードの最初のスナップショット読み取りでは、システム内で現在アクティブな他のトランザクションを記録するためのスナップショット、つまり読み取りビューが作成され、スナップショット読み取りを呼び出すときにそれが使用されます。ビュー。他のトランザクションが更新をコミットする前に現在のトランザクションがスナップショット読み取りを使用している限り、後続のスナップショット読み取りでは同じ読み取りビューが使用されるため、後続の変更は表示されません。

2. RR レベルでは、読み取りビューを生成するためにスナップショットが読み取られると、読み取りビューはこの時点で他のすべてのアクティビティとトランザクションのスナップショットを記録します。これらのトランザクションの変更は、現在のトランザクションには表示されません。読み取りビューよりも前に作成された変更が表示されます

3. RC レベルでは、トランザクション内の各スナップショット読み取りによって新しいスナップショットと読み取りビューが生成されます。そのため、トランザクション内の他のトランザクションによって送信された更新を RC レベルで確認できます。

概要: RC 分離レベルでは、各スナップショット読み取りは最新の読み取りビューを生成して取得しますが、RR 分離レベルでは、同じトランザクション内の最初のスナップショット読み取りのみが読み取りビューを作成し、後続のスナップショット読み取りは同じ読み取りビュー。

mysqlのマスタースレーブレプリケーションとは何ですか?

MySQL マスター/スレーブ レプリケーションとは、MySQL データベース サーバーのマスター ノードから 1 つ以上のスレーブ ノードにデータをレプリケートできることを意味します。MySQL はデフォルトで非同期レプリケーション方式を採用しているため、スレーブ ノードは自身のデータを更新するためにマスター サーバーにアクセスし続ける必要がなく、データの更新はリモート接続で実行でき、スレーブ ノードはサーバー内のすべてのデータベースをレプリケートできます。マスター データベース、特定のデータベース、または特定のテーブル。

なぜ mysql にはマスターとスレーブの同期が必要なのでしょうか?

1. 複雑なビジネス システムでは、SQL ステートメントでテーブルをロックする必要がある状況があり、その結果、読み取りサービスが一時的に使用できなくなり、ビジネスの実行に大きな影響を及ぼします。マスター/スレーブ レプリケーションを使用し、マスターデータベースが書き込みを担当し、スレーブライブラリが読み取りを担当することで、メインライブラリがテーブルをロックしても、スレーブライブラリが読み出すことで業務の正常な動作が保証されます。

2. データのホットバックアップを実行する

3. 構造の拡張。業務量が増大し、I/Oアクセス頻度が高くなって1台のマシンでは対応できない場合、マルチライブラリストレージを利用することでディスクI/Oアクセス頻度を低減し、I/Oパフォーマンスを向上させています。単一のマシンの。

mysqlレプリケーションの原理は何ですか?

(1) マスター サーバーはデータの変更をバイナリ バイナリ ログ ログに記録し、マスター上のデータが変更されると、その変更をバイナリ ログに書き込みます。

(2) スレーブ サーバーは、マスター バイナリ ログが一定の時間内に変更されたかどうかを検出し、変更された場合は、I/OThread を開始してマスター バイナリ イベントを要求します。

(3) 同時に、マスター ノードは各 I/O スレッドのダンプ スレッドを開始し、これを使用してバイナリ イベントを送信し、スレーブ ノードのローカル リレー ログに保存します。リレー ログから読み取る SQL スレッド バイナリ ログを取得し、データがマスター ノードのデータと一致するようにローカルで再生します 最後に、I/OThread と SQLThread はスリープ状態になり、次のウェイクアップを待ちます。

つまり、次のようになります。

  • ライブラリから 2 つのスレッド (1 つの I/O スレッドと 1 つの SQL スレッド) が生成されます。
  • I/O スレッドはメイン ライブラリの binlog を要求し、取得した binlog をローカルのリレー ログ (リレー ログ) ファイルに書き込みます。
  • メイン ライブラリはログ ダンプ スレッドを生成して、binlog をスレーブ ライブラリ I/O スレッドに渡します。
  • SQL スレッドはリレー ログ ファイル内のログを読み取り、SQL ステートメントに解析して 1 つずつ実行します。

知らせ:

1 – マスターは操作ステートメントを binlog ログに記録し、スレーブにリモート接続の許可を付与します (マスターは binlog バイナリ ログ機能を有効にする必要があります。通常はデータ セキュリティを考慮して、スレーブも binlog 機能を有効にします)。
2 – スレーブは、IO スレッドと SQL スレッドの 2 つのスレッドを開始します。その中で、IO スレッドはマスターのバイナリ ログ コンテンツをリレー ログに読み取る役割を果たし、SQL スレッドはバイナリ ログ コンテンツをリレー ログから読み取ってスレーブ データベースに更新する役割を果たします。マスターデータを合意したままにすることができます。
3-Mysql レプリケーションには少なくとも 2 つの Mysql サービスが必要です。もちろん、Mysql サービスは異なるサーバーに分散することも、1 つのサーバーで複数のサービスを開始することもできます。
4 – Mysql レプリケーションでは、マスター サーバーとスレーブ サーバーの Mysql のバージョンが同じであることを確認するのが最適です (バージョンが満足できない場合は、マスター マスター ノードのバージョンがスレーブ ノードのバージョンよりも低いことを確認してください)。 5 – マスターノードとスレーブノード間の時間を
同期する必要がある

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-ZnKkBiT5-1662355491063)(images/master-slave priority.png)]

具体的な手順:

1. スレーブ ライブラリは、change master to ステートメントを手動で実行してマスター ライブラリに接続し、接続されたユーザーのすべての条件 (ユーザー、パスワード、ポート、IP) を提供し、スレーブ ライブラリにバイナリ ログの開始点を知らせます。 (ファイル名、位置番号); スレーブを開始します

2. スレーブ ライブラリの IO スレッドとメイン ライブラリのダンプ スレッド間の接続を確立します。

3. Change master to ステートメントによって指定されたファイル名と位置番号に従って、IO スレッドはマスター ライブラリへのバイナリログ要求を開始します。

4. メイン ライブラリのダンプ スレッドは、スレーブ ライブラリの要求に従って、ローカル バイナリ ログをスレーブ ライブラリの IO スレッドにイベントの形式で送信します。

5. ライブラリ IO スレッドから binlog イベントを受信し、ローカルのリレーログに保存します。送信された情報は master.info に記録されます。

6. データベース SQL スレッドからリレー ログを適用し、適用されたレコードをリレーログ.info に記録します。デフォルトでは、適用されたリレーは自動的に消去されます。

Myisam と Innodb の違いを簡単に説明してください。

InnoDB ストレージ エンジン: 主に OLTP (Online Transaction Processing、オンライン トランザクション処理) アプリケーション向けで、ACID トランザクションを完全にサポートする最初のストレージ エンジンです (トランザクションをサポートする BDB の最初のストレージ エンジンは開発が停止されました)。
特徴:

1 行ロックをサポート
2 外部キーをサポート
3 列 AUTO_INCREMENT 属性の自動追加をサポート
4 トランザクションを
サポート 5 MVCC モードでの読み取りおよび書き込みをサポート 6
読み取り効率は MYISAM より低い
7. 書き込み効率は MYISAM より高い
8. 頻繁な変更に適しており、安全に設計されています。 9. テーブル全体をクリアする場合
、Innodb は行ごとに削除します。

MyISAM ストレージ エンジン: MySQL が提供する公式のストレージ エンジンで、主に OLAP (オンライン分析処理、オンライン分析処理) アプリケーション向けに提供されます。

特徴:

1 オペレーティング システムに関係なく、MyISAM ストレージ エンジン テーブルが作成されると、ローカル ディスクに 3 つのファイルが作成されます。たとえば、tb_demo テーブルを作成すると、tb_demo.frm、tb_demo の 3 つのファイルが生成されます。 MYD、および tb_demo.MYI
2 はトランザクションをサポートしません。3
はテーブル ロックとフルテキスト インデックスをサポートします
。4 MyISAM ストレージ エンジン テーブルは MYD と MYI で構成されます。MYD はデータ ファイルの格納に使用され、MYI はインデックス ファイルの格納に使用されます。MySQL データベースはインデックス ファイルのみをキャッシュし、データ ファイルのキャッシュはオペレーティング システム自体に委ねられます。5
MySQL 5.0 以降、MyISAM はデフォルトで 256T の単一テーブル データをサポートします。6.
集中的なテーブルの選択: MYISAM ストレージ エンジンは、大量のデータをフィルタリングする データが非常に高速であり、これが最大の利点です
7. InnoDB よりも読み取り効率が良い
8. InnoDB よりも書き込み効率が低い
9. クエリおよび挿入ベースに適していますアプリケーション
10. テーブル全体がクリアされると、MYISAM は新しい 1 つのサーフェスを作成します

mysql のインデックスの種類と、それらがデータベースのパフォーマンスに与える影響について簡単に説明してください。

通常のインデックス: インデックス付きデータ列に重複した値を含めることができます。

一意のインデックス: データ レコードの一意性を保証できます。

主キー インデックス: 特別な一意のインデックスです。テーブル内に定義できる主キー インデックスは 1 つだけです。主キーは、レコードを一意に識別するために使用されます。主キー キーワードを使用して作成されます。

結合インデックス: インデックスは複数のデータ列をカバーできます。

全文インデックス:転置インデックスを設けることで検索効率が大幅に向上し、フィールドが含まれるかどうかの判定問題は現在検索エンジンで使用されているキーテクノロジーです

インデックスにより、データのクエリ速度が大幅に向上します。

インデックスを使用すると、クエリ プロセス中に最適化ハイダーを使用してシステムのパフォーマンスを向上させることができます。

ただし、テーブルの挿入、削除、更新の速度は低下します。これは、これらの書き込み操作を実行するときにインデックス ファイルも操作する必要があるためです。

インデックスは物理領域を占有する必要があります。データ テーブルが占有するデータ領域に加えて、各インデックスも一定量の物理領域を占有します。クラスタ化インデックスを再開する場合、必要な領域はさらに大きくなります。多数のインデックスが存在する場合、必要な領域はさらに大きくなります。非クラスター化インデックス (1 回) クラスター化インデックスが変更されると、すべての非クラスター化インデックスもそれに応じて変更されます。

バイトコードとは何ですか?

JVM はさまざまなオペレーティング システムおよびプラットフォーム向けにカスタマイズされているため、プラットフォームに関係なく、 javac コマンドを使用して、.java ファイルを JVM で使用できる固定形式のバイトコード (.class ファイル) にコンパイルできます。バイトコードと呼ばれる理由は、**.class ファイルは 16 進値で構成されており、JVM はそれを 2 つの 16 進値のグループとしてバイト単位で読み取るためです**。形式は次のとおりです [外部リンク画像の転送に失敗しました。
ソース
サイトリーチ防止メカニズムがある場合があります。画像を保存して直接アップロードすることをお勧めします (img-kUw2NtHG-1662355491063)(images/bytecode.png)]

バイトコードの構造は何ですか?

JVM にはバイトコードの仕様に関する要件があり、以下の図に示すように、各バイトコード ファイルが 10 個の部分で一定の順序で構成されている必要があります: 画像を保存して直接アップロードします (img-4f7itZ44-1662355491063
) (画像/bytecode2.png)]

  1. 魔数

すべての .class ファイルの最初の 4 バイトはマジック ナンバーです。マジック ナンバーはファイルの先頭に配置される固定値 0xCAFEBABE であり、JVM はファイルが .class ファイルであるかどうかをその先頭に基づいて判断できます。このマジックナンバーの固定値は Java の父である James Gosling によって指定されたもので、CafeBabe (コーヒーベイビー) を意味します。

  1. バージョンナンバー

バージョン番号はマジックの後の 4 バイトで、最初の 2 バイトはマイナー バージョン番号 (Minor Version) を表し、最後の 2 バイトはメジャー バージョン番号 (Major Version) を表します。上記の 0000 0032、マイナー バージョン番号 0000 は変換されます10 進数が 0 の場合、メイン バージョン番号 0032 は 10 進数の 50 に変換され、下図のバージョン マッピング関係に対応します。対応する Java バージョン番号は 1.6 であることがわかります。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-BWXDThgf-1662355491063)(images/bytecodeversion.png)]

  1. 定数プール

紧接着主版本号之后的字节为常量池入口,常量池中有两类常量:字面量和符号引用,字面量是代码中申明为Final的常量值,符号引用是如类和接口的全局限定名、字段的名称和描述符、方法的名称和描述符。常量池整体分为两个部分:常量池计数器以及常量池数据区
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FaFnqfhL-1662355491064)(images/changlangchi.png)]

  1. 访问标志

常量池结束后的两个字节,描述的是类还是接口,以及是否被Public、Abstract、Final等修饰符修饰,JVM规范规定了9种访问标示(Access_Flag)JVM是通过按位或操作来描述所有的访问标示的,比如类的修饰符是Public Final,则对应的访问修饰符的值为ACC_PUBLIC | ACC_FINAL,即0x0001 | 0x0010=0x0011
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2L3mXkBx-1662355491064)(images/access_flag.png)]

  1. 当前类索引

访问标志后的两个字节,描述的是当前类的全限定名,这两个字节保存的值是常量池中的索引值,根据索引值就能在常量池中找到这个类的全限定名

  1. 父类索引

当前类名后的两个字节,描述的父类的全限定名,也是保存的常量池中的索引值

  1. 接口索引

父类名称后的两个字节,是接口计数器,描述了该类或者父类实现的接口数量,紧接着的n个字节是所有接口名称的字符串常量的索引值

  1. 字段表

クラスおよびインターフェイスで宣言された変数を記述するために使用されます。これには、クラス レベルの変数とインスタンス変数が含まれますが、メソッド内で宣言されたローカル変数は含まれません。フィールド テーブルも 2 つの部分に分かれています。最初の部分は 2 バイトで、フィールド Number、 2 番目の部分は各フィールドの詳細情報です field_info
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-DPervFv5-1662355491065)(images/フィールド.png)]

  1. メソッドテーブル

フィールドテーブルがメソッドテーブルになった後、メソッドテーブルも2つの部分に分かれており、最初の部分はメソッドの数を表す2バイトで、2番目の部分は各メソッドの詳細情報です。
メソッド アクセス フラグ、メソッド名、メソッド記述子、およびメソッド属性を含む、より複雑です
: [外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img -RPW3Acwg-1662355491065)(画像/メソッド.png)]

  1. 追加の属性

バイトコードの最後の部分であるこの項目には、このファイル内のクラスまたはインターフェイスによって定義されたプロパティの基本情報が格納されます。

クラスの初期化プロセスとは何ですか?

まず、クラスロードメカニズムのプロセスは、ロード、検証、準備、解析、初期化の 5 つの部分に分かれています。
[外部リンク画像の転送に失敗しました。ソースサイトにはリーチ防止メカニズムがある可能性があります。画像を保存してアップロードすることをお勧めします。直接 (img-ZiEZ1NrE-1662355491066 )(images/class-int.png)]

ここでは主にクラスの初期化プロセスを分析します。

  1. クラスの初期化フェーズとは、クラス内に定義されたJavaプログラムコード(バイトコード)を実際に実行開始し、プログラマの意図に従ってクラス変数を初期化する処理です。より直接的には、初期化フェーズはクラス コンストラクター () メソッドを実行するプロセスです。() メソッドは、コンパイラによって自動的に収集されるクラス内のすべてのクラス変数の代入アクションと、静的コード ブロック static{} 内のステートメントの組み合わせによって生成されます。コンパイラによる収集の順序は、次の順序によって決まります。ここで、ステートメントはソース ファイル内に表示されます。
  2. クラス初期化の順序について ** (静的変数、静的初期化ブロック: クラス内での出現順序による) > (変数、初期化ブロック: クラス内での出現順による) > コンストラクター * *
  3. クラス初期化の詳細なプロセスについては、Java 仮想マシン仕様を参照してください。クラス初期化プロセスは次のとおりです。
    1. 各クラスには初期化ロック LC があり、プロセスが LC を取得します。この操作により、現在のスレッドは LC ロックが取得されるまで待機します。
    2. C が他のスレッドによって初期化されている場合、現在のスレッドは LC を解放してブロック状態に入り、C の初期化が完了するまで待機します。この時点で、現在のスレッドはプロセスを再試行する必要があります。初期化プロセスの実行中、スレッドの割り込みステータスは影響を受けません。
    3. C がこのスレッドによって初期化されている場合、つまり再帰的に初期化されている場合は、LC を解放して通常に戻ります。
    4. C が初期化されている場合は、LC を解放して正常に戻ります。
    5. C がエラー状態にあり、初期化を完了できないことを示している場合は、LC を解放し、例外 NoClassDefFoundError を発生させます。
    6. それ以外の場合は、C をこのスレッドによって初期化されるものとしてマークし、LC を解放し、その後、最終型および基本型のクラス メンバー変数を初期化します。
    7. C がインターフェイスではなくクラスであり、C の親クラス スーパー クラス (SC) と各インターフェイス SI_n (implements 句の順序に従って) が初期化されていない場合、SC に対して完全な初期化プロセスを再帰的に実行します。必要に応じて、最初に SC を検証して準備する必要があります。SC または SIn の初期化中に例外がスローされた場合は、LC を取得し、C をエラー状態としてマークし、待機中のすべてのスレッドに通知し、次に LC を解放して、同じ異常をスローします。 。
    8. Cのクラスローダーからアサーションアサーションメカニズムがオンになっているかどうかを取得します
    9. 次に、クラス変数の初期化と静的コード ブロック、またはインターフェイスのフィールド初期化をテキストの順序で実行し、それらを個別のコード ブロックとして扱います。
    10. 実行が正常であれば、LC を取得し、C オブジェクトを初期化済みとしてマークし、待機中のすべてのスレッドに通知してから、LC を解放し、プロセス全体を正常に終了します。
    11. それ以外の場合、例外 E がスローされると、終了は中止されます。E が Error でない場合は、E をパラメータとして新しい例外 ExceptionInInitializerError を作成します。OutOfMemoryError が原因で ExceptionInInitializerError を作成できない場合は、OutOfMemoryError が E として返されます。
    12. LC を取得し、C をエラー状態にマークし、待機中のすべてのスレッドに通知し、LC を解放して、例外 E をスローします。

JLS では、親クラスが最初に初期化され、静的ブロックとクラス変数の割り当てがテキスト順に行われることが規定されていることがわかります。

JVM内存模型如何分配的?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pkq1JL6U-1662355491066)(images/javammode.png)]

JVM性能调优的原则有哪些?

  1. 多数的Java应用不需要在服务器上进行GC优化,虚拟机内部已有很多优化来保证应用的稳定运行,所以不要为了调优而调优,不当的调优可能适得其反
  2. 在应用上线之前,先考虑将机器的JVM参数设置到最优(适合)
  3. 在进行GC优化之前,需要确认项目的架构和代码等已经没有优化空间。我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用,通过GC优化令其性能达到一个质的飞跃
  4. GC优化是一个系统而复杂的工作,没有万能的调优策略可以满足所有的性能指标。GC优化必须建立在我们深入理解各种垃圾回收器的基础上,才能有事半功倍的效果
  5. 处理吞吐量和延迟问题时,垃圾处理器能使用的内存越大,即java堆空间越大垃圾收集效果越好,应用运行也越流畅。这称之为GC内存最大化原则
  6. 在这三个属性(吞吐量、延迟、内存)中选择其中两个进行jvm调优,称之为GC调优3选2

什么情况下需要JVM调优?

  • Heap内存(老年代)持续上涨达到设置的最大内存值
  • Full GC 次数频繁
  • GC 停顿(Stop World)时间过长(超过1秒,具体值按应用场景而定)
  • 应用出现OutOfMemory 等内存异常
  • 应用出现OutOfDirectMemoryError等内存异常( failed to allocate 16777216 byte(s) of direct memory (used: 1056964615, max: 1073741824))
  • 应用中有使用本地缓存且占用大量内存空间
  • 系统吞吐量与响应性能不高或下降
  • 应用的CPU占用过高不下或内存占用过高不下

在JVM调优时,你关注哪些指标?

  1. **スループット: **ユーザー コード時間 / (ユーザー コード実行時間 + ガベージ コレクション時間)。ガベージ コレクターの能力を評価するための重要な指標の 1 つであり、ガベージ コレクションによる一時停止時間やメモリ消費量を考慮せずに、ガベージ コレクターがアプリケーションをサポートできるかどうかを示す最高のパフォーマンス指標です。スループットが高いほど、アルゴリズムが優れています。
  2. **低遅延:** STW が短いほど、応答時間は長くなります。ガベージ コレクションの能力を評価するための重要な指標であり、アプリケーション実行時のジッターを回避するために、ガベージ コレクションによる一時停止時間を短縮するか、ガベージ コレクションによる一時停止を完全に排除するかが指標となります。一時停止時間が短いほど、アルゴリズムは優れています
  3. GC アルゴリズムを設計 (または使用) するときは、目標を決定する必要があります。GC アルゴリズムは、2 つの目標のうち 1 つだけを目指すこともできます (つまり、最大スループットまたは最小休止時間のみに焦点を当てる) ことも、2 つの目標の間の妥協点を見つけようとすることもできます。
  4. MinorGC は、できるだけ多くのガベージ オブジェクトを収集します。これを「MinorGC 原則」と呼び、この原則に従うことで、アプリケーションでの FullGC の頻度を減らすことができます。FullGC は時間がかかり、アプリケーションがレイテンシー要件やスループットを満たさない原因となります。
  5. ヒープサイズ調整の開始点と分析点:
    1. 統計 マイナー GC 期間
    2. マイナー GC の数をカウントする
    3. フル GC の最長期間の統計
    4. 統計的最悪のケースのフル GC 頻度
    5. 統計的 GC の期間と頻度は、ヒープ サイズを最適化するための主な開始点です
    6. 業務システムの遅延やスループットの要件に応じて、これらの分析に従って各領域のサイズを調整できます。
  6. 一般的に言えば、スループット優先のガベージ コレクター: -XX:+UseParallelGC -XX:+UseParallelOldGC、つまり従来型 (PS/PO)
  7. 応答時間優先のガベージ コレクター: CMS、G1

JVMの共通パラメータは何ですか?

  1. Xms とは、プログラムの起動時にプログラムが占有するメモリ サイズを設定することを指します。一般的に、サイズが大きいほどプログラムの起動は速くなりますが、一時的にマシンの速度が低下する可能性もあります。
  2. Xmx は、プログラムの実行中に占有できる最大メモリ サイズの設定を指します。プログラムがこの設定値を超えるメモリを使用する必要がある場合、OutOfMemory 例外がスローされます。
  3. Xss は、各スレッドのスタック サイズを設定することを指します。これは、プログラム、スレッドが占有する必要があるメモリの量、同時に実行できるスレッドの数などによって異なります。
  4. **-Xmn、-XX:NewSize/-XX:MaxNewSize、-XX:NewRatio **
    1. 高優先度: -XX:NewSize/-XX:MaxNewSize
    2. 中優先度: -Xmn (デフォルトの -Xmn=-XX:NewSize=-XX:MaxNewSize=? と同等)
    3. 低優先度: -XX:NewRatio
  5. クラスのロードとクラスのアンロードをログで追跡したい場合は、起動パラメータ **-XX:TraceClassLoading -XX:TraceClassUnloading** を使用できます。

JVM で一般的に使用されるパフォーマンス チューニング ツールは何ですか?

  1. マット

    1. メモリリークの可能性を示唆するポイント
  2. jvisualvm

  3. jコンソール

  4. アルサス

  5. show-busy-java-threads

    1. https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads

オンライン トラブルシューティングの一般的なプロセスは何ですか?

  1. 過剰な CPU 使用率のトラブルシューティング プロセス
    1. pid が 9876 の場合、top コマンドを使用して最高の CPU を備えたプロセスの pid を確認します。
    2. 次に、プロセス [top -Hp 9876] で最も占有率の高いスレッド ID を確認します。
    3. 占有率が最も高いスレッド ID が 6900 であると仮定し、16 進数に変換します (Java ネイティブ スレッドは 16 進数で出力されるため) [printf '%x\n' 6900]
    4. jstack を使用して Java スレッドの呼び出しスタック情報 [jstack 9876 | grep '0x1af4' -A 50 --color] を出力すると、問題をより正確に特定できるようになります。
  2. 過剰なメモリ使用量のトラブルシューティング プロセス
    1. プロセス ID を見つけます: [top -d 2 -c]
    2. JVM ヒープ メモリ割り当てを確認します: jmap -heap pid
    3. 大量のメモリを占有するオブジェクトを表示する jmap -histo pid | head -n 100
    4. 多くのメモリを占有している生き残ったオブジェクトを表示する jmap -histo:live pid | head -n 100

OOM はどのような状況でスローされますか?

  • JVM 時間の 98% はメモリのリサイクルに費やされます
  • 毎回回復されるメモリは 2% 未満です

これら 2 つの条件を満たすと OutOfMemoryException がトリガーされ、手動でヒープ ダンプを出力するなど、ダウンする前にシステムがいくつかの操作を実行するための小さなギャップが残ります。メモリがなくなっても
スローされ

システム OOM が発生する前の現象は何ですか?

  • 各ガベージ コレクションの時間は以前の 10 ミリ秒から約 50 ミリ秒とどんどん長くなり、FullGC の時間も以前の 0.5 秒から 4、5 秒に延長されました。
  • FullGC の数は増加しており、最も頻繁に発生する FullGC は 1 分未満です。
  • 旧世代のメモリはますます大きくなり、各 FullGC の後、旧世代の少量のメモリのみが解放されます。

ヒープダンプファイルを分析するにはどうすればよいですか?

起動パラメーター -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/app/data/dump/heapdump.hpro を指定すると、OOM が発生したときにダンプ
ファイルを

GC ログ分析を行うにはどうすればよいですか?

GC ログ情報の分析を容易にするために、起動パラメーター [-Xloggc: app-gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps] を指定して、GC ログ情報を詳細に表示できます。

  1. [jinfo pid] を使用して、現在の JVM ヒープの関連パラメータを表示します
  2. 引き続き [jstat -gcutil 2315 1s 10] を使用して、10 秒以内の現在のヒープ占有率を表示します。
  3. [jmap -heap pid] を使用して、現在の JVM ヒープの状況を表示することもできます。
  4. [jmap -F -histo pid | head -n 20] を引き続き使用して、印刷の最初の 20 行を表示する、つまり、現在の上位 20 個のラージ オブジェクトを表示することができます。通常、ここからいくつかの異常なラージ オブジェクトを見つけることができます。そうでない場合は、引き続きトップ 50 のラージ オブジェクト、分析のランク付けを続けることができます。
  5. 最後に、[jmap -F -dump:file=a.bin pid] を使用します。ダンプ ファイルが非常に大きい場合は、[tar -czvf a.tar.gz a.bin] で圧縮できます。
  6. その後、ダンプ ファイルが分析され、MAT を使用してメモリ リークが分析されます。
  7. 参考事例: https: //www.lagou.com/lgeduarticle/142372.html

オンラインのデッドロックをトラブルシューティングするにはどうすればよいですか?

  1. jps は問題がある可能性のあるプロセス IDを探します
  2. 次に、[jstack -Fプロセス ID ]を実行します。
  3. JVM へのリモート接続が可能な環境の場合は、jconsole または jvisualvm を使用して、グラフィカル インターフェイスでデッドロックが発生しているかどうかを確認できます。

時間がかかりすぎるオンラインYGCの最適化ソリューションとは何ですか?

  1. ライフサイクルの長いオブジェクト (グローバル変数や静的変数など) が増えると、ラベル付けとコピーの時間のかかるプロセスが増加します。
  2. 生き残ったオブジェクトをマークするのに時間がかかりすぎる: たとえば、Object クラスの Finalize メソッドがオーバーロードされているため、Final Reference をマークするのに時間がかかったり、String.intern メソッドが不適切に使用され、YGC がStringTable のスキャン時間が長すぎます。時間のかかるGC処理は以下のパラメータで表示可能 -XX:+PrintReferenceGC
  3. 長期オブジェクトの過剰な蓄積: たとえば、ローカル キャッシュの不適切な使用や、多数の存続オブジェクトの蓄積、または深刻なロック競合によりスレッドのブロックが発生し、ローカル変数のライフ サイクルが長くなります。
  4. 事例参照: https://my.oschina.net/lishangzhi/blog/4703942

オンラインで頻繁に使用される FullGC 最適化ソリューションとは何ですか?

  1. 頻繁に発生するオンライン FullGC には、一般に次の特徴があります。
    1. ライン上の複数のスレッドの CPU が 100% を超えており、jstack コマンドを使用すると、これらのスレッドは主にガベージ コレクション スレッドであることがわかります。
    2. jstat コマンドで GC の状況を監視すると、Full GC の数が非常に多く、その数が増加していることがわかります。
  2. トラブルシューティングのプロセス:
    1. top は、CPU 使用率が最も高いプロセス
    2. 次に、[top -Hp process id] で、CPU 使用率が最も高いスレッド
    3. [printf "%x\n" thread id]、16 進数の結果が
    4. jstack スレッド ID | grep '0xa' -A 50 --color
    5. 通常のユーザー スレッドの場合は、スレッドのスタック情報をチェックして、より多くの CPU を消費するユーザー コードが実行されている場所を確認します。
    6. スレッドが VMThread の場合、jstat-gcutil コマンドを使用して現在のシステムの GC ステータスを監視し、jmapdump:format=b,file= を使用してシステムの現在のメモリ データをエクスポートします。エクスポートした後、メモリ状況を Eclipse のマット ツールに入力して分析すると、メモリ内のどのオブジェクトがより多くのメモリを消費しているかを確認し、関連するコードを処理できます。通常の状況では、VM スレッドが参照していることがわかります。ガベージコレクションのスレッド
    7. 次に、[jstat -gcutil **プロセス ID] を実行し、** 結果を確認します。FGC の数が多く増加している場合は、メモリ オーバーフローによる頻繁な FullGC が原因でシステムが遅くなっていることがわかります。
    8. 次に、メモリ ログをダンプし、MAT ツールを使用してどのオブジェクトが大量のメモリを占有しているかを分析し、オブジェクトが作成された場所を見つけて処理します。
  3. 参考事例: https://mp.weixin.qq.com/s/g8KJhOtiBHWb6wNFrCcLVg

オンラインのオフヒープ メモリ リークを分析するにはどうすればよいですか? (特に Netty が一般的です)

  1. JVM のオフヒープ メモリ リークの場所は常に難しい問題でした。
  2. 外部メモリのリーク解析は、通常、ヒープ内メモリ解析のプロセスから派生します。ヒープ内のメモリ リークを分析する過程で、計算した JVM ヒープ メモリが実際にはJVM 全体のXmxサイズよりも大きかったことが判明した可能性があります。これは、追加のヒープ メモリが追加のヒープ メモリであることを意味します。
  3. Netty オフヒープ メモリを使用すると、サードパーティのツールを使用せずに自分でオフヒープ メモリの使用状況を監視できます。オフヒープ メモリの取得には「リフレクション」を使用します。
  4. バグが見つかるまで、徐々に範囲を狭めていきます。特定のスレッドの実行によってバグが発生したことを確認した場合、シングルステップ実行またはバイナリ実行を実行できます。コードの特定の行を見つけたら、このコードに従い、シングルステップ実行またはバイナリ実行を続けて最後の行を見つけます。バグコード。この方法は何度も試行され、最終的に必要なバグをいつでも見つけることができます。
  5. アイデアのデバッグに精通しているので、電光石火のような速さで「バグを捕まえる」ことにしましょう (これが「The Flash」の誕生です)。ここで、最も一般的なデバッグ方法は、式を事前に実行することであり、オブジェクトを見つめながらスレッド呼び出しスタックを通じて、このオブジェクトの定義、割り当てなどを把握できます。
  6. ダイレクト メモリを使用するプロジェクトでは、システムが実際に到達できる最大ダイレクト メモリ値を設定するように -XX:MaxDirectMemorySize を構成することが最善です。デフォルトの最大ダイレクト メモリ サイズは、-Xmx の値と同じです。
  7. ヒープ外リークのトラブルシューティングを行うには、起動パラメータ -XX:NativeMemoryTracking=summary -Dio.netty.leakDetection.targetRecords=100-Dio.netty.leakDetection.level=PARANOID を指定することをお勧めします。後の 2 つのパラメータはNetty関連のメモリリーク検出とサンプリングのレベル
  8. 参考事例:https://tech.meituan.com/2018/10/18/netty-direct-memory-screening.html

オンライン メタスペース メモリ リークに対する最適化ソリューションは何ですか?

  1. 注意すべき点の 1 つは、Java8 および Java8+ JVM は永続世代を破棄し、メタスペースに置き換えていることです。メタスペースは JVM ヒープ内にあるかどうかに関係なく、オフヒープ メモリに属し、最大物理メモリによって制限されます。 。ベスト プラクティスは、起動パラメーターで -XX:MetaspaceSize=1024m -XX:MaxMetaspaceSize=1024m を設定することです。具体的な値は状況に応じて設定される。動的適用を避けるために、最大値に直接設定できます。
  2. メタスペースには主にクラスメタデータが格納されており、メタスペースは、そのクラスメタデータをロードするクラスローダがリサイクルできるかどうかでクラスメタデータがリサイクルできるかどうかを判断し、クラスローダがリサイクルできない限り、それを通じてロードされたクラスメタデータはリサイクルされません。リサイクルされた。そのため、フレームワークではバイトコードの強化やプロキシ クラスの生成に ASM や javassist などのツールがよく使用されるため、オンラインで問題が発生することがあります。メインスレッドがプロジェクト内で動的プロキシ クラスを頻繁に生成すると、メタスペースが急速にいっぱいになり、リサイクルできなくなります。
  3. 特定のケースについては、https: //zhuanlan.zhihu.com/p/200802910を参照してください。

Javaクラスローダーとは何ですか?

ブートストラップクラスローダー

起動クラス ローダーは主に JVM 自体に必要なクラスをロードします。このクラス ローダーは C++ 言語で実装されており、親クラスはありません。仮想マシン自体の一部です。コア クラス ライブラリまたは* jarを担当します。*-Xbootclasspath パラメータで指定されたパスにある package** がメモリにロードされます。仮想マシンは、rt.jar などのファイル名に従って jar パッケージをロードする必要があることに注意してください。ファイル名が仮想マシンでは、たとえ jar であっても、パッケージを lib ディレクトリにドロップしても意味がありません (セキュリティ上の理由から、ブートストラップ起動クラス ローダーは、java、javax、sun などで始まるパッケージ名のみをロードします)。

拡張クラスローダー

拡張クラス ローダーは、Sun によって実装された sun.misc.Launcher$ExtClassLoader クラスを参照します。これはJava 言語によって実装されます。親クラス ローダーは null で、Launcher の静的な内部クラスです。** のロードを担当します。 <JAVA_HOME>/lib/ ext ディレクトリ、またはシステム変数 -Djava.ext.dir** で指定されたビット パス内のクラス ライブラリでは、開発者は標準の拡張クラス ローダーを直接使用できます

](https://blog.csdn.net/javazejian/article/details/73413292)

アプリケーションクラスローダー

アプリケーション ローダーという用語は、Sun によって実装された sun.misc.Launcher$AppClassLoader を指します。親クラス ローダーは ExtClassLoader で、システム クラス パス java -classpathまたは **-D java.class.path で指定されたパスの下にクラス ライブラリ** をロードします。これは開発者がよく使用するクラスパス パスです。直接使用できます。 システム クラス ローダー。通常の状況では、このクラス ローダーはプログラム内のデフォルトのクラス ローダーであり、クラス
ローダー

カスタムカスタムクラスローダー

アプリケーションはクラス ローダーをカスタマイズでき、親クラス ローダーは AppClassLoader です。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-OvU1xwZf-1662355491066)(images/classloader.png)]

親委任メカニズムとは何ですか?

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-Z73BLK5J-1662355491067)(images/classloader2.png)]

双亲委派机制
双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

双亲委派的好处

  • 每一个类都只会被加载一次,避免了重复加载
  • 每一个类都会被尽可能的加载(从引导类加载器往下,每个加载器都可能会根据优先次序尝试加载它)
  • 有效避免了某些恶意类的加载(比如自定义了Java.lang.Object类,一般而言在双亲委派模型下会加载系统的Object类而不是自定义的Object类)

另外,可以多讲一下,如何破坏双亲委派模型

  1. 双亲委派模型的第一次“被破坏”是重写自定义加载器的loadClass(),jdk不推荐。一般都只是重写findClass(),这样可以保持双亲委派机制.而loadClass方法加载规则由自己定义,就可以随心所欲的加载类
  2. 双亲委派模型的第二次“被破坏”是ServiceLoader和Thread.setContextClassLoader()。即线程上下文类加载器(contextClassLoader)。双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码,那该怎么办呢?线程上下文类加载器就出现了。
    1. SPI。このクラス ローダーは、java.lang.Thread クラスの setContextClassLoader() メソッドを通じて設定できます。スレッドの作成時に設定されていない場合は、親スレッドから継承されます。 application のグローバル スコープの場合、このクラス ローダーはデフォルトでアプリケーション クラス ローダーになります。スレッド コンテキスト クラス ローダーがあることに加えて、JNDI サービスはこのスレッド コンテキスト クラス ローダーを使用して必要な SPI コードをロードします。つまり、親クラス ローダーは子クラス ローダーにクラス ロード アクションを完了するよう要求します。この動作は実際には接続されています。 . クラスローダーを逆に使うための親委任モデルの階層構造が親委任モデルに違反していますが、これもどうしようもないものです。JNDI、JDBC、JCE、JAXB、JBI など、Java の SPI に関連するすべてのロード アクションは、基本的にこのメソッドを使用します。
    2. スレッド コンテキスト クラス ローダーはデフォルトで AppClassLoader なので、getSystemClassLoader() を通じてクラス ローダーを直接取得して、クラスパスの下のクラスをロードしてみてはいかがでしょうか。実際、これは実現可能ですが、getSystemClassLoader() メソッドを直接使用して AppClassLoader ロード クラスを取得することには欠点があります。つまり、コードを Java にデプロイするなど、コードを別のサービスにデプロイするときに問題が発生します。 Web アプリケーション サービスや EJB などのサービスでは、これらのサービスで使用されるスレッド コンテキスト クラス ローダーが AppClassLoader ではなく、Java Web アプリケーション サーバー自体のクラス ローダーと異なるため、問題が発生します。したがって、アプリケーションでは getSystemClassLoader() の使用を減らす必要があります。つまり、サービスごとに異なるデフォルトの ClassLoader が使用される可能性がありますが、スレッド コンテキスト クラス ローダーを使用すると、常に現在のプログラム実行と同じ ClassLoader を取得できるため、不要な問題を回避できます。
  3. 親委任モデルの 3 番目の「破壊」は、ユーザーがプログラムのダイナミクスを追求したことによるものです。ここで言及されている「ダイナミシティ」とは、コードのホット リプレースメント、モジュールのホット デプロイメントなど、非常に「人気のある」用語を指します。マシンは再起動する必要はなく、デプロイされている限り使用できます。

GC はオブジェクトがリサイクル可能であるとどのように判断するのでしょうか?

  1. 参照カウント (廃止されたアルゴリズム)
    1. 各オブジェクトには参照属性があり、参照が追加されると 1 ずつ増加し、参照が解放されると 1 ずつ減少し、カウントが 0 に達するとリサイクルできます。

しかし、この計算方法には循環参照の問題を解決できないという致命的な問題があります。

  1. 到達可能性分析アルゴリズム (ルート参照)
    1. GcRoot から下方向に検索を開始し、検索によって移動されるパスは参照チェーンと呼ばれます。オブジェクトがどの参照チェーンによっても GcRoot に接続されていない場合、オブジェクトが利用できないことが証明され、仮想マシンはリサイクルを決定できます。
    2. では、GcRoot とは何でしょうか?
      1. 仮想マシンスタックで参照されるオブジェクト
      2. メソッド領域の静的プロパティによって参照されるオブジェクト。
      3. メソッド領域の定数によって参照されるオブジェクト
      4. ローカル メソッド スタックで参照されるオブジェクト (つまり、一般的にはネイティブ メソッド)
  2. さらに、参照タイプが異なれば回復メカニズムも異なります。
    1. 強参照: new キーワードを介して渡されたオブジェクトは強参照オブジェクトであり、強参照によって指定されたオブジェクトは常にリサイクルされず、むしろ OOM によってリサイクルされることはありません。
    2. ソフト参照: オブジェクトがソフト参照を保持している場合、JVM ヒープ スペースが不足すると、そのソフト参照がリサイクルされます。クラスへのソフト参照は、java.lang.ref.SoftReference を介して保持できます。
    3. 弱参照: オブジェクトが弱参照を保持している場合、GC 中に弱参照オブジェクトが見つかる限り、そのオブジェクトはリサイクルされます。クラスへの弱参照は、java.lang.ref.WeakReference を介して保持できます。
    4. ファントム参照: ほとんど何もなく、リサイクルの準備ができています。PhantomReference によってホールドされます。

メモリ オブジェクトを再利用する方法、再利用アルゴリズムは何ですか?

1. マーククリア(マークスイープ)アルゴリズム

マーキングとクリアの 2 段階に分かれています。最初にリサイクルが必要なすべてのオブジェクトにマーキングを行い、マーキングが完了した後、マーキングされたすべてのオブジェクトを均一にリサイクルします。[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-CubqTrTX-1662355491067)(images/before.png)]

これには 2 つの主な欠点があります。

  • 効率の問題、マーキングとクリアの 2 つのプロセスの効率は高くありません。
  • スペースの問題。マークがクリアされた後、大量の不連続なメモリ フラグメントが生成されます。スペースのフラグメンテーションが多すぎると、将来プログラムが大きなオブジェクトを割り当てる必要があるときに、十分な連続メモリを見つけることができず、事前に別のガベージ コレクションをトリガーするアクション。
  1. コピーアルゴリズム

効率の問題を解決するために、使用可能なメモリを容量に応じて同じサイズの 2 つに分割し、一度に 1 つだけを使用する、コピー (Copying) と呼ばれる収集アルゴリズムが登場しました。このブロックのメモリがなくなったら、残ったオブジェクトを別のブロックにコピーし、使用済みのメモリ空間を一度にクリーンアップします。これにより、毎回半分の領域全体が回収されるため、メモリを割り当てる際にメモリの断片化などの複雑な状況を考慮する必要がなく、ヒープの先頭にポインタを移動して順番にメモリを割り当てるだけで済みます。実装が簡単で、効率的に操作できます。
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-9hCmmQL4-1662355491067)(images/copy.png)] コピー アルゴリズムのコストメモリを元の半分に減らし
実際に使用可能なメモリを減らします。現在の商用仮想マシンは、この収集アルゴリズムを使用して新世代をリサイクルしています。IBM の特別調査によると、新世代のオブジェクトの 98% は「生きて死ぬ」ため、1: の比率に従ってオブジェクトを分割する必要はありません。 1. 代わりに、メモリは大きな Eden スペースと 2 つの小さな Survivor スペースに分割され、Eden と Survivor スペースの 1 つが毎回使用されます。リサイクルする場合は、Eden と Survivor に残っているオブジェクトを別の Survivor スペースに一度にコピーし、最後に、使用したばかりの Eden と Survivor スペースをクリーンアップします。HotSpot 仮想マシンの Eden と Survivor のデフォルトの比率は 8:1 です。つまり、各新しい世代で利用可能なメモリ領域は、新しい世代の容量全体の 90% (80%+10%) であり、メモリは「無駄」になります。」もちろん、一般的なシナリオでは、リサイクル可能なオブジェクトの 98% は単なるデータです。各リサイクルで生き残るオブジェクトが 10% 以下であることを保証する方法はありません。Survivor スペースが十分でない場合は、他のスペースに依存する必要があります。メモリ(ここでは旧世代を指します)を割り当てて、割り当て保証(ハンドルプロモーション)を実行します。

  1. マーク照合アルゴリズム

コピー収集アルゴリズムは、オブジェクトの生存率が高い場合、より多くのコピー操作を実行するため、効率が低くなります。さらに重要なのは、スペースの 50% を無駄にしたくない場合は、使用されているメモリ内のすべてのオブジェクトが 100% 生きているという極端な状況に対処するために、割り当てを保証するための追加のスペースが必要であるため、通常は旧世代のアルゴリズムでこれを直接選択します。旧世代の特性に従って、誰かが別のマーク-コンパクト (Mark-Compact) アルゴリズムを提案しました。マーク プロセスは依然としてマーク-クリア アルゴリズムと同じですが、次のステップはリサイクル可能なオブジェクトを直接クリーンアップすることではありません。ただし、生き残ったすべてのオブジェクトを一方の端に移動させてから、端の境界の外側のメモリを直接クリーンアップします。
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-ZOEmY8xH-1662355491067)(images/3-1621487892206.png)]

  1. 世代別収集アルゴリズム

現在の商用仮想マシンのガベージコレクションには、オブジェクトのライフサイクルに応じてメモリをいくつかのブロックに分割するGenerational Collection(Generational Collection)アルゴリズムが採用されているが、これには特に目新しい発想はない。一般にJavaヒープは新世代と旧世代に分かれており、それぞれの時代の特性に応じて最適な収集アルゴリズムが採用されています。新しい世代では、ガベージ コレクションのたびに多数のオブジェクトが消滅し、少数のオブジェクトだけが生き残ることがわかり、コピー アルゴリズムが使用され、コピーのコストを支払うだけでコレクションを完了できます。生き残った少数のオブジェクト。旧世代では、オブジェクトの生存率が高く、割り当て保証のための追加スペースがないため、リサイクルにはマーククリーンまたはマークオーガナイズアルゴリズムを使用する必要があります。

jvm にはどのようなガベージ コレクターがあり、実際にそれらを選択する方法は何ですか?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0sv85lgb-1662355491068)(images/gcollector.png)]
图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
新生代收集器(全部的都是复制算法):Serial、ParNew、Parallel Scavenge
老年代收集器:CMS(标记-清理)、Serial Old(标记-整理)、Parallel Old(标记整理)
整堆收集器: G1(一个Region中是标记-清除算法,2个Region之间是复制算法)
同时,先解释几个名词:
1,并行(Parallel):多个垃圾收集线程并行工作,此时用户线程处于等待状态
2,并发(Concurrent):用户线程和垃圾收集线程同时执行
3,吞吐量:运行用户代码时间/(运行用户代码时间+垃圾回收时间)
1.Serial收集器是最基本的、发展历史最悠久的收集器。
**特点:**单线程、简单高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。
应用场景: クライアント モードの仮想マシンに適用されます。
Serial / Serial Old コレクターの動作の概略図
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-MnwsJ3YB-1662355491068)(画像) /serial.png)] 2. ParNew
コレクション コレクターは、実際にはシリアル コレクターのマルチスレッド バージョンです。
マルチスレッドの使用を除いて、残りの動作はシリアル コレクターとまったく同じです (パラメーター制御、収集アルゴリズム、Stop The World、オブジェクト割り当てルール、リサイクル戦略など)。
機能: マルチスレッド、デフォルトで ParNew コレクターによって有効にされるコレクション スレッドの数は CPU の数と同じです。多くの CPU を備えた環境では、-XX:ParallelGCThreads パラメーターを使用して、コレクション スレッドの数を制限できます。ガベージコレクション用のスレッド。
   Serial コレクターと同様に、Stop The World 問題があります
アプリケーション シナリオ: ParNew コレクターは、サーバー モードで実行されている多くの仮想マシンにとって推奨される新世代コレクターです。シリアルコレクター、作業に協力します。
ParNew/Serial Old 組み合わせコレクターの動作図は次のとおりです。
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-5Ol3aUJp-1662355491068) )(images/parnew.png)]
3 .Parallel Scavenge コレクターはスループットと密接に関連しているため、スループット優先コレクターとも呼ばれます。
特徴: 新世代コレクターもコピー アルゴリズムを使用するコレクターであり、並列マルチスレッド コレクターでもあります (ParNew コレクターと同様)。
コレクターの目標は、管理可能なスループットを達成することです。注目に値するもう 1 つの点は次のとおりです。 GC 適応調整戦略 (ParNew コレクターとの最も重要な違い)
GC 適応調整戦略: Parallel Scavenge コレクターは、-XX:+UseAdptiveSizePolicy パラメーターを設定できます。スイッチをオンにすると、新しい世代のサイズ (-Xmn)、Eden と Survivor 領域の比率 (-XX:SurvivorRation)、古い世代に昇格するオブジェクトの経過時間を手動で指定する必要はありません ( -XX:PretenureSizeThreshold) など、仮想マシンはシステム ステータスに従って実行され、パフォーマンス監視情報を収集し、最適な一時停止時間と最高のスループットを提供するためにこれらのパラメータを動的に設定します。この調整方法は GC の適応調整戦略と呼ばれます。
Parallel Scavenge コレクターは、次の 2 つのパラメーターを使用してスループットを制御します。

  • XX:MaxGCPauseMillis は、ガベージ コレクションの最大一時停止時間を制御します
  • XX:GCRatio はスループットのサイズを直接設定します。

4.Serial Old は、シリアル コレクターの古いバージョンです。
特徴: マークソートアルゴリズムを使用したシングルスレッドコレクターでもあります。
適用シナリオ: 主にクライアント モードの仮想マシンで使用されます。サーバーモードでも使用できます。
サーバー モードには主に 2 つの用途があります (詳細は後続で説明します):

  1. JDK1.5 以前のバージョンでは、Parallel Scavenge コレクターと組み合わせて使用​​されます。
  2. CMS コレクターのバックアップ ソリューションとして、同時モード障害が同時に収集される場合に使用されます。

シリアル/シリアル古いコレクターの作業プロセス図 (シリアル コレクターのアイコンは同じです):
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-y0H2ZdA5) -1662355491068)(images /serial-old.png)]
5.Parallel Old は、Parallel Scavenge コレクターの古いバージョンです。
特徴: マルチスレッド、マークソートアルゴリズムを使用。
アプリケーション シナリオ: パラレル スカベンジ + パラレル オールド コレクターは、高スループットと CPU リソースの感度を重視する場合に優先されます。
パラレル スカベンジ/パラレル オールド コレクターの作業プロセス図:
6. CMS コレクターは、回復一時停止時間を最短にすることを目標とするコレクターです。
特徴: マークスイープアルゴリズムに基づいて実装されています。同時収集、短い一時停止。
適用シナリオ: サービスの応答速度が重視され、システムの停止時間が最短になることが期待され、ユーザーにより良いエクスペリエンスがもたらされるシナリオに適用されます。Web プログラム、B/S サービスなど。
CMS コレクターの操作プロセスは、次の 4 つのステップに分かれています。
初期マーク: GC ルートが直接到達できるオブジェクトをマークします。高速ですが、Stop The World の問題がまだ残っています。
同時マーキング: 存続するオブジェクトとユーザー スレッドを見つけるための GC ルート トレースのプロセスは、同時に実行できます。
再マーキング: 同時マーク中にユーザプログラムの継続動作によりマークが変化したオブジェクト部分のマーク記録を修正すること。ストップ・ザ・ワールドの問題はまだ残っています。
同時クリーンアップ: マークされたオブジェクトをクリアしてリサイクルします。
CMS コレクターのメモリ回復プロセスは、ユーザー スレッドと同時に実行されます。
CMS コレクターの作業プロセス図:
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-ZxJmtrqv-1662355491069)(images/cms. png)] CMS
コレクターの欠点:

  • CPU リソースに非常に敏感です。
  • 浮遊ガベージを処理できないため、同時モデル障害が発生し、別のフル GC が発生する可能性があります。
  • マーククリア アルゴリズムが使用されるため、スペースの断片化の問題が発生し、大きなオブジェクトがスペースの割り当てに失敗し、事前にフル GC をトリガーする必要があります。[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-yeyh0bkl-1662355491069)(images/cms2.png)]

7. G1 コレクターは、サーバー側アプリケーション用のガベージ コレクターです。
特徴は次のとおりです。
並列および同時: G1 は、マルチ CPU およびマルチコア環境でハードウェアの利点を最大限に活用し、複数の CPU を使用して Stop-The-World の一時停止時間を短縮できます。一部のコレクターでは、GC アクションを実行するために元々 Java スレッドを停止する必要がありますが、G1 コレクターでは Java プログラムを並行して実行し続けることができます。
世代別コレクション: G1 は Java ヒープ全体を独立して管理し、さまざまな方法を使用して、新しく作成されたオブジェクトと、一定期間存続し、複数の GC を乗り越えた古いオブジェクトを処理して、より良い収集結果を得ることができます。
スペースの統合: G1 は動作中にスペース フラグメントを生成せず、収集後に通常の利用可能なメモリを提供できます。
予測可能な休止時間: G1 は、低休止時間の追求に加えて、予測可能な休止時間モデルも構築できます。ユーザーは、M ミリ秒の期間内でガベージ コレクションに費やされる時間が N ミリ秒を超えないよう明示的に指定できます。
G1 コレクターの動作の概略図:
[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-36nWucES-1662355491069)(images/g1) .png)
]

gc の選択について
アプリケーションに非常に厳密な一時停止時間の要件がない限り、最初にアプリケーションを実行し、VM がコレクターを選択できるようにしてください (特別な要件がない場合。VM によって提供されるデフォルトの GC を使用してください)。
必要に応じて、ヒープ サイズを調整してパフォーマンスを向上させます。それでもパフォーマンスが目標を達成できない場合は、コレクターを選択する出発点として次のガイドラインを使用してください。

  • アプリケーションのデータ セットが小さい場合 (最大約 100 MB)、オプション -XX:+UseSerialGC を使用してシリアル コレクターを選択します。
  • アプリケーションが単一プロセッサ上で実行され、一時停止時間の要件がない場合は、オプション -XX:+UseSerialGC を使用してシリアル コレクターを選択します。
  • (a) アプリケーションのピーク パフォーマンスが最優先であり、(b) 一時停止時間の要件がない、または 1 秒以上の一時停止が許容される場合は、VM にコレクタを選択させるか、-XX:+UseParallelGC を使用してパラレル コレクタを選択させます。コレクター。
  • 全体的なスループットよりも応答時間が重要で、ガベージ コレクションの一時停止を約 1 秒以内に抑える必要がある場合は、-XX:+UseG1GC を選択します。(JDK9 の CMS は非推奨になっており、使用できないことに注意してください。このオプションは削除してください)
  • jdk8 を使用し、ヒープ メモリが 16G に達した場合は、G1 コレクタを使用して各ガベージ コレクションの時間を制御することをお勧めします。
  • 応答時間が優先される場合、または使用されるヒープが非常に大きい場合は、-XX:UseZGC を使用して完全並行コレクタを選択します。(JDK11 では ZGC を開始できることに注意してください。ただし、ZGC は現時点では実験的なものです。JDK15 [202009 年リリース] では、実験的なラベルはキャンセルされ、直接表示して有効にすることができますが、JDK15 のデフォルトの GC は依然として G1 です。 )

パフォーマンスはヒープのサイズ、アプリケーションによって維持されるリアルタイム データの量、および使用可能なプロセッサの数と速度に依存するため、これらのガイドラインはコレクターを選択するための出発点にすぎません。
推奨されるコレクターが望ましいパフォーマンスを達成できない場合は、まず、望ましい目標を達成するためにヒープと若い世代のサイズを調整してみてください。それでもパフォーマンスが不十分な場合は、他のコレクターを使用してみてください。
一般原則: STOP THE WORD 時間を短縮し、同時コレクター (CMS+ParNew、G1 など) を使用して一時停止時間を短縮し、応答時間を高速化し、並列コレクターを使用してマルチプロセッサーを増やします。ハードウェアの全体的なスループット。

JVM8 ではなぜメタスペースが増加するのでしょうか?

理由:
1. 文字列は永続世代に存在するため、パフォーマンス上の問題やメモリ オーバーフローが発生しやすくなります。
2. クラスやメソッド情報のサイズを決めるのが難しいため、永続世代のサイズを指定するのが難しく、小さすぎると永続世代のオーバーフローが発生しやすく、大きすぎると永続世代のサイズを指定することができません。古い世代のオーバーフローが簡単に発生します。
3. 永続的な生成は GC に不必要な複雑さをもたらし、リサイクル効率が低くなります。

JVM8のメタスペースの特徴は何ですか?

1. 各ローダーには専用の保管スペースがあります。
2. 特定のクラスは個別にリサイクルされません。
3. メタスペース内のオブジェクトの位置は固定されています。
4. 特定のローダーの在庫がなくなったことが判明した場合、該当するスペースは完全に回収されます。

オンラインGCが頻繁に発生する問題を解決するにはどうすればよいですか?

  1. 監視を確認して、いつ問題が発生したか、現在の FGC の頻度を確認します (通常の状況と比較して、頻度が正常であるかどうかを確認します)。
  2. この時点より前にオンラインのプログラムや基本コンポーネントのアップグレードなどが存在するかどうかを確認します。
  3. ヒープ領域の各領域のサイズ設定、新世代と旧世代でどのガベージコレクタが使用されているかなど、JVM のパラメータ設定を理解し、JVM パラメータ設定が妥当かどうかを分析します。
  4. 次に、ステップ 1 にリストされている考えられる原因を排除します。その中には、メタスペースがいっぱいである、メモリ リークが含まれる、コードが明示的に gc メソッドを呼び出すなどがあります。これにより、トラブルシューティングが容易になります。
  5. 大きなオブジェクトやライフサイクルの長いオブジェクトによって発生する FGC の場合は、 jmap -histo コマンドを使用してダンプ ヒープ メモリ ファイルを結合してさらに分析できます。まず、疑わしいオブジェクトを特定する必要があります。
  6. 特定のコードから不審物を特定し、再解析するが、その際、GC原理とJVMパラメータ設定を組み合わせて、不審物が老朽化の条件を満たしているかどうかを調べてから結論を出す必要がある。

メモリ オーバーフローの原因とオンラインの問題のトラブルシューティング方法は何ですか?

  1. java.lang.OutOfMemoryError: ...Java ヒープ スペース... スタック オーバーフロー、コードの問題の可能性が非常に高い
  2. java.lang.OutOfMemoryError: GC オーバーヘッド制限を超えました システムが高頻度 GC 状態にあり、回復効果がまだ良好ではない場合、このエラーが報告され始めます。この場合、解放できないオブジェクトの多くはこのエラーは、参照の不適切な使用または大きなオブジェクトの適用が原因である可能性がありますが、Java ヒープ領域のメモリ オーバーフローがこのエラーを事前に報告しない可能性があります。つまり、メモリ不足が直接の原因である可能性があります。高周波 GC よりも優れています。
  3. java.lang.OutOfMemoryError: PermGen space jdk1.7 問題が発生する前は、システムに大量のコードまたは多くのサードパーティ パッケージが参照されているか、コード内で多くの定数が使用されているか、定数がインターンを通じて注入されるか、動的コードロードなどの方法を通じて、定数プールの拡張が引き起こされます。
  4. java.lang.OutOfMemoryError: ダイレクト バッファ メモリ ダイレクト メモリが不足しています。jvm ガベージ コレクションはダイレクト メモリのメモリを再利用しないため、考えられる理由は、ByteBuffer の assignDirect メソッドが直接または間接的に使用されるときに、バッファー メモリが不足していることです。クリア
  5. java.lang.StackOverflowError - Xss の設定が小さすぎます
  6. java.lang.OutOfMemoryError: 新しいネイティブ スレッドを作成できませんオフヒープ メモリが不足しているため、スレッドにメモリ領域を割り当てることができません
  7. java.lang.OutOfMemoryError: スワップ アドレス空間外の {} のリクエスト {} バイトが十分ではありません

Happens-Before ルールとは何ですか?

  1. プログラム順序の規則: スレッド内のすべての操作は、そのスレッド内の後続の操作よりも前に発生します。
  2. 監視ルール: ロックのロック解除は、その後のロックのロックよりも前に行われます。
  3. Volatile ルール: volatile 変数への書き込みは、その後の volatile 変数の読み取りよりも前に行われます。
  4. 推移性: A が B より前に発生し、B が C より前に発生する場合、A は C より前に発生します。
  5. スレッド開始ルール: Thread オブジェクトの start() メソッド。このスレッドの後続の操作の前に発生します。
  6. スレッド終了ルール: スレッド内のすべての操作は、発生前にスレッドの終了を監視します。Thread.join() メソッドの終了と Thread.isAlive() の戻り値によって、スレッドが実行を終了したことを検出できます。
  7. スレッド割り込み操作: スレッド中断() メソッドの呼び出し。中断されたスレッドのコードが割り込みイベントの発生を検出する前に、Thread.interrupted() メソッドを使用してスレッドが中断されたかどうかを検出できます。
  8. オブジェクトの終了規則: オブジェクトの初期化が完了し、このオブジェクトの Finalize() メソッドの開始時に前発生が開始されます。

スレッドのライフサイクルとステータスを紹介しますか?

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-6jUAd6xH-1662355491069)(images/life.jpg)] 1.スレッドが
作成
されると、スレッドは新しく作成された状態 (初期状態) になります。この時点では、他の Java オブジェクトと同じで、Java によってメモリのみが割り当てられます。仮想マシンとそのメンバー変数の値が初期化されます。このとき、スレッド オブジェクトはスレッドの動的特性を示さず、プログラムはスレッドのスレッド実行本体を実行しません。
2. Ready
スレッド オブジェクトが Thread.start() メソッドを呼び出すと、スレッドは Ready 状態になります。Java 仮想マシンはメソッド呼び出しスタックとそのプログラム カウンターを作成します。この状態のスレッドは実行を開始するのではなく、スレッドが実行できることを意味します。start() のソース コードを見ると、開始後にスレッド リストに追加され、その後ネイティブ層の VM に追加されることがわかります。スレッドがいつ実行を開始するかについては、スレッドのスケジュールに依存します。 JVM のスケジューラ (OS スケジューリングが選択されている場合は、実行状態になります)。
3. 実行
中 スレッド オブジェクトが Thread.start() メソッドを呼び出すと、スレッドは準備完了状態になります。スレッドリストに追加され、OS スケジュールが選択されている場合、実行状態になります。
4.
ブロッキング ブロッキング状態とは、スレッドが何らかの理由で CPU の使用権を放棄し、一時的に実行を停止する状態です。スレッドが準備完了状態になるまで、実行状態に移行する機会があります。ブロックには大きく分けて 3 つのタイプがあります。

  • 1.ブロッキングの待機: 実行中のスレッドは wait() メソッドを実行し、JVM はスレッドを待機プールに入れます。(待機すると保持されていたロックが解除されます)
  • 2.同期ブロック: 実行中のスレッドがオブジェクトの同期ロックを取得するときに、同期ロックが他のスレッドによって占有されている場合、JVM はスレッドをロック プールに入れます。
  • 3.その他のブロック: 実行中のスレッドが sleep() または join() メソッドを実行するか、I/O リクエストを送信すると、JVM はスレッドをブロック状態にします。sleep () 状態がタイムアウトになると、join () はスレッドが終了するかタイムアウトになるまで待機します。あるいは、I/O 処理が完了すると、スレッドは再び準備完了状態に移行します。(スリープしても保持されたロックは解除されないことに注意してください)。
  • スレッド スリープ: Thread.sleep(long millis) メソッドは、スレッドをブロック状態にします。millis パラメータはスリープ時間をミリ秒単位で設定します。スリープが終了すると、Ready (Runnable) 状態になります。sleep() はプラットフォームの移植性に優れています。
  • 待機中のスレッド: Object クラスの wait() メソッドにより、現在のスレッドは、他のスレッドがこのオブジェクトの Notice() メソッドまたは NotifyAll() ウェイクアップ メソッドを呼び出すまで待機します。これら 2 つのウェイクアップ メソッドも Object クラスのメソッドであり、その動作は wait(0) を呼び出すことと同等です。スレッドを起動すると、準備完了 (実行可能) 状態になります。
  • スレッドの収量: Thread.yield() メソッドは、現在実行中のスレッド オブジェクトを一時停止し、同じかそれより高い優先度を持つスレッドに実行の機会を与えます。
  • スレッド結合: join() メソッド。他のスレッドが終了するまで待ちます。別のスレッドの join() メソッドが現在のスレッドで呼び出された場合、現在のスレッドは他のプロセスの実行が終了するまでブロック状態になり、その後、現在のスレッドはブロック状態から準備完了状態に変わります。
  • スレッド I/O: スレッドはいくつかの IO 操作を実行し、関連リソースを待機しているためブロック状態に入ります。たとえば、system.in を聞いているがキーボード入力を受け取っていない場合、ブロック状態に入ります。
  • スレッドのウェイクアップ: Object クラスの Notice() メソッドは、このオブジェクトのモニターを待機している単一のスレッドをウェイクアップします。すべてのスレッドがこのオブジェクトを待機している場合、そのうちの 1 つが起動するように選択されます。選択は任意であり、実装時に行われます。同様のメソッドには、このオブジェクト モニターで待機しているすべてのスレッドを起動するnotifyAll()もあります。

5. 死亡
スレッドは次の 3 つの方法のいずれかで終了し、終了後は停止状態になります。

  • run()メソッドが実行され、スレッドが正常に終了します。
  • スレッドは、キャッチされなかった例外またはエラーをスローします。
  • スレッドの stop() メソッドを直接呼び出してスレッドを終了します。このメソッドはデッドロックが発生しやすいため、通常はお勧めできません。

スレッドのスリープ、待機、参加、譲歩を使用するにはどうすればよいですか?

sleep : スレッドをスリープさせ、その間 CPU は解放されます。同期コード ブロックでは、ロックは解放されません。
wait (呼び出す前に対応するロックを取得する必要があります): スレッドを待機状態にし、解放します。現在のスレッドによって保持されているロック リソース スレッド。notify または NoticeAll メソッドは呼び出し後にウェイクアップされ、ロックを競合します。
join : スレッド間の調整、使用シナリオ: スレッド A は、スレッド B の実行が完了するまで待機する必要があります。実行できる場合は、それをスレッド A のコードに追加できます ThreadB.join();
yield : 現在実行中のスレッドを実行可能な状態に戻し、同じ優先順位の他のスレッドが実行できるようにします。したがって、yield() を使用する目的は、同じ優先順位を持つスレッド間で適切なローテーションを可能にすることです。ただし、実際には、yield() がyieldの目的を達成するという保証はありません。これは、yield スレッドがスレッド スケジューラによって再度選択される可能性があるためです。

スレッドを作成するにはどのような方法がありますか?

1) Thread クラスを継承してスレッドを作成する
2) Runnable インターフェイスを実装してスレッドを作成する
3) Callable と Future を使用してスレッドを作成する
4) Executor フレームワークなどのスレッド プールを使用する

デーモンスレッドとは何ですか?

Java には、ユーザー スレッド (ユーザー スレッド)、デーモン スレッド (デーモン スレッド) の 2 種類のスレッドがあります。デーモン スレッドは、非デーモン スレッドが存在する限り、
JVM 全体のすべての非デーモン スレッドの子守です。
現在の JVM インスタンス内 終了後、デーモン スレッドはすべて動作しますが、最後の非デーモン スレッドが終了した場合にのみ、デーモン スレッドは JVM とともに動作を終了します。デーモンの役割は、他のスレッドの操作に便利なサービスを提供することであり、デーモン スレッドの最も典型的なアプリケーションは、非常に有能なガーディアンである GC (ガベージ コレクター) です。
ユーザーとデーモンにはほとんど違いはありませんが、唯一の違いは仮想マシンの終了です。ユーザー スレッドがすべて終了し、デーモン スレッドのみが存在する場合、仮想マシンも終了します。ガーディアンが存在しないため、デーモンは何もする必要がなく、プログラムを実行し続ける必要はありません。
注:
(1) thread.setDaemon(true) は thread.start() の前に設定する必要があります。そうしないと、IllegalThreadStateException が発生します。スレッドの実行が開始される前にのみ、デーモン スレッドとして設定できます。
(2) デーモンスレッド内に生成された新しいスレッドもデーモンのものです。
(3) すべてのアプリケーションを読み取りおよび書き込み操作や計算ロジックのためにデーモンに割り当てることができるとは考えないでください。これは、データの不整合な状態に戻る可能性があるためです。

ThreadLocal の原理と使用シナリオは何ですか?

ThreadクラスにはthreadLocalsとinheritableThreadLocalsという2つの変数があり、どちらもThreadLocalの内部クラスThreadLocalMap型の変数であり、内部のThreadLocalMapを見ると実際にはHashMapに似ていることがわかります。各スレッドのデフォルトでは、両方の変数が null です。

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

これらは、スレッドが初めて ThreadLocal の set メソッドまたは get メソッドを呼び出したときにのみ作成されます。

public T get() {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
}
    
ThreadLocalMap getMap(Thread t) {
    
    
        return t.threadLocals;
}

除此之外,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的载体,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量,防止出现内存泄漏。

public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
public void remove() {
    
    
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}

ThreadLocal有哪些内存泄露问题,如何避免?

每个Thread都有一个ThreadLocal.ThreadLocalMap的map,该map的key为ThreadLocal实例,它为一个弱引用,我们知道弱引用有利于GC回收。当ThreadLocal的key == null时,GC就会回收这部分空间,但是value却不一定能够被回收,因为他还与Current Thread存在一个强引用关系,如下

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-8vAmxjAn-1662355491070)(images/threadlocal.png)]この強い参照関係があるため、値をリサイクルすることはできません。このスレッド オブジェクトが破棄されない場合、この強参照関係が常に存在し、メモリ リークが発生します。したがって、このスレッド オブジェクトが GC によって時間内にリサイクルできる限り、メモリ リークは発生しません。スレッド プールに遭遇すると、事態はさらに悪化します。では、この問題を回避するにはどうすればよいでしょうか? 前述したように、ThreadLocalMap の setEntry() および getEntry() では、key == null の場合、値は null に設定されます。もちろん、ThreadLocal の Remove() メソッドを明示的に呼び出して処理することもできます。以下は ThreadLocal の簡単な概要です。

  • ThreadLocal は共有変数の問題を解決するために使用されるものではなく、スレッドの同期を調整するために存在するものでもありませんが、各スレッドが独自の状態を処理できるようにするために導入されたメカニズムです。これは非常に重要です。
  • 各 Thread には、ThreadLocal.ThreadLocalMap 型のメンバー変数があり、ThreadLocal 変数の実際のコピーを格納するために使用されます。
  • ThreadLocal はスレッドのオブジェクトのコピーを保存せず、インデックスとしてのみ機能します。その主な考え方は、各スレッドがクラスのインスタンスを分離し、このインスタンスのスコープがスレッドの内部に制限されるということです。

スレッド プールを使用する理由

作成および破棄されるスレッドの数を減らし、各スレッドを複数回使用できるようにするために、システムの状況に応じて実行されるスレッドの数を調整して過剰なメモリ消費を防ぐために、スレッド プールを使用できます。

スレッド プールのスレッド再利用の原理は何ですか?

次のような質問について考えてみましょう。タスクが終了した後、スレッドはリサイクルされるでしょうか?
答えは、allowCoreThreadTimeOut コントロールです。

/java/util/concurrent/ThreadPoolExecutor.java:1127
final void runWorker(Worker w) {
    
    
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
    
    
            while (task != null || (task = getTask()) != null) {
    
    ...执行任务...}
            completedAbruptly = false;
        } finally {
    
    
            processWorkerExit(w, completedAbruptly);
        }
    }
首先线程池内的线程都被包装成了一个个的java.util.concurrent.ThreadPoolExecutor.Worker,然后这个worker会马不停蹄的执行任务,执行完任务之后就会在while循环中去取任务,取到任务就继续执行,取不到任务就跳出while循环(这个时候worker就不能再执行任务了)执行 processWorkerExit方法,这个方法呢就是做清场处理,将当前woker线程从线程池中移除,并且判断是否是异常的进入processWorkerExit方法,如果是非异常情况,就对当前线程池状态(RUNNING,shutdown)和当前工作线程数和当前任务数做判断,是否要加入一个新的线程去完成最后的任务(防止没有线程去做剩下的任务).
那么什么时候会退出while循环呢?取不到任务的时候(getTask() == null).下面看一下getTask方法

private Runnable getTask() {
    
    
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);

            //(rs == SHUTDOWN && workQueue.isEmpty()) || rs >=STOP
            //若线程池状态是SHUTDOWN 并且 任务队列为空,意味着已经不需要工作线程执行任务了,线程池即将关闭
            //若线程池的状态是 STOP TIDYING TERMINATED,则意味着线程池已经停止处理任何任务了,不在需要线程
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            	//把此工作线程从线程池中删除
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            //allowCoreThreadTimeOut:当没有任务的时候,核心线程数也会被剔除,默认参数是false,官方推荐在创建线程池并且还未使用的时候,设置此值
            //如果当前工作线程数 大于 核心线程数,timed为true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			
            //(wc > maximumPoolSize || (timed && timedOut)):当工作线程超过最大线程数,或者 允许超时并且超时过一次了
            //(wc > 1 || workQueue.isEmpty()):工作线程数至少为1个 或者 没有任务了
            //总的来说判断当前工作线程还有没有必要等着拿任务去执行
            //wc > maximumPoolSize && wc>1 : 就是判断当前工作线程是否超过最大值
            //或者 wc > maximumPoolSize && workQueue.isEmpty():工作线程超过最大,基本上不会走到这,
            //		如果走到这,则意味着wc=1 ,只有1个工作线程了,如果此时任务队列是空的,则把最后的线程删除
            //或者(timed && timedOut) && wc>1:如果允许超时并且超时过一次,并且至少有1个线程,则删除线程
            //或者 (timed && timedOut) && workQueue.isEmpty():如果允许超时并且超时过一次,并且此时工作					队列为空,那么妥妥可以把最后一个线程(因为上面的wc>1不满足,则可以得出来wc=1)删除
            if ((wc > maximumPoolSize  || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
    
    
                if (compareAndDecrementWorkerCount(c))
                	//如果减去工作线程数成功,则返回null出去,也就是说 让工作线程停止while轮训,进行收尾
                    return null;
                continue;
            }

            try {
    
    
            	//判断是否要阻塞获取任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
    
    
                timedOut = false;
            }
        }
    }
    
//综上所述,如果allowCoreThreadTimeOut为true,并且在第1次阻塞获取任务失败了,那么当前getTask会返回null,不管是不是核心线程;那么runWorker中将推出while循环,也就意味着当前工作线程被销毁

上記の質問から結論が得られます。スレッド プールのパラメータが適切に設定されていれば、タスクを実行したスレッドは破棄されず、タスク キューからタスクを取り出して実行を継続します。

デッドロックを防ぐにはどうすればよいでしょうか?

  1. まず第一に、デッドロックが必要な条件であることを伝える必要があります。
    1. 相互排他条件では、一度に 1 つのスレッドのみがリソースを取得できます。
    2. 非プリエンプティブ条件 スレッドによってすでに占有されているリソースは、解放されるまで他のスレッドによってプリエンプトされません。
    3. リクエストおよびホールド条件のスレッドは、待機中に占有されているリソースを解放しません。
    4. 循環待機条件 複数のスレッドが相互にリソースを解放するのを待機します。
  2. デッドロックを防ぐには、この 4 つの必要条件を破壊する必要があります
    1. リソースの相互排他はリソース使用の固有の機能であり変更できないため、ここでは説明しません。
    2. 不可侵条件の違反
      1. プロセスが必要なリソースをすべて取得できない場合、そのプロセスは待機状態になります。待機期間中、プロセスが占有しているリソースは暗黙的に解放され、システム リソース リストに再追加され、他のプロセスが使用できるようになります。待機中のプロセスは回復のみ可能です 元のリソースと新しく適用されたリソースのみが再起動および実行可能です
  3. 破棄要求と保留条件
    1. 1 つ目の方法は静的割り当てです。つまり、各プロセスは実行開始時に必要なすべてのリソースを適用します。
    2. 2 つ目は動的割り当てです。つまり、各プロセスは必要なリソースを申請するときにシステム リソースを占有しません。
  4. ブレークループ待機条件
    1. リソースを規則的に割り当てる基本的な考え方は、システム内のすべてのリソースに順番に番号を付け、希少なリソースには大きな番号を使用することです。リソースを申請するときは、番号の順序で行う必要があります。プロセスで実行できるのは、番号の小さいプロセスを取得し、番号の大きいプロセスを申請するには。

スレッドセーフなアクティブ状態の問題について説明してください。

スレッドセーフなアクティビティの問題は、デッドロック、ライブロック、スターベーションに分類できます。

  1. ライブロックとは、スレッドがブロックされていないにもかかわらず、実行を続行できない状況が時々発生することを意味します。ライブロックはスレッドをブロックせず、スレッドは常に同じ操作を繰り返し実行し、常に再試行に失敗します。
    1. 開発で使用した非同期メッセージキューはライブロックの問題を引き起こす可能性があり、メッセージキューのコンシューマー側に正しい ack メッセージがなく、実行中にエラーが報告された場合、メッセージヘッダーが再度戻され、その後、ライブロックの問題が発生する可能性があります。実行のために持ち出され、失敗を繰り返してきました。この問題は、正しい ACK に加えて、失敗したメッセージを遅延キューに入れ、一定の遅延を待ってから再試行することで解決されることがよくあります。
    2. ライブロックの解決策は非常に簡単です。ランダムな時間を待って、その時間に従って再試行してください。
  2. 飢餓とは、スレッドが必要なリソースにアクセスできないために実行を継続できない状況です。
    1. 飢餓には 2 つのタイプがあります。
      1. 一种是其他的线程在临界区做了无限循环或无限制等待资源的操作,让其他的线程一直不能拿到锁进入临界区,对其他线程来说,就进入了饥饿状态
      2. 另一种是因为线程优先级不合理的分配,导致部分线程始终无法获取到CPU资源而一直无法执行
    2. 解决饥饿的问题有几种方案:
      1. 保证资源充足,很多场景下,资源的稀缺性无法解决
      2. 公平分配资源,在并发编程里使用公平锁,例如FIFO策略,线程等待是有顺序的,排在等待队列前面的线程会优先获得资源
      3. 避免持有锁的线程长时间执行,很多场景下,持有锁的线程的执行时间也很难缩短
  3. 死锁 线程在对同一把锁进行竞争的时候,未抢占到锁的线程会等待持有锁的线程释放锁后继续抢占,如果两个或两个以上的线程互相持有对方将要抢占的锁,互相等待对方先行释放锁就会进入到一个循环等待的过程,这个过程就叫做死锁

线程安全的竞态条件有哪些?

  1. 同一个程序多线程访问同一个资源,如果对资源的访问顺序敏感,就称存在竞态条件,代码区成为临界区。 大多数并发错误一样,竞态条件不总是会产生问题,还需要不恰当的执行时序
  2. 最常见的竞态条件为
    1. 先检测后执行执行依赖于检测的结果,而检测结果依赖于多个线程的执行时序,而多个线程的执行时序通常情况下是不固定不可判断的,从而导致执行结果出现各种问题,见一种可能 的解决办法就是:在一个线程修改访问一个状态时,要防止其他线程访问修改,也就是加锁机制,保证原子性
    2. 延迟初始化(典型为单例)

程序开多少线程合适?

  1. CPU 密集型程序,一个完整请求,I/O操作可以在很短时间内完成,CPU还有很多运算要处理,也就是说 CPU 计算的比例占很大一部分,线程等待时间接近0
    1. 单核CPU: 一个完整请求,I/O操作可以在很短时间内完成, CPU还有很多运算要处理,也就是说 CPU 计算的比例占很大一部分,线程等待时间接近0。单核CPU处理CPU密集型程序,这种情况并不太适合使用多线程。
    2. マルチコア: マルチコア CPU が CPU 集中型プログラムを処理する場合、CPU コアの数を最大限に活用し、同時プログラミングを適用して効率を向上させることができます。CPU を大量に使用するプログラムの最適なスレッド数は次のとおりです。理論的には、スレッド数 = CPU コア (ロジック) の数ですが、実際には、この数は通常、CPU コアの数 (ロジック) + 1 に設定されます (経験値)、計算 (CPU) 集中型 タイプのスレッドが、ページ フォールトまたはその他の理由により、ある時点でたまたま中断された場合、この場合に CPU サイクルが中断されないことを保証する「追加の」スレッドが存在するだけです。
  2. I/O 集中型プログラムは、CPU 集中型プログラムとは対照的に、完全なリクエストであり、CPU 操作が完了した後も、実行すべき I/O 操作がまだ多くあります。つまり、I/O 操作が 1 つのリクエストに相当します。スレッド待機時間の割合が高くなるほど、より多くのスレッドが必要になります。スレッドの CPU 時間の割合が高くなるほど、必要なスレッドは少なくなります。
    1. I/O 集中型プログラムの最適なスレッド数は次のとおりです。 最適なスレッド数 = CPU コア数(1/CPU 使用率) = CPU コア数(1 + (I/O 時間の消費/CPU 時間の消費) )
    2. ほとんどすべての I/O 時間がかかる場合、CPU 時間は限りなく 0 に近くなります。したがって、純粋理論では 2N (N=CPU コア数) であると言えます。もちろん 2N もあります。 + 1、1 はバックアップにする必要があります
    3. 一般的には 2N + 1 で十分だと言われます

同期とロックの違いは何ですか?

差分タイプ 同期した ロック
存在レベル Java キーワード (jvm レベル) JVMへのインターフェースです
ロックの取得 スレッド A がロックを取得し、スレッド B が待機しているとします。スレッド A がブロックされると、スレッド B は永久に待機します。 状況に応じて、Lock にはロックを取得する複数の方法があります。基本的には、ロックの取得を試みることができ、スレッドは常に待機する必要はありません (tr​​yLock を使用してロックがあるかどうかを判断できます)。
ロック解除 1. ロックを取得したスレッドで同期コードを実行し、ロックを解放します。 2. スレッドの実行で例外が発生した場合、jvm はスレッドを解放させます。 ロックは最終的に解放する必要があります。そうしないと、スレッドのデッドロックが発生しやすくなります。
ロックタイプ ロックは再入可能で中断不可能で不公平です 再入可能、決定可能、公平 (両方)
パフォーマンス 同期が少ない たくさんの同期に適しています
ロックをサポートするシナリオ 1.排他ロック 1. 公平なロックと不公平なロック

ABA の問題に遭遇したことがありますか?詳しく教えてください。

  1. 有两个线程同时去修改一个变量的值,比如线程1、线程2,都更新变量值,将变量值从A更新成B。
  2. 首先线程1获取到CPU的时间片,线程2由于某些原因发生阻塞进行等待,此时线程1进行比较更新(CompareAndSwap),成功将变量的值从A更新成B。
  3. 更新完毕之后,恰好又有线程3进来想要把变量的值从B更新成A,线程3进行比较更新,成功将变量的值从B更新成A。
  4. 线程2获取到CPU的时间片,然后进行比较更新,发现值是预期的A,然后有更新成了B。但是线程1并不知道,该值已经有了A->B->A这个过程,这也就是我们常说的ABA问题。

volatile的可见性和禁止指令重排序怎么实现的?

  • 可见性:
    volatile的功能就是被修饰的变量在被修改后可以立即同步到主内存,被修饰的变量在每次是用之前都从主内存刷新。本质也是通过内存屏障来实现可见性
    写内存屏障(Store Memory Barrier)可以促使处理器将当前store buffer(存储缓存)的值写回主存。读内存屏障(Load Memory Barrier)可以促使处理器处理invalidate queue(失效队列)。进而避免由于Store Buffer和Invalidate Queue的非实时性带来的问题。
  • 禁止指令重排序:
    volatile是通过内存屏障来禁止指令重排序
    JMM内存屏障的策略
    • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
    • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
    • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。
    • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障。

ConcurrentHashMap底层原理是什么?

1.7
数据结构:
内部主要是一个Segment数组,而数组的每一项又是一个HashEntry数组,元素都存在HashEntry数组里。因为每次锁定的是Segment对象,也就是整个HashEntry数组,所以又叫分段锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyN7RRBk-1662355491070)(images/1.7ConcurrentHashMap.png)]
1.8
数据结构:
与HashMap一样采用:数组+链表+红黑树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUOEWSMx-1662355491070)(images/ConCurrentHashMap.png)]
底层原理则是采用锁链表或者红黑树头结点,相比于HashTable的方法锁,力度更细,是对数组(table)中的桶(链表或者红黑树)的头结点进行锁定,这样锁定,只会影响数组(table)当前下标的数据,不会影响其他下标节点的操作,可以提高读写效率。
putVal执行流程:

  1. 判断存储的key、value是否为空,若为空,则抛出异常
  2. 计算key的hash值,随后死循环(该循环可以确保成功插入,当满足适当条件时,会主动终止),判断table表为空或者长度为0,则初始化table表
  3. 根据hash值获取table中该下标对应的节点,如果该节点为空,则根据参数生成新的节点,并以CAS的方式进行更新,并终止死循环。
  4. 如果该节点的hash值是MOVED(-1),表示正在扩容,则辅助对该节点进行转移。
  5. 对数组(table)中的节点,即桶的头结点进行锁定,如果该节点的hash大于等于0,表示此桶是链表,然后对该桶进行遍历(死循环),寻找链表中与put的key的hash值相等,并且key相等的元素,然后进行值的替换,如果到链表尾部都没有符合条件的,就新建一个node,然后插入到该桶的尾部,并终止该循环遍历。
  6. 如果该节点的hash小于0,并且节点类型是TreeBin,则走红黑树的插入方式。
  7. 判断是否达到转化红黑树的阈值,如果达到阈值,则链表转化为红黑树。

分布式id生成方案有哪些?

UUID,数据库主键自增,Redis自增ID,雪花算法。

描述 优点 缺点
UUID UUID是通用唯一标识码的缩写,其目的是让分布式系统中的所有元素都有唯一的辨识信息,而不需要通过中央控制器来指定唯一标识。 1. 降低全局节点的压力,使得主键生成速度更快;
2. 生成的主键全局唯一;
3. 跨服务器合并数据方便。
1. UUID占用16个字符,空间占用较多;
2. 不是递增有序的数字,数据写入IO随机性很大,且索引效率下降
数据库主键自增 MySQL数据库设置主键且主键自动增长 1. INT和BIGINT类型占用空间较小;
2. 主键自动增长,IO写入连续性好;
3. 数字类型查询速度优于字符串
1. 并发性能不高,受限于数据库性能;
2. 分库分表,需要改造,复杂;
3. 自增:数据和数据量泄露
Redis自增 Redis计数器,原子性自增 使用内存,并发性能好 1. 数据丢失;
2. 自增:数据量泄露
雪花算法(snowflake) 大名鼎鼎的雪花算法,分布式ID的经典解决方案 1. 不依赖外部组件;
2. 性能好
时钟回拨

雪花算法生成的ID由哪些部分组成?

  1. 符号位,占用1位。
  2. 时间戳,占用41位,可以支持69年的时间跨度。
  3. 机器ID,占用10位。
  4. 序列号,占用12位。一毫秒可以生成4095个ID。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z39P5ofv-1662355491070)(images/image-20210521124236027.png)]

分布式锁在项目中有哪些应用场景?

使用分布式锁的场景一般需要满足以下场景:

  1. 系统是一个分布式系统,集群集群,java的锁已经锁不住了。
  2. 操作共享资源,比如库里唯一的用户数据。
  3. 同步访问,即多个进程同时操作共享资源。

分布锁有哪些解决方案?

  1. Reids的分布式锁,很多大公司会基于Reidis做扩展开发。setnx key value ex 10s,Redisson。

    watch dog.

  2. 基于Zookeeper。临时节点,顺序节点。

  3. 基于数据库,比如Mysql。主键或唯一索引的唯一性。

Redis做分布式锁用什么命令?

SETNX
格式:setnx key value 将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作,操作失败。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

加锁:set key value nx ex 10s

释放锁:delete key

Redis做分布式锁死锁有哪些情况,如何解决?

情况1:加锁,没有释放锁。需要加释放锁的操作。比如delete key。

情况2:加锁后,程序还没有执行释放锁,程序挂了。需要用的key的过期机制。

Redis如何做分布式锁?

假设有两个服务A、B都希望获得锁,执行过程大致如下:

Step1: 服务A为了获得锁,向Redis发起如下命令: SET productId:lock 0xx9p03001 NX EX 30000 其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一,“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"EX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。

Step2: 服务B为了获得锁,向Redis发起同样的命令: SET productId:lock 0000111 NX EX 30000
由于Redis内已经存在同名key,且并未过期,因此命令执行失败,服务B未能获得锁。服务B进入循环请求状态,比如每隔1秒钟(自行设置)向Redis发送请求,直到执行成功并获得锁。

Step3: 服务A的业务代码执行时长超过了30秒,导致key超时,因此Redis自动删除了key。此时服务B再次发送命令执行成功,假设本次请求中设置的value值为0000222。此时需要在服务A中对key进行续期,watch dog。

Step4: 服务A执行完毕,为了释放锁,服务A会主动向Redis发起删除key的请求。注意: 在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。比如当前场景下,Redis中的锁早就不是服务A持有的那一把了,而是由服务2创建,如果贸然使用服务A持有的key来删除锁,则会误将服务2的锁释放掉。此外,由于删除锁时涉及到一系列判断逻辑,因此一般使用lua脚本,具体如下:

if redis.call("get", KEYS[1])==ARGV[1] then
	return redis.call("del", KEYS[1])
else
	return 0
end

基于 ZooKeeper 的分布式锁实现原理是什么?

顺序节点特性:

使用 ZooKeeper 的顺序节点特性,假如我们在/lock/目录下创建3个节点,ZK集群会按照发起创建的顺序来创建节点,节点分别为/lock/0000000001、/lock/0000000002、/lock/0000000003,最后一位数是依次递增的,节点名由zk来完成。

临时节点特性:

ZK中还有一种名为临时节点的节点,临时节点由某个客户端创建,当客户端与ZK集群断开连接,则该节点自动被删除。EPHEMERAL_SEQUENTIAL为临时顺序节点。

根据ZK中节点是否存在,可以作为分布式锁的锁状态,以此来实现一个分布式锁,下面是分布式锁的基本逻辑:

  1. 客户端1调用create()方法创建名为“/业务ID/lock-”的临时顺序节点。
  2. 客户端1调用getChildren(“业务ID”)方法来获取所有已经创建的子节点。
  3. 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,就是看自己创建的序列号是否排第一,如果是第一,那么就认为这个客户端1获得了锁,在它前面没有别的客户端拿到锁。
  4. 如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。

ZooKeeper和Reids做分布式锁的区别?

Reids:

  1. Redis只保证最终一致性,副本间的数据复制是异步进行(Set是写,Get是读,Reids集群一般是读写分离架构,存在主从同步延迟情况),主从切换之后可能有部分数据没有复制过去可能会 「丢失锁」 情况,故强一致性要求的业务不推荐使用Reids,推荐使用zk。
  2. Redis集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公网集群影响因素偏大),但是极限qps可以达到最大且基本无异常

ZooKeeper:

  1. 使用ZooKeeper集群,锁原理是使用ZooKeeper的临时顺序节点,临时顺序节点的生命周期在Client与集群的Session结束时结束。因此如果某个Client节点存在网络问题,与ZooKeeper集群断开连接,Session超时同样会导致锁被错误的释放(导致被其他线程错误地持有),因此ZooKeeper也无法保证完全一致。
  2. ZK具有较好的稳定性;响应时间抖动很小,没有出现异常。但是随着并发量和业务数量的提升其响应时间和qps会明显下降。

总结:

  1. Zookeeper每次进行锁操作前都要创建若干节点,完成后要释放节点,会浪费很多时间;
  2. 而Redis只是简单的数据操作,没有这个问题。

MySQL如何做分布式锁?

在Mysql中创建一张表,设置一个 主键或者UNIQUE KEY 这个 KEY 就是要锁的 KEY(商品ID),所以同一个 KEY 在mysql表里只能插入一次了,这样对锁的竞争就交给了数据库,处理同一个 KEY 数据库保证了只有一个节点能插入成功,其他节点都会插入失败。

DB分布式锁的实现:通过主键id 或者 唯一索性 的唯一性进行加锁,说白了就是加锁的形式是向一张表中插入一条数据,该条数据的id就是一把分布式锁,例如当一次请求插入了一条id为1的数据,其他想要进行插入数据的并发请求必须等第一次请求执行完成后删除这条id为1的数据才能继续插入,实现了分布式锁的功能。

这样 lock 和 unlock 的思路就很简单了,伪代码:

def lock :
    exec sql: insert into locked—table (xxx) values (xxx)
    if result == true :
        return true
    else :
        return false

def unlock :
    exec sql: delete from lockedOrder where order_id='order_id'

计数器算法是什么?

​ 计数器算法,是指在指定的时间周期内累加访问次数,达到设定的阈值时,触发限流策略。下一个时间周期进行访问时,访问次数清零。此算法无论在单机还是分布式环境下实现都非常简单,使用redis的incr原子自增性,再结合key的过期时间,即可轻松实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d1CFRBdI-1662355491071)(images/4-6 计数器算法-1621753094321.jpg)]

​ 从上图我们来看,我们设置一分钟的阈值是100,在0:00到1:00内请求数是60,当到1:00时,请求数清零,从0开始计算,这时在1:00到2:00之间我们能处理的最大的请求为100,超过100个的请求,系统都拒绝。

​ 这个算法有一个临界问题,比如在上图中,在0:00到1:00内,只在0:50有60个请求,而在1:00到2:00之间,只在1:10有60个请求,虽然在两个一分钟的时间内,都没有超过100个请求,但是在0:50到1:10这20秒内,确有120个请求,虽然在每个周期内,都没超过阈值,但是在这20秒内,已经远远超过了我们原来设置的1分钟内100个请求的阈值。

滑动时间窗口算法是什么?

​ 为了解决计数器算法的临界值的问题,发明了滑动窗口算法。在TCP网络通信协议中,就采用滑动时间窗口算法来解决网络拥堵问题。

​ 滑动时间窗口是将计数器算法中的实际周期切分成多个小的时间窗口,分别在每个小的时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的小时间窗口的总的请求数即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D07oqC5n-1662355491071)(images/4-7 滑动窗口算法-1621753118270.jpg)]

​ 在上图中,假设我们设置一分钟的请求阈值是100,我们将一分钟拆分成4个小时间窗口,这样,每个小的时间窗口只能处理25个请求,我们用虚线方框表示滑动时间窗口,当前窗口的大小是2,也就是在窗口内最多能处理50个请求。随着时间的推移,滑动窗口也随着时间往前移动,比如上图开始时,窗口是0:00到0:30的这个范围,过了15秒后,窗口是0:15到0:45的这个范围,窗口中的请求重新清零,这样就很好的解决了计数器算法的临界值问题。

​ 在滑动时间窗口算法中,我们的小窗口划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

漏桶限流算法是什么?

​ 漏桶算法的原理就像它的名字一样,我们维持一个漏斗,它有恒定的流出速度,不管水流流入的速度有多快,漏斗出水的速度始终保持不变,类似于消息中间件,不管消息的生产者请求量有多大,消息的处理能力取决于消费者。

​ 漏桶的容量=漏桶的流出速度*可接受的等待时长。在这个容量范围内的请求可以排队等待系统的处理,超过这个容量的请求,才会被抛弃。

​ 在漏桶限流算法中,存在下面几种情况:

  1. 当请求速度大于漏桶的流出速度时,也就是请求量大于当前服务所能处理的最大极限值时,触发限流策略。

  2. 请求速度小于或等于漏桶的流出速度时,也就是服务的处理能力大于或等于请求量时,正常执行。

    漏桶算法有一个缺点:当系统在短时间内有突发的大流量时,漏桶算法处理不了。

令牌桶限流算法是什么?

​ 令牌桶算法,是增加一个大小固定的容器,也就是令牌桶,系统以恒定的速率向令牌桶中放入令牌,如果有客户端来请求,先需要从令牌桶中拿一个令牌,拿到令牌,才有资格访问系统,这时令牌桶中少一个令牌。当令牌桶满的时候,再向令牌桶生成令牌时,令牌会被抛弃。

​ 在令牌桶算法中,存在以下几种情况:

  1. 请求速度大于令牌的生成速度:那么令牌桶中的令牌会被取完,后续再进来的请求,由于拿不到令牌,会被限流。

  2. 请求速度等于令牌的生成速度:那么此时系统处于平稳状态。

  3. 请求速度小于令牌的生成速度:那么此时系统的访问量远远低于系统的并发能力,请求可以被正常处理。

    令牌桶算法,由于有一个桶的存在,可以处理短时间大流量的场景。这是令牌桶和漏桶的一个区别。

你设计微服务时遵循什么原则?

  1. 单一职责原则:让每个服务能独立,有界限的工作,每个服务只关注自己的业务。做到高内聚。
  2. 服务自治原则:每个服务要能做到独立开发、独立测试、独立构建、独立部署,独立运行。与其他服务进行解耦。
  3. 轻量级通信原则:让每个服务之间的调用是轻量级,并且能够跨平台、跨语言。比如采用RESTful风格,利用消息队列进行通信等。
  4. 粒度进化原则:对每个服务的粒度把控,其实没有统一的标准,这个得结合我们解决的具体业务问题。不要过度设计。服务的粒度随着业务和用户的发展而发展。

​ 总结一句话,软件是为业务服务的,好的系统不是设计出来的,而是进化出来的。

CAP定理是什么?

​ CAP定理,又叫布鲁尔定理。指的是:在一个分布式系统中,最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

  • C:一致性(Consistency),数据在多个副本中保持一致,可以理解成两个用户访问两个系统A和B,当A系统数据有变化时,及时同步给B系统,让两个用户看到的数据是一致的。

  • A:可用性(Availability),系统对外提供服务必须一直处于可用状态,在任何故障下,客户端都能在合理时间内获得服务端非错误的响应。

  • P:分区容错性(Partition tolerance),在分布式系统中遇到任何网络分区故障,系统仍然能对外提供服务。网络分区,可以这样理解,在分布式系统中,不同的节点分布在不同的子网络中,有可能子网络中只有一个节点,在所有网络正常的情况下,由于某些原因导致这些子节点之间的网络出现故障,导致整个节点环境被切分成了不同的独立区域,这就是网络分区。

    我们来详细分析一下CAP,为什么只能满足两个。看下图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OReNtnqZ-1662355491071)(images/10-4 CAP演示-1617721637028.jpg)]

    ​ 用户1和用户2分别访问系统A和系统B,系统A和系统B通过网络进行同步数据。理想情况是:用户1访问系统A对数据进行修改,将data1改成了data2,同时用户2访问系统B,拿到的是data2数据。

    ​ 但是实际中,由于分布式系统具有八大谬论:

    • 网络相当可靠

    • 延迟为零

    • 传输带宽是无限的

    • 网络相当安全

    • 拓扑结构不会改变

    • 必须要有一名管理员

    • 传输成本为零

    • 网络同质化

    我们知道,只要有网络调用,网络总是不可靠的。我们来一一分析。

    1. 当网络发生故障时,系统A和系统B没法进行数据同步,也就是我们不满足P,同时两个系统依然可以访问,那么此时其实相当于是单机系统,就不是分布式系统了,所以既然我们是分布式系统,P必须满足。
    2. 当P满足时,如果用户1通过系统A对数据进行了修改将data1改成了data2,也要让用户2通过系统B正确的拿到data2,那么此时是满足C,就必须等待网络将系统A和系统B的数据同步好,并且在同步期间,任何人不能访问系统B(让系统不可用),否则数据就不是一致的。此时满足的是CP。
    3. 当P满足时,如果用户1通过系统A对数据进行了修改将data1改成了data2,也要让系统B能继续提供服务,那么此时,只能接受系统A没有将data2同步给系统B(牺牲了一致性)。此时满足的就是AP。

​ 我们在前面学过的注册中心Eureka就是满足 的AP,它并不保证C。而Zookeeper是保证CP,它不保证A。在生产中,A和C的选择,没有正确的答案,是取决于自己的业务的。比如12306,是满足CP,因为买票必须满足数据的一致性,不然一个座位多卖了,对铁路运输都是不可以接受的。

BASE理论是什么?

由于CAP中一致性C和可用性A无法兼得,eBay的架构师,提出了BASE理论,它是通过牺牲数据的强一致性,来获得可用性。它由于如下3种特征:

  • Basically Available(基本可用):分布式系统在出现不可预知故障的时候,允许损失部分可用性,保证核心功能的可用。

  • Soft state(软状态):软状态也称为弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。、

  • Eventually consistent(最终一致性):最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

​ BASE理论并没有要求数据的强一致性,而是允许数据在一定的时间段内是不一致的,但在最终某个状态会达到一致。在生产环境中,很多公司,会采用BASE理论来实现数据的一致,因为产品的可用性相比强一致性来说,更加重要。比如在电商平台中,当用户对一个订单发起支付时,往往会调用第三方支付平台,比如支付宝支付或者微信支付,调用第三方成功后,第三方并不能及时通知我方系统,在第三方没有通知我方系统的这段时间内,我们给用户的订单状态显示支付中,等到第三方回调之后,我们再将状态改成已支付。虽然订单状态在短期内存在不一致,但是用户却获得了更好的产品体验。

2PC提交协议是什么?

二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol))。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

所谓的两个阶段是指:第一阶段:**准备阶段(投票阶段)**和第二阶段:提交阶段(执行阶段)

准备阶段

事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。

可以进一步将准备阶段分为以下三个步骤:

1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。

2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)

3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。

提交阶段

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

接下来分两种情况分别讨论提交阶段的过程。

当协调者节点从所有参与者节点获得的相应消息都为”同意”时:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pYVAeh57-1662355491072)(images/success.png)]

1)协调者节点向所有参与者节点发出”正式提交(commit)”的请求。

2)参与者节点正式完成操作,并释放在整个事务期间内占用的资源。

3)参与者节点向协调者节点发送”完成”消息。

4)协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zfYp4thj-1662355491072)(images/fail.png)]

1)协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。

2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。

3)参与者节点向协调者节点发送”回滚完成”消息。

4)协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。

不管最后结果如何,第二阶段都会结束当前事务。

2PC提交协议有什么缺点?

  1. 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

  2. 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

  3. 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

  4. 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

3PC提交协议是什么?

CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

2.响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

PreCommit阶段

协调者根据参与者的反应情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。

2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。

3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

1.发送中断请求 协调者向所有参与者发送abort请求。

2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

pre阶段参与者没收到请求,rollback。

doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

3.响应反馈 事务提交完之后,向协调者发送Ack响应。

4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

1.发送中断请求 协调者向所有参与者发送abort请求

2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息

4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

2PC和3PC的区别是什么?

1.3pc比2pc多了一个can commit阶段,减少了不必要的资源浪费。因为2pc在第一阶段会占用资源,而3pc在这个阶段不占用资源,只是校验一下sql,如果不能执行,就直接返回,减少了资源占用。

2.引入超时机制。同时在协调者和参与者中都引入超时机制。

2pc:只有协调者有超时机制,超时后,发送回滚指令。

3pc:协调者和参与者都有超时机制。

协调者超时: can commit,pre commit中,如果收不到参与者的反馈,则协调者向参与者发送中断指令。
参与者超时: pre commit阶段,参与者进行中断; do commit阶段,参与者进行提交。

  • TCC解决方案是什么?

    ​ TCC(Try-Confirm-Cancel)是一种常用的分布式事务解决方案,它将一个事务拆分成三个步骤:

    • T(Try):业务检查阶段,这阶段主要进行业务校验和检查或者资源预留;也可能是直接进行业务操作。

    • C(Confirm):业务确认阶段,这阶段对Try阶段校验过的业务或者预留的资源进行确认。

    • C(Cancel):业务回滚阶段,这阶段和上面的C(Confirm)是互斥的,用于释放Try阶段预留的资源或者业务。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NYnvX9q9-1662355491072)(images/image-20210521230854476-1621753201509.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8bLayWMy-1662355491073)(images/image-20210521230904203-1621753201509.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ocDVMJK-1662355491073)(images/image-20210521230912365-1621753201509.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0p3gNwD5-1662355491073)(images/image-20210521230919795-1621753201509.png)]

TCC空回滚是解决什么问题的?

​ 在没有调用TCC资源Try方法的情况下,调用了二阶段的Cancel方法。比如当Try请求由于网络延迟或故障等原因,没有执行,结果返回了异常,那么此时Cancel就不能正常执行,因为Try没有对数据进行修改,如果Cancel进行了对数据的修改,那就会导致数据不一致。
​ 解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道Try阶段是否执行,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。建议TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条。再额外增加一张分支事务记录表,其中有全局事务ID和分支事务ID,第一阶段Try方法里会插入一条记录,表示Try阶段执行了。Cancel接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。

如何解决TCC幂等问题?

为了保证TCC二阶段提交重试机制不会引发数据不一致,要求TCC的二阶段Confirm和Cancel接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。
解决思路在上述 分支事务记录中增加执行状态,每次执行前都查询该状态。

分布式锁。

如何解决TCC中悬挂问题?

悬挂就是对于一个分布式事务,其二阶段Cancel接口比Try接口先执行。
出现原因是在调用分支事务Try时,由于网络发生拥堵,造成了超时,TM就会通知RM回滚该分布式事务,可能回滚完成后,Try请求才到达参与者真正执行,而一个Try方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后无法继续处理。
解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下,判断分支事务记录表中是否已经有二阶段事务记录,如果有则不执行Try。

可靠消息服务方案是什么?

​ 可靠消息最终一致性方案指的是:当事务的发起方(事务参与者,消息发送者)执行完本地事务后,同时发出一条消息,事务参与方(事务参与者,消息的消费者)一定能够接受消息并可以成功处理自己的事务。

​ 这里面强调两点:

  1. 可靠消息:发起方一定得把消息传递到消费者。
  2. 最终一致性:最终发起方的业务处理和消费方的业务处理得完成,达成最终一致。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6wlvNwy5-1662355491073)(images/image-20210522125830646.png)]

最大努力通知方案的关键是什么?

  1. 有一定的消息重复通知机制。因为接收通知方(上图中的我方支付系统)可能没有接收到通知,此时要有一定的机制对消息重复通知。
  2. 消息校对机制。如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。

什么是分布式系统中的幂等?

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

例如,“getUsername()和 setTrue()”函数就是一个幂等函数. 更复杂的操作幂等保证是利用唯一交易号(流水号)实现. 我的理解:幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的。

操作:查询,set固定值。逻辑删除。set 固定值。

流程:分布式系统中,网络调用,重试机制。

幂等有哪些技术解决方案?

1.查询操作

查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select 是天然的幂等操作;

2.删除操作

删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回 0,删除的数据多条,返回结果多个。

3.唯一索引

防止新增脏数据。比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建多个资金账户,那么给资金账户表中的用户 ID 加唯一索引,所以一个用户新增成功一个资金账户记录。要点:唯一索引或唯一组合索引来防止新增数据存在脏数据(当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可。

4.token 机制

防止页面重复提交。

**业务要求:**页面的数据只能被点击提交一次;

**发生原因:**由于重复点击或者网络重发,或者 nginx 重发等情况会导致数据被重复提交;

**解决办法:**集群环境采用 token 加 redis(redis 单线程的,处理需要排队);单 JVM 环境:采用 token 加 redis 或 token 加 jvm 锁。

处理流程:

  1. 数据提交前要向服务的申请 token,token 放到 redis 或 jvm 内存,token 有效时间;
  2. 提交后后台校验 token,同时删除 token,生成新的 token 返回。

**token 特点:**要申请,一次有效性,可以限流。

注意:redis 要用删除操作来判断 token,删除成功代表 token 校验通过。

  1. traceId

    操作时唯一的。

对外提供的API如何保证幂等?

举例说明: 银联提供的付款接口:需要接入商户提交付款请求时附带:source 来源,seq 序列号。

source+seq 在数据库里面做唯一索引,防止多次付款(并发时,只能处理一个请求) 。重点:对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源 source,一个是来源方序列号 seq,这个两个字段在提供方系统里面做联合唯一索引,这样当第三方调用时,先在本方系统里面查询一下,是否已经处理过,返回相应处理结果;没有处理过,进行相应处理,返回结果。

注意,为了幂等友好,一定要先查询一下,是否处理过该笔业务,不查询直接插入业务系统,会报错,但实际已经处理。

双写一致性问题如何解决?

先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力更新即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。
在这里,我们讨论三种更新策略:

  1. 先更新缓存,再更新数据库。(不可取)
  2. 先更新数据库,再更新缓存。(不可取)
  3. 先删除缓存,再更新数据库。(不可取)
  4. 先更新数据库,再删除缓存。(可取,有问题待解决)

大前提:

先读缓存,如果缓存没有,才从数据库读取。

(1)先更新数据库,再更新缓存

这套方案,大家是普遍反对的。为什么呢?有如下两点原因。
原因一(线程安全角度)
同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
原因二(业务场景角度)
有如下两点:
(1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。
(2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。

(2)先删缓存,再更新数据库

该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
那么,如何解决呢?采用延时双删策略

(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠1秒,再次淘汰缓存
这么做,可以将1秒内所造成的缓存脏数据,再次删除。
那么,这个1秒怎么确定的,具体该休眠多久呢?
针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
如果你用了mysql的读写分离架构怎么办?
ok,在这种情况下,造成数据不一致的原因如下,还是两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。
(1)请求A进行写操作,删除缓存
(2)请求A将数据写入数据库了,
(3)请求B查询缓存发现,缓存没有值
(4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值
(5)请求B将旧值写入缓存
(6)数据库完成主从同步,从库变为新值
上述情形,就是数据不一致的原因。还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。
采用这种同步淘汰策略,吞吐量降低怎么办?
ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。
第二次删除,如果删除失败怎么办?
这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
(6)请求A试图去删除,请求B写入对的缓存值,结果失败了。
ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。
如何解决呢?

(3)先更新数据库,再删缓存

首先,先说一下。老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。
这种情况不存在并发问题么?
不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据。
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
假设,有人非要抬杠,有强迫症,一定要解决怎么办?
如何解决上述并发问题?
首先,给缓存设有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。
还有其他造成不一致的原因么?
有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。这也是缓存更新策略(2)里留下的最后一个疑问。
如何解决?
提供一个保障的重试机制即可,这里给出两套方案。
方案一
如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hovIyfTr-1662355491074)(images/o_update1.png)]
流程如下所示
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功
然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
方案二
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VLHdWIBE-1662355491074)(images/o_update2.png)]
流程如下图所示:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。

**备注说明:**上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。至于oracle中,博主目前不知道有没有现成中间件可以使用。另外,重试机制,博主是采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。

分布式微服务项目你是如何设计的?

我一般设计成两层:业务层和能力层(中台),业务层接受用户请求,然后通过调用能力层来完成业务逻辑。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O8qjlqVb-1662355491074)(images/image-20210522172654370.png)]

认证 (Authentication) 和授权 (Authorization)的区别是什么?

Authentication(认证) 是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
Authorization(授权) 发生在 Authentication(认证) 之后。授权,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。
这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。

Cookie 和 Session 有什么区别?如何使用Session进行身份验证?

Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。

那么,如何使用Session进行身份验证?

很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:用户成功登陆系统,然后返回给客户端具有 SessionID 的 Cookie,当用户向后端发起请求的时候会把 SessionID 带上,这样后端就知道你的身份状态了。关于这种认证方式更详细的过程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbeLwBMN-1662355491074)(images/image-20210520130119426.png)]

用户向服务器发送用户名和密码用于登陆系统。
服务器验证通过后,服务器为用户创建一个 Session,并将 Session信息存储 起来。
服务器向用户返回一个 SessionID,写入用户的 Cookie。
当用户保持登录状态时,Cookie 将与每个后续请求一起被发送出去。
服务器可以将存储在 Cookie 上的 Session ID 与存储在内存中或者数据库中的 Session 信息进行比较,以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。
使用 Session 的时候需要注意下面几个点:

依赖Session的关键业务一定要确保客户端开启了Cookie。
注意Session的过期时间

为什么Cookie 无法防止CSRF攻击,而token可以?

**CSRF(Cross Site Request Forgery)**一般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪造 呢?说简单用你的身份去发送一些对你不友好的请求。举个简单的例子:

小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了10000元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。

<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科学理财,年盈利率过万</>
进行Session 认证的时候,我们一般使用 Cookie 来存储 SessionId,当我们登陆后后端生成一个SessionId放在Cookie中返回给客户端,服务端通过Redis或者其他存储工具记录保存着这个Sessionid,客户端登录以后每次请求都会带上这个SessionId,服务端通过这个SessionId来标示你这个人。如果别人通过 cookie拿到了 SessionId 后就可以代替你的身份访问系统了。

Session 认证中 Cookie 中的 SessionId是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。

但是,我们使用 token 的话就不会存在这个问题,在我们登录成功获得 token 之后,一般会选择存放在 local storage 中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。

什么是 Token?什么是 JWT?如何基于Token进行身份验证?

我们知道 Session 信息需要保存一份在服务器端。这种方式会带来一些麻烦,比如需要我们保证保存 Session 信息服务器的可用性、不适合移动端(依赖Cookie)等等。

有没有一种不需要自己存放 Session 信息就能实现身份验证的方式呢?使用 Token 即可!JWT (JSON Web Token) 就是这种方式的实现,通过这种方式服务器端就不需要保存 Session 数据了,只用在客户端保存服务端返回给客户的 Token 就可以了,扩展性得到提升。

JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。

下面是 RFC 7519 对 JWT 做的较为正式的定义。

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT)

JWT 由 3 部分构成:

Header :描述 JWT 的元数据。定义了生成签名的算法以及 Token 的类型。
Payload(负载):用来存放实际需要传递的数据
Signature(签名):服务器通过Payload、Header和一个密钥(secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
在基于 Token 进行身份验证的的应用程序中,服务器通过Payload、Header和一个密钥(secret)创建令牌(Token)并将 Token 发送给客户端,客户端将 Token 保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization字段中:Authorization: Bearer Token。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRsu0aSm-1662355491075)(images/image-20210520130410868.png)]

用户向服务器发送用户名和密码用于登陆系统。
身份验证服务响应并返回了签名的 JWT,上面包含了用户是谁的内容。
用户以后每次向后端发请求都在Header中带上 JWT。
服务端检查 JWT 并从中获取用户相关信息。

分布式架构下,Session 共享有什么方案?

  1. 不要有session:但是确实在某些场景下,是可以没有session的,其实在很多接口类系统当中,都提倡【API无状态服务】;也就是每一次的接口访问,都不依赖于session、不依赖于前一次的接口访问;
  2. 存入cookie中:将session存储到cookie中,但是缺点也很明显,例如每次请求都得带着session,数据存储在客户端本地,是有风险的;
  3. session同步:对个服务器之间同步session,这样可以保证每个服务器上都有全部的session信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败;
  4. 使用Nginx(或其他复杂均衡软硬件)中的ip绑定策略,同一个ip只能在指定的同一个机器访问,但是这样做风险也比较大,而且也是去了负载均衡的意义;
  5. 我们现在的系统会把session放到Redis中存储,虽然架构上变得复杂,并且需要多访问一次Redis,但是这种方案带来的好处也是很大的:实现session共享,可以水平扩展(增加Redis服务器),服务器重启session不丢失(不过也要注意session在Redis中的刷新/失效机制),不仅可以跨服务器session共享,甚至可以跨平台(例如网页端和APP端)。

springcloud核心组件有哪些?

服务注册与发现——Netflix Eureka、Nacos、Zookeeper

客户端负载均衡——Netflix Ribbon、SpringCloud LoadBalancer

服务熔断器——Netflix Hystrix、Alibaba Sentinel、Resilience4J

服务网关——Netflix Zuul、SpringCloud Gateway

服务接口调用——Netflix Feign、 Resttemplate、Openfeign

链路追踪——Netflix Sleuth、Skywalking、Pinpoint

聚合Hystrix监控数据——Netflix Turbine

监控中心---- SpringBoot Admin

配置中心——Spring Cloud Config 、Apollo、nacos

微服务架构原理是什么?

主要是面向SOA理念,更细小粒度服务的拆分,将功能分解到各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。

注册中心的原理是什么?

服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用

配置中心的原理是什么?

在服务运行之前,将所需的配置信息从配置仓库拉取到本地服务,达到统一化配置管理的目的

配置中心是如何实现自动刷新的?

  1. 配置中心Server端承担起配置刷新的职责

  2. 提交配置触发post请求给server端的bus/refresh接口

  3. server端接收到请求并发送给Spring Cloud Bus总线

  4. Spring Cloud bus接到消息并通知给其它连接到总线的客户端

  5. 其它客户端接收到通知,请求Server端获取最新配置

  6. 全部客户端均获取到最新的配置

配置中心是如何保证数据安全的?

1.保证容器文件访问的安全性,即保证所有的网络资源请求都需要登录

2.将配置中心里所有配置文件中的密码进行加密,保证其密文性

3.开发环境禁止拉取生产环境的配置文件

用zookeeper和eureka做注册中心有什么区别?

Zookeeper保证的是CP(一致性,容错性), 而Eureka则是AP(可用性,容错性)。

Spring Cloud和Dubbo有哪些区别?

  1. dubbo 是二进制传输,对象直接转成二进制,使用RPC通信。

SpringCloud是http 传输,同时使用http协议一般会使用JSON报文,json再转二进制,消耗会更大。

  1. Dubbo只是实现了服务治理,而Spring Cloud下面有几十个子项目分别覆盖了微服务架构下的方方面面,服务治理只是其中的一个方面,一定程度来说,Dubbo只是Spring Cloud Netflix中的一个子集。

Ribbon负载均衡原理是什么?

  1. Ribbon通过ILoadBalancer接口对外提供统一的选择服务器(Server)的功能,此接口会根据不同的负载均衡策略(IRule)选择合适的Server返回给使用者。

  2. IRule是负载均衡策略的抽象,ILoadBalancer通过调用IRule的choose()方法返回Server

  3. IPing用来检测Server是否可用,ILoadBalancer的实现类维护一个Timer每隔10s检测一次Server的可用状态

  4. IClientConfig主要定义了用于初始化各种客户端和负载均衡器的配置信息,器实现类为DefaultClientConfigImpl

微服务熔断降级机制是什么?

微服务框架是许多服务互相调用的,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。Hystrix 是隔离、熔断以及降级的一个框架。如果调用某服务报错(或者挂了),就对该服务熔断,在 5 分钟内请求此服务直接就返回一个默认值,不需要每次都卡几秒,这个过程,就是所谓的熔断。但是熔断了之后就会少调用一个服务,此时需要做下标记,标记本来需要做什么业务,但是因为服务挂了,暂时没有做,等该服务恢复了,就可以手工处理这些业务。这个过程,就是所谓的降级。

什么是Hystrix?实现原理是什么?

Hystrix是一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,停止级联故障,并在 不可避免发生故障的复杂分布式系统中实现快速恢复。主要靠Spring的AOP实现

实现原理

正常情况下,断路器关闭,服务消费者正常请求微服务

一段事件内,失败率达到一定阈值,断路器将断开,此时不再请求服务提供者,而是只是快速失败的方法(断路方法)

断路器打开一段时间,自动进入“半开”状态,此时,断路器可允许一个请求方法服务提供者,如果请求调用成功,则关闭断路器,否则继续保持断路器打开状态。

断路器hystrix是保证了局部发生的错误,不会扩展到整个系统,从而保证系统的即使出现局部问题也不会造成系统雪崩

注册中心挂了,或者服务挂了,应该如何处理?

注册中心挂了,可以读取本地持久化里的配置

服务挂了 应该配有服务监控中心 感知到服务下线后可以通过配置的邮件通知相关人员排查问题。

说说你对RPC、RMI如何理解?

RPC 远程过程调用协议,通过网络从远程计算机上请求调用某种服务。

RMI:远程方法调用 能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java 虚拟机中的对象上的方法。

redis持久化机制:RDB和AOF

Redis 持久化

Redis 提供了不同级别的持久化方式:

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.

  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.

  • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

  • 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

  • 最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:

    RDB的优点

  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.

  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.

  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.

  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

    RDB的缺点

  • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.

  • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

    AOF 优点

  • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.

  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.

  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

    AOF 缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。

  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

    4.X版本的整合策略

    在AOF重写策略上做了优化

    在重写AOF文件时,4.x版本以前是把内存数据集的操作指令落地,而新版本是把内存的数据集以rdb的形式落地

    这样重写后的AOF依然追加的是日志,但是,在恢复的时候是先rdb再增量的日志,性能更优秀

扩展知识

异步线程知识点

计算机组成原理

fork

copy on write

系统IO

pagecache

fsync


redis的过期键有哪些删除策略

过期精度

在 Redis 2.4 及以前版本,过期期时间可能不是十分准确,有0-1秒的误差。

从 Redis 2.6 起,过期时间误差缩小到0-1毫秒。

过期和持久

Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的。

要想过期的工作处理好,计算机必须采用稳定的时间。 如果你将RDB文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的 keys装载时就会过期)。

即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。

Redis如何淘汰过期的keys

Redis keys过期有两种方式:被动和主动方式。

当一些客户端尝试访问它时,key会被发现并主动的过期。

当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。

具体就是Redis每秒10次做的事情:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步奏1.

这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。

在复制AOF文件时如何处理过期

为了获得正确的行为而不牺牲一致性,当一个key过期,DEL将会随着AOF文字一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。

然而,当slaves连接到master时,不会独立过期keys(会等到master执行DEL命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master。

扩展

绝对时间点过期

相对时间点过期

时钟轮算法


redis线程模型有哪些,单线程为什么快

IO模型维度的特征

IO模型使用了多路复用器,在linux系统中使用的是EPOLL

类似netty的BOSS,WORKER使用一个EventLoopGroup(threads=1)

单线程的Reactor模型,每次循环取socket中的命令然后逐一操作,可以保证socket中的指令是按顺序的,不保证不同的socket也就是客户端的命令的顺序性

命令操作在单线程中顺序操作,没有多线程的困扰不需要锁的复杂度,在操作数据上相对来说是原子性质的

架构设计模型

自身的内存存储数据,读写操作不设计磁盘IO

redis除了提供了Value具备类型还为每种类型实现了一些操作命令

实现了计算向数据移动,而非数据想计算移动,这样在IO的成本上有一定的优势

且在数据结构类型上,丰富了一些统计类属性,读写操作中,写操作会O(1)负载度更新length类属性,使得读操作也是O(1)的


缓存雪崩、缓存穿透、缓存击穿在实际中如何处理

缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案

1.使用互斥锁(mutex key)
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间

2."提前"使用互斥锁(mutex key):
在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。

3.“永远不过期”:
这里的“永远不过期”包含两层意思:

(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

总结

穿透:缓存不存在,数据库不存在,高并发,少量key

击穿:缓存不存在,数据库存在,高并发,少量key

雪崩:缓存不存在,数据库存在,高并发,大量key

语义有些许差异,但是,都可以使用限流的互斥锁,保障数据库的稳定


redis事务是怎么实现的

MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

EXEC 命令负责触发并执行事务中的所有命令:

如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。

然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。

如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。

使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。

从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作,具体信息请参考文档的后半部分。

事务中的错误

使用事务时可能会遇上以下两种错误:

事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。

对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。

不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。

在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。

至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。

为什么 Redis 不支持回滚(roll back)

如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。


redis集群方案有哪些

常见集群分类

主从复制集群

分片集群

redis有那些:

主从复制集群,手动切换

带有哨兵的HA的主从复制集群

客户端实现路由索引的分片集群

使用中间件代理层的分片集群

redis自身实现的cluster分片集群


redis主从复制的原理是什么

主从复制机制

当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave , :包括客户端的写入、key 的过期或被逐出等等。

当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。

当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。

主从复制的关注点

Redis 使用异步复制,slave 和 master 之间异步地确认处理的数据量

一个 master 可以拥有多个 slave

slave 可以接受其他 slave 的连接。除了多个 slave 可以连接到同一个 master 之外, slave 之间也可以像层叠状的结构(cascading-like structure)连接到其他 slave 。自 Redis 4.0 起,所有的 sub-slave 将会从 master 收到完全一样的复制流。

Redis 复制在 master 侧是非阻塞的。这意味着 master 在一个或多个 slave 进行初次同步或者是部分重同步时,可以继续处理查询请求。

复制在 slave 侧大部分也是非阻塞的。当 slave 进行初次同步时,它可以使用旧数据集处理查询请求,假设你在 redis.conf 中配置了让 Redis 这样做的话。否则,你可以配置如果复制流断开, Redis slave 会返回一个 error 给客户端。但是,在初次同步之后,旧数据集必须被删除,同时加载新的数据集。 slave 在这个短暂的时间窗口内(如果数据集很大,会持续较长时间),会阻塞到来的连接请求。自 Redis 4.0 开始,可以配置 Redis 使删除旧数据集的操作在另一个不同的线程中进行,但是,加载新数据集的操作依然需要在主线程中进行并且会阻塞 slave 。

复制既可以被用在可伸缩性,以便只读查询可以有多个 slave 进行(例如 O(N) 复杂度的慢操作可以被下放到 slave ),或者仅用于数据安全。

可以使用复制来避免 master 将全部数据集写入磁盘造成的开销:一种典型的技术是配置你的 master Redis.conf 以避免对磁盘进行持久化,然后连接一个 slave ,其配置为不定期保存或是启用 AOF。但是,这个设置必须小心处理,因为重新启动的 master 程序将从一个空数据集开始:如果一个 slave 试图与它同步,那么这个 slave 也会被清空。
任何时候数据安全性都是很重要的,所以如果 master 使用复制功能的同时未配置持久化,那么自动重启进程这项应该被禁用。

Redis 复制功能是如何工作的

每一个 Redis master 都有一个 replication ID :这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master 也持有一个偏移量,master 将自己产生的复制流发送给 slave 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新 slave 的状态。复制偏移量即使在没有一个 slave 连接到 master 时,也会自增,所以基本上每一对给定的

Replication ID, offset

都会标识一个 master 数据集的确切版本。

当 slave 连接到 master 时,它们使用 PSYNC 命令来发送它们记录的旧的 master replication ID 和它们至今为止处理的偏移量。通过这种方式, master 能够仅发送 slave 所需的增量部分。但是如果 master 的缓冲区中没有足够的命令积压缓冲记录,或者如果 slave 引用了不再知道的历史记录(replication ID),则会转而进行一个全量重同步:在这种情况下, slave 会得到一个完整的数据集副本,从头开始。

下面是一个全量同步的工作细节:

master 开启一个后台保存进程,以便于生产一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master 将数据集文件传输给 slave, slave将之保存在磁盘上,然后加载文件到内存。再然后 master 会发送所有缓冲的命令发给 slave。这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同。

你可以用 telnet 自己进行尝试。在服务器正在做一些工作的同时连接到 Redis 端口并发出 SYNC 命令。你将会看到一个批量传输,并且之后每一个 master 接收到的命令都将在 telnet 回话中被重新发出。事实上 SYNC 是一个旧协议,在新的 Redis 实例中已经不再被使用,但是其仍然向后兼容:但它不允许部分重同步,所以现在 PSYNC 被用来替代 SYNC。

之前说过,当主从之间的连接因为一些原因崩溃之后, slave 能够自动重连。如果 master 收到了多个 slave 要求同步的请求,它会执行一个单独的后台保存,以便于为多个 slave 服务。

无需磁盘参与的复制

正常情况下,一个全量重同步要求在磁盘上创建一个 RDB 文件,然后将它从磁盘加载进内存,然后 slave以此进行数据同步。

如果磁盘性能很低的话,这对 master 是一个压力很大的操作。Redis 2.8.18 是第一个支持无磁盘复制的版本。在此设置中,子进程直接发送 RDB 文件给 slave,无需使用磁盘作为中间储存介质。


redis缓存如何回收

回收策略

noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键

如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。

一般的经验规则:

  • 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
  • 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
  • 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。

allkeys-lruvolatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。

为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。

回收进程如何工作

理解回收进程如何工作是非常重要的:

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

RabbitMQ的架构设计是什么样的

是AMQP的实现,相关概念语义

Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输

Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

Queue:消息的载体,每个消息都会被投到一个或多个队列。

Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.

Routing Key:路由关键字,exchange根据这个关键字进行消息投递。

vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。

Producer:消息生产者,就是投递消息的程序.

Consumer:消息消费者,就是接受消息的程序.

Channel:消息通道,在客户端的每个连接里,可建立多个channel.

核心概念

在mq领域中,producer将msg发送到queue,然后consumer通过消费queue完成P.C解耦

kafka是由producer决定msg发送到那个queue

rabbitmq是由Exchange决定msg应该怎么样发送到目标queue,这就是binding及对应的策略

Exchange

Direct Exchange:直接匹配,通过Exchange名称+RountingKey来发送与接收消息.
Fanout Exchange:广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略Routing Key.
Topic Exchange:主题匹配订阅,这里的主题指的是RoutingKey,RoutingKey可以采用通配符,如:*或#,RoutingKey命名采用.来分隔多个词,只有消息这将队列绑定到该路由器且指定RoutingKey符合匹配规则时才能收到消息;
Headers Exchange:消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头:(如:x-mactch=all或者x_match=any),只有请求头与消息头匹配,才能接收消息,忽略RoutingKey.
默认的exchange:如果用空字符串去声明一个exchange,那么系统就会使用”amq.direct”这个exchange,我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去

复杂与精简

在众多的MQ中间件中,首先学习Rabbitmq的时候,就理解他是一个单机的mq组件,为了系统的解耦,可以自己在业务层面做AKF

其在内卷能力做的非常出色,这得益于AMQP,也就是消息的传递形式、复杂度有exchange和queue的binding实现,这,对于P.C有很大的帮助


RabbitMQ如何确保消息发送和消息接收

消息发送确认

1 ConfirmCallback方法

ConfirmCallback 是一个回调接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中。

2 ReturnCallback方法

通过实现 ReturnCallback 接口,启动消息失败返回,此接口是在交换器路由不到队列时触发回调,该方法可以不使用,因为交换器和队列是在代码里绑定的,如果消息成功投递到 Broker 后几乎不存在绑定队列失败,除非你代码写错了。

消息接收确认

RabbitMQ 消息确认机制(ACK)默认是自动确认的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,假如你用回滚了也只是保证了数据的一致性,但是消息还是丢了,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。

消息确认模式有:

AcknowledgeMode.NONE:自动确认。
AcknowledgeMode.AUTO:根据情况确认。
AcknowledgeMode.MANUAL:手动确认。
消费者收到消息后,手动调用 Basic.Ack 或 Basic.Nack 或 Basic.Reject 后,RabbitMQ 收到这些消息后,才认为本次投递完成。

Basic.Ack 命令:用于确认当前消息。
Basic.Nack 命令:用于否定当前消息(注意:这是AMQP 0-9-1的RabbitMQ扩展) 。
Basic.Reject 命令:用于拒绝当前消息。
Nack,Reject后都有能力要求是否requeue消息或者进入死信队列


RabbitMQ事务消息原理是什么

事务V.S确认

确认是对一件事的确认

事务是对批量的确认

增删改查中,事务是对于增删改的保证

发送方事务

开启事务,发送多条数据,事务提交或回滚是原子的,要么都提交,要么都回滚

消费方事务

消费方是读取行为,那么事务体现在哪里呢

rabbitmq的消费行为会触发queue中msg的是否删除、是否重新放回队列等行为,类增删改

所以,消费方的ack是要手动提交的,且最终确定以事务的提交和回滚决定


RabbitMQ死信队列、延时队列分别是什么

死信队列

DLX(Dead Letter Exchange),死信交换器

当队列中的消息被拒绝、或者过期会变成死信,死信可以被重新发布到另一个交换器,这个交换器就是DLX,与DLX绑定的队列称为死信队列。
造成死信的原因:

  • 信息被拒绝
  • 信息超时
  • 超过了队列的最大长度

过期消息:

在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。

队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒

单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒

延迟队列

延迟队列存储的是延迟消息

延迟消息指的是,当消息被发发布出去之后,并不立即投递给消费者,而是在指定时间之后投递。如:

在订单系统中,订单有30秒的付款时间,在订单超时之后在投递给消费者处理超时订单。

rabbitMq没有直接支持延迟队列,可以通过死信队列实现。

在死信队列中,可以为普通交换器绑定多个消息队列,假设绑定过期时间为5分钟,10分钟和30分钟,3个消息队列,然后为每个消息队列设置DLX,为每个DLX关联一个死信队列。

当消息过期之后,被转存到对应的死信队列中,然后投递给指定的消费者消费。


简述kafka架构设计是什么样

语义概念

1 broker
Kafka 集群包含一个或多个服务器,服务器节点称为broker。

broker存储topic的数据。如果某topic有N个partition,集群有N个broker,那么每个broker存储该topic的一个partition。

如果某topic有N个partition,集群有(N+M)个broker,那么其中有N个broker存储该topic的一个partition,剩下的M个broker不存储该topic的partition数据。

如果某topic有N个partition,集群中broker数目少于N个,那么一个broker存储该topic的一个或多个partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。

2 Topic
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)

类似于数据库的表名

3 Partition
topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个segment文件存储。partition中的数据是有序的,不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。

4 Producer
生产者即数据的发布者,该角色将消息发布到Kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partition。

5 Consumer
消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。

6 Consumer Group
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个CG。topic的消息会复制-给consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。

7 Leader
每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的partition。

8 Follower
Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(ISR)列表中删除,重新创建一个Follower。

9 Offset
kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka

KAFKA天生是分布式的,满足AKF的XYZ轴特点,扩展性,可靠性,高性能是没得说

而且,kafka具备自己的特色,比如动态ISR集合,是在强一致性,过半一致性之外的另一个实现手段



Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失

broker在故障后的消息丢失

消费者在消费过程中的消息丢失

ACK机制

ack有3个可选值,分别是1,0,-1。

ack=0:生产者在生产过程中的消息丢失

简单来说就是,producer发送一次就不再发送了,不管是否发送成功。

ack=1:broker在故障后的消息丢失

简单来说就是,producer只要收到一个分区副本成功写入的通知就认为推送消息成功了。这里有一个地方需要注意,这个副本必须是leader副本。只有leader副本成功写入了,producer才会认为消息发送成功。

注意,ack的默认值就是1。这个默认值其实就是吞吐量与可靠性的一个折中方案。生产上我们可以根据实际情况进行调整,比如如果你要追求高吞吐量,那么就要放弃可靠性。

ack=-1:生产侧和存储侧不会丢失数据

简单来说就是,producer只有收到分区内所有副本的成功写入的通知才认为推送消息成功了。

Offset机制

kafka消费者的三种消费语义

at-most-once:最多一次,可能丢数据

at-least-once:最少一次,可能重复消费数据

exact-once message:精确一次


Kafka是pull?push?以及优劣势分析

Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。

Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。

一些消息系统比如Scribe和Apache Flume采用了push模式,将消息推送到下游的consumer。

这样做有好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。

消息系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。

最终Kafka还是选取了传统的pull模式。

Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。

Push模式必须在不知道下游consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。

如果为了避免consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。

Pull模式下,consumer就可以根据自己的消费能力去决定这些策略。

Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到达。

为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发


Kafka中zk的作用是什么

Zookeeper是分布式协调,注意它不是数据库

kafka中使用了zookeeper的分布式锁和分布式配置及统一命名的分布式协调解决方案

在kafka的broker集群中的controller的选择,是通过zk的临时节点争抢获得的

brokerID等如果自增的话也是通过zk的节点version实现的全局唯一

kafka中broker中的状态数据也是存储在zk中,不过这里要注意,zk不是数据库,所以存储的属于元数据

而,新旧版本变化中,就把曾经的offset从zk中迁移出了zk


Kafka中高性能如何保障

首先,性能的最大瓶颈依然是IO,这个是不能逾越的鸿沟

虽然,broker在持久化数据的时候已经最大努力的使用了磁盘的顺序读写

更进一步的性能优化是零拷贝的使用,也就是从磁盘日志到消费者客户端的数据传递,因为kafka是mq,对于msg不具备加工处理,所以得以实现

然后就是大多数分布式系统一样,总要做tradeoff,在速度与可用性/可靠性中挣扎

ACK的0,1,-1级别就是在性能和可靠中权衡


kafka的rebalance机制是什么

消费者分区分配策略

Range 范围分区(默认的)

RoundRobin 轮询分区

Sticky策略

触发 Rebalance 的时机

Rebalance 的触发条件有3个。

  • 组成员个数发生变化。例如有新的 consumer 实例加入该消费组或者离开组。
  • 订阅的 Topic 个数发生变化。
  • 订阅 Topic 的分区数发生变化。

Coordinator协调过程

消费者如何发现协调者

消费者如何确定分配策略

如果需要再均衡分配策略的影响


zk的数据模型和节点类型有哪些

ZooKeeper数据模型

ZooKeeper的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper树中的每个节点被称为—Znode。

和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。但也有不同之处:

	Znode兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子Znode。用户对Znode具有增、删、改、查等操作(权限允许的情况下)
	
	Znode具有原子性操作,读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作
	
	Znode存储数据大小有限制。ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,当时常规使用中应该远小于此值
	
	Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。

节点类型

Znode有两种,分别为临时节点和永久节点。
节点的类型在创建时即被确定,并且不能改变。
临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。

永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
  
Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10位数字,没有数值的数位用0补充,例如“0000000001”)

在ZooKeeper中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。

1、持久节点(PERSISTENT)

该数据节点别创建后,就会一直存在于ZooKeeper服务器上,直到有删除操作来主动删除该节点。

2、持久顺序节点(PERSISTENT_SEQUENTIAL)

持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。

3、临时节点(EPHEMERAL)

临时节点的生命周期和客户端的回话绑定在一起,如果客户端会话失效,那么这个节点就会被自动地清理掉。

ZooKeeper规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。

4、临时顺序节点(EPHEMERAL_SEQUENTIAL)


Zookeeper watch机制是什么

ZooKeeper是用来协调(同步)分布式进程的服务,提供了一个简单高性能的协调内核,用户可以在此之上构建更多复杂的分布式协调功能。

多个分布式进程通过ZooKeeper提供的API来操作共享的ZooKeeper内存数据对象ZNode来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型,与Java的多线程并发模型一致,他们的线程或进程都是”共享式内存通信“。

Java没有直接提供某种响应式通知接口来监控某个对象状态的变化,只能要么浪费CPU时间毫无响应式的轮询重试,或基于Java提供的某种主动通知(Notif)机制(内置队列)来响应状态变化,但这种机制是需要循环阻塞调用。

而ZooKeeper实现这些分布式进程的状态(ZNode的Data、Children)共享时,基于性能的考虑采用了类似的异步非阻塞的主动通知模式即Watch机制,使得分布式进程之间的“共享状态通信”更加实时高效,其实这也是ZooKeeper的主要任务决定的—协调。Consul虽然也实现了Watch机制,但它是阻塞的长轮询。

ZooKeeper的Watch特性

  1. Watch是一次性的,每次都需要重新注册,并且客户端在会话异常结束时不会收到任何通知,而快速重连接时仍不影响接收通知。
  2. Watch的回调执行都是顺序执行的,并且客户端在没有收到关注数据的变化事件通知之前是不会看到最新的数据,另外需要注意不要在Watch回调逻辑中阻塞整个客户端的Watch回调
  3. Watch是轻量级的,WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径。ZooKeeper服务端只会通知客户端发生了什么,并不会告诉具体内容。

Zookeeper状态

Disconnected:客户端是断开连接的状态,不能连接服务集合中的任意一个
SyncConnected:客户端是连接状态,连接其中的一个服务
AuthFailed:鉴权失败
ConnectedReadOnly:客户端连接只读的服务器
SaslAuthenticated:SASL认证
Expired:服务器已经过期了该客户端的Session

Zookeeper事件类型

None:无
NodeCreated:节点创建
NodeDeleted:节点删除
NodeDataChanged:节点数据改变
NodeChildrenChanged:子节点改变(添加/删除)

Watcher使用的注意事项

Watcher是一次触发器,假如需要持续监听数据变更,需要在每次获取时设置Watcher
会话过期:当客户端会话过期时,该客户端注册的Watcher会失效
事件丢失:在接收通知和注册监视点之间,可能会丢失事件,但Zookeeper的状态变更和数据变化,都会记录在状态元数据信息和ZK数据节点上,所以能够获取最终一致的ZK信息状态
避免Watcher过多:服务器会对每一个注册Watcher事件的客户端发送通知,通知通过Socket连接的方式发送,当Watcher过多时,会产生一个尖峰的通知

zk的命名服务、配置管理、集群管理分别是什么

分布式协调

大于等于一的情况下,才会有协调,在协调的事务进行分类得到一些名词,语义能够接受就可以

命名服务

通过使用有序节点的特性做到协调命名规则

通过zk的事务ID递增,做到有序行命名规则

通过使用自己点做map映射,做到1:N的命名映射,比如DNS

顺序关系、映射关系

構成管理

構成、メタデータ、ステータスなどのセマンティクスは、ZK のノード 1MB または ZK のノードのディレクトリ構造特性を通じて保存できます。

また、監視メカニズムを通じて、構成変更のグローバル通知機能が満たされます。

クラスタ管理

zkの独占性により、秩序

分散ロック、分散マスター選択、キューロックを満たす

シリアル化されたコールバックディスパッチ

分散スケジューリングなど

2048.kafka ファイルを見つけてください。もちろん、最初のオフセットは 00000000000.kafka です。


KAFKA天生是分布式的,满足AKF的XYZ轴特点,扩展性,可靠性,高性能是没得说

而且,kafka具备自己的特色,比如动态ISR集合,是在强一致性,过半一致性之外的另一个实现手段





---



---

# Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失

broker在故障后的消息丢失

消费者在消费过程中的消息丢失

## ACK机制

ack有3个可选值,分别是1,0,-1。

## ack=0:生产者在生产过程中的消息丢失

简单来说就是,producer发送一次就不再发送了,不管是否发送成功。

## ack=1:broker在故障后的消息丢失

简单来说就是,producer只要收到一个分区副本成功写入的通知就认为推送消息成功了。这里有一个地方需要注意,这个副本必须是leader副本。只有leader副本成功写入了,producer才会认为消息发送成功。

注意,ack的默认值就是1。这个默认值其实就是吞吐量与可靠性的一个折中方案。生产上我们可以根据实际情况进行调整,比如如果你要追求高吞吐量,那么就要放弃可靠性。

## ack=-1:生产侧和存储侧不会丢失数据

简单来说就是,producer只有收到分区内所有副本的成功写入的通知才认为推送消息成功了。

## Offset机制

kafka消费者的三种消费语义

at-most-once:最多一次,可能丢数据

at-least-once:最少一次,可能重复消费数据

exact-once message:精确一次





















---

# Kafka是pull?push?以及优劣势分析



Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。

Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。

一些消息系统比如Scribe和Apache Flume采用了push模式,将消息推送到下游的consumer。

这样做有好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。

消息系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。

最终Kafka还是选取了传统的pull模式。

Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。

Push模式必须在不知道下游consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。

如果为了避免consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。

Pull模式下,consumer就可以根据自己的消费能力去决定这些策略。

Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到达。

为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发



















---

# Kafka中zk的作用是什么

Zookeeper是分布式协调,注意它不是数据库

kafka中使用了zookeeper的分布式锁和分布式配置及统一命名的分布式协调解决方案

在kafka的broker集群中的controller的选择,是通过zk的临时节点争抢获得的

brokerID等如果自增的话也是通过zk的节点version实现的全局唯一

kafka中broker中的状态数据也是存储在zk中,不过这里要注意,zk不是数据库,所以存储的属于元数据

而,新旧版本变化中,就把曾经的offset从zk中迁移出了zk















---

# Kafka中高性能如何保障

首先,性能的最大瓶颈依然是IO,这个是不能逾越的鸿沟

虽然,broker在持久化数据的时候已经最大努力的使用了磁盘的顺序读写

更进一步的性能优化是零拷贝的使用,也就是从磁盘日志到消费者客户端的数据传递,因为kafka是mq,对于msg不具备加工处理,所以得以实现

然后就是大多数分布式系统一样,总要做tradeoff,在速度与可用性/可靠性中挣扎

ACK的0,1,-1级别就是在性能和可靠中权衡















---

# kafka的rebalance机制是什么

## 消费者分区分配策略

Range 范围分区(默认的)

RoundRobin 轮询分区

Sticky策略

## 触发 Rebalance 的时机

Rebalance 的触发条件有3个。

- 组成员个数发生变化。例如有新的 consumer 实例加入该消费组或者离开组。
- 订阅的 Topic 个数发生变化。
- 订阅 Topic 的分区数发生变化。

## Coordinator协调过程

消费者如何发现协调者

消费者如何确定分配策略

如果需要再均衡分配策略的影响















---

# zk的数据模型和节点类型有哪些

## ZooKeeper数据模型

ZooKeeper的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper树中的每个节点被称为—Znode。

和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。但也有不同之处:

Znode兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子Znode。用户对Znode具有增、删、改、查等操作(权限允许的情况下)

Znode具有原子性操作,读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作

Znode存储数据大小有限制。ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,当时常规使用中应该远小于此值

Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。

## 节点类型

Znode有两种,分别为临时节点和永久节点。
节点的类型在创建时即被确定,并且不能改变。
临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。

永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
  
Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10位数字,没有数值的数位用0补充,例如“0000000001”)

在ZooKeeper中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。

1、持久节点(PERSISTENT)

该数据节点别创建后,就会一直存在于ZooKeeper服务器上,直到有删除操作来主动删除该节点。

2、持久顺序节点(PERSISTENT_SEQUENTIAL)

持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。

3、临时节点(EPHEMERAL)

临时节点的生命周期和客户端的回话绑定在一起,如果客户端会话失效,那么这个节点就会被自动地清理掉。

ZooKeeper规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。

4、临时顺序节点(EPHEMERAL_SEQUENTIAL)







---

# Zookeeper watch机制是什么

ZooKeeper是用来协调(同步)分布式进程的服务,提供了一个简单高性能的协调内核,用户可以在此之上构建更多复杂的分布式协调功能。

多个分布式进程通过ZooKeeper提供的API来操作共享的ZooKeeper内存数据对象ZNode来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型,与Java的多线程并发模型一致,他们的线程或进程都是”共享式内存通信“。

Java没有直接提供某种响应式通知接口来监控某个对象状态的变化,只能要么浪费CPU时间毫无响应式的轮询重试,或基于Java提供的某种主动通知(Notif)机制(内置队列)来响应状态变化,但这种机制是需要循环阻塞调用。

而ZooKeeper实现这些分布式进程的状态(ZNode的Data、Children)共享时,基于性能的考虑采用了类似的异步非阻塞的主动通知模式即Watch机制,使得分布式进程之间的“共享状态通信”更加实时高效,其实这也是ZooKeeper的主要任务决定的—协调。Consul虽然也实现了Watch机制,但它是阻塞的长轮询。

## ZooKeeper的Watch特性

1. Watch是一次性的,每次都需要重新注册,并且客户端在会话异常结束时不会收到任何通知,而快速重连接时仍不影响接收通知。
2. Watch的回调执行都是顺序执行的,并且客户端在没有收到关注数据的变化事件通知之前是不会看到最新的数据,另外需要注意不要在Watch回调逻辑中阻塞整个客户端的Watch回调
3. Watch是轻量级的,WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径。ZooKeeper服务端只会通知客户端发生了什么,并不会告诉具体内容。

## Zookeeper状态

Disconnected:客户端是断开连接的状态,不能连接服务集合中的任意一个
SyncConnected:客户端是连接状态,连接其中的一个服务
AuthFailed:鉴权失败
ConnectedReadOnly:客户端连接只读的服务器
SaslAuthenticated:SASL认证
Expired:服务器已经过期了该客户端的Session

## Zookeeper事件类型

None:无
NodeCreated:节点创建
NodeDeleted:节点删除
NodeDataChanged:节点数据改变
NodeChildrenChanged:子节点改变(添加/删除)

## Watcher使用的注意事项

Watcher是一次触发器,假如需要持续监听数据变更,需要在每次获取时设置Watcher
会话过期:当客户端会话过期时,该客户端注册的Watcher会失效
事件丢失:在接收通知和注册监视点之间,可能会丢失事件,但Zookeeper的状态变更和数据变化,都会记录在状态元数据信息和ZK数据节点上,所以能够获取最终一致的ZK信息状态
避免Watcher过多:服务器会对每一个注册Watcher事件的客户端发送通知,通知通过Socket连接的方式发送,当Watcher过多时,会产生一个尖峰的通知





















# zk的命名服务、配置管理、集群管理分别是什么

## 分布式协调

大于等于一的情况下,才会有协调,在协调的事务进行分类得到一些名词,语义能够接受就可以

## 命名服务

通过使用有序节点的特性做到协调命名规则

通过zk的事务ID递增,做到有序行命名规则

通过使用自己点做map映射,做到1:N的命名映射,比如DNS

顺序关系、映射关系

## 配置管理

配置、元数据、状态等语义可以通过ZK的节点1MB存储,或者通过zk的节点目录结构特性存储

并且通过watch机制,满足配置变化的全局通知能力

## 集群管理

通过zk的排他性,有序性

满足分布式锁、分布式选主、队列锁

串行化回调调度

分布式调度等











おすすめ

転載: blog.csdn.net/m0_47987937/article/details/126703320