【面试】2021后端面试题

自己整理了 java相关的面试题
这里面大部分都是摘自其他文章,在此感谢各位面试题的提供者
目前全部存放在了一片文章当中,以后会进行分类

【面试】2021后端面试题_第1张图片

面试专题

Java

基础

什么是JVM、JDK、JRE、JMM

  • JVM
    java虚拟机
    其主要是用来执行java字节码(二进制的形式)的虚拟计算机。运行在操作系统之上的,与硬件没有任何关系。

  • JDK
    Java开发工具包(Java Development Kit),其包含包括 Java 运行环境(Java Runtime Envirnment,简称 JRE),Java 工具(比如 javac、java、javap 等等),以及 Java 基础类库(比如 rt.jar)

  • JRE
    他包含运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。

  • JMM
    Java内存模型,一个抽象的概念,主要在并发编程时用到,具有原子性,有序性,一致性。

字节码

java代码需要编译成字节码文件才能被计算机执行,字节码的开头是CAFFBABE,正好对应Java的图标。

Java有几种数据类型,占多少字节?

Java 语言提供了 8 种基本类型,【面试】2021后端面试题_第2张图片

char可以存储汉字吗?

当然是可以的,char类型中存储的是Unicode编码,Unicode编码中是存在中文的,所以char自然可以存储汉字,但是!仅限于Unicode中存在的汉字。

一个汉字的占两个字节,一个Unicode也是占两个字节 ,char存储汉字完全没有问题。

基本数据类型和引用数据类型有什么区别?

简单来说,所有的非基本数据类型都是引用数据类型,除了基本数据类型对应的引用类型外,类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型都属于引用类型。

主要有以下区别:

  • 存储位置

    • 基本变量类型在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
    • 引用数据类型变量其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址
  • 传递方式

    • 基本数据类型是按值传递
    • 引用数据类型是按引用传递

内存

局部变量 基本类型存放在栈中 引用类型存放在堆中
之所以说 值类型的实例一般分配在 栈上而非 全部分配在栈上的原因是由一下几种情况, 值类型也有可能分配在 堆上。
这些特殊的情况包括数数组的元素、引用类型中的值类型字段、迭代器块中的局部变量、闭包情况下匿名函数( Lamda )中的局部变量。这是由于在这几种情况下的值类型实例如果分配在 线程栈上,有可能会出现线程栈中的方法己经调用结束,但是还会访问这些值的情况。也就是说如果分配在 线程栈上,有可能会随着被调用方法的返回而被清除掉。因此它们也被分配在了托管堆上,以满足在方法返回之后还能够被访问的要求。所以单纯地说“引用类型保存在托管堆上,值类型保存在线程栈上”是不准确的。将这句话一分为二看待,引用类型的确总是分配在托管堆上, 但是值类型并非总是分配在线程栈上有可能分配在堆上。

类变量(静态变量)存在于方法区!!引用类型的局部变量声明在栈,存储在堆

    • 1.存放局部变量
    • 2.不可以被多个线程共享
    • 3.空间连续,速度快
    • 1.存放对象
    • 2.可以被多个线程共享
    • 3.空间不连续,速度慢,但是灵活
  • 方法区
    • 1.存放类的信息:代码、静态变量、字符串常量等等
    • 2.可以被多个线程共享
    • 3.空间不连续,速度慢,但是灵活

总的来说:我们先来记住两条黄金法则:
1.引用类型总是被分配到“堆”上。不论是成员变量还是局部
2.基础类型总是分配到它声明的地方:成员变量在堆内存里,局部变量在栈内存里。

==和equals()有什么区别

我们都知道==操作符用来两个对象的地址是否相同,即是否是指相同一个对象。

equals()比较的两个对象的值是否相同,不管是不是一个对象。

但其实object类下的equals()和==是一样的,我们用的都是被重写之后的。

String、StringBuffer、StringBuilder什么区别?

三者共同之处:

都是final类,不允许被继承,所以string每次改变值都会新建一个对象。

StringBuffer是线程安全,可以不需要额外的同步用于多线程中;

StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了;

StringBuffer与StringBuilder两者共同之处:可以通过append、indert进行字符串的操作。

如何理解接口和抽象类?

  1. 抽象类:
    抽象类是特殊的类,只是不能被实例化;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。

  2. 接口:
    接口是引用类型的,类似于类,和抽象类的相似之处有三点:
    1. 不能实例化;
    2. 包含未实现的方法声明;
    3. 派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员);

另外,接口有如下特性:
接口除了可以包含方法之外,还可以包含属性、索引器、事件,而且这些成员都被定义为公有的。除此之外,不能包含任何其他的成员,例如:常量、域、构造函数、析构函数、静态成员。一个类可以直接继承多个接口,但只能直接继承一个类(包括抽象类)。

  1. 抽象类和接口的区别:

    1. 类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类.而接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”.抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中.
    2. 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;
    3. 一个类一次可以实现若干个接口,但是只能扩展一个父类
    4. 接口可以用于支持回调,而继承并不具备这个特点.
    5. 抽象类不能被密封。
    6. 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的.
    7. (接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。
    8. 抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。
    9. 好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。
      10.尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直 接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.(结合堆栈原理理解)。同时,有心的朋友可以留意到微软在构建一个类时,很多时候用到了对象组合的方法。比如asp.net中,Page类,有Server Request等属性,但其实他们都是某个类的对象。使用Page类的这个对象来调用另外的类的方法和属性,这个是非常基本的一个设计原则。
    10. 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法.
  2. 抽象类和接口的使用:

    1. 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。
    2. 如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。
    3. 如果要设计大的功能单元,则使用抽象类.如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。
    4. 抽象类主要用于关系密切的对象;而接口适合为不相关的类提供通用功能。

大白话:

接口是抽象行为 跨类调用
抽象类 抽象行为和知识
概念不一样,接口是对动作的抽象,抽象类是对根源的抽象。你关注一个事物本质的时候,用抽象类,关注一个操作的时候,用接口。

接口中属性的类型

默认 : public+static+final

如何理解final关键字

final翻译成中文是“不可更改的,最终的”,顾名思义,他的功能就是不能再修改,不能再继承。我们常见的String类就是被final修饰的。
将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
按照Java代码惯例,final变量就是常量,而且通常常量名要大写:

  • final关键字可以用于成员变量、本地变量、方法以及类。
  • final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
  • 不能够对final变量再次赋值。
  • final方法不能被重写。
  • final类不能被继承。
  • 接口中声明的所有变量本身是final的。
  • final和abstract这两个关键字是反相关的,final类就不可能是abstract的。

重写和重载

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

重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。

构造器是否可被重写?

构造器不能被继承,因此不能被重写,但可以被重载。

泛型

泛”就是宽泛,泛指的意思,所谓泛型就是不指定具体类型,而是作为参数传递。

  • 泛型的优点:
    使用泛型类时指明了数据类型,赋给其他类型的值会抛出异常,既不需要向下转型,也没有潜在的风险。
    除了定义泛型类,还可以定义泛型接口和泛型方法,使用泛型方法时不必指明参数类型,编译器会根据传递的参数自动查找出具体的类型。

  • 限制泛型的可用类型:
    通过 extends 关键字可以限制泛型的类型

  • 泛型代码与JVM:
    虚拟机中没有泛型,只有普通类和方法。
    在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。(类型擦除)
    在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。

面向对象的意义

对象(new)是对概念(class)的具象产物

封装,继承,多态

  1. 封装
    • 提高了代码的安全性,使代码的修改变的更加容易,代码以一个个独立的单元存在,高内聚,低耦合。
    • 封装的设计使使整个软件开发复杂度大大降低。我只需要使用别人的类,而不必关心其内部逻辑是如何实现的。我能很容易学会使用别人写好的代码,这就让软件协同开发的难度大大降低。
    • 封装还避免了命名冲突的问题。

封装 方法和方法需要的知识(成员变量)放到一起
对象需要有 行为(方法)和知识(成员变量)

  1. 继承
    继承的主要思想就是将子类的对象作为父类的对象来使用。比如王者荣耀的英雄作为父类,后裔作为子类。后裔有所有英雄共有的属性,同时也有自己独特的技能。

  2. 多态

    • 多态的定义:
      指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
      简单来说,同样调用攻击这个方法,后裔的普攻和亚瑟的普攻是不一样的。

    • 多态的条件:
      要有继承
      要有重写
      父类引用指向子类对象

    • 多态的好处:
      多态对已存在代码具有可替换性。
      多态对代码具有可扩充性。
      它在应用中体现了灵活多样的操作,提高了使用效率。
      多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

  • Java中多态的实现方式:
    接口实现
    继承父类进行方法重写
    同一个类中进行方法重载

stream流

  • 大白话理念 :
    Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
    Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
    这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等
  • 集合接口分为 串行流stream() 和 并行流parallelStream()
  • 主要分为 中间操作和最终操作
    • 中间操作:
      • 过滤类型 : filter(过滤),distinct(去重),limit(限流),skip(跳过)
      • 映射 : map,flatMap
    • 最终操作 :
      • 查找 : allMatch,anyMatch,noneMathch,findFirst,findAny
      • 归约 : reduce
      • 收集 : counting(总数), maxBy(最大值), minBy(最小值), summingInt, summingLong, summingDouble(求和), averageInt, averageLong, averageDouble(平均数), summarizingInt, summarizingLong, summarizingDouble(一次性查询元素个数、总和、最大值、最小值和平均值),joining(字符串拼接), groupingBy(分组), partitioningBy(分区)

lambda和接口

接口的默认方法 :

  • 默认方法允许我们在接口里添加新的方法,而不会破坏实现这个接口的已有类的兼容性,也就是说不会强迫实现接口的类实现默认方法
    默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供一个默认的方法实现,所有这个接口的
    实现类都会通过继承得倒这个方法(如果有需要也可以重写这个方法)
  • 默认方法是可以在接口中写执行体的。主要作用:
    • 1.接口升级,可以避免改变其他实现类。
    • 2.函数拼接
public interface Animal {
    default void fly() {
        System.out.println("birds can fly...");
    }

    default void swim() {
        System.out.println("fishes can swim......");
    }
}

public class Bird implements Animal {
}

public class TestMain {

    public static void main(String[] args) {

        Bird bird = new Bird();
        bird.fly();

        Fish fish = new Fishe();
        fish.swim();
    }
}

接口的静态方法 :

  • 因为静态方法不可以实例化,在接口中也是一样的
  • 所以在接口中定义静态方法的作用就是静态方法的作用:
  • 不需要实例化,直接使用,节省内存空间
  • 注意:接口中静态方法和类中静态方法一样,只能通过接口.静态方法名的方式调用
public interface AnimalFactory {

    static Animal create(Supplier<Animal> supplier) {
        return supplier.get();
    }
}

public class TestAnimalFactory {

    public static void main(String[] args) {
        // 生产一只鸟
        Animal bird = AnimalFactory.create(Bird::new);
        bird.fly();
     // 生产一条鱼
        Animal fish = AnimalFactory.create(Fishe::new);
        fish.swim();
    }
}

函数式接口

  • 每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
  • JDK 1.8 之前已有的函数式接口:
    • java.lang.Runnable
    • java.util.concurrent.Callable
    • java.security.PrivilegedAction
    • java.util.Comparator
    • java.io.FileFilter
    • java.nio.file.PathMatcher
    • java.lang.reflect.InvocationHandler
    • java.beans.PropertyChangeListener
    • java.awt.event.ActionListener
    • javax.swing.event.ChangeListener
  • JDK 1.8 新增加的函数接口:
    • java.util.function
@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

Lambda 表达式

  • Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
  • ambda表达式的重要特征:
    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

执行结果:
$ javac Java8Tester.java 
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

Lambda 作用域 :

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

list

扩容机制

  • 初始化 new ArrayList(),在jdk1.6之前默认容量是10.在容量超过当前容量时进行扩容,扩容大小为当前的1.5倍+1
  • jdk1.7之后默认容量是0,在第一次add时扩容为10,add超过当前容量时再次扩容,扩容大小为当前容量的1.5倍
  • 当初始化时指定容量时new ArrayList(20),在初始化就生成指定容量的list,所以并未扩容
  • ArrayList底层是数组,每次扩容是生成新的数组并将之前list中的数组赋值到当前数组中

常用List集合

  • ArrayList
    • 基于数组实现的非线程安全的集合。查询任意位置元素都很快,插入或删除头部元素比linkedList慢的多。
    • Jdk1.7之前ArrayList默认大小是10 扩容大小为当前的1.5倍+1,JDK1.7之后是0,第一次add时扩容为10,每次约按1.5倍扩容。
    • 当初始化时指定容量时new ArrayList(20),在初始化就生成指定容量的list,所以并未扩容
    • ArrayList底层是数组,每次扩容是生成新的数组并将之前list中的数组赋值到当前数组中
  • LinkedList
    • 基于链表实现的非线程安全的集合。查询中间元素比ArrayList慢的多,其他差距不大。插入或删除中间元素比ArrayList慢的多。
  • Vector
    • Vector是线程安全的
    • Vector 每次扩容都以当前数组大小的 2 倍去扩容

map

HashMap
  • HashMap是数组和链表组成的,链表过长会将链表转成红黑树
  • 默认大小为16,当hashmap中的元素个数超过数组大小*loadFactor(默认值为0.75)时就会把数组的大小扩展为原来的两倍大小,然后重新计算每个元素在数组中的位置。
  • HashMap不是线程安全的。线程安全的有HashTable、ConcurrentHashMap、SynchronizedMap,性能最好的是ConcurrentHashMap。
  • 无序的
LinkedHashMap
  • 有序的
HashTable
  • HashTable和HashMap的实现原理几乎一样
  • 差别无非是
  • HashTable不允许key和value为null
  • HashTable是线程安全的
  • HashTable线程安全的策略实现代价很大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个HashTable加锁。
  • 多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。
ConcurrentHashMap
  • 在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现,也就是只对数据所在的分段进行加锁,ConcurrentHashMap默认是16个分段
  • JDK1.8采用CAS+Synchronized保证线程安全,数组+链表+红黑树的结构。调整为对每个数组元素加锁(Node)
HashMap为什么采用数组和链表来存储元素呢?
  • 数组的特点:查询效率高,插入,删除效率低。
  • 链表的特点:查询效率低,插入删除效率高。
  • 在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。

什么情况下 i+1
int i = Integer.MAX_VALUE;
System.out.println(i+1);//结果-2147483648

因为在java中整型值是有范围的,它的最大值为2^31
-1,也就是2147483647,最小值是-2^31-1,也就是-2147483648。
当对最大值进行+1时,就变成2147483648(越界了),就溢出了,那么此值为多少呢?结果是-2147483648,即为Integer.MIN_VALUE,所以就有了Integer.MAX_VALUE + 1=Integer.MIN_VALUE

Exception和Error的区别

  • Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。
  • Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常状态,不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类。
  • Exception又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分。前面我们介绍的不可查的Error,是Throwable不是Exception。

StringBuilder和 StringBuffer 区别

  • 线程安全:
    • StringBuffer:线程安全。因为 StringBuffer 的所有公开方法都是 synchronized 修饰的.
    • StringBuilder:线程不安全 。StringBuilder 并没有 synchronized 修饰。
  • 缓冲区: StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。
  • 性能 : 既然 StringBuffer 是线程安全的,它的所有公开方法都是同步的,StringBuilder 是没有对方法加锁同步的,所以毫无疑问,StringBuilder 的性能要远大于 StringBuffer。

线程的状态有哪些?

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

  1. 阻塞(BLOCKED):表示线程阻塞于锁。

  2. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

  3. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

  4. 终止(TERMINATED):表示该线程已经执行完毕。


springboot 框架

springboot 1.x 和 2.x 的区别

  • 2.x 至少需要 JDK 8 的支持
  • 2.x 增加响应式 Spring 编程支持
  • 2.x 增加 HTTP/2 支持
  • 部分配置有改动

何为IOC

IOC 被称为控制反转 或 依赖注入

白话: spring将配置为bean的对象放入ioc容器中,并且需要的地方将对象进行注入

  • 控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的(new Object()),而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
  • 依赖注入是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

Spring Boot @RestController和@controller有什么区别?

  • @RestController注解相当于@ResponseBody + @Controller合在一起的作用,返回的是json或文本数据。
  • @Controller 可以配合视图解析器进行路由控制

@Transactional注解为什么要加rollbackFor = Exception.class

  • 当只加@Transactional注解时,那么业务代码抛RuntimeException和Error时,事务管理器会识别到这类异常来进行回滚,但是非RuntimeException的异常抛出时,事务管理器是不会回滚事务的。如果加了属性rollbackFor = Exception.class,那么事务管理器会捕获到Exception及子类的所有异常和Error来回滚事务。

Spring Boot 的配置文件有哪几种格式?它们有什么区别?

  • .properties 和 .yml,它们的区别主要是书写格式不同。

Spring Boot 自动配置原理是什么?

  • 注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。

Spring Boot 有哪几种读取配置的方式?

  • Spring Boot 可以通过 @PropertySource,@Value,@Environment, @ConfigurationProperties 来绑定变量

过滤器(Filter)和拦截器(Interceptor)的区别?

  • 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器
  • 拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预他,通过验证的少点,顺便干点别的东西)。
  • 请求进入顺序 服务器->filter-servlet-interceptor-Controller-interceptor-servlet-filter, 所以filter在interceptor之前执行
  • 拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用(比如对静态文件的请求)。
  • 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问

Mybatis 框架

#{}和${}的区别是什么?

  • #{}是预编译处理,${}是字符串替换。
  • Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
  • Mybatis在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值。
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。

当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

  • 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
  • 通过来映射字段名和实体类属性名的一一对应的关系。

模糊查询like语句该怎么写?

  • like concat(’%’, #{name}, ‘%’) 有效防止SQL注入

如何执行批量插入?

  • 使用

如何获取自动生成的(主)键值?

在mapper中如何传递多个参数?

  • 在映射文件中使用#{0},#{1}代表传递进来的第几个参数
  • 使用@param注解:来命名参数
  • 使用Map集合作为参数来装载

接口绑定有几种实现方式,分别是怎么实现的?

  • 接口绑定有两种实现方式:
    • 通过注解绑定,在接口的方法上面加上@Select@Update等注解里面包含Sql语句来绑定
    • 通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名.

Mysql

mysql优化

  • 字段类型选择尽可能小(占用存储空间少)、尽可能定长(占用存储空间固定)、尽可能使用整数。
  • 索引 首先应考虑在 where 及 order by 涉及的列上建立索引。
  • 少用(不用)多表操作(子查询,联合查询),而是将复杂的SQL拆分多次执行。如果查询很原子(很小),会增加查询缓存的利用率。
  • 分页查询优化 在子查询中只查询数据的id并分页 然后根据子查询的id在进行查询完整数据
  • 使用join 代替 子查询
  • 多表联查优化:
    • on的字段建外键、索引
    • 条件中尽量能够过滤一些行将驱动表变得小一点,用小表去驱动大表
    • 先将数据量最大表的满足条件的ID查询出来,创建临时表,再用这个临时表去关联这个表本身以及其他表
select p.*,b.supplier,t.type,c.org from
        (select po.id from porder po where po.mark = 0 order by po.id desc limit 800000,500) a
        inner join porder p on a.id = p.id and p.mark = 0
        left JOIN brand b on p.supplier = b.supplier_id and b.mark = 0
        left JOIN purchase c on p.org = c.id and c.mark = 0
        left JOIN type t on c.category = t.type_id and t.mark = 0;
  • 对查询单条是 添加limit 1
  • 不要在列上进行运算
  • 不使用NOT IN和<>操作 NOT IN可以NOT EXISTS代替,id<>3则可使用id>3 or id<3来代替。
  • 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描
    select id from t where num=10 or Name = 'admin'
    #可以这样查询:
    select id from t where num = 10
    union all
    select id from t where Name = 'admin'
  • 如果查询的两个表大小相当,那么用in和exists差别不大;如果两个表中一个较小一个较大,则子查询表大的用exists,子查询表小的用in;
  • 分页时每页条数过大时 要先分页再JOIN
  • 不要使用 select *
  • 如何避免死锁
    • 注意程序的逻辑
        根本的原因是程序逻辑的顺序,最常见的是交差更新
    
        Transaction 1: 更新表A -> 更新表B
        
        Transaction 2: 更新表B -> 更新表A
        
        这类问题要从程序上避免,所有的更新需要按照一定的顺序
    
    • 保持事务的轻量
      • 提高运行的速度
      • 尽量快提交事务,减少持有锁的时间
    • 设置锁等待超时参数,我们可以通过 innodb_lock_wait_timeout 设置合理的等待超时阈值,特别是在一些高并发的业务中,我们可以尽量将该值设置得小一些,避免大量事务等待,占用系统资源,造成严重的性能开销。

mysql面试题:

行转列 (GROUP_CONCAT)

正常情况:

SELECT
	account,
	nick_name 
FROM
	account

执行结果:

account nick_name
sysAdmin 管理员
chichi1 痴痴

行转列:

SELECT
	GROUP_CONCAT( account ) account,
	GROUP_CONCAT( nick_name ) nick_name 
FROM
	account

执行结果:

account nick_name
sysAdmin,chichi1 管理员,痴痴

使用链表时,不使用连接条件会发生什么事情

  • LEFT JOIN 或 RIGHT JOIN 或 FULL JOIN 会报语法错误
  • INNER JOIN 或 CROSS JOIN 返回被连接的两个表的笛卡尔积,返回结果的行数等于两个表行数的乘积

事物的四个特性(ACID)

  • 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。

eg:拿转账来说,假设用户A和用户B两者的钱加起来一共是20000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是20000,这就是事务的一致性。

  • 一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。

  • 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

  • 持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。

eg: 例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

如何跨数据库访问

同数据库实例可以直接使用库名调用
非同数据库可以使用FEDERATED引擎进行映射表

MyISAM与InnoDB 的区别

  1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;

  2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;

  3. InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大

  4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件);

  5. Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引领域的查询效率上MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了

  6. MyISAM表格可以被压缩后进行查询操作

  7. InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁

  8. InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有

  9. Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI

    • innodb:frm是表定义文件,ibd是数据文件

    • Myisam:frm是表定义文件,myd是数据文件,myi是索引文件

MyISAM与InnoDB如何选择:

  1. 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;

  2. 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。

  3. 系统奔溃后,MyISAM恢复起来更困难,能否接受;

  4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。


redis

常用数据类型

  • String(字符串):Redis最基本的数据类型,一个键对应一个值,一个键值最大存储512MB
  • Hash(哈希):hash是一个键值对的集合,是一个String类型的field和value的映射表,适合用于存储对象.可用作简易的消息队列
  • List(列表):是redis的简单的字符串列表,按插入顺序排序
  • Set(集合):是String字符串类型的无序集合,也不可重复
  • ZSet(sorted set 有序集合)是String类型的有序集合,也不可重复。有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。可以做排行榜

redis为什么这么快

  • 采用了多路复用io阻塞机制
  • 数据结构简单,操作节省时间
  • 运行在内存中,自然速度快

Redis应用场景,能做什么

  1. 数据缓存(最常用)
  2. 会话session管理器(最常用)
  3. 消息队列(支付)
  4. 活动排行榜或计数
  5. 发布,订阅消息(消息通知)
  6. 商品列表,评论列表

缓存穿透

  • 描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大
  • 解决方案:
    • 接口层增加校验,如使用布隆过滤器封装全部id ,查询时 先去redis查询,如果没有,再去查看布隆过滤器中是否存在. 如果不存在直接返回 存在再到数据库中进行查询;
    • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿

  • 描述:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
  • 解决方案
    • 设置热点数据永远不过期
    • 加互斥锁

缓存雪崩

  • 描述:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
  • 解决方案
    • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
    • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
    • 设置热点数据永远不过期。

布隆过滤器

Memcache与Redis的区别都有哪些?

  • 存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据
  • 数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储
  • redis的速度比memcached快很多
    -Redis支持数据的备份,即master-slave模式的数据备份。

单线程的redis为什么这么快

  • 纯内存操作
  • 单线程操作,避免了频繁的上下文切换
  • 采用了非阻塞I/O多路复用机制

如果redis没有设置expire,他是否默认永不过期?

  • Redis无论有没有设置expire,他都会遵循redis的配置好的删除机制,在配置文件里设置:
    redis最大内存不足"时,数据清除策略,默认为"volatile-lru"。
    • volatile-lru ->对"过期集合"中的数据采取LRU(近期最少使用)算法.如果对key使用"expire"指令指定了过期时间,那么此key将会被添加到"过期集合"中。将已经过期/LRU的数据优先移除.如果"过期集合"中全部移除仍不能满足内存需求,将OOM.
    • allkeys-lru ->对所有的数据,采用LRU算法
    • volatile-random ->对"过期集合"中的数据采取"随即选取"算法,并移除选中的K-V,直到"内存足够"为止. 如果如果"过期集合"中全部移除全部移除仍不能满足,将OOM
    • allkeys-random ->对所有的数据,采取"随机选取"算法,并移除选中的K-V,直到"内存足够"为止
    • volatile-ttl ->对"过期集合"中的数据采取TTL算法(最小存活时间),移除即将过期的数据.
    • noeviction ->不做任何干扰操作,直接返回OOM异常

Redis 部署方式

  • 单机
  • 哨兵
  • 集群

分布式锁

  • 加锁 : 使用SETNX, 只在键 key 不存在的情况下, 将键 key 的值设置为 value 。
    若键 key 已经存在, 则 SETNX 命令不做任何动作。
    SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
  • 解锁 : 使用lua脚本 避免死锁

Redis的持久化

  • Redis持久有两种方式:快照(RDB),仅附加文件(AOF)

Redis 发布订阅

  • Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
  • Redis 客户端可以订阅任意数量的频道。

linux

常用命令

  • ls : 展示文件夹内内容
  • cd : 切换到目录
  • tree : 显示树形的层级目录结构,非原生命令,需要安装tree
  • cp : 复制文件
  • rm : 删除文件
  • mv : 移动文件
  • pwd : 显示当前所在实际路径
  • tar : 压缩解压
  • mkdir : 创建目录
  • rmdir : 删除目录 , -p 递归删除目录
  • ps : 显示运行的进程信息
  • kill : 终止进程 , -9 强制中断一个进程的进行
  • crontab : 启动linux定时任务的服务
  • free : 显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer
  • top : Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器
  • chmod : 配置 文件权限 ,-R:进行递归的持续更改,即连同子目录下的所有文件都会更改
  • useradd : 建立用户账号
  • vi/vim : 使用vi编辑器
  • cat : 连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。
  • tail : 将文件写到标准输出, -f 可以方便的查阅正在改变的日志文件
  • ifconfig : 用来查看和配置网络设备
  • whereis : 只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)
  • grep : 该命令常用于分析一行的信息,若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工等等,比如可以加在ps, tail, cat后面

查看端口

  • netstat -tunlp | grep 8888
netstat命令各个参数说明如下:
-t : 指明显示TCP端口 
-a : 显示所有的套接字
-u : 指明显示UDP端口 
-l : 仅显示监听套接字(所谓套接字就是使应用程序能够读写与收发通讯协议(protocol)与资料的程序) 
-p : 显示进程标识符和程序名称,每一个套接字/端口都属于一个程序。 
-n : 不进行DNS轮询,显示IP(可以加速操作) 
  • lsof -i:80 : 查看进程占用哪些文件的
  • fuser 22/tcp -v : fuser命令和lsof正好相反,是查看某个文件被哪个进程占用的。

RocketMq

使用场景:

  • 异步处理:场景说明:用户注册后,需要发注册邮件和注册短信。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略
  • 应用解耦:场景说明:用户下单后,订单系统需要通知库存系统。订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
    库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
  • 流量削锋:应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。a、可以控制活动的人数.b、可以缓解短时间内高流量压垮应用.例如 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
    秒杀业务根据消息队列中的请求信息,再做后续处理
  • 日志处理:
    • 日志采集客户端,负责日志数据采集,定时写受写入mq队列
    • mq消息队列,负责日志数据的接收,存储和转发
    • 日志处理应用:订阅并消费mq队列中的日志数据
  • 消息通讯:消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等.以上实际是消息队列的两种消息模式,点对点或发布订阅模式。

RocketMQ如何保证消息不丢失:

  • 从Producer分析:如何确保消息正确的发送到了Broker?

    • 采取send()同步发消息,发送结果是同步感知的。
    • 发送失败后可以重试,设置重试次数。默认3次。
  • 从Broker分析:如果确保接收到的消息不会丢失?

    • 修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。
    • 集群部署,主从模式,高可用。
  • 从Cunmser分析:如何确保拉取到的消息被成功消费?

    • 完全消费正常后在进行手动ack确认

消息重复:

影响消息正常发送和消费的重要原因是网络的不确定性。

引起重复消费的原因

ACK

正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从queue中剔除
当ack因为网络原因无法发送到broker,broker会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer

消费模式

在CLUSTERING模式下,消息在broker中会保证相同group的consumer消费一次,但是针对不同group的consumer会推送多次

解决方案

数据库表

保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现
利用一张日志表来记录已经处理成功的消息的ID,如果新到的消息ID已经在日志表中,那么就不再处理这条消息。

Map

单机时可以使用map ConcurrentHashMap -> putIfAbsent guava cache

Redis

分布式锁搞起来。

多个mq如何选型?

MQ 描述
RabbitMQ erlang开发,对消息堆积的支持并不好,当大量消息积压的时候,会导致 RabbitMQ 的性能急剧下降。每秒钟可以处理几万到十几万条消息。
RocketMQ java开发,面向互联网集群化功能丰富,对在线业务的响应时延做了很多的优化,大多数情况下可以做到毫秒级的响应,每秒钟大概能处理几十万条消息。
Kafka Scala开发,面向日志功能丰富,性能最高。当你的业务场景中,每秒钟消息数量没有那么多的时候,Kafka 的时延反而会比较高。所以,Kafka 不太适合在线业务场景。
ActiveMQ java开发,简单,稳定,性能不如前面三个。小型系统用也ok,但是不推荐。推荐用互联网主流的

为什么要使用MQ?

因为项目比较大,做了分布式系统,所有远程服务调用请求都是同步执行经常出问题,所以引入了mq

作用 描述
解耦 系统耦合度降低,没有强依赖关系
异步 不需要同步执行的远程调用可以有效提高响应时间
削峰 请求达到峰值后,后端service还可以保持固定消费速率消费,不会被压垮

RocketMQ由哪些角色组成,每个角色作用和特点是什么?

角色 作用
Nameserver 注册中心,Broker会向所有的NameServer上注册自己的信息无状态,动态列表;这也是和zookeeper的重要区别之一。zookeeper是有状态的。
Producer 消息生产者,负责发消息到Broker。
Broker 就是MQ本身,负责收发消息、持久化消息等。
Consumer 消息消费者,负责从Broker上拉取消息进行消费,消费完进行ack。

RocketMQ Broker中的消息被消费后会立即删除吗?

不会,每条消息都会持久化到CommitLog中,每个Consumer连接到Broker后会维持消费进度信息,当有消息消费后只是当前Consumer的消费进度(CommitLog的offset)更新了。

追问:那么消息会堆积吗?什么时候清理过期消息?
4.6版本默认48小时后会删除不再使用的CommitLog文件

检查这个文件最后访问时间
判断是否大于过期时间
指定时间删除,默认凌晨4点

RocketMQ消费模式有几种?

消费模型由Consumer决定,消费维度为Topic。

集群消费

1.一条消息只会被同Group中的一个Consumer消费

2.多个Group同时消费一个Topic时,每个Group都会有一个Consumer消费到数据

广播消费

消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。即即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。

消费消息是push还是pull?

RocketMQ没有真正意义的push,都是pull,虽然有push类,但实际底层实现采用的是长轮询机制,即拉取方式

broker端属性 longPollingEnable 标记是否开启长轮询。默认开启

追问:为什么要主动拉取消息而不使用事件监听方式?

事件驱动方式是建立好长连接,由事件(发送数据)的方式来实时推送。
如果broker主动推送消息的话有可能push速度快,消费速度慢的情况,那么就会造成消息在consumer端堆积过多,同时又不能被其他consumer消费的情况。而pull的方式可以根据当前自身情况来pull,不会造成过多的压力而造成瓶颈。所以采取了pull的方式。


ElasticSearch

如何保证数据一致性,实时性,防止数据脏读?

一个index是由若干个segment组成,随着每个segment的不断增长,我们索引一条数据后可能要经过分钟级别的延迟才能被搜索,为什么有种这么大的延迟,这里面的瓶颈点主要在磁盘。

持久化一个segment需要fsync操作用来确保segment能够物理的被写入磁盘以真正的避免数据丢失,但是fsync操作比较耗时,所以它不能在每索引一条数据后就执行一次,如果那样索引和搜索的延迟都会非常之大。

所以这里需要一个更轻量级的处理方式,从而保证搜索的延迟更小。这就需要用到上面提到的FileSystem Cache,所以在es中新增的document会被收集到indexing buffer区后被重写成一个segment然后直接写入filesystem cache中,这个操作是非常轻量级的,相对耗时较少,之后经过一定的间隔或外部触发后才会被flush到磁盘上,这个操作非常耗时。但只要sengment文件被写入cache后,这个sengment就可以打开和查询,从而确保在短时间内就可以搜到,而不用执行一个full commit也就是fsync操作,这是一个非常轻量级的处理方式而且是可以高频次的被执行,而不会破坏es的性能。

在elasticsearch里面,这个轻量级的写入和打开一个cache中的segment的操作叫做refresh,默认情况下,es集群中的每个shard会每隔1秒自动refresh一次,这就是我们为什么说es是近实时的搜索引擎而不是实时的,也就是说给索引插入一条数据后,我们需要等待1秒才能被搜到这条数据,这是es对写入和查询一个平衡的设置方式,这样设置既提升了es的索引写入效率同时也使得es能够近实时检索数据。

refresh操作相比commit操作是非常轻量级的但是它仍然会耗费一定的性能,所以不建议在每插入一条数据后就执行一次refresh命令,es默认的1秒的延迟对于大多数场景基本都可以接受。

简洁回答:当我们把一条数据写入到Elasticsearch中后,它并不能马上被用于搜索。新增的索引必须写入到Segment后才能被搜索到,因此我们把数据写入到内存缓冲区之后并不能被搜索到。新增了一条记录时,Elasticsearch会把数据写到translog和in-memory buffer(内存缓存区)中,如果希望该文档能立刻被搜索,需要手动调用refresh操作。在Elasticsearch中,默认情况下_refresh操作设置为每秒执行一次。 在此操作期间,内存中缓冲区的内容将复制到内存中新创建的Segment中,操作如下

#手动刷新my_logs索引。
POST /my_logs/_refresh

主从搭建

#每个节点都要进行设置
#当前节点名称
node.name: es-node1
#是否有资格成为主节点
node.master: true
#是否存储数据
node.data: true
#写入候选节点 包括当前节点,在开启服务后可以被选为主节点
discovery.seed_hosts: ["10.100.0.7", "10.100.0.8"]
#通过此配置选举主节点
cluster.initial_master_nodes: ["es-node1","es-node2"]

详细描述一下ES更新和删除文档的过程?

删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更。

磁盘上的每个段都有一个相应的 .del 文件。当删除请求发送后,文档并没有真的被删除,而是在 .del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在 .del 文件中被标记为删除的文档将不会被写入新段。

在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在 .del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。

结构

  • 索引(index) 一个索引相当于一个数据库
  • 映射(mapping) 定义每个字段的类型,每个mapping相当于一个表
  • 文档(document) 一个document 相当于一行数据
  • 集群(cluster) 集群有一个或多个节点组成
  • 节点(node) 集群的节点,一台机器或一个进程
  • 分片和副本(shard) 单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。

shiro

认证流程

  1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
  2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
  3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
  5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。

授权流程

  1. 首先调用 Subject.isPermitted/hasRole接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
  2. Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
  3. 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
  4. Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted/hasRole 会返回 true,否则返回 false 表示授权失败。继承 AuthorizingRealm 而不是实现 Realm 接口;
  5. 推荐使用 AuthorizingRealm,因为: AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):表示获取身份验证信息;AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):表示根据用户身份获取授权信息。这种方式的好处是当只需要身份验证时只需要获取身份验证信息而不需要获取授权信息。

常用api

  • subject.login(token) //登录
  • subject.logout();//注销
  • subject.getPrincipal() //获取当前登录用户信息
  • subject.isPermitted(string) //判断已登陆用户是否具有某权限 返回boolean
  • subject.checkPermission(string) //判断已登陆用户是否具有某权限 它在判断为false的情况下会抛出UnauthorizedException异常
  • subject.hasRole(“admin”) //判断已登陆用户是否具有某角色 返回boolean
  • subject.checkRole(“admin”) //判断已登陆用户是否具有某角色 它在判断为false的情况下会抛出UnauthorizedException异常

springcloud

spring cloud 五大组件

  1. 注册中心 可选组件:Eureka,zookeeper,consul,nacos
  2. 服务调用 可选组件:ribbon,loadBalancer,feign,openfeign
  3. 断路器 可选组件:hystrix,resilience4j,sentinel
  4. 网关 可选组件:zuul,Gateway
  5. 服务配置 可选组件:config,nacos

讲解分布式应用核心CAP知识

  • CAP定理: 指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得

    • 一致性(C):所有节点都可以访问到最新的数据
    • 可用性(A):每个请求都是可以得到响应的,不管请求是成功还是失败
    • 分区容错性(P):除了全部整体网络故障,其他故障都不能导致整个系统不可用
  • CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡

【面试】2021后端面试题_第3张图片

常见注册中心:zk、eureka、nacos 怎么选

【面试】2021后端面试题_第4张图片

分布式CAP的权衡结果 BASE理论

【面试】2021后端面试题_第5张图片

高并发下的微服务存在的问题

【面试】2021后端面试题_第6张图片

高并发下的微服务容错方案

【面试】2021后端面试题_第7张图片
【面试】2021后端面试题_第8张图片
【面试】2021后端面试题_第9张图片

Nacos

Nacos概念

  • 地域(Region):物理的数据中心,资源创建成功后不能更换
  • 可用区(Available Zone):同一地域内,电力和网络互相独立的物理区域。同一可用区内,实例的网络延迟较低。
  • 接入点(Endpoint):地域的某个服务的入口域名
  • 命名空间(Namespace):用于进行租户粒度隔离。不同的命名空间下,可以存在相同的Group或Data ID的配置。
  • 配置(Configuration):从代码中分离出来独立管理的变量、需要变更的参数等
  • 配置管理(Configuration Management):系统配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动。
  • 配置项(Configuration Item):一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在。
  • 配置集(Configuration Set):一组相关或者不相关的配置项的集合。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。
  • 配置集ID(Data ID):某个配置集的ID,是组织划分配置的维度之一,通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识,通常采用类 Java包的命名规则保证全局唯一性(此命名规则非强制)。
  • 配置分组(Group):一组配置集,是组织配置的维度之一,通过一个有意义的字符串对配置集进行分组,从而区分 Data ID 相同的配置集。创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用DEFAULT_GROUP。
  • 配置快照(Configuration Snapshot):Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。
  • 服务(Service):通过预定义接口网络访问的提供给客户端的软件功能。
  • 服务名(Service Name):服务提供的标识,通过该标识可以唯一确定其指代的服务。
  • 服务注册中心(Service Registry):存储服务实例和服务负载均衡策略的数据库。
  • 服务元数据(Service Metadata):服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据
  • 服务提供方(Service Provider):是指提供可复用和可调用服务的应用方
  • 服务消费方(Service Consumer):是指会发起对某个服务调用的应用方
  • 服务发现(Service Discovery):在计算机网络上,对服务下的实例的地址和元数据进行探测,并以预先定义的接口提供给客户端进行查询。
  • 服务分组(Service Group):不同的服务可以归类到同一分组。
  • 名字服务(Naming Service):提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务
  • 配置服务(Configuration Service):在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。
  • 元数据(Metadata):Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。
  • 应用(Application):用于标识服务提供方的服务的属性。
  • 虚拟集群(Virtual Cluster):同一个服务下的所有服务实例组成一个默认集群,集群可以被进一步按需划分,划分的单位可以是虚拟集群。
  • 实例(Instance):提供一个或多个服务的具有可访问网络地址(IP:Port)的进程。
  • 权重(Weight):实例级别的配置,权重为浮点数,权重越大,分配给该实例的流量越大。
  • 健康检查(Health Check):以指定方式检查服务下挂载的实例的健康度,从而确认该实例是否能够提供服务。根据检查结果,实例会被判断是否健康,对服务发起解析请求时,不健康的实例不会返回给客户端。
  • 健康保护阈值(Protect Threshold):为防止因过多实例不健康导致流量全部流向健康的实例,继而造成流量压力把健康的实例压垮并形成雪崩效应,应将健康保护阈值定义未一个0~1之间的浮点数,当域名健康实例占总服务实例的比例小于该值时,无论实例是否健康,都会将这个实例返回给客户端,这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例能够正常工作。

与eureka区别

nacos :

  • 自带配置中心
  • 属于外部应用,侵入性小
  • 通知遵循CP原则(一致性+分离容忍) 和AP原则(可用性+分离容忍)
  • 支持Dubbo 、SpringCloud、K8S集成
  • 访问协议:HTTP/动态DNS/UDP

eureka:

  • 直接集成到应用中,依赖于应用自身完成服务的注册与发现,
  • 遵循AP(可用性+分离容忍)原则,有较强的可用性,服务注册快,但牺牲了一定的一致性。
  • 只支持SpringCloud集成
  • 访问协议:HTTP

常用注解

  • @EnableDiscoveryClient 注解启用发现客户端。
  • @NacosValue 注解设置属性值
  • @RefreshScope 刷新配置

是否支持自动更新配置

支持

  1. Nacos服务端创建一个配置后,客户端可对此配置信息进行监听;
  2. 客户端通过定时任务每间隔10ms来检查配置信息是否变更;
  3. 服务端配置信息发生变更,客户端将会获取到变更的数据,并将新的配置数据更新到CacheData中,并计算CacheData的新的md5属性值;
  4. 比较CacheData的新的md5值是否和所持有的listeners的md5值一致,不一致,则回调listener的receiveConfigInfo方法并更新listenerWrap的md5值;
  5. 其中,出于对服务端故障的考虑,客户端会将最新数据获取后会保存在本地的snapshot文件中,在此之后会优先从本地文件中获取配置信息。

@RefreshScope

@RefreshScope 添加到类上,就是说明在项目运行过程中,如果远程配置文件有做修改,修改的信息在@RefreshScope添加的类中有使用,就会将原有的实例注销,在重新创建一个新的实例运行。这样就能保证在项目不重启的情况下读取到最新的修改信息。如果修改的配置信息在没有添加@RefreshScope的类中使用,这个类的实例就不会注销,所以不管怎么修改,没有加@RefreshScope的类请求结果一直都会是项目运行时加载的旧数据。

集群部署

需要将每个nacos server 实例配置mysql(conf目录下的nacos-mysql.sql文件)
需要将每个nacos server 配置一份集群节点信息,配置文件在conf目录下的cluster.conf.example文件,我们进行重命名成cluster.conf。 然后编辑cluster.conf文件,增加3个节点的信息,格式为IP:PORT,三个目录都一致即可。
启动的话直接到bin目录下,执行./startup.sh就可以了,默认就是集群模式,不需要加任何参数

Nacos Server 的配置数据是存在哪里

Nacos Server 的数据源是用 Derby 或 MySQL 完全是由其运行模式决定的:
standalone 的话仅会使用 Derby,即使在 application.properties 里边配置 MySQL 也照样无视;
cluster 模式会自动使用 MySQL,这时候如果没有 MySQL 的配置,是会报错的。

架构

【面试】2021后端面试题_第10张图片

nacos 原理

【面试】2021后端面试题_第11张图片
Nacos注册中心分为server与client,server采用Java编写,为client提供注册发现服务与配置服务。而client可以用多语言实现,client与微服务嵌套在一起,nacos提供sdk和openApi,如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑
【面试】2021后端面试题_第12张图片

Nacos服务领域模型主要分为命名空间、集群、服务。在下图的分级存储模型可以看到,在服务级别,保存了健康检查开关、元数据、路由机制、保护阈值等设置,而集群保存了健康检查模式、元数据、同步机制等数据,实例保存了该实例的ip、端口、权重、健康检查状态、下线状态、元数据、响应时间
【面试】2021后端面试题_第13张图片

注册中心原理

【面试】2021后端面试题_第14张图片
服务注册方法:以Java nacos client v1.0.1 为例子,服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。

配置中心原理

【面试】2021后端面试题_第15张图片

Nacos特性

Nacos主要提供以下四大功能:

  1. 服务发现与服务健康检查
    Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。

  2. 动态配置管理
    动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos消除了在更新配置时重新部署应用程序,这使配置的更改更加高效和灵活。

  3. 动态DNS服务
    Nacos提供基于DNS协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以域名的方式暴露端点,让三方应用方便地查阅及发现。

  4. 服务和元数据管理
    Nacos能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。


OpenFeign

概念

Feign是声明性Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用HttpMessageConvertersSpring Web中默认使用的注释。Spring Cloud集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用Feign时提供负载平衡的http客户端。


使编写Java Http客户端更加容易

使用 RestTemplate+Ribbon 时,利用 RestTemplate 对http 请求的封装处理,形成一套模板化的调用方法,但是在实际中,由于对服务的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由他来帮助我们定义和实现服务接口的定义。在Feign的实现下我们只需要创建一个接口并使用注解来配置它(以前是Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可)。自动封装服务调用客户端的开发量。

Feign集成了Ribbon

利用Ribbon维护了Payment的服务列表信息,并且实现了轮询实现客户端的负载均衡。而与Ribbon不同的是,feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现服务调用。

使用方式

  • 启动类上标注 @EnableFeignClients
  • feign接口上标注 @FeignClient(value = “system-server”, path = “/system”)
    value是服务名
    path 当前FeignClient的统一前缀
  • 通过 @GetMapping @PostMapping等 指定接口

Gateway

什么是网关

【面试】2021后端面试题_第16张图片

主流的网关

在这里插入图片描述
nginx+lua:是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求

springcloud gateway: Spring公司专门开发的网关,替代zuul
AlibabaCloud全家桶还没对应的网关,我们就用SpringCloud官方推荐的Gateway

什么是 SpringCloud Gateway

【面试】2021后端面试题_第17张图片

交互流程

【面试】2021后端面试题_第18张图片

路由

【面试】2021后端面试题_第19张图片

什么是Gateway路由断言

【面试】2021后端面试题_第20张图片

什么是网关的过滤器

【面试】2021后端面试题_第21张图片
【面试】2021后端面试题_第22张图片


Sentinel

什么是Sentinel

  • 阿里巴巴开源的分布式系统流控工具
  • 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
  • 丰富的应用场景:消息削峰填谷、集群流量控制、实时熔断下游不可用应用等
  • 完备的实时监控:Sentinel 同时提供实时的监控功能
  • 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合

核心概念

  • 资源:是 Sentinel 中的核心概念之一,可以是java程序中任何内容,可以是服务或者方法甚至代码,总结起来就是我们要保护的东西
  • 规则:定义怎样的方式保护资源,主要包括流控规则、熔断降级规则等
    【面试】2021后端面试题_第23张图片

Oauth2

OAuth2 的概念

OAuth是一个关于授权的开放网络标准,OAuth2是其2.0版本。
它规定了四种操作流程(授权模式)来确保安全
应用场景有第三方应用的接入、微服务鉴权互信、接入第三方平台、第一方密码登录等

OAuth2授权模式

OAuth2定义了四种授权模式(授权流程)来对资源的访问进行控制

  • 密码模式(resource owner password credentials)(为遗留系统设计)(支持refresh token)
    适用于 : 需要输入账号密码,极度不安全,需要高度信任第三方应用
  • 授权码模式(authorization code)(正宗方式)(支持refresh token)
    适用于 : 安全性高,使用率高,流程复杂。要求第三方应用必须有服务器。对安全性要求较高,web项目中一般使用授权码模式。
  • 隐式授权模式(简化模式)(implicit)(为web浏览器应用设计)(不支持refresh token)
    适用于 : 流程简单;适用于纯前端应用,不安全。Token有效期短,浏览器关闭即失效
  • 客户端模式(client credentials)(为后台api服务消费者设计)(不支持refresh token)
    适用于 : 授权维度为应用维度,而不是用户维度。因此有可能多个用户共用一个Token的情况。适用于应用维度的共享资源。适用于服务器之间交互,不需要用户参与。

无论哪个模式(流程)都拥有三个必要角色:客户端、授权服务器、资源服务器,有的还有用户(资源拥有者)

授权码模式(Authorization Code Grant)

授权码模式是OAuth2目前最安全最复杂的授权流程
大致分为三大部分

  • Client Side:用户+客户端与授权服务器的交互

这个客户端可以是浏览器,

  1. 客户端将client_id + client_secret + 授权模式标识(grant_type) + 回调地址(redirect_uri)拼成url访问授权服务器授权端点
  2. 授权服务器返回登录界面,要求用户登录(此时用户提交的密码等直接发到授权服务器,进行校验)
  3. 授权服务器返回授权审批界面,用户授权完成
  4. 授权服务器返回授权码到回调地址
  • Server Side:客户端与授权服务器之间的交互

客户端使用授权码换token

  1. 客户端接收到授权码,并使用授权码 + client_id + client_secret访问授权服务器颁发token端点
  2. 授权服务器校验通过,颁发token返回给客户端
  3. 客户端保存token到存储器(推荐cookie)
  • Check Access Token:客户端与资源服务器之间的交互 + 资源服务器与授权服务器之间的交互

客户端使用token访问资源

  1. 客户端在请求头中添加token,访问资源服务器
  2. 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
  3. 资源服务器校验成功,返回资源

整体上来说,可以用一句话概括授权码模式授权流程
客户端换取授权码,客户端使用授权码换token,客户端使用token访问资源

隐式授权模式(简化模式)(Implicit Grant)

隐式授权模式大致可分为两部分:

  • Client Side:用户+客户端与授权服务器的交互
    客户端让用户登录授权服务器换token
  1. 客户端(浏览器或单页应用)将client_id + 授权模式标识(grant_type)+ 回调地址(redirect_uri)拼成url访问授权服务器授权端点
  2. 授权服务器跳转用户登录界面,用户登录
  3. 用户授权
  4. 授权服务器访问回调地址返回token给客户端
  • Check Access Token:客户端与资源服务器之间的交互 + 资源服务器与授权服务器之间的交互

客户端使用token访问资源

  1. 客户端在请求头中添加token,访问资源服务器
  2. 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
  3. 资源服务器校验成功,返回资源

用一句话概括隐式授权模式授权流程
客户端让用户登录授权服务器换token,客户端使用token访问资源

密码模式(Resource Owner Password Credentials Grant)

密码模式大体上也分为两部分:

  • Client Side: 用户与客户端交互,客户端与授权服务器交互

用户在客户端提交账号密码换token

  1. 客户端要求用户登录
  2. 用户输入密码,客户端将表单中添加客户端的client_id + client_secret发送给授权服务器颁发token端点
  3. 授权服务器校验用户名、用户密码、client_id、client_secret,均通过返回token到客户端
  4. 客户端保存token
  • Check Access Token:客户端与资源服务器之间的交互 + 资源服务器与授权服务器之间的交互

客户端使用token访问资源

  1. 客户端在请求头中添加token,访问资源服务器
  2. 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
  3. 资源服务器校验成功,返回资源

一句话概括用户名密码模式流程:
用户在客户端提交账号密码换token,客户端使用token访问资源

客户端模式(Client Credentials Grant)

客户端模式大体上分为两部分:

  • Server Side: 客户端与授权服务器之间的交互

客户端使用自己的标识换token

  1. 客户端使用client_id + client_secret + 授权模式标识访问授权服务器的颁发token端点
  2. 授权服务器校验通过返回token给客户端
  3. 客户端保存token
  • Check Access Token: 客户端与资源服务器,资源服务器与授权服务器之间的交互

客户端使用token访问资源

  1. 客户端在请求头中添加token,访问资源服务器
  2. 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
  3. 资源服务器校验成功,返回资源

一句话概括客户端模式授权流程:
客户端使用自己的标识换token,客户端使用token访问资源

你可能感兴趣的:(【战略升级】,面试,java,mysql,消息队列,1024程序员节)