InterviewBook - 图2
Java面试题

目录
一、 Java基础 1
1.Java 基础 知识 1
2.集合(必会) 9
3. 多线程(必会) 13
4.线程池 19
6. Jvm 22
二、 Web 46
1.网络通讯部分 65
2.cookie和session的区别?(必会) 69
3.Jsp和Servlet(了解) 67
4.Ajax的介绍(必会) 69
三、 数据库 46
1.连接查询(必会) 46
2.聚合函数(必会) 47
3.SQL关键字(必会) 47
4. SQL Select 语句完整的执行顺序: (必会) 47
5. 数据库三范式(必会) 48
6. 存储引擎 (高薪常问) 48
1.MyISAM存储引擎 48
2.InnoDB存储引擎 48
7.数据库事务(必会) 49
8.索引 49
9.数据库锁(高薪常问) 54
1.行锁和表锁 54
2.悲观锁和乐观锁 54
10.MySql优化(高薪常问) 55
1) 定位执行效率慢的sql语句.(了解) 55
2) 优化索引(高薪) 55
3) Sql语句调优(高薪) 56
4) 合理的数据库设计(了解) 57
四. 框架 72
1. Mybatis框架 72
2. Spring框架 72
3.SpringMVC框架 78
4. Dubbo 88
5. Zookeeper 90
6.SpringBoot 82
7. SpringCloud 103
五.技术点 112
1. Redis 112
2. RocketMQ 121
3. MongoDb 126
4. Nginx 112
5. FastDFS 131
6. JWT 133

1 Java基础

1.Java 基础 知识

1.1面向对象的特征(了解)

面向对象的特征:封装、继承、多态、抽象。
封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节,就是把不想告诉或者不该告诉别人的东西隐藏起来,把可以告诉别人的公开,别人只能用我提供的功能实现需求,而不知道是如何实现的。增加安全性。
继承:子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,提高了代码的复用性。
多态:指允许不同的对象对同一消息做出相应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。封装和继承几乎都是为多态而准备的,在执行期间判断引用对象的实际类型,根据其实际的类型调用其相应的方法。
抽象表示对问题领域进行分析、设计中得出的抽象的概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。在Java中抽象用 abstract 关键字来修饰,用 abstract 修饰类时,此类就不能被实例化,从这里可以看出,抽象类(接口)就是为了继承而存在的。

1.2 Java的基本数据类型有哪些(了解)

InterviewBook - 图3

1.3JDK JRE JVM 的区别 (必会)

InterviewBook - 图4
InterviewBook - 图5
JDK(Java Development Kit)是整个 Java 的核心,是java开发工具包,包括了 Java 运行环境 JRE、Java 工具和 Java 基础类库。
JRE(Java Runtime Environment)是运行 JAVA 程序所必须的环境的集合,包含java虚拟机和java程序的一些核心类库。
JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部分,能够运行以 Java 语言写作的软件程序。

1.4重载和重写的区别(必会)

重载: 发生在同一个类中,方法名必须相同,参数类型不同.个数不同.顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名.参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,
访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

1、 关键字static的作用是什么?

static关键字主要有两种作用:
第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。
第二,实现某个方法或属性与类而不是对象关联在一起
具体而言,在Java语言中,static主要有4中使用情况:成员变量、成员方法、代码块和内部类
(1)static成员变量:
Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量。静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使用了。对静态变量的引用有两种方式,分别是“类.静态变量”和”对象.静态变量”
实例变量属于对象,只有对象被创建后,实例变量才会被分配内存空间,才能被使用,它在内存中存在多个复制,只有用“对象.实例变量”的方式来引用。
(2)static成员方法:
Java中提供了static方法和非static方法。static方法是类的方法,不需要创建对象就可以被调用,而非static方法是对象的方法,只有对象被创建出来后才可以被使用
static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和成员方法,因为当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。同理,static方法也不能访问非static类型的变量。
单例设计模式:
static一个很重要的用途就是实现单例设计模式。单利模式的特点是该类只能有一个实例,为了实现这一功能,必须隐藏类的构造函数,即把构造函数声明为private,并提供一个创建对象的方法,由于构造对象被声明为private,外界无法直接创建这个类型的对象,只能通过该类提供的方法来获取类的对象,要达到这样的目的只能把创建对象的方法声明为static,程序实例如下:
class Singleton{
private static Singleton instance=null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
(3)static代码块
static代码块在类中是独立于成员变量和成员函数的代码块的。注意: 这些static代码块只会被执行一次
(4)static与final结合使用表示的意思:(常量)
对于变量,若使用static final修饰,表示一旦赋值不能修改,并且通过类名可以访问。
对于方法,若使用static final修饰,表示该方法不可被覆盖,并且可以通过类名直接访问。
public class Test{
public static int testStatic(){
static final int i=0;
System.out.println(i++);
}
public static void main(String[] args){
Test test=new Test();
test.testStatic();
}
}
上述程序运行的结果:
A 0 B 1 C 2 D 编译失败
答案:D
在Java语言中,不能在成员函数(方法)内部定义static变量

final在Java中的作用,有哪些用法?
在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。
  1.修饰类
  当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
InterviewBook - 图6
  在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
  2.修饰方法
  下面这段话摘自《Java编程思想》第四版第143页:
  “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
  因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。
  注:类的private方法会隐式地被指定为final方法。
  3.修饰变量
  修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:
  对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

1.5 Java中==和equals的区别(必会)

== 的作用:
  基本类型:比较的就是值是否相同
  引用类型:比较的就是地址值是否相同
equals 的作用:  引用类型:默认情况下,比较的是地址值。
特:String、Integer、Date这些类库中equals被重写,比较的是内容而不是地址!
面试题:请解释字符串比较之中 “ == ” 和 equals() 的区别?
答: ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较; equals():比较的是两个字符串的内容,属于内容比较。

1.6 String、StringBuffer、StringBuilder三者之间的区别(必会)

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

String 中的String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[] ,String对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。

1.7接口和抽象类的区别是什么?(必会)

实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符

Object类常用方法
Object是所有类的父类,任何类都默认继承Object。Object类到底实现了哪些方法?
(1)clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
(2)getClass方法
final方法,获得运行时类型。
(3)toString方法
该方法用得比较多,一般子类都有覆盖。
(4)finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
(5)equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
(6)hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
(7)wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
(8)notify方法
该方法唤醒在该对象上等待的某个线程。
(9)notifyAll方法
该方法唤醒在该对象上等待的所有线程

java math类方法_JAVA中常用到的Math类中的方法有哪些?
Math类是一个包含了很多数学常量与计算方法的类,里面的方法全是静态方法。Math类位于java.lang包下,一般能够自动导入.

Java中Math类常用方法
数学常量:
常用方法:
求随机数
求绝对值
求最大值
求最小值
取整
四舍五入
最接近的整数
三角计算
反三角计算
开方计算
对数计算
乘方计算
数学常量:
static double E; //自然对数的基数:e
static double PI; //圆周率:π
常用方法
求随机数
static double random(); //返回一个大于等于0.0且小于1.0的double值。
求绝对值
static double abs(double a);
static float abs(float a);
static int abs(int a);
static long abs(long a);
求最大值
static double max(double a, double b);
static float max(float a, float b);
static int max(int a, int b);
static long max(long a, long b)
```;
求最小值
static double min(double a, double b);
static float min(float a, float b);
static int min(int a, int b);
static long min(long a, long b);
取整
static double ceil(double a); //向上取整:返回大于等于参数的最小整数值。
static double floor(double a); //向下取整:返回小于等于参数的最大整数值。
四舍五入
static long round(double a);
static int round(float a);
最接近的整数
static double rint(double a); //返回最接近参数的整数。
三角计算
static double sin(double a); //正弦函数
static double cos(double a); //余弦函数
static double tan(double a); //正切函数
static double sinh(double x); //双曲正弦函数
static double cosh(double x); //双曲余弦函数
static double tanh(double x); //双曲正切函数
反三角计算
static double acos(double a); //反余弦函数,返回的角度在0.0到pi的范围内。
static double asin(double a); //反正弦函数; 返回的角度在-pi/2到pi/2的范围内。
static double atan(double a); //反正切函数; 返回的角度在-pi/2到pi/2的范围内。
开方计算
**static double sqrt(double a); //平方根。
static double cbrt(double a); //立方根。
static double hypot(double x, double y); //返回sqrt(x^2 + y^2)。

对数计算
static double log(double a); //以自然常数e为底的对数。
static double log10(double a); //以10为底的对数。
static double log1p(double x); //返回 x+1 的自然对数。
乘方计算
static double pow(double a, double b); //幂函数:返回计算a的b次方。
static double exp(double a); //返回自然底数e的参数次方。
static double expm1(double x); //返回 exp(x)-1
角度转换
static double toRadians(double angdeg); //将以度为单位的角度转换为以弧度为单位测量的近似等效角度。
static double toDegrees(double angrad); //将以弧度测量的角度转换为以度为单位测量的近似等效角度。

1.8 string常用的方法有哪些?(了解)

indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
String 类 是 final类 ,不可以被继承。 继承String本身就是一个错误的行为,对String类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。

1.9什么是单例模式?有几种?(必会)

单例模式:某个类的实例在 多线程环境下只会被创建一次出来。
单例模式有饿汉式单例模式、懒汉式单例模式和双检锁单例模式三种。
饿汉式:线程安全,一开始就初始化。
InterviewBook - 图7
懒汉式:非线程安全,延迟初始化。
InterviewBook - 图8
双检锁:线程安全,延迟初始化。
InterviewBook - 图9

1.10反射(了解)

在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;
这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。
获取 Class 对象的 3 种方法 :
调用某个对象的 getClass()方法
Person p=new Person();
Class clazz=p.getClass();
调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz=Person.class;
使用 Class 类中的 forName()静态方法(最安全/性能最好)
Class clazz=Class.forName(“类的全路径”); (最常用)

1.11 jdk1.8的新特性(高薪常问)


1 Lambda 表达式
Lambda 允许把函数作为一个方法的参数。
InterviewBook - 图10
2 方法引用
方法引用允许直接引用已有 Java 类或对象的方法或构造方法。
InterviewBook - 图11
上例中我们将 System.out::println 方法作为静态方法来引用。
3 函数式接口
有且仅有一个抽象方法的接口叫做函数式接口,函数式接口可以被隐式转换为 Lambda 表达式。通常函数式接口
上会添加@FunctionalInterface 注解。
4 接口允许定义默认方法和静态方法
从 JDK8 开始,允许接口中存在一个或多个默认非抽象方法和静态方法。
5 Stream API
新添加的 Stream API(java.util.stream)把真正的函数式编程风格引入到 Java 中。这种风格将要处理的元素集
合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
InterviewBook - 图12
6 日期/时间类改进
之前的 JDK 自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如 commons-lang
包等。不过 JDK8 出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。
这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
7 Optional 类
Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。
InterviewBook - 图13
InterviewBook - 图14
8 Java8 Base64 实现
Java 8 内置了 Base64 编码的编码器和解码器。

1.12 Java的异常(必会)

InterviewBook - 图15
Throwable是所有Java程序中错误处理的父类,有两种资类:Error和Exception。
Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。
Exception:表示可恢复的例外,这是可捕捉到的。
1.运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
2.非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
常见的RunTime异常几种如下

NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常

1.13 BIO、NIO、AIO 有什么区别?(高薪常问)

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

1.14 手写冒泡排序?(必会)

InterviewBook - 图16

2.集合(必会)

2.1常见的数据结构(了解)

常用的数据结构有:数组,栈,队列,链表,树,散列(哈希表),堆,图等
InterviewBook - 图17
数组是最常用的数据结构,数组的特点是长度固定,数组的大小固定后就无法扩容了 ,数组只能存储一种类型的数据 ,添加,删除的操作慢,因为要移动其他的元素。
是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先被读取出来。
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结节(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构等等。有二叉树、平衡树、红黑树、B树、B+树。
散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
是计算机学科中一类特殊的数据结构的统称,堆通常可以被看作是一棵完全二叉树的数组对象。
的定义:图是由一组顶点和一组能够将两个顶点相连的边组成的

2.2集合和数组的区别(了解)

区别:数组长度固定 集合长度可变
数组中存储的是同一种数据类型的元素,可以存储基本数据类型,也可以存储引用数据类型;集合存储的都是对象,而且对象的数据类型可以不一致。在开发当中一般当对象较多的时候,使用集合来存储对象。

2.3List 和 Map、Set 的区别(必会)

List和Set是存储单列数据的集合,Map是存储键值对这样的双列数据的集合;
List中存储的数据是有顺序的,并且值允许重复;
Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;
Set中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的hashcode决定,即位置是固定的(Set集合是根据hashcode来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)。

ArrayList():初始值为0,加了第一个元素后,底层数组长度为10,后面当元素满了时,再扩容,扩容为当前的1.5倍

ArrayList的特点:
  1.ArrayList的底层数据结构是数组,所以查找遍历快,增删慢。
  2.ArrayList可随着元素的增长而自动扩容,正常扩容的话,每次扩容到原来的1.5倍。
  3.ArrayList的线程是不安全的。
ArrayList的扩容:
  扩容可分为两种情况:
  第一种情况,当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的ArrayList在扩容时略有不同:
    1.无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,此后若需要扩容,则正常扩容。
    2.传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
    3.传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
  第二种情况,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。

  1. LinkedList适合删除操作,因为删除不会发生移位;
  2. LinkedList可以包含重复的元素;

(1)允许null值
(2)内部以双向链表的形式来保存集合中的元素
(3)实现了Deque接口,意味着可以当做双端队列、栈来使用
(4)线程不安全,线程安全的可以使用:List list = Collections.synchronizedList(new LinkedList(…));
(5)fail-fast机制
(6)所有指定位置的操作都是从头开始遍历进行的
(7)元素是有序的,输出顺序与输入顺序一致

2.4 List 和 Map、Set 的实现类(必会)

InterviewBook - 图18
(1)Connection接口:
List 有序,可重复
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低, 已给舍弃了
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高Set 无序,唯一
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
依赖两个方法:hashCode()和equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
1. 如何保证元素排序的呢?
自然排序
比较器排序
2.如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
(2)Map接口有四个实现类:
HashMap
基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键, 线程不安全。
HashTable
线程安全,低效,不支持 null 值和 null 键;
LinkedHashMap
线程不安全,是 HashMap 的一个子类,保存了记录的插入顺序;
TreeMap
能够把它保存的记录根据键排序,默认是键值的升序排序,线程不安全。

2.5Hashmap的底层原理(高薪常问)

HashMap在JDK1.8之前的实现方式 数组+链表,
但是在JDK1.8后对HashMap进行了底层优化,改为了由 数组+链表或者数值+红黑树实现,主要的目的是提高查找效率

InterviewBook - 图19
InterviewBook - 图20

  1. Jdk8数组+链表或者数组+红黑树实现,当链表中的元素超过了 8 个以后, 会将链表转换为红黑树,当红黑树节点 小于 等于6 时又会退化为链表。
  2. 当new HashMap():底层没有创建数组,首次调用put()方法示时,底层创建长度为16的数组,jdk8底层的数组是:Node[],而非Entry[],用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.

默认的负载因子大小为0.75,数组大小为16。也就是说,默认情况下,那么当HashMap中元素个数超过160.75=12的时候,就把数组的大小扩展为216=32,即扩大一倍。

  1. 在我们Java中任何对象都有hashcode,hash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机,

map.put(k,v)实现原理
(1)首先将k,v封装到Node对象当中(节点)。
(2)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
map.get(k)实现原理
(1)、先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(2)、在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

  1. Hash冲突
    不同的对象算出来的数组下标是相同的这样就会产生hash冲突,当单线链表达到一定长度后效率会非常低。
  2. 在链表长度大于8的时候,将链表就会变成红黑树,提高查询的效率。

2.6 Hashmap和hashtable ConcurrentHashMap区别(高薪常问)

区别对比一(HashMap 和 HashTable 区别):
1、HashMap 是非线程安全的,HashTable 是线程安全的。
2、HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。
3、因为线程安全的问题,HashMap 效率比 HashTable 的要高。
4、Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线
程环境,而 Hashtable 适合于多线程环境。一般现在不建议用 HashTable, ①
是 HashTable 是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,
现在也有同步的 ConcurrentHashMap 替代,没有必要因为是多线程而用
HashTable。

区别对比二(HashTable 和 ConcurrentHashMap 区别):
HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是JDK1.7使用了锁分段技术来保证线程安全的。JDK1.8ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

2 多线程(必会)

3.1 Threadloal的原理(高薪常问)

ThreadLocal:为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。
ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,但是这里要注意的是它作为一个key用的是弱引用,因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会在ThreadLocalMap中存在一些key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,但是这些Entry本身是不会被清除的。如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
使用完ThreadLocal之后,记得调用remove方法。 在不使用线程池的前提下,即使不调用remove方法,线程的”变量副本”也会被gc回收,即不会造成内存泄漏的情况。

3.2 同步锁、死锁、乐观锁、悲观锁 (高薪常问)

同步锁:
当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。
死锁:
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_conditio机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

3.3说一下 synchronized 底层实现原理?(高薪常问)

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里面的对象

    3.4 synchronized 和 volatile 的区别是什么?(高薪常问)

    volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
    volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
    volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
    volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
    volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

    3.5synchronized 和 Lock 有什么区别? (高薪常问)

    首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
    synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
    synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
    用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
    synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
    Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

3.6什么是线程?线程和进程的区别?(了解)

线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比进程更小的
可以独立运行的基本单位。
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作
系统进行资源分配和调度的一个独立单位。
特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行
时各自内存单元相互独立,线程之间 内存共享,这使多线程编程可以拥有更好
的性能和用户体验。

3.7创建线程有几种方式(必会)

1.继承Thread类并重写 run 方法创建线程,实现简单但不可以继承其他类
2.实现Runnable接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实现解耦。
3..实现 Callable接口并重写 call 方法,创建线程。可以获取线程执行结果的返回值,并且可以抛出异常。
4.使用线程池创建(使用java.util.concurrent.Executor接口)
image.png
InterviewBook - 图22
InterviewBook - 图23

3.8Runnable和Callable的区别?(必会)day09

主要区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息

3.9如何启动一个新线程、调用start和run方法的区别?(必会)

InterviewBook - 图24
线程对象调用run方法不开启线程。仅是对象调用方法。
线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行
调用start方法可以启动线程,并且使得线程进入就绪状态,而run方法只是thread的一个普通方法,还是在主线程中执行。

3.10线程有哪几种状态以及各种状态之间的转换?(必会)

  1. 第一是new->新建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
    2. 第二是Runnable->就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。
    3. 第三是Running->运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码
    4. 第四是阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (1)等待 – 通过调用线程的wait() 方法,让线程等待某工作的完成。
    (2)超时等待 – 通过调用线程的sleep() 或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    (3)同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
    5. 第五是dead->死亡状态: 线程执行完了或者因异常退出了run()方法,该线程结束生命周期.

InterviewBook - 图25

3.11线程相关的基本方法?(必会)

线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
1.线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中
断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方
法一般用在同步方法或同步代码块中。
2.线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占
有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法
会导致当前线程进入 WATING 状态.
3.线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争
CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU
时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。
4.线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的
一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)
5.Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方
法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变
为就绪状态,等待 cpu 的宠幸.
6.线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如
果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并
在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视
器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类
似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。

3.12 wait()和sleep()的区别?(必会)

1. 来自不同的类
wait():来自Object类;
sleep():来自Thread类;
2.关于锁的释放:
wait():在等待的过程中会释放锁;
sleep():在等待的过程中不会释放锁
3.使用的范围:
wait():必须在同步代码块中使用;
sleep():可以在任何地方使用;
4.是否需要捕获异常
wait():不需要捕获异常;
sleep():需要捕获异常;

4. 线程池

4.1为什么需要线程池(了解)

在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处:
1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建销毁时造成的消耗
2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
3、通过线程池可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等

创建线程池的几种方式

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,
  8. 1655025271575.png

    4.2线程池的分类(高薪常问)

    InterviewBook - 图27

  9. newCachedThreadPool:创建一个可进行缓存重复利用的线程池

  10. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
  11. newSingleThreadExecutor:创建一个使用单个 worker 线程的Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程将会排在队列中以此执行
  12. newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
  13. newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
  14. newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的CPU个数

    4.3核心参数(高薪常问)

    corePoolSize:核心线程池的大小
    maximumPoolSize:线程池能创建线程的最大个数
    keepAliveTime:空闲线程存活时间
    unit:时间单位,为keepAliveTime指定时间单位
    workQueue:阻塞队列,用于保存任务的阻塞队列
    threadFactory:创建线程的工程类
    handler:饱和策略(拒绝策略)

    4.4线程池的原理(高薪常问)

    InterviewBook - 图28
    线程池的工作过程如下:
    当一个任务提交至线程池之后, 1. 线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作线程来执行任务。否则进入2.
    2. 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入3.
    3. 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。如果线程池满了,则交给饱和策略来处理任务。

    4.5拒绝策略(了解)

    ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出RejectedExecutionException异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略
    ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。

    4.6线程池的关闭(了解)

    关闭线程池,可以通过shutdown和shutdownNow两个方法
    原理:遍历线程池中的所有线程,然后依次中断
    1、shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
    2、shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程

    3 Jvm

    虚拟机,一种能够运行java字节码的虚拟机。
    6.1 JDK1.8 JVM运行时内存(高薪)
    InterviewBook - 图29
    程序计数器:
    线程私有的(每个线程都有一个自己的程序计数器), 是一个指针. 代码运行, 执行命令. 而每个命令都是有行号的,会使用程序计数器来记录命令执行到多少行了.记录代码执行的位置
    Java虚拟机栈:
    线程私有的(每个线程都有一个自己的Java虚拟机栈). 一个方法运行, 就会给这个方法创建一个栈帧, 栈帧入栈执行代码, 执行完毕之后出栈(弹栈)存引用变量,基本数据类型
    本地方法栈:
    线程私有的(每个线程都有一个自己的本地方法栈), 和Java虚拟机栈类似, Java虚拟机栈加载的是普通方法,本地方法加载的是native修饰的方法.
    native:在java中有用native修饰的,表示这个方法不是java原生的.
    堆:
    线程共享的(所有的线程共享一份). 存放对象的,new的对象都存储在这个区域.还有就是常量池.
    元空间: 存储.class 信息, 类的信息,方法的定义,静态变量等.而常量池放到堆里存储
    JDK1.8和JDK1.7的jvm内存最大的区别是, 在1.8中方法区是由元空间(元数据区)来实现的, 常量池.
    1.8不存在方法区,将方法区的实现给去掉了.而是在本地内存中,新加入元数据区(元空间).

    6.2 JDK1.8堆内存结构(高薪常问)

    InterviewBook - 图30
    Young 年轻区(代): Eden+S0+S1, S0和S1大小相等, 新创建的对象都在年轻代
    Tenured 年老区: 经过年轻代多次垃圾回收存活下来的对象存在年老代中.
    Jdk1.7和Jdk1.8的区别在于, 1.8将永久代中的对象放到了元数据区, 不存永久代这一区域了.

    6.3 Gc垃圾回收(高薪常问)

    JVM的垃圾回收动作可以大致分为两大步,首先是「如何发现垃圾」,然后是「如何回收垃圾」。说明一点, 线程私有的不存在垃圾回收, 只有线程共享的才会存在垃圾回收, 所以堆中存在垃圾回收.
    6.3.1 如何发现垃圾
    Java语言规范并没有明确的说明JVM使用哪种垃圾回收算法,但是常见的用于「发现垃圾」的算法有两种,引用计数算法和根搜索算法。

  15. 引用计数算法

该算法很古老(了解即可)。核心思想是,堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1,当对象的引用计数器为0时可以被当作垃圾收集。
优点:快。
缺点:无法检测出循环引用。如两个对象互相引用时,他们的引用计数永远不可能为0。

  1. 根搜索算法(也叫可达性分析)

根搜索算法是把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。
Java中可作为GC Root的对象有
  1.虚拟机栈中引用的对象
  2.本地方法栈引用的对象
  2.方法区中静态属性引用的对象
  3.方法区中常量引用的对象
6.3.2 如何回收垃圾
Java中用于「回收垃圾」的常见算法有4种:

  1. 标记-清除算法(mark and sweep)

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片。

  1. 标记-整理算法

是在标记-清除算法基础上做了改进,标记阶段是相同的,但标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
优点:内存被整理后不会产生大量不连续内存碎片。

  1. 复制算法(copying)

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。
缺点:可使用的内存只有原来一半。

  1. 分代收集算法(generation)

当前主流JVM都采用分代收集(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。
InterviewBook - 图31

  1. 年轻代(Young Generation)

1.所有新生成的对象首先都是放在年轻代的。
2.新生代内存按照8:1:1的比例分为一个eden区和两个Survivor(survivor0,survivor1)区。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
3.当survivor1区不足以存放eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

  1. 年老代(Old Generation)

1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
2.内存比新生代也大很多(大概是2倍),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率比较高。

  1. 持久代(Permanent Generation)

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,从JDK8以后已经废弃, 将存放静态文件,如Java类、方法等这些存储到了元数据区.

6.4 JVM调优参数(了解)

这里只给出一些常见的性能调优的参数及其代表的含义。(大家记住5.6个就行, 并不需要都记住.)
InterviewBook - 图32
图:JVM参数配置参考
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。注意:此值一般设置成和-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss256k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,以前每个线程栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。
InterviewBook - 图33
图:JVM参数参考
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4。(该值默认为2)
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4。

6.5Static关键字有什么作用?

Static可以修饰内部类、方法、变量、代码块
Static修饰的类是静态内部类
Static修饰的方法是静态方法,表示该方法属于当前类的,而不属于某个对象的,静态方法也不能被重写,可以直接使用类名来调用。在static方法中不能使用this或者super关键字。
Static修饰变量是静态变量或者叫类变量,静态变量被所有实例所共享,不会依赖于对象。静态变量在内存中只有一份拷贝,在JVM加载类的时候,只为静态分配一次内存。
Static修饰的代码块叫静态代码块,通常用来做程序优化的。静态代码块中的代码在整个类加载的时候只会执行一次。静态代码块可以有多个,如果有多个,按照先后顺序依次执行

6.6Java中的继承是单继承还是多继承 ?

Java中既有单继承,又有多继承。对于java类来说只能有一个父类,对于接口来说可以同时继承多个接口

6.7fifinal在java中的作用,有哪些用法?

fifinal也是很多面试喜欢问的地方,但我觉得这个问题很无聊,通常能回答下以下5点就不错了:
1. 被fifififinal修饰的类不可以被继承
2. 被fifififinal修饰的方法不可以被重写
3. 被fifififinal修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
4. 被fifififinal修饰的方法,JVM会尝试将其内联,以提高运行效率
5. 被fifififinal修饰的常量,在编译阶段会存入常量池中.
除此之外,编译器对fifififinal域要遵守的两个重排序规则更好:
在构造函数内对一个fifififinal域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
初次读一个包含fifififinal域的对象的引用,与随后初次读这个fifififinal域,这两个操作之间不能重排序

6.8面向对象的特征有哪些方面?

抽象: 抽象是将一类对象的共同特征总结出来构造类的过程, 包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属 性和行为,并不关注这些行为的细节是什么。

继承:继承是从已有类得到继承信息创建新类的过程.提供继承信息的类被称为父类(超类、基类) ;得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性 ,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博土的《Java 与模式》或《设计模式精解》中.关于桥梁模式的部分)。

封装:
通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的(就像电动剃须刀是 A 系统,它的供电系统是 B 系统,B 系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A 系统只会通过 B 类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载
(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)

6.9重载和重写的区别

重写(Override)
从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
InterviewBook - 图34
原因:
在某个范围内的整型数值的个数是有限的,而浮点数却不是。
重写 总结:
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载(Overload)
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载
InterviewBook - 图35
重载总结:
1.重载Overload是一个类中多态性的一种表现
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。

6.10、instanceof关键字的作用

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:
InterviewBook - 图36
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定 类型,则通过编译,具体看运行时定。
InterviewBook - 图37

6.11Object类常用方法有那些?

InterviewBook - 图38

6.12是否可以继承 String 类?

String 类是 fifinal 类,不可以被继承,继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。

6.13讲下java中的math类有那些常用方法?

Pow():幂运算
Sqrt():平方根
Round():四舍五入
Abs():求绝对值
Random():生成一个0-1的随机数,包括0不包括

6.14ArrayList和linkedList的区别

Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据, (因为删除数据以后, 需要把后面所有的数据前移)缺点: 数组初始化必须指定初始化的长度, 否则报错
例如:
InterviewBook - 图39
List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
List有两个重要的实现类:ArrayList和LinkedList
ArrayList: 可以看作是能够自动增长容量的数组
ArrayList的toArray方法返回一个数组
ArrayList的asList方法返回一个列表
ArrayList底层的实现是Array, 数组扩容实现
LinkList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于 ArrayList.当然,这些对比都是指数据量很大或者操作很频繁。

6.15说说ArrayList的特点

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。 当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。

6.16说说LinkList(链表)

LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用

6.17泛型常用特点

泛型是Java SE 1.5之后的特性,《Java 核心技术》中对泛型的定义是:“泛型” 意味着编写的代码可以被不同类型的对象所重用。“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的 ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如
InterviewBook - 图40
使用泛型的好处?
以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。

6.18Collection包结构,与Collections的区别

Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set; Collections是集合类的一个帮助类,它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

6.19Error与Exception区别?

Error和Exception都是java错误处理机制的一部分,都继承了Throwable类。
Exception表示的异常,异常可以通过程序来捕捉,或者优化程序来避免。
Error表示的是系统错误,不能通过程序来进行错误处理。

6.20Thow与thorws区别

位置不同
1. throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的是异常对象。
功能不同:
2. throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方
式;throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并
将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语
句,因为执行不到。
3. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象。
4. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异
常,真正的处理异常由函数的上层调用处理。

6.21Java 中 IO 流?

Java 中 IO 流分为几种?
1. 按照流的流向分,可以分为输入流和输出流;
2. 按照操作单元划分,可以划分为字节流和字符流;
3. 按照流的角色划分为节点流和处理流。
Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
1. InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
2. OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

6.22字节流与字符流的区别

以字节为单位输入输出数据,字节流按照8位传输
以字符为单位输入输出数据,字符流按照16位传输

6.23常用io类有那些

InterviewBook - 图41

6.24二分查找

又叫折半查找,要求待查找的序列有序。每次取中间位置的值与待查关键字比较,如果中间位置的值比待查关键字大,则在前半部分循环这个查找的过程,如果中间位置的值比待查关键字小,则在后半部分循环这个查找的过程。直到查找到了为止,否则序列中没有待查的关键字。
InterviewBook - 图42

6.25什么是java序列化,如何实现java序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

6.26 Java IO与 NIO的区别

NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO 主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。

6.27网络 7 层架构

7 层模型主要包括:
1. 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率 等。它的主要作用是传输比特流(就是由 1、0 转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的模数转换与数模转换)。这一层的数据叫做比特。
2. 数据链路层:主要将从物理层接收的数据进行 MAC 地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。
3. 网络层:主要将从下层接收到的数据进行 IP 地址(例 192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。
4. 传输层:定义了一些传输数据的协议和端口号(WWW 端口 80 等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与 TCP 特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如 QQ 聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段进行传输,到达目的地址后在进行重组。常常把这一层数据叫做段。
5. 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或或者接受会话请求(设备之间需要互相认识可以是 IP 也可以是 MAC 或者是主机名)
6. 表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等))
7. 应用层 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(你就把它理解成我们在电脑屏幕上可以看到的东西.就 是终端应用)。
InterviewBook - 图43

6.28. TCP 三次握手/四次挥手

TCP 在传输之前会进行三次沟通,一般称为“三次握手”,传完数据断开的时候要进行四次沟通,一般称为“四次挥手”。
第一次握手:主机A发送位码为 syn=1,随机产生 seq number=1234567 的数据包到服务器,主机B由 SYN=1 知道,A 要求建立联机;
第二次握手:主机 B 收到请求后要确认联机信息,向 A 发 送 ack number=( 主 机 A 的seq+1),syn=1,ack=1,随机产生 seq=7654321 的包
1、第三次握手:主机 A 收到后检查 ack number 是否正确,即第一次发送的 seq number+1,以及位码ack 是否为 1,若正确,主机 A 会再发送 ack number=(主机 B 的 seq+1),ack=1,主机 B 收到后确认seq 值与 ack=1 则连接建立成功。
InterviewBook - 图44
2、四次挥手
TCP 建立连接要进行三次握手,而断开连接要进行四次。这是由于 TCP 的半关闭造成的。因为 TCP 连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭。这个单方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个 FIN 来向另一方通告将要终止这个方向的连接。
1) 关闭客户端到服务器的连接:首先客户端 A 发送一个 FIN,用来关闭客户到服务器的数据传送,然后等待服务器的确认。其中终止标志位 FIN=1,序列号 seq=u
2) 服务器收到这个 FIN,它发回一个 ACK,确认号 ack 为收到的序号加 1。
3) 关闭服务器到客户端的连接:也是发送一个 FIN 给客户端。
4) 客户段收到 FIN 后,并发回一个 ACK 报文确认,并将确认序号 seq 设置为收到序号加 1。
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
InterviewBook - 图45
主机 A 发送 FIN 后,进入终止等待状态, 服务器 B 收到主机 A 连接释放报文段后,就立即给主机 A 发送确认,然后服务器 B 就进入 close-wait 状态,此时 TCP 服务器进程就通知高层应用进程,因而从 A 到 B 的连接就释放了。此时是“半关闭”状态。即 A 不可以发送给B,但是 B 可以发送给 A。此时,若 B 没有数据报要发送给 A 了,其应用进程就通知 TCP 释放连接,然后发送给 A 连接释放报文段,并等待确认。A 发送确认后,进入 time-wait,注意,此时 TCP 连接还没有释放掉,然后经过时间等待计时器设置的 2MSL 后,A 才进入到close 状态。

6.29. TCP和UDP的区别
连接性
TCP是面向连接的协议,在收发数据前必须和对方建立可靠的连接,建立连接的3次握手、断开连接的4次挥手,为数据传输打下可靠基础;UDP是一个面向无连接的协议,数据传输前,源端和终端不建立连接,发送端尽可能快的将数据扔到网络上,接收端从消息队列中读取消息段。
可靠性
TCP提供可靠交付的服务,传输过程中采用许多方法保证在连接上提供可靠的传输服务,如编号与确认、流量控制、计时器等,确保数据无差错,不丢失,不重复且按序到达;UDP使用尽可能最大努力交付,但不保证可靠交付。
报文首部
TCP报文首部有20个字节,额外开销大;UDP报文首部只有8个字节,标题短,开销小。
InterviewBook - 图46
UDP报文结构
InterviewBook - 图47
TCP报文结构
TCP协议面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配;UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用。
吞吐量控制
TCP拥塞控制、流量控制、重传机制、滑动窗口等机制保证传输质量;UDP没有。
双工性
TCP只能点对点全双工通信;UDP支持一对一、一对多、多对一和多对多的交互通信。
TCP和UDP的编程步骤
InterviewBook - 图48
TCP编程步骤
InterviewBook - 图49
UDP编程步骤
从上面TCP、UDP编程步骤可以看出,UDP 服务器端不需要调用监听(listen)和接收(accept)客户端连接,而客户端也不需要连接服务器端(connect)。UDP协议中,任何一方建立socket后,都可以用sendto发送数据、用recvfrom接收数据,不必关心对方是否存在,是否发送了数据。
TCP和UDP的使用场景
为了实现TCP网络通信的可靠性,增加校验和、序号标识、滑动窗口、确认应答、拥塞控制等复杂的机制,建立了繁琐的握手过程,增加了TCP对系统资源的消耗;TCP的重传机制、顺序控制机制等对数据传输有一定延时影响,降低了传输效率。TCP适合对传输效率要求低,但准确率要求高的应用场景,比如万维网(HTTP)、文件传输(FTP)、电子邮件(SMTP)等。
UDP是无连接的,不可靠传输,尽最大努力交付数据,协议简单、资源要求少、传输速度快、实时性高的特点,适用于对传输效率要求高,但准确率要求低的应用场景,比如域名转换(DNS)、远程文件服务器(NFS)等。

6.29. 反射机制的优缺点:

优点:
1)能够运行时动态获取类的实例,提高灵活性;
2)与动态编译结合
缺点:

  1. 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。

2)相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
缺点1)解决方案:
1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度;
2、多次创建一个类的实例时,有缓存会快很多
3、ReflectASM工具类,通过字节码生成的方式加快反射速度

6.30获取 Class 对象有几种方法

调用某个对象的 getClass()方法
InterviewBook - 图50调用某个类的 class 属性来获取该类对应的 Class 对象
InterviewBook - 图51
使用 Class 类中的 forName()静态方法(最安全/性能最好)
InterviewBook - 图52
当我们获得了想要操作的类的 Class 对象后,可以通过 Class 类中的方法获取并查看该类中的方法和属性。
InterviewBook - 图53

6.31、4种标准元注解是哪四种?

元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
@Target 修饰的对象范围
@Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target可更加明晰其修饰的目标
@Retention 定义 被保留的时间长短
Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:
1. SOURCE:在源文件中有效(即源文件保留) source
2. CLASS:在 class 文件中有效(即 class 保留) class
3. RUNTIME:在运行时有效(即运行时保留) runtime
4.
@Documented 描述-javadoc
@ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。@Inherited 阐述了某个被标注的类型是被继承的
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。

6.32注解是什么?

Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。 Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

6.33 Java的数据结构有那些?

1.线性表(ArrayList)
2.链表(LinkedList)
3.栈(Stack)
4.队列(Queue)
5.图(Map)
6.树(Tree)

6.34Java语言有哪些特点

  1. 简单易学、有丰富的类库
    2. 面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)
    3. 与平台无关性(JVM是Java跨平台使用的根本)
    4. 可靠安全
    5. 支持多线程

    6.35面向对象和面向过程的区别

  2. 面向过程:
    一种较早的编程思想,顾名思义就是该思想是站着过程的角度思考问题,强调的就是功能行为,功能的执行过程,即先后顺序,而每一个功能我们都使用函数(类似于方法)把这些步骤一步一步实现。使用的时候依次调用函数就可以了。
    2. 面向对象:
    一种基于面向过程的新编程思想,顾名思义就是该思想是站在对象的角度思考问题,我们把多个功能合理放到不同对象里,强调的是具备某些功能的对象。
    具备某种功能的实体,称为对象。面向对象最小的程序单元是:类。面向对象更加符合常规的思维方式,稳定性好,可重用性强,易于开发大型软件产品,有良好的可维护性。
    在软件工程上,面向对象可以使工程更加模块化,实现更低的耦合和更高的内聚。

    6.36 HashSet( Hash 表)

    哈希表边存放的是哈希值。 HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同)而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true , HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。
    哈希值相同 equals 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。 如图 1 表示 hashCode 值不相同的情况;
    图 2 表示 hashCode 值相同,但 equals 不相同的情况。
    InterviewBook - 图54
    HashSet 通过 hashCode 值来确定元素在内存中的位置。 一个 hashCode 位置上可以存放多个元素。

4 数据库

1.连接查询(必会)

1.左连接
(左外连接)以左表为基准进行查询,左表数据会全部显示出来,右表 如果和左表匹配 的数据则显示相应字段的数据,如果不匹配,则显示为 NULL;
2.右连接
(右外连接)以右表为基准进行查询,右表数据会全部显示出来,右表 如果和左表匹配的数据则显示相应字段的数据,如果不匹配,则显示为 NULL;

2.聚合函数(必会)

1.聚合函数
SQL中提供的聚合函数可以用来统计、求和、求最值等等。
2.分类
COUNT:统计行数量
SUM:获取单个列的合计值
AVG:计算某个列的平均值
MAX:计算列的最大值
MIN:计算列的最小值

3.SQL关键字(必会)

1.分页
MySQL的分页关键词limit
SELECT FROM student3 LIMIT 2,6; 查询学生表中数据,从第三条开始显示,显示6条
分页公式:
起始索引=(当前页-1)
每页条数
总页数公式:总页数=(总数据条数+每页条数-1)/每页条数
2.分组
MySQL的分组关键字:group by
SELECT sex, count(*) FROM student3 GROUP BY sex;

3. 去重
去重关键字:distinct
select DISTINCT NAME FROM student3;

  1. 排序:

order by
根据字段排序

4. SQL Select 语句完整的执行顺序: (必会)

查询中用到的关键词主要包含如下展示,并且他们的顺序依次为form…on…left join…where…group by…avg()/sum()…having..select…
order by…asc/desc…limit…
from: 需要从哪个数据表检索数据
where: 过滤表中数据的条件
group by: 如何将上面过滤出的数据分组算结果
order by : 按照什么样的顺序来查看返回的数据

5. 数据库三范式(必会)

第一范式:1NF 原子性,列或者字段不能再分,要求属性具有原子性,不可再分解;
第二范式:2NF 唯一性,一张表只说一件事,是对记录的惟一性约束,要求记录有惟一标识,
第三范式:3NF 直接性,数据不能存在传递关系,即每个属性都跟主键有直接关系,而不是间接关系。

6. 存储引擎 (高薪常问)

1.MyISAM存储引擎

主要特点:
MySQL5.5版本之前的默认存储引擎
支持表级锁(表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁);
不支持事务,外键。
适用场景:对事务的完整性没有要求,或以select、insert为主的应用基本都可以选用MYISAM。在Web、数据仓库中应用广泛。
特点:
1、不支持事务、外键
2、每个myisam在磁盘上存储为3个文件,文件名和表名相同,扩展名分别是
.frm ———-存储表定义
.MYD ————MYData,存储数据
.MYI ————MYIndex,存储索引

2.InnoDB存储引擎

主要特点:
MySQL5.5版本之后的默认存储引擎;
支持事务;
支持行级锁(行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁);
支持聚集索引方式存储数据。

7.数据库事务(必会)

1.事务特性

原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行。
一致性:事务的执行使得数据库从一种正确状态转换成另一种正确状态
隔离性:在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务,
持久性:事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。

2.隔离级别

(1)读未提交(read Uncommited):
在该隔离级别,所有的事务都可以读取到别的事务中未提交的数据,会产生脏读问题,在项目中基本不怎么用, 安全性太差;
(2) 读已提交(read commited):
这是大多数数据库默认的隔离级别,但是不是MySQL的默认隔离级别;这个隔离级别满足了简单的隔离要求:一个事务只能看见已经提交事务所做的改变,所以会避免脏读问题;
由于一个事务可以看到别的事务已经提交的数据,于是随之而来产生了不可重复读和虚读等问题(下面详细介绍这种问题,结合问题来理解隔离级别的含义);
(3 ) 可重复读(Repeatable read):
这是MySQL的默认隔离级别,它确保了一个事务中多个实例在并发读取数据的时候会读取到一样的数据;不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
(4) 可串行化(serializable):
事物的最高级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争,一般为了提升程序的吞吐量不会采用这个;

8.索引

5 索引的概念和优点(了解)

概念:
索引存储在内存中,为服务器存储引擎为了快速找到记录的一种数据结构。索引的主要作用是加快数据查找速度,提高数据库的性能。
优点:

  1. 创建唯一性索引,保证数据库表中每一行数据的唯一性
  2. 大大加快数据的检索速度,这也是创建索引的最主要的原因
  3. 加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
  4. 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

    6 索引的分类(必会)

  5. 普通索引:最基本的索引,它没有任何限制。

  6. 唯一索引:与普通索引类似,不同的就是索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
  7. 主键索引:它是一种特殊的唯一索引,用于唯一标识数据表中的某一条记录,不允许有空值,一般用 primary key 来约束。
  8. 联合索引(又叫复合索引):多个字段上建立的索引,能够加速复合查询条件的检索。
  9. 全文索引:老版本 MySQL 自带的全文索引只能用于数据库引擎为 MyISAM 的数据表,新版本 MySQL 5.6 的 InnoDB 支持全文索引。默认 MySQL 不支持中文全文检索,可以通过扩展 MySQL,添加中文全文检索或为中文内容表提供一个对应的英文索引表的方式来支持中文。

    7 索引的底层实现原理(高薪常问)

  10. 索引结构

索引是在Mysql的存储引擎(InnoDB,MyISAM)层中实现的, 而不是在服务层实现的. 所以每种存储引擎的索引都不一定完全相同, 也不是所有的存储引擎都支持所有的索引类型的, Mysql目前提供了以下4种索引:
B+Tree 索引: 最常见的索引类型, 大部分索引都支持B+树索引.
Hash 索引: 只有Memory引擎支持, 使用场景简单.

  1. Tree索引(空间索引): 空间索引是MyISAM引擎的一个特殊索引类型, 主要地理空间数据, 使用也很少.
  2. Full-text(全文索引): 全文索引也是MyISAM的一个特殊索引类型, 主要用于全文索引, InnoDB从Mysql5.6版本开始支持全文索引.
    InterviewBook - 图55
  3. BTree结构

B+Tree是在BTree基础上进行演变的, 所以我们先来看看BTree, BTree又叫多路平衡搜索树, 一颗m叉BTree特性如下:

  1. 树中每个节点最多包含m个孩子.
  2. 除根节点与叶子节点外, 每个节点至少有[ceil(m/2)] 个孩子(ceil函数指向上取整).
  3. 若根节点不是叶子节点, 则至少有两个孩子.
  4. 每个非叶子节点由n个Key和n+1个指针组成, 其中 [ceil(m/2) -1 ] <= n <= m-1.

以5叉BTree为例, key的数量: 公式推导 [ceil(m/2) -1 ] <= n <= m-1.
所以 2 <= n <= 4, 中间节点分裂父节点,两边节点分裂.
InterviewBook - 图56
3.B+Tree 结构
B+Tree为BTree的变种, B+Tree与BTree的区别:
1.B+Tree的叶子节点保存所有的key信息, 依key大小顺序排列.
2.B+Tree叶子节点元素维护了一个单项链表.
所有的非叶子节点都可以看作是key的索引部分.
InterviewBook - 图57
由于B+Tree只有叶子节点保存key信息, 查询任何key都要从root走的叶子. 所以B+Tree查询效率更稳定.
Mysql中的B+Tree
MySql索引数据结构对经典的B+Tree进行了优化, 在原B+Tree的基础上, 增加了一个指向相邻叶子节点的链表指针, 就形成了带有顺序指针的B+Tree, 提高区间访问的性能.
MySql中的B+Tree索引结构示意图:
InterviewBook - 图58

8 如何避免索引失效(高薪常问)

  1. 范围查询, 右边的列不能使用索引, 否则右边的索引也会失效.

索引生效案例
select from tb_seller where name = “小米科技” and status = “1” and address = “北京市”;
select
from tb_seller where name = “小米科技” and status >= “1” and address = “北京市”;
索引失效案例
select * from tb_seller where name = “小米科技” and status > “1” and address = “北京市”;
address索引失效, 因为status是大于号, 范围查询.

  1. 不要在索引上使用运算, 否则索引也会失效.

比如在索引上使用切割函数, 就会使索引失效.
select * from tb_seller where substring(name, 3, 2) = “科技”;

  1. 字符串不加引号, 造成索引失效.

如果索引列是字符串类型的整数, 条件查询的时候不加引号会造成索引失效. Mysql内置的优化会有隐式转换.
索引失效案例
select * from tb_seller where name = “小米科技” and status = 1

  1. 尽量使用覆盖索引, 避免select *, 这样能提高查询效率.

如果索引列完全包含查询列, 那么查询的时候把要查的列写出来, 不使用select *
select sellerid, name, status from tb_seller where name = “小米科技” and staus = “1” and address = “西安市”;

  1. or关键字连接

用or分割开的条件, 如果or前面的列有索引, or后面的列没有索引, 那么查询的时候前后索引都会失效
如果一定要用or查询, 可以考虑下or连接的条件列都加索引, 这样就不会失效了.

  • 索引失效案例:

select * from tb_seller where name = “小米科技” or createTiem = “2018-01-01 00:00:00”;
InterviewBook - 图59

9.数据库锁(高薪常问)

1.行锁和表锁

1.主要是针对锁粒度划分的,一般分为:行锁、表锁、库锁
行锁:访问数据库的时候,锁定整个行数据,防止并发错误。
表锁:访问数据库的时候,锁定整个表数据,防止并发错误。
2.行锁 和 表锁 的区别:
表锁: 开销小,加锁快,不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
行锁: 开销大,加锁慢,会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

2.悲观锁和乐观锁

(1)悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
(2)乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

10.MySql优化(高薪常问)

InterviewBook - 图60

  1. 定位执行效率慢的sql语句.(了解)
  • 命令:show status like ‘Com__‘,通过这条命令, 我们可以知道当前数据库是以查询为主还是更新为主. 如果是查询为主, 就重点查询; 如果增删改多就重点优化写入操作.
  • explain + sql语句查询sql执行过程, 通过执行计划,我们能得到哪些信息:

A:哪些步骤花费的成本比较高
B:哪些步骤产生的数据量多,数据量的多少用线条的粗细表示,很直观
C:这条sql语句是否走索引

  • show profile分析SQL,可以查看所有sql语句的执行效率(所用时间). 前提是这个命令需要被打开, 严格的说也就是打开这个命令后执行的所有sql语句, 它都能记录下执行时间, 并展示出来. 可以通过这个命令分析哪些sql语句执行效率低. 耗时长, 就更有针对性的优化这条sql.
  • 慢查询日志(常用的工具)

慢查询日志记录了所有执行时间超过参数 long_query_time的sql语句的日志, long_query_time默认为10秒(可以通过配置文件设置), 日志保存在 /var/lib/mysql/目录下, 有个slow_query.log文件,

2) 优化索引(高薪)

2.1 索引设计原则
索引的设计需要遵循一些已有的原则, 这样便于提升索引的使用效率, 更高效的使用索引.

  • 对查询频次较高, 且数据量比较大的表, 建立索引.
  • 索引字段的选择, 最佳候选列应当从where子句的条件中提取, 如果where子句中的组合比较多, 那么应当挑选最常用, 过滤效果最好的列的组合.
  • 使用唯一索引, 区分度越高, 使用索引的效率越高.
  • 索引并非越多越好, 如果该表赠,删,改操作较多, 慎重选择建立索引, 过多索引会降低表维护效率.
  • 使用短索引, 提高索引访问时的I/O效率, 因此也相应提升了Mysql查询效率.
  • 如果where后有多个条件经常被用到, 建议建立符合 索引, 复合索引需要遵循最左前缀法则, N个列组合而成的复合索引, 相当于创建了N个索引.

复合索引命名规则 index表名列名1列名2列明3
比如:create index idx_seller_name_sta_addr on tb_seller(name, status, address)
2.2 避免索引失效

  • 如果在查询的时候, 使用了复合索引, 要遵循最左前缀法则, 也就是查询从索引的最左列开始, 并且不能跳过索引中的列.
  • 尽量不要在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
  • 不做列运算where age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数.计算表达式等, 都会是索引失效.
  • 查询 like,如果是 ‘%aaa’ 也会造成索引失效.
  • 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描

3) Sql语句调优(高薪)

  • 根据业务场景建立复合索引只查询业务需要的字段,如果这些字段被索引覆盖,将极大的提高查询效率.
  • 多表连接的字段上需要建立索引,这样可以极大提高表连接的效率.
  • where条件字段上需要建立索引, 但Where条件上不要使用运算函数,以免索引失效.
  • 排序字段上, 因为排序效率低, 添加索引能提高查询效率.
  • 优化insert语句: 批量列插入数据要比单个列插入数据效率高.
  • 优化order by语句: 在使用order by语句时, 不要使用select *, select 后面要查有索引的列, 如果一条sql语句中对多个列进行排序, 在业务允许情况下, 尽量同时用升序或同时用降序.
  • 优化group by语句: 在我们对某一个字段进行分组的时候, Mysql默认就进行了排序, 但是排序并不是我们业务所需的, 额外的排序会降低效率. 所以在用的时候可以禁止排序, 使用order by null禁用.

select age, count(*) from emp group by age order by null

  • 尽量避免子查询, 可以将子查询优化为join多表连接查询.

4) 合理的数据库设计(了解)
根据数据库三范式来进行表结构的设计。设计表结构时,就需要考虑如何设计才能更有效的查询, 遵循数据库三范式:

  1. 1. **第一范式**:数据表中每个字段都必须是不可拆分的最小单元,也就是确保每一列的原子性;
  2. 1. **第二范式**:满足一范式后,表中每一列必须有唯一性,都必须依赖于主键;
  3. 1. **第三范式**:满足二范式后,表中的每一列只与主键直接相关而不是间接相关(外键也是直接相关),字段没有冗余。

注意:没有最好的设计,只有最合适的设计,所以不要过分注重理论。三范式可以作为一个基本依据,不要生搬硬套。有时候可以根据场景合理地反规范化:
A:保留冗余字段。当两个或多个表在查询中经常需要连接时,可以在其中一个表上增加若干冗余的字段,以 避免表之间的连接过于频繁,一般在冗余列的数据不经常变动的情况下使用。
B:增加派生列。派生列是由表中的其它多个列的计算所得,增加派生列可以减少统计运算,在数据汇总时可以大大缩短运算时间, 前提是这个列经常被用到, 这也就是反第三范式。
C:分割表。
数据表拆分:主要就是垂直拆分和水平拆分。
水平切分:将记录散列到不同的表中,各表的结构完全相同,每次从分表中查询, 提高效率。
垂直切分:将表中大字段单独拆分到另外一张表, 形成一对一的关系。
D: 字段设计

  1. 表的字段尽可能用NOT NULL
  2. 字段长度固定的表查询会更快
  3. 把数据库的大表按时间或一些标志分成小表

    11、SQL 语言包括哪几部分?每部分都有哪些操作关键

    SQL 语言包括数据定义(DDL)、数据操纵(DML),数据控制(DCL)和数据查询(DQL)四个部分。
    数据定义:Create Table,Alter Table,Drop Table, Craete/Drop Index 等
    数据操纵:Select ,insert,update,delete,
    数据控制:grant,revoke
    数据查询:select

    12、CHAR 和 VARCHAR 的区别?

    1、CHAR 和 VARCHAR 类型在存储和检索方面有所不同
    2、CHAR 列长度固定为创建表时声明的长度,长度值范围是 1 到 255 当 CHAR值被存储时,它们被用空格填充到特定长度,检索 CHAR 值时需删除尾随空格。

    13、LIKE 声明中的%和_是什么意思?

    %对应于 0 个或更多字符,_只是 LIKE 语句中的一个字符
    如何在 Unix 和 MySQL 时间戳之间进行转换?
    UNIX_TIMESTAMP 是从 MySQL 时间戳转换为 Unix 时间戳的命令
    FROM_UNIXTIME 是从 Unix 时间戳转换为 MySQL 时间戳的命令

    14、如何显示前 50 行?

    在 MySQL 中,使用以下代码查询显示前 50 行:
    InterviewBook - 图61

    15.数据库中的事务是什么?

    事务(transaction)是作为一个单元的一组有序的数据库操作。如果组中的所有操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功。如果所有操作完成,事务则提交,其修改于所有其他数据库进程。如果一个操作失败,则事务将回滚,该事务所有操作的影响都将取消。
    事务四大特性(简称ACID)
    1、原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行。
    2、一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致。
    3、隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
    4、持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。

    16、数据库【JDBC】

    1、JDBC操作的步骤
    加载数据库驱动类
    打开数据库连接
    执行sql语句
    处理返回结果
    关闭资源

  4. 在使用jdbc的时候,如何防止出现sql注入的问题。

使用PreparedStatement类,而不是使用Statement类
3、是否了解连接池,使用连接池有什么好处?
数据库连接是非常消耗资源的,影响到程序的性能指标。连接池是用来分配、管理、释放数据库连接的,可以使应用程序重复使用同一个数据库连接,而不是每次都创建一个新的数据库连接。通过释放空闲时间较长的数据库连接避免数据库因为创建太多的连接而造成的连接遗漏问题,提高了程序性能。
4、你所了解的数据源技术有那些?使用数据源有什么好处?
dbcp,c3p0等,用的最多还是c3p0,因为c3p0比dbcp更加稳定,安全;通过配置文件的形式来维护数据库信息,而不是通过硬编码。当连接的数据库信息发生改变时,不需要再更改程序代码就实现了数据库信息的更新。

9 框架(一)

1. Mybatis框架

1.1谈一谈你对Mybatis框架的理解(了解)

MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.2在mybatis中,${} 和 #{} 的区别是什么?(必会)

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

1.3 MyBatis编程步骤是什么样的?(了解)

1、 创建SqlSessionFactory
2、 通过SqlSessionFactory创建SqlSession
3、 通过sqlsession执行数据库操作
4、 调用session.commit()提交事务
5、 调用session.close()关闭会话
InterviewBook - 图62
InterviewBook - 图63
InterviewBook - 图64

1.4在mybatis中,resultType和ResultMap的区别是什么?(必会)

如果数据库结果集中的列名和要封装实体的属性名完全一致的话用 resultType 属性
如果数据库结果集中的列名和要封装实体的属性名有不一致的情况用 resultMap 属性,通过resultMap手动建立对象关系映射,resultMap要配置一下表和类的一一对应关系,所以说就算你的字段名和你的实体类的属性名不一样也没关系,都会给你映射出来

1.5在Mybatis中你知道的动态SQL的标签有哪些?作用分别是什么?(必会)

  1. if是为了判断传入的值是否符合某种规则,比如是否不为空.
    2. where标签可以用来做动态拼接查询条件,当和if标签配合的时候,不用显示的声明类型where 1 = 1这种无用的条件
    3. foreach标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参数传到sql语句中.
    4. include可以把大量的重复代码整理起来,当使用的时候直接include即可,减少重复代码的编写;
    5. 适用于更新中,当匹配某个条件后,才会对该字段进行跟新操作

    1.6谈一下你对mybatis缓存机制的理解?(了解)

    InterviewBook - 图65
    Mybatis有两级缓存,一级缓存是SqlSession级别的,默认开启,无法关闭;二级缓存是Mapper级别的,二级缓存默认是没有开启的,但是手动开启
    1. 一级缓存:基础PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session flush或close之后,Session中的所有Cache就将清空
    2. 二级缓存其存储作用域为Mapper(Namespace),使用二级缓存属性类需要实现Serializable序列化接口
    3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C(增加)/U(更新)/D(删除)操作后,默认该作用域下所有select中的缓存将被clear.
    需要在setting全局参数中配置开启二级缓存,如下MyBatis-conf.xml配置:
    InterviewBook - 图66
    当我们的配置文件配置了cacheEnabled=true时,就会开启二级缓存,二级缓存是mapper级别的,如果你配置了二级缓存,那么查询数据的顺序应该为:二级缓存→一级缓
    存→数据库。

  2. 需要在*Mapper.XML配置文件中的SQL语句的最上面加上

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

insert 方法 总是 返回 一个 int 值 ,这 个值 代表 的是 插入 的行 数。 如果 采用 自增 长策 略,自动 生成 的键 值在 insert 方法 执行 完后 可以 被设 置到 传入的参 数对 象中 。 示例 :

insert into names (name) values (#{name})

InterviewBook - 图67

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

10 第一种: DAO 层的函数

InterviewBook - 图68

2、第二种:

使用 @param 注解
InterviewBook - 图69
然后,就可以在 xml 像下面这样使用(推荐封装为一个 map,作为单个参数传递给mapper
InterviewBook - 图70

11 第三种:多个参数封装成 map

InterviewBook - 图71

12 第三种:多个参数封装成 实体类(如:User、Brand)

1.9MyBatis 实现一对一有几种方式?具体怎么操作的?

有联合查询和嵌套查询,
联合查询是几个表联合查询,只查询一次, 通过在resultMap 里面配置 association 节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置

1.10MyBatis 实现一对多有几种方式,怎么操作的?

有联合查询和嵌套查询。
联合查询是几个表联合查询,只查询一次, 通过在resultMap 里面的 collection 节点配置一对多的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的外键 id,去再另外一个表里面查询数据,也是通过配置 collection,但另外一个表的查询通过 select 节点配置。

1.11Mybatis 的一级、二级缓存

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当 Session flflush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
4)、需要在*Mapper.XML配置文件中的SQL语句的最上面加上

13 Web

1.网络通讯部分

1.1 TCP 与 UDP 区别? (了解)

InterviewBook - 图72

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、可靠的、 基于IP的传输层协议。
UDP是User Datagram Protocol的简称,中文名是用户数据报协议,是OSI参考模型中的传输层协议,它是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
TCP和UDP都是来自于传输层的协议。传输层位于应用层和网络层之间,负责位于不同主机中进程之间的通信。
TCP 与 UDP 区别
InterviewBook - 图73
1.TCP基于连接UDP无连接
2.TCP要求系统资源较多,UDP较少
3.TCP保证数据正确性,UDP可能丢包
4.TCP保证数据顺序,UDP不保证

1.2什么是HTTP协议?

客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”。
是一个基于请求与响应模式的、无状态的、应用层的协议,基于 TCP 的连接方式

1.3 TCP的三次握手

为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
InterviewBook - 图74

为什么要三次握手?
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是 双方确认自己与对方的发送与接收是正常的。
SYN:同步序列编号(Synchronize Sequence Numbers)。是TCP/IP建立连接时使用的握手信号。
第一次握手:客户端给服务器发送一个SYN。客户端发送网络包,服务端收到了。服务器得出结论:客户端的发送能力,服务端的接收能力正常。
第二次握手:服务端收到SYN报文之后,会应答一个SYN+ACK报文。服务端发包,客户端收到了。客户端得出结论:服务端的接收和发送能力,客户端的接收和发送能力正常。但是此时服务端不能确认客户端的接收能力是否正常。
第三次握手;客户端收到SYN+ACK报文之后,回应一个ACK报文。客户端发包,服务端收到了。服务器得出结论:客户端的接收和发送能力,自己的接收发送能力都正常。
通过三次握手,双方都确认对方的接收以及发送能力正常。

六、GIT版本控制
1.git的作用
代码回溯
版本控制
多人协作
远程备份
2.git的工作流程
Git工作组成:本地版本库、远程版本库、工作区、暂存区 工作区《=》暂存区《=》本地版本库《=》远程版本库 本地版本库:本地的.git隐藏文件夹就是本地版本库,版本库中存储了很多配置信息、日志信息和文件版本信息等 远程版本库:在远程代码托管服务器上管理版本库。用于协同共享。 工作区:包含.git文件夹的目录就是工作区,也称为工作目录,主要用于存放开发的代码 暂存区:.git文件夹中有很多文件,其中有一个index文件就是暂存区,也可以叫做stage。暂存区是一个临时保存修改文件的地方
3.git的本地仓库操作命令
git status 查看文件状态
git add 将文件的修改加入暂存区
git reset 将暂存区的文件取消暂存或者是切换到指定版本
git commit 将暂存区的文件修改提交到版本库
git log 查看日志
4.git的远程仓库操作命令
image.png
5.git的分支操作命令

命令名称 作用
git branch 分支名 创建分支
git branch -v 查看分支
git checkout 分支名 切换分支
git merge 分支名 把指定的分支合并到当前分支上

image.png

七、前端【html&css】
1.html的作用
Hyper Text Markup language 超文本标记语言。用于布局标签数据。
2.html有哪些常用标签
InterviewBook - 图77
3.div与span的区别
相同点:都是容器
不同点:div独占一行 大范围布局使用,span通常是做为小范围容器
4.表格标签都有那些?作用是什么?
InterviewBook - 图78
5.表单的作用和常用表单元素
InterviewBook - 图79
八、前端【JavaScript】
1.javascript的作用
Javascript是运行在网页上的脚本语言,可以在网页上进行逻辑控制、浏览器对象操作、网页标签数据操作管理等开发。
2.javascript的BOM对象有哪些?
window窗口对象
location地址栏对象
history历史记录对象
3.javascript的DOM对象如何获取?
document.getElementById() 通过id获取一个元素
document.getElementsByTagName() 通过标签名字获取一组元素
document.getElementsByClassName() 通过类名获取一组元素
document.getElementsByName() 通过name属性值获取一组元素
4.javascript有哪些常用的事件?
onload 某个页面或图像被完成加载
onsubmit 当表单提交时触发该事件
onclick 鼠标单击事件
ondblclick 鼠标双击事件
onblur 元素失去焦点
onfocus 元素获得焦点
onchange 用户改变域的内容
5.javascript内置对象有哪些?
Array 数组对象
RegExp 正则表达式定义对象
String 字符串对象
Date 日期对象

九、Tomcat+HTTP+Servlet
1.什么是HTTP协议?
客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”。 是一个基于请求与响应模式的、无状态的、应用层的协议,基于 TCP 的连接方式

2.Servlet的生命周期
init() 用户第1次访问,初始化的时候执行1次
service() 每次用户请求都会执行
destroy() Tomcat关闭或重启,执行1次

3.Servlet的访问地址有几种匹配方式
精确匹配 /地址完全相同
目录匹配,匹配某个目录地址 /目录名/
扩展名匹配,匹配某种文件类型
.do .jsp
任意匹配,所有的地址都匹配 /*

Servlet的生命周期
InterviewBook - 图80

servlet的访问地址有几种匹配方式
InterviewBook - 图81
一个Servlet可以配置多个url-pattern

URL 配置格式 三种:

1、完全路径匹配 (以/开始 ) 例如:/hello /init

* 当前工程没有被正确发布,访问该工程所有静态资源、动态资源 发生404 ——- 工程启动时出错了

* 查看错误时 分析错误

1) 单一错误 : 从上到下 查看第一行你自己写代码 (有的错误与代码无关,查看错误信息)

2)复合错误 Caused by —— 查看最后一个Caused by

* Invalid init2 in servlet mapping

2、目录匹配 (以/开始) 例如:/ /abc/ 即只要输入了/abc/后面再输任何的内容都可以匹配

/ 代表网站根目录

3、扩展名 (不能以/开始) 例如:.do .action 即只要以.do或是.action结尾的都可以匹配

典型错误 /*.do 切记不能用/开头

优先级:完全匹配>目录匹配 > 扩展名匹配

tomcat部署web应用的4种方法

4.Tomcat有哪几种部署方式?分别如何部署?

1.将整个项目复制到webapps目录
2.在tomcat安装目录/conf/catalina/localhost目录下创建xml文件,其中文件名就是访问地址, 内容,
path:浏览器上访问地址,如果没有指定则使用文件名做为访问地址
docBase:服务器项目真实目录

在Tomcat中有四种部署Web应用的方式,简要的概括分别是:
(1)利用Tomcat自动部署
(2)利用控制台进行部署
(3)增加自定义的Web部署文件(%Tomcat_Home%\conf\Catalina\localhost\AppName.xml)
(4)手动修改%Tomcat_Home%\conf\server.xml文件来部署web应用
第一种方式:利用Tomcat自动部署
利用Tomcat自动部署方式是最简单的、最常用的方式。若一个web应用结构为D:\workspace\WebApp\AppName\WEB-INF*,只要将一个Web应用的WebContent级的AppName直接扔进%Tomcat_Home%\webapps文件夹下,系统会把该web应用直接部署到Tomcat中。所以这里不再赘述。
第二种方式:利用控制台进行部署
若一个web应用结构为D:\workspace\WebApp\AppName\WEB-INF*,利用控制台进行部署的方式如下:进入tomcat的manager控制台的deploy区域——在Context path中键入”XXX”(可任意取名)——在WAR or Directory URL:键入D:\workspace\WebApp\AppName (表示去寻找此路径下的web应用)——点击deploy按钮。
然后在%Tomcat_Home%\webapps路径下将会自动出现一个名为XXX的文件夹,其内容即是D:\workspace\WebApp\AppName的内容,只是名字是XXX而已(这就是前面在Context path键入XXX的结果)。
以上说明利用控制台进行部署的实质仍然是利用Tomcat的自动部署。
第三种方式:增加自定义的Web部署文件
若一个web应用结构为D:\workspace\WebApp\AppName\WEB-INF*,这种部署方式稍微复杂一点,我们需要在%Tomcat_Home%\conf路径下新建一个文件夹catalina——再在其中新建一个localhost文件夹——最后再新建一个XML文件,即增加两层目录并新增XML文件:%Tomcat_Home%\conf\Catalina\localhost\web应用配置文件.xml ,该文件就是部署Web应用的配置文件。例如,我们新建一个%Tomcat_Home%\conf\Catalina\localhost\XXX.xml, 该文件的内容如下:

注意:
(1)以上代码中的workDir表示将该Web应用部署后置于的工作目录(Web应用中JSP编译成的Servlet都可在其中找到),如果使用的Eclipse作为IDE,一般可人为设置在WebApp的work目录下。
如果自定义web部署文件XXX.xml中未指明workdir,则web应用将默认部署在%Tomcat_Home%\work\Catalina\localhost\路径下新建的以XXX命名的文件夹下。(Web应用中JSP编译成的Servlet都可在其中找到)
(2)Context path即指定web应用的虚拟路径名。docBase指定要部署的Web应用的源路径。
其实开发者可以使用安装有Tomcat插件eclipse自动创建部署文件来部署Web应用而不必再手动建立该文件,方法如下:
1. 打开Eclipse——打开菜单栏window选择preference(首选项)——左侧选择Tomcat,如下图示:
2. 可以看到上图中高亮画出的Context declaration mode(Context 声明模式)中选择以Context files增加自定义部署文件的形式部署web应用——然后Contexts directory中指定上述文件的上级目录(即%Tomcat_Home%\conf\Catalina\localhost )——点击Apply或OK。
3. 完上述步骤,再选中Web项目右键点击properties(属性)——选择右侧的Tomcat ,如下图所示:
4. 勾上”Is a Tomcat project”前的checkbox,将项目关联至Tomcat。
在Context name中填入XXX,即Web应用自定义部署文件名和Context path名。
在Subdirectory to set as web application root (optional)中填入要部署的Web应用的实际路径(即WEB-INF上级目录)。
注意:Eclipse会自动地将workdir设置在Workspace\WebApp\work下。
如此便自动创建了%Tomcat_Home%\conf\Catalina\localhost\XXX.xml 文件。启动Tomcat 即可自动部署Web应用。
第四种方式:手动修改%Tomcat_Home%\conf\server.xml文件来部署web应用
此方法即打开%Tomcat_Home%\conf\server.xml文件并在其中增加以下元素:

    1. 然后启动Tomcat即可。<br /> 当然如果使用Eclipse,在Eclipse中的设置也有改变:打开菜单栏window选择preference(首选项)——左侧选择Tomcat——可以看到上图中高亮画出的Context declaration modeContext 声明模式)中选择以Server.xml文件来部署web应用。

    3.Jsp和Servlet(了解)

    3.1 Get和Post的区别?(web阶段day05-Request&Response)

    1. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
    2. Get传送的数据量较小,一般传输数据大小不超过1k-18k(根据浏览器不同,限制不一样,但相差不大这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
    3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
    4. Get执行效率却比Post方法好。Get是form提交的默认方法。

    3.2 HTTP中重定向和请求转发的区别?

    实现 转发:用request的getRequestDispatcher()方法得到ReuqestDispatcher对象,调用forward()方法
    request.getRequestDispatcher(“other.jsp”).forward(request, response);
    重定向:调用response的sendRedirect()方法
    response.sendRedirect(“other.jsp”);
    1> 重定向2次请求,请求转发1次请求
    2> 重定向地址栏会变,请求转发地址栏不变
    3> 重定向是浏览器跳转,请求转发是服务器跳转
    4> 重定向可以跳转到任意网址,请求转发只能跳转当前项目
    5> 重定向会丢失请求域中的数据,请求转发不会。

request中获取GET和POST请求参数
InterviewBook - 图82
URL和参数列表
一 获取请求方式
request.getMethod(); get和post都可用,
二 获取请求类型
request.getContentType(); get和post都可用,示例值:application/json ,multipart/form-data, application/xml等
三 获取所有参数key
request.getParameterNames(); get和post都可用,注:不适用contentType为multipart/form-data
四 获取参数值value
request.getParameter(“test”); get和post都可用,注:不适用contentType为multipart/form-data
五 获取取参数请求集合
request.getParameterMap(); get和post都可用,注: 不适用contentType为multipart/form-data
六 获取文本流
request.getInputStream() 适用于如:application/json,xml,multipart/form-data文本流或者大文件形式提交的请求或者xml等形式的报文
七 获取URL
getRequestURL()
八 获取参数列表:
1.getQueryString()
只适用于GET,比如客户端发送http://localhost/testServlet?a=b&c=d&e=f,通过request.getQueryString()得到的是a=b&c=d&e=f.
2.getParameter()
GET和POST都可以使用
但如果是POST请求要根据
表单提交数据的编码方式来确定能否使用.当编码方式是(application/x- www-form-urlencoded)时才能使用.
这种编码方式(application/x-www-form-urlencoded)虽然简单,但对于传输大块的二进制数据显得力不从心.
对于传输大块的二进制数这类数据,浏览器采用了另一种编码方式(“multipart/form-data”),这时就需要使用下面的两种方法.
3.getInputStream()
4.getReader()
上面两种方法获取的是Http请求包的包体,因为GET方式请求一般不包含包体.所以上面两种方法一般用于POST请求获取参数.
需要注意的是:
request.getParameter()、 request.getInputStream()、request.getReader()这三种方法是有冲突的,因为流只能被读一次。
比如:
当form表单内容采用 enctype=application/x-www-form-urlencoded编码时,先通过调用request.getParameter()方法得到参数后,
再调用request.getInputStream()或request.getReader()已经得不到流中的内容,
因为在调用 request.getParameter()时系统可能对表单中提交的数据以流的形式读了一次,反之亦然。
当form表单内容采用 enctype=multipart/form-data编码时,即使先调用request.getParameter()也得不到数据,
所以这时调用request.getParameter()方法对 request.getInputStream()或request.getReader()没有冲突,
即使已经调用了 request.getParameter()方法也可以通过调用request.getInputStream()或request.getReader()得到表单中的数据,
而request.getInputStream()和request.getReader()在同一个响应中是不能混合使用的,如果混合使用就会抛异常

Request和response中作用域是什么?操作作用的方法有哪些?

作用域:用于在服务器上实现数据共享,底层是一个Map结构

1.setAttribute(键,值) 向请求域中添加键和值
2.Object getAttribute(键) 通过键来获取值
3.removeAttribute(键) 通过键来删除键和值

请求域相关的方法:

1、setAttribut(“键”,Object值) 向请求域中添加一对键和值
2、Object getAttribute(“键”) 通过键获取值
3、RemoveAttribute(“键”) 通过键删除键值对
范围:只在一次请求中起作用

InterviewBook - 图83

13.1 Jsp和Servlet的区别?

相同点
jsp经编译后就变成了servlet,jsp本质就是servlet。
jvm只能识别java的类,不能识别jsp代码,web容器将jsp的代码编译成jvm能够识别的java类。其实就是当你通过 http 请求一个 JSP 页面是,首先 Tomcat 会将JSP翻译并编译成为 Servlet,然后执行 Servlet的生命周期方法处理请求与响应。

不同点
JSP侧重视图展现数据,Sevlet主要用于控制逻辑获取数据。
Servlet中没有内置对象 。
JSP中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。

3.JSP九大内置对象

out对象:用于向客户端、浏览器输出数据。
request对象:封装了来自客户端、浏览器的各种信息。
response对象:封装了服务器的响应信息。
exception对象:封装了jsp程序执行过程中发生的异常和错误信息。
config对象:封装了应用程序的配置信息。
page对象:指向了当前jsp程序本身,也就是当前Servlet对象。
session对象:用来保存会话信息。也就是说,可以实现在同一用户的不同请求之间共享数
application对象:代表了当前应用程序的上下文。可以在不同的用户之间共享信息。
pageContext对象:提供了对jsp页面所有内置对象的获取、四大域对象自动查找等。
JSP中的foreach标签有什么作用?常用的属性有哪些?
InterviewBook - 图84
JSP+Cookie+Session中有哪四个作用域?作用范围分别是什么?InterviewBook - 图85InterviewBook - 图86
InterviewBook - 图87InterviewBook - 图88InterviewBook - 图89

2.cookie和session的区别?(必会)

1.存储位置不同
cookie的数据信息存放在客户端浏览器上。
session的数据信息存放在服务器上。
2.存储容量不同
单个cookie保存的数据<=4KB,一个站点一般保存20~50个Cookie(不同浏览器不一样,Sarafi和Chrome对每个域的Cookie数目没有严格限制)。
对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东 西,并且设置session删除机制(或者采用缓存技术代替session)。
3.存储方式不同
cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。 ~~
session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。 ~~
4.隐私策略不同
cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。
session存储在服务器上,不存在敏感信息泄漏的风险。
5. 有效期上不同
开发可以通过设置cookie的属性,达到使cookie长期有效的效果。
session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。
6.服务器压力不同
cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。
session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。
InterviewBook - 图90

4.Ajax的介绍(必会)

概念介绍
Ajax 即”Asynchronous JavaScript And XML”(异步 JavaScript 和 XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。

第一种实现方式:Javascript原生ajax实现
1.创建ajax核心对象 let xmlHttp = new XMLHttpRequest();
2.设置请求信息
Get异步请求设置和传递数据: xmlHttp.open(“get”,“url?key1=value…”,true);
Post异步请求设置:
xmlHttp.open(“post”,“url”,true);
xmlhttp.setRequestHeader(“Content-type”,”application/x-www-form-urlencoded”);
3.设置回调函数(里面经过判断用于获取服务器响应的数据)
xmlHttp.onreadystatechange=function(){
if(xmlHttp.readyState==4 && xmlHttp.status==200){
xmlHttp.responseText; //获取服务器响应的数据
}
};
4.发送异步请求
Get异步发送:xmlHttp.send();
Post异步发送:xmlHttp.send(“key1=value1&key2=value2…”);
第一种实现方式:axios组件封装AJAX简化开发实现

方法名 作用
get(url?键=值&键=值) GET请求地址和参数,参数直接写在地址后面
get(url, {params:{键:值}}) 服务器端要以JSON格式获取,在Servlet中直接获取不到数据,用于SpringMVC中
post(url, “键=值&键=值”) POST请求的地址和参数,通常参数与地址要分开写,也可以与get的第一种方式一样写
post(url, {键:值}) 服务器端要以JSON格式获取,在Servlet中直接获取不到数据,用于SpringMVC中
then(正常回调函数) 回调函数的参数是服务器返回的对象,包含以下属性:
data:表示服务器返回的数据
status:表示服务器的状态码
注:如果使用匿名的回调函数的写法,在函数体内的this不是Vue对象。但用ES6的箭头语法,则可以使用this来引用Vue中data数据
catch(异常回调函数) 回调函数的参数是服务器返回的错误对象,包含以下属性:
message:服务器返回的错误信息

例如:post异步请求
axios.post(url,params)
.then(resp=>{
resp.data;//获取返回数据
}).catch(resp=>{
//错误回调函数
});

Ajax应用程序的优势
1. 通过异步模式,提升了用户体验
2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用
3. Ajax引擎在客户端运行,承担了一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载。

14 filter和listener

5.1 javaweb的三大组件及作用

Servlet: 用于处理请求与响应
Filter: 用于拦截请求与响应
Lisenter: 用于监听三大域对象request、session、servletContext的创建与销毁,和域中数据放生变化的时候会调用监听器实现逻辑控制。

5.2 过滤器的执行流程

访问过滤器拦截的路径,进入过滤器,首先拦截请求,其次放行给目标资源去执行,目标资源执行后会返回到过滤器中,最后执行拦截响应。

14.1 过滤器对象什么时候创建和什么时候销毁

过滤器对象是在服务器启动时创建,服务器关闭之前销毁过滤器对象。

5.4 过滤器的应用场景

权限控制 -比如:未登录访问资源、对公共资源放行
过滤-敏感词
全局乱码统一处理等

14.2 监听器的开发步骤

监听Attribute域对象的创建与销毁
创建类实现监听器接口
ServletContextListener
监听创建方法-contextInitialized
监听销毁方法-contextDestroyed
ServletContextAttributeListener
监听属性删除方法-attributeRemoved
监听属性修改方法-attributeReplaced
监听属性添加方法-attributeAdded
重写监听器接口的所有方法
编写注解@WebListener定义当前类为监听器类

五、框架(二)

1. Spring框架

1.1 Spring的两大核心是什么?谈一谈你对IOC的理解? 谈一谈你对DI的理解? 谈一谈你对AOP的理解?(必会)

Spring框架的两大核心特征:AOP和IoC

IoC(控制反转)是Spring的一个容器,他不是一种技术,而是一种思想,依旧是基于面向对象编程的。它能指导我们怎么样设计出松耦合、更优良的程序。

简单来说,如果现在有两个(甚至更多)类,A类和B类,A类要引用B类中的某个方法,传统编程是在A类中实例化一个B类,也就是通过new,然后打点调方法,而我们知道,代码高耦合最大的原因就是用了new。利用Spring框架就将实例化的过程交给了IoC容器,通过配置文件中的设置Bean或者B类中添加注解,A类可以不能new通过ApplicationContext的getBean方法得到实例,然后打点调方法就可以了,减少了两个类之间的耦合度。

IoC三种注入方式:

(1)接口注入(不推荐)

(2)构造方法注入(死的应用)。时效性好,但是灵活性差。

(3)赋值方式注入(常用)。时效性差,但是灵活性好,需要有set方法。

AOP(面向切面(接口)编程),同样的,他也是一种思想,而不是技术。和OOP(面向对象编程)相比较,AOP是对OOP的补充。OOP是静态的抽象,而AOP是动态的抽象。关于AOP的概念,简单来说就是将一个工程中与源代码无关,但是很多地方都要用,抽出来也不影响源代码上下文的那一部分代码抽出来,然后要用的时候就织入进去,进行使用,至于是在指定的代码之前使用还是之后又或者异常使用等,可以动态的进行。就好比你要结婚,婚礼的整个流程你可以自己负责,但是会耗费你的时间精力,这时候,你也可以选择将婚礼交给婚庆公司,你不再需要负责婚礼的具体筹备过程,只需要在婚礼当天使用婚庆公司给你的成果。所以说AOP是基于代理模式下进行的。

AOP的七个专业术语,为了理解,我们将整个AOP的专业属于比喻成在切片吐司上抹面包酱。

(1)目标对象(Target):我们需要对他进行操作的业务类。

我们要抹面包酱,就需要一片土司,那么这个吐司就是我们的目标对象。

(2)连接点(Joinpoint):连接点就是程序执行的某个特定的位置。

有了吐司之后,要抹面包,我们要找到抹的位置。

(3)切点(Pointcut):一个项目中有很多的类,一个类有很多个连接点,当我们需要在某个方法前插入一段增强(advice)代码时,我们就需要使用切点信息来确定,要在哪些连接点上添加增强。

要抹面包酱,我们需要知道连接点,也就是抹的位置,一块吐司的面很大,哪里都能抹下去,但是我们不能瞎抹,要知道磨的位置的信息,根据这些信息找到位置

(4)增强(也有叫通知的)(Advice):AOP(切面编程)是用来给某一类特殊的连接点,添加一些特殊的功能,那么我们添加的功能也就是增强。

抹面包,我们除了有吐司,还需要面包酱,增强就是这个面包酱

(5)切面(Aspect):是通知和切点的结合,通知和切点共同定义了切面的全部内容

(6)织入(Weaving):织入就是将增强添加到目标类具体连接点上的过程

将面包酱摸到吐司的抹个面上,就是织入

(7)AOP代理(AOP proxy):一个类被AOP织入后生成出了一个结果类,它是融合了原类和增强逻辑的代理类

吐司抹好了的那一面以及没有抹的那几面,构成的新吐司就是AOP代理

浅谈对Spring IOC以及DI的理解

1、IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如下图1,都是主动去创建相关对象然后再组合起来:
image.png
图1 传统程序设计结构示意图

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2所示

图2 有IoC/DI容器后程序结构示意图

2、IoC能做什么
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
3、IoC和DI
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

1、IoC(控制反转)
  首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看看找到自己喜欢的,然后打听她们的兴趣爱好、qq号、电话号………,想办法认识她们,投其所好送其所要……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个),使用完之后还要将对象销毁,对象始终会和其他的接口或类耦合起来。

  那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个要求的列表,告诉它我想找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个女孩,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

2、DI(依赖注入)
  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就是通过反射来实现注入的。

  1. Spring的两大核心是:IOC(控制翻转)和AOP(面向切面编程) DI(依赖注入)
    2. IOC的意思是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。最直观的表达就是,IOC让对象的创建不用去new了,可以由spring根据我们提供的配置文件自动生产,我们需要对象的时候,直接从Spring容器中获取即可.
    Spring的配置文件中配置了类的字节码位置及信息, 容器生成的时候加载配置文件识别字节码信息, 通过反射创建类的对象.
    Spring的IOC有三种注入方式 :构造器注入, setter方法注入, 根据注解注入。
    3. DI的意思是依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖Io c容器来动态注入对象需要的外部资源。
    4. AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect). SpringAOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
    5. Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:
    (1)JDK 动态代理只提供接口代理,不支持类代理,核心 InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起,Proxy 利用 InvocationHandler 动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
    (2) 如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

    1.2 Spring的生命周期?(高薪常问)

  2. 实例化一个Bean,也就是我们通常说的new
    2. 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入
    3. 如果这个Bean实现dao了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID
    4. 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
    5. 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法
    6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术
    7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法
    8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法
    注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
    9. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法
    10. 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法

    1.3 Spring支持bean的作用域有几种吗? 每种作用域是什么样的?(必会)

    Spring支持如下5种作用域:
    (1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
    (2)prototype:为每一个bean请求创建一个实例。
    (3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
    (4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
    (5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

    1.4 BeanFactory和ApplicationContext有什么区别(了解)

    BeanFactory:
    Spring最顶层的接口,实现了Spring容器的最基础的一些功能, 调用起来比较麻烦, 一般面向Spring自身使用
    BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去实例化
    ApplicationContext:
    是BeanFactory的子接口,扩展了其功能, 一般面向程序员身使用
    ApplicationContext在启动的时候就把所有的Bean全部实例化了

    1.5 Spring的对象默认是单例的还是多例的? 单例bean存不存在线程安全问题呢?(必会)

  3. 在spring中的对象默认是单例的,但是也可以配置为多例。
    2. 单例bean对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时,多线程操作该bean对象时会出现线程安全问题。
    原因是:多线程操作如果改变成员变量,其他线程无法访问该bean对象,造成数据混乱。
    解决办法:在bean对象中避免定义可变成员变量;
    在bean对象中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。

1.6@Resource和@Autowired依赖注入的区别是什么? @Qualifier使用场景是什么?(了解)

1.7 Spring框架中都用到了哪些设计模式?(必会)

  1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
    2. 单例模式:Bean默认为单例模式
    3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
    4. 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
    5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现—ApplicationListener

1.8 Spring事务的实现方式和实现原理(必会)

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

spring事务实现主要有两种方法
1、编程式,beginTransaction()、commit()、rollback()等事务管理相关的方法
2、声明式,利用注解Transactional 或者aop配置

1.9 你知道的Spring的通知类型有哪些,分别在什么时候执行?(了解)

Spring的通知类型有四种,分别为:
前置通知[]before]:在切点运行之前执行
后置通知[after-returning]:在切点正常结束之后执行
异常通知[after-throwing]:在切点发生异常的时候执行
最终通知[after]:在切点的最终执行
Spring还有一种特殊的通知,叫做环绕通知
环绕通知运行程序员以编码的方式自己定义通知的位置, 用于解决其他通知时序问题

@Resource
只能放在属性上,表示先按照属性名匹配IOC容器中对象id给属性注入值若没有成功,会继续根据当前属性的类型匹配IOC容器中同类型对象来注入值
若指定了name属性@Resource(name = “对象id”),则只能按照对象id注入值。
@Autowird
放在属性上:表示先按照类型给属性注入值如果IOC容器中存在多个与属性同类型的对象,则会按照属性名注入值
也可以配合@Qualifier(“IOC容器中对象id”)注解直接按照名称注入值。
放在方法上:表示自动执行当前方法,如果方法有参数,会自动从IOC容器中寻找同类型的对象给参数传值
也可以在参数上添加@Qualifier(“IOC容器中对象id”)注解按照名称寻找对象给参数传值。
@Qualifier使用场景:
@Qualifier(“IOC容器中对象id”)可以配合@Autowird一起使用, 表示根据指定的id在Spring容器中匹配对象

1.10 Spring的常用注解(必会)

  1. @Component(任何层) @Controller @Service @Repository(dao): 用于实例化对象
    2. @Scope : 设置Spring对象的作用域
    3. @PostConstruct @PreDestroy : 用于设置Spring创建对象在对象创建之后和销毁之前要执行的方法
    4. @Value: 简单属性的依赖注入
    5. @Autowired: 对象属性的依赖注入
    6. @Qualifier: 要和@Autowired联合使用,代表在按照类型匹配的基础上,再按照名称匹配。
    7. @Resource 按照属性名称依赖注入
    8. @ComponentScan: 组件扫描
    9. @Bean: 表在方法上,用于将方法的返回值对象放入容器
    10. @PropertySource: 用于引入其它的properties配置文件
    11. @Import: 在一个配置类中导入其它配置类的内容
    12. @Configuration: 被此注解标注的类,会被Spring认为是配置类。Spring在启动的时候会自动扫描并加载所有配置类,然后将配置 类中bean放入容器
    13. @Transactional 此注解可以标在类上,也可以表在方法上,表示当前类中的方法具有事务管理功能。

    1.11 Spring的事务传播行为(高薪常问)

    spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
    备注(方便记忆): propagation传播
    require必须的/suppor支持/mandatory 强制托管/requires-new 需要新建/ not -supported不支持/never从不/nested嵌套的
    ① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
    ② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
    ③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
    ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
    ⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
    ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

    1.12 Spring中的隔离级别

    ISOLATION隔离的意思
    ① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
    ② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
    ③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。解决脏读问题
    ④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。行锁
    ⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。表锁

    2.SpringMVC框架

    2.1谈一下你对SpringMVC框架的理解(了解)

    SpringMVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
    在我看来,SpringMVC就是将我们原来开发在servlet中的代码拆分了,一部分由SpringMVC完成,一部分由我们自己完成

    2.2 SpringMVC主要组件(必会)

    前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
    处理器映射器 HandlerMapping:根据请求的URL来查找Handler
    处理器适配器 HandlerAdapter:负责执行Handler
    处理器 Handler:处理业务逻辑的Java类
    视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view)
    视图View:View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等

2.3谈一下SpringMVC的执行流程以及各个组件的作用(必会)

InterviewBook - 图92
1. 用户发送请求到前端控制器(DispatcherServlet)
2. 前端控制器(DispatcherServlet)收到请求调用处理器映射器(HandlerMapping),去查找处理器(Handler)
3. 处理器映射器(HandlerMapping)找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4. 前端控制器(DispatcherServlet)调用处理器映射器(HandlerMapping)
5. 处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller,也叫后端控制器)。
6.自定义的处理器类(Controller,也叫后端控制器)将得到的参数进行处理并返回结果给处理器映射器(HandlerMapping)
7. 处理器适配器(HandlerAdapter)将得到的结果返回给前端控制器(DispatcherServlet)
8. DispatcherServlet(前端控制器)将ModelAndView传给视图解析器(ViewReslover)
9. 视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet)

  1. 前端控制器(DispatcherServlet)调用物理视图进行渲染并返回
    11. 前端控制器(DispatcherServlet)将渲染后的结果返回
    1654960473371.png

    2.4说一下SpringMVC支持的转发和重定向的写法(必会)

    1)转发:
    forward方式:在返回值前面加”forward:”,比如”””forward:user.do?name=method4”

2) 重定向:
redirect方式:在返回值前面加redirect:, 比如”redirect:http://www.baidu.com

2.5 SpringMVC的常用注解(必会)

1.@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
2.@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
3.@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
4.@PathVariable 用户从url路径上获取指定参数,标注在参数前 @PathVariabel(“要获取的参数名”)。
5.@RequestParam: 标注在方法参数之前,用于对传入的参数做一些限制,支持三个属性:
- value:默认属性,用于指定前端传入的参数名称
- required:用于指定此参数是否必传
- defaultValue:当参数为非必传参数且前端没有传入参数时,指定一个默认值

  1. @ControllerAdvice 标注在一个类上,表示该类是一个全局异常处理的类。
    ( restful风格:@RestControllerAdvice 等于:@ControllerAdvice+@ResponseBody)
    7. @ExceptionHandler(Exception.class) 标注在异常处理类中的方法上,表示该方法可以处理的异常类型。

    2.6谈一下SpringMVC统一异常处理的思想和实现方式(必会)

    使用SpringMVC之后,代码的调用者是SpringMVC框架,也就是说最终的异常会抛到框架中,然后由框架指定异常处理类进行统一处理
    方式一: 创建一个自定义异常处理器(实现HandlerExceptionResolver接口),并实现里面的异常处理方法,然后将这个类交给Spring容器管理
    方式二: 在类上加注解(@ControllerAdvice)表明这是一个全局异常处理类
    在方法上加注解(@ExceptionHandler),在ExceptionHandler中有一个value属性,可以指定可以处理的异常类型

    2.7在SpringMVC中, 如果想通过转发将数据传递到前台,有几种写法?(必会)

    方式一:直接使用request域进行数据的传递
    request.setAttirbuate(“name”, value);

  2. 传统方式:三大作用域.setAttribute,在转发到页面,在页面中就可以取出数据,但是这种只支持同步请求

image.png
方式二:使用Model进行传值,底层会将数据放入request域进行数据的传递
model.addAttribuate(“name”, value);

  1. 使用Model

image.png
方式三:使用ModelMap进行传值,底层会将数据放入request域进行数据的传递
modelmap.put(“name”,value);

  1. 使用ModelMap

image.png
方式四:借用ModelAndView在其中设置数据和视图
mv.addObject(“name”,value);
mv.setView(“success”);
return mv;

  1. 使用ModelAndView

image.png
方式五:@ResponseBody注解将返回的值【对象、数组、集合、Map集合】自动转化为JSON格式字符串,再利用响应对象中的输出流,这种方式是今后用得最多的,支持异步请求。
image.png

2.8在SpringMVC中拦截器的使用步骤是什么样的?(必会)

1 定义拦截器类:
SpringMVC为我们提供了拦截器规范的接口,创建一个类实现HandlerInterceptor,重写接口中的抽象方法;
preHandle方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,处理器也不会调用
postHandle方法:在调用完处理器后调用该方法
afterCompletion方法:在前端控制器渲染页面完成之后调用此方法
2 注册拦截器:
在SpringMVC核心配置文件中注册自定义的拦截器






2.9 在SpringMVC中文件上传的使用步骤是什么样的? 前台三要素是什么?(必会)

文件上传步骤:
1.加入文件上传需要的commons-fileupload包
2.配置文件上传解析器,springmvc的配置文件的文件上传解析器的id属性必须为multipartResolver
3.后端对应的接收文件的方法参数类型必须为MultipartFile,参数名称必须与前端的name属性保持一致
文件上传前端三要素:
1.form表单的提交方式必须为post
2.enctype属性需要修改为:multipart/form-data
3.必须有一个type属性为file的input标签,其中需要有一个name属性;如果需要上传多个文件需要添加multiple属性

2.10 SpringMVC 中如何解决GET|POST请求中文乱码问题?(了解)

(1)解决post请求乱码问题:在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;


CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter

encoding
utf-8



CharacterEncodingFilter
/*

  1. get请求中文参数出现乱码解决方法有两个:

①修改tomcat配置文件添加编码与工程编码一致,如下:

②另外一种方法对参数进行重新编码:
String userName= new String(request.getParamter(“userName”).getBytes(“ISO8859-1”),”utf-8”)
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。

3.SpringBoot

3.1 SpringBoot是什么(了解)

是Spring的子项目,主要简化Spring开发难度,去掉了繁重配置,提供各种启动器,可以让程序员很快上手,节省开发时间.

3.2 SpringBoot的优点(必会)

SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
版本锁定:解决是maven依赖版本容易冲突的问题,集合了常用的并且测试过的所有版本
使用了Starter(启动器)管理依赖并能对版本进行集中控制,如下的父工程带有版本号, 就是对版本进行了集中控制.


org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE

起步依赖 :解决了完成某一个功能要整合的jar包过多的问题,集合了常用的jar包
自动配置:解决了整合框架或者技术的配置文件过多,集合了所有的约定的默认配置
内置Tomcat:通过内置的tomcat,无需再用其他外置的Tomcat就直接可以运行javaEE程序
总之:人们把Spring Boot 称为搭建程序的脚手架。其最主要作用就是帮我们快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让我们关注与业务而非配置。

3.3 运行SpringBoot项目的方式(必会)

  • 可以打包
  • 可以使用Maven插件直接运行.
  • 直接运行main方法.

    3.4 SpringBoot的启动器starter(必会)

    (1)什么是starter?
    starter启动器,可以通过启动器集成其他的技术,比如说: web, mybatis, redis等等.可以提供对应技术的开发和运行环境.
    比如: pom中引入spring-boot-starter-web, 就可以进行web开发.
    (2)starter执行原理?

  • SpringBoot在启动时候会去扫描jar包中的一个名为spring.factories.

  • 根据文件中的配置,去加载自动配置类. 配置文件格式是key=value, value中配置了很多需要Spring加载的类.
  • Spring会去加载这些自动配置类, Spring读取后,就会创建这些类的对象,放到Spring容器中.后期就会从Spring容器中获取这些类对象.

(3)SpringBoot中常用的启动器

  • spring-boot-starter-web, 提供web技术支持
  • spring-boot-starter-test
  • spring-boot-starter-jdbc
  • spring-boot-starter-jpa
  • spring-boot-starter-redis…等等

    3.5 SpringBoot运行原理剖析(必会)

    (一) SpringApplication类作用及run()方法作用

  • SpringApplication这个类整合了其他框架的启动类, 只要运行这一个类,所有的整合就都完成了.

  • 调用run函数, 将当前启动类的字节码传入(主要目的是传入@SpringBootApplication这个注解), 以及main函数的args参数.
  • 通过获取当前启动类的核心信息, 创建IOC容器.

(二) 当前启动类@SpringBootApplication详细剖析
run函数传入的当前启动类字节码, 最重要的是传入了@SpringBootApplication, 点开该注解源码, 会发现有多个注解组成,接下来会详细解释每个注解的含义.
点开这个注解源码, 发现有4类注解.
InterviewBook - 图99
(1) 第一类: JDK原生注解4个
@Target(ElementType.TYPE) //当前注解的使用范围
@Retention(RetentionPolicy.RUNTIME) //生命周期
@Documented //声明在生成doc文档时是否带着注解
@Inherited //声明是否子类会显示父类的注解

(2)第二类: @SpringBootConfiguration
点开该注解源码, 会发现本质是@Configuration,定义该类是个配置类功能等同于xml配置文件.
InterviewBook - 图100

提到@Configuration就要提到他的搭档@Bean, 使用这两个注解就可以创建一个简单的Spring配置类, 可以用来替代相应的xml配置文件.可以理解为创建了IOC容器了.
(3)第三类: @ComponentScan, 包扫描功能.
这个注解对应Spring的XML配置中的@ComponentScan,其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义, 最终将这些bean定义加载到IoC容器中.
也可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围, 如果不指定, 则默认扫描@ComponentScan所在类的package及子包进行扫描。注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages, 这样能扫描root package及子包下的所有类.
(4)第四类: @EnableAutoConfiguration
点开源码会发现,本质是@import, 自动导入功能
InterviewBook - 图101
1. @EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器.
@EnableAutoConfiguration会根据类路径中的jar依赖为项目进行自动配置, 如:添加了spring-boot-starter-web依赖, 会自动添加Tomcat和SpringMVC的依赖, SpringBoot会对Tomcat和SpringMVC进行自动配置.
2. 那么SpringBoot是如何完成自动配置的呢?
A. SpringBoot自动配置的注解是 @EnableAutoConfiguration.B. 我们用的时候是在启动类上加@SpringBootApplication,这个注解是复合注解,内部包含 @EnableAutoConfigurationC. @EnableAutoConfiguration内部有一个@Import, 这个注解才是完成自动配置的关键.D. @Import导入一个类(AutoConfigurationImportSelector),这个类内部提供了一个方法(selectImports). 这个方法会扫描导入的所有jar包下的spring.factories文件. 解析文件中自动配置类key=value, 将列表中的类创建,并放到Spring容器中.
1655027118775.png
8.5.3 总结
总之一个@SpringBootApplication注解就搞定了所有事, 它封装了核心的@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan这三个类,大大节省了程序员配置时间,这就是SpringBoot的核心设计思想.

3.6 SpringBoot热部署(了解)

导入spring-boot-devtools这个jar包: 就可以完成热部署了.

3.7 SpringBoot中的配置文件(必会)

(1)有哪些配置文件?
bootstrap: yml/application
application: yml/application
(2)上面两种配置文件有什么区别?

  1. bootstrap由父ApplicationContext加载, 比application配置文件优先被加载.
  2. bootstarp里的属性不能被覆盖.
  3. application: springboot项目中的自动化配置.
  4. bootstrap:
    使用spring cloud config配置中心时, 需要加载连接配置中心的配置属性的, 就 可以使用bootstrap来完成.
    加载不能被覆盖的属性.
    加载一些加密/解密的数据.

(3)读取配置文件的方式?

  • 读取默认配置文件

需要注入Environment类, 使用environment.getProperty(peorperties中的key), 这样就能获得key对应的value值
@value(${key.value}) 直接读取

  • 读取自定义配置文件
  • 自定义配置文件后缀必须是.propeties
    • 编写和自定义配置文件对应的java类, 类上放3个注解
  • @ConfigurationProperties(“前缀”)
  • @PropertySource(“指定配置文件”)
  • @@Component包扫描
  • 读取的时候就跟读取默认配置文件一样.

    3.8 SpringBoot支持哪些日志框架(了解)

    Java Utils logging
    Log4j2
    Lockback
    如果你使用了启动器,那么springboo默认将Lockback作为日志框架.

    3.9 SpringBoot常用注解(必会)

  • @SpringBootApplication:它封装了核心的@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan这三个类,大大节省了程序员配置时间,这就是SpringBoot的核心设计思想.

  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器
  • @MapperScan:spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis接口的扫描
  • @RestController 是@Controller 和@ResponseBody的结合,一个类被加上@RestController注解,数据接口中就不再需要添加@ResponseBody,更加简洁。
  • @RequestMapping,我们都需要明确请求的路径.
  • @GetMappping,@PostMapping, @PutMapping, @DeleteMapping 结合@RequestMapping使用, 是Rest风格的, 指定更明确的子路径.
  • @PathVariable:路径变量注解,用{}来定义url部分的变量名.
  • @Service这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个注解交给spring容器。事务的切面也会配置在这一层。当让 这个注解不是一定要用。有个泛指组件的注解,当我们不能确定具体作用的时候 可以用泛指组件的注解托付给spring容器
  • @Component和spring的注解功能一样, 注入到IOC容器中.
  • @ControllerAdvice 和 @ExceptionHandler 配合完成统一异常拦截处理.

备注: 面试的时候记住6.7个即可~

24. 使用PageHelper分页查询的步骤是怎么样的?

一、pagehelper的功能:
·实现数据库的分页查询

二、操作步骤:

1、在pom文件中加入maven依赖

com.github.pagehelper

pagehelper

5.1.10
2、调用startPage方法,设置分页参数,这句话正好放在查询所有记录的代码前面

3、查询所有记录

4、封装到分页对象中,需要将查询到的记录放到PageInfo对象中
5、从分页对象中获取属性,重新封装成自定义Page对象,响应给前端

1655027744455.png

25. 使用AOP设置通用的属性的步骤是怎么样的?

1、定义一个类,加上@Advice注解表明这是一个切面类,加上@Component注解放到SpringBoot容器中

2、定义一个方法加上@Before前置增强注解、并且在execution参数中指定增加或修改方法

3、获取原始方法参数

4、利用反射,获取类对象;

5、根据反射得到的类对象和目标方法名-获取对应的方法对象;

6、再通过invoke执行方法并设置通用属性赋值,其中当前登录用户id从会话域中获取

7、输出日志。

1655028454116.png

26. 发送阿里云短信的步骤是怎么样的?

1 注册账号

2 开通短信服务

3 设置短信签名

那么什么是短信签名呢?

短信签名是短信发送者的署名,表示发送方的身份。我们要调用阿里云短信服务发送短信,签名是比不可少的部分。

4 设置短信模板

那么什么是模板呢?

短信模板包含短信发送内容、场景、变量信息。

5 设置AccessKey

AccessKey 是访问阿里云 API 的密钥,具有账户的完全权限,我们要想在后面通过API调用阿里云短信服务的接口发送短信,那么就必须要设置AccessKey。

6 配置权限

上述我们已经创建了子用户, 但是这个子用户,目前没有任何权限,接下来,我们需要为创建的这个用户来分配权限。

7 禁用/删除AccessKey

如果在使用的过程中 AccessKey 不小心泄漏了,我们可以在阿里云控制台中, 禁用或者删除该AccessKey。

然后再创建一个新的AccessKey, 保存好AccessKeyId和AccessKeySecret。

注意: 创建好了AccessKey后,请及时保存AccessKeyId 和 AccessKeySecret ,弹窗关闭后将无法再次获取该信息,但您可以随时创建新的 AccessKey。

27. 添加菜品到购物车的主要步骤是怎样的?

1、根据参数中的菜品id或套餐id是否为空,判断是菜品还是套餐

2、调用查询方法查询购物车中是否已存在

3、不存在,则将商品数量初始值设置为一,并将请求过来的实体类中为空的属性初始值设置后,添加入数据库中

4、若已存在,则将查询出来的实体类中商品数量加一、并更新数据库

5、统一返回响应结果

28. 使用Redis缓存菜品的步骤是怎么样的?

1.list方法查询菜品时,先从缓存取数据,如果缓存没有数据再查询数据库,并将查询结果放入缓存
2.改造保存和修改方法,保存或修改后需要清理缓存,保证下次查询到的结果时正确的
*使用缓存时注意:数据库的数据发生变化时,要及时清理缓存

29. SpringCache有哪些常用注解,作用分别是什么?

Spring Cache常用注解详解
@EnableCaching
开启Spring Cache框架支持。解析对应的注解,实现缓存读写访问

@CacheConfig
缓存配置,可以配置当前类型中所用缓存注解的通用信息

示例:配置当类前所有缓存注解的缓存前缀

@CacheConfig(cacheNames = “cache:prefix”)

@Cacheable
表示要对方法返回值进行缓存

注解属性:

cacheNames : 缓存key前缀名字
key :缓存key后缀
condition : SpringEL表达式,判断返回结果为true,缓存数据到redis。结果为false,不缓存数据到redis。

unless:SpringEL表达式,判断返回结果为false,缓存数据到redis。结果为true,不缓存数据到redis。
示例:

//执行方法时,返回结果做缓存
@Cacheable(cacheNames=”cache:prefix”,key = “‘all:values’”)

//方法参数id 作为key的一部分,做缓存
@CachePut(key = “‘TestServiceImpl:getById:’+#id”)

//方法参数id 作为key的一部分,做缓存,方法返回结果为null时,不做缓存
@Cacheable(key = “‘testUnless(‘+#id+’)’”,unless = “#result==null”)

//方法参数id大于0时 作为key的一部分,做缓存
@Cacheable(key = “‘TestServiceImpl:getById:’+#id”, condition = “#id > 0”)

@CacheEvict
淘汰缓存注解

注解属性:

allEntries 代表是否删除cacheNames对应的全部的缓存。 ,默认false,可选true。
注解属性和Cacheable相似
示例:

//执行方法时,根据key删除缓存
@CacheEvict(allEntries = true)

@CachePut
更新缓存,如果key存在覆盖缓存数据。key不存在,新增数据到缓存。

注解属性:跟@Cacheable相似

示例

//方法参数id 作为key的一部分,根据key更新缓存
@CachePut(key = “‘TestServiceImpl:getById:’+#id”)

30. Swagger生成文档有哪些常用注解,作用分别是什么?

1.@EnableSwagger2 @EnableKnife4j:开启Swagger和Knife4j的功能

2.@Api:用在Controller类上,表示对类的说明

3.@ApiModel:用在实体类上,描述实体类的作用

4.@ApiModelProperty:用在实体类的属性上,描述实体类的属性

5.@ApiOperation:用在方法上,说明方法的用途、作用

6.@ApiImplicitParams:用在方法上,表示一组参数说明

7.@ApiImplicitParam:用在方法上,用在@ApiImplicitParams注解中,指定一个请求参数的各个方面的属性
1655020144727.png
1655020149921.png

15 Dubbo

4.1 什么是dubbo(必会)

工作在soa面向服务分布式框架中的服务管理中间件。Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。关于注册中心、协议支持、服务监控等内容。
Dubbo使用的是缺省协议, 采用长连接和nio异步通信, 适合小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
反之, dubbo缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。

4.2 Dubbo的实现原理(必会)

InterviewBook - 图107

4.3 节点角色说明(必会)

Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。

4.4 调用关系说明(必会)

  1. 服务容器负责启动,加载,运行服务提供者。
    2. 服务提供者在启动时,向注册中心注册自己提供的服务。
    3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
    4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
    5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
    6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

    4.5 在实际开发的场景中应该如何选择RPC框架(了解)

    SpringCloud : Spring全家桶,用起来很舒服,只有你想不到,没有它做不到。可惜因为发布的比较晚,国内还没出现比较成功的案例,大部分都是试水,不过毕竟有Spring作背景,还是比较看好。
    Dubbox:相对于Dubbo支持了REST,估计是很多公司选择Dubbox的一个重要原因之一,但如果使用Dubbo的RPC调用方式,服务间仍然会存在API强依赖,各有利弊,懂的取舍吧。
    Thrift: 如果你比较高冷,完全可以基于Thrift自己搞一套抽象的自定义框架吧。
    Hessian:如果是初创公司或系统数量还没有超过5个,推荐选择这个,毕竟在开发速度.运维成本.上手难度等都是比较轻量.简单的,即使在以后迁移至SOA,也是无缝迁移。
    rpcx/gRPC:在服务没有出现严重性能的问题下,或技术栈没有变更的情况下,可能一直不会引入,即使引入也只是小部分模块优化使用。

1. Zookeeper是什么(了解)

Zookeeper 是一个分布式协调服务的开源框架, 主要用来解决分布式集群中应用系统的一致性问题, 例如怎样避免同时操作同一数据造成脏读的问题.
ZooKeeper 本质上是一个分布式的小文件存储系统. 提供基于类似于文件系统的目录树方式的数据存储, 并且可以对树中的节点进行有效管理. 从而用来维护和监控你存储的数据的状态变化. 通过监控这些数据状态的变化,从而可以达到基于数据的集群管理.
在大数据生态系统里,很多组件的命名都是某种动物,比如hadoop就是大象, hive就是蜜蜂, 而Zookeeper就是动物管理员.

2. Zookeeper的数据模型(必会)

  • ZK本质上是一个分布式的小文件存储系统.
  • ZK表现为一个分层的文件系统目录树结构, 既能存储数据, 而且还能像目录一样有子节点. 每个节点可以存最多1M左右的数据.
  • 每个节点称做一个Znode, 每个Znode都可以通过其路径唯一标识.
  • 而且客户端还能给节点添加watch, 也就是监听器, 可以监听节点的变化, 这个功能常在实际开发中作为监听服务器集群机器上下线操作.

2.1 节点结构
InterviewBook - 图108
图中的每个节点称为一个 Znode。 每个 Znode 由 3 部分组成:
① stat:此为状态信息, 描述该 Znode 的版本, 权限等信息
② data:与该 Znode 关联的数据
③ children:该 Znode 下的子节点
2.2 节点类型
Znode 有2大类4小类, 两大类分别为永久节点和临时节点.

  • 永久节点(Persistent): 客户端和服务器端断开连接后,创建的节点不会消失, 只有在客户端执行删除操作的时候, 他们才能被删除.
  • 临时节点(Ephemeral): 客户端和服务器端断开连接后,创建的节点会被删除.

Znode 还有一个序列化的特性, 这个序列号对于此节点的父节点来说是唯一的, 这样便会记录每个子节点创建的先后顺序. 它的格式为“%10d”(10 位数字, 没有数值的数位用 0 补充, 例如“0000000001”),因此节点可以分为4小类:

  1. - 永久节点(Persistent)
  2. - 永久_序列化节点(Persistent_Sequential)
  3. - 临时节点(Ephemeral)
  4. - 临时_序列化节点(Ephemeral_Sequential)

3. Zookeeper的watch监听机制(高薪常问)

  • 在ZooKeeper中还支持一种watch(监听)机制, 它允许对ZooKeeper注册监听, 当监听的对象发生指定的事件的时候, ZooKeeper就会返回一个通知.
  • Watcher 分为以下三个过程:客户端向ZK服务端注册 Watcher、服务端事件发生触发 Watcher、客户端回调 Watcher 得到触发事件情况.
    触发事件种类很多,如:节点创建,节点删除,节点改变,子节点改变等。
  • Watcher是一次性的. 一旦被触发将会失效. 如果需要反复进行监听就需要反复进行注册.

    3.1 监听器原理

    InterviewBook - 图109

  • 首先要有一个main()线程

  • 在main线程中创建Zookeeper客户端, 这时就会创建两个线程, 一个复制网络连接通信(connect), 一个负责监听(listener).
  • 通过connect线程将注册的监听事件发送给zk, 常见的监听有

监听节点数据的变化get path [watch]
监听节点状态的变化 stat path [watch]
监听子节点增减的变化 ls path [watch]

  • 将注册的监听事件添加到zk的注册的监听器列表中
  • 监听到有数据或路径变化, 就会将这个消息发送给listener线程.
  • listener线程内部调用了process()方法.此方法是程序员自定义的方法, 里面可以写明监听到事件后做如何的通知操作.

    3.2 监听器实际应用

    监听器+ZK临时节点能够很好的监听服务器的上线和下线.
    InterviewBook - 图110

  • 第一步: 先想zk集群注册一个监听器, 监听某一个节点路径

  • 第二步: 主要服务器启动, 就去zk上指定路径下创建一个临时节点.
  • 第三步: 监听器监听servers下面的子节点有没有变化, 一旦有变化, 不管新增(机器上线)还是减少(机器下线)都会马上给对应的人发送通知.

    4. Zookeeper的应用场景(高薪常问)

    ZK提供的服务包括: 统一命名服务, 统一配置管理, 统一集群管理, 集群选主, 服务动态上下线, 分布式锁等.

    4.1 统一命名服务

    统一命名服务使用的是ZK的node节点全局唯一的这个特点.
    在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。例如:IP不容易记住,而域名容易记住。创建一个节点后, 节点的路径就是全局唯一的, 可以作为全局名称使用.
    InterviewBook - 图111

    4.2 统一配置管理

    统一配置管理, 使用的是Zookeeper的watch机制

  • 需求: 分布式环境下, 要求所有节点的配置信息是一致的, 比如Kafka集群. 对配置文件修改后, 希望能够快速同步到各个节点上.

  • 方案: 可以把所有的配置都放在一个配置中心, 然后各个服务分别去监听配置中心, 一旦发现里面的内容发生变化, 立即获取变化的内容, 然后更新本地配置即可.
  • 实现: 配置管理可交由Zookeeper实现
  • 可将配置信息写入Zookeeper上的一个Znode.
  • 各个客户端服务器监听这个Znode.
  • 一旦Znode中的数据被修改, Zookeeper将通知各个客户端服务器.

InterviewBook - 图112

4.3 统一集群管理

统一集群管理使用的是Zookeeper的watch机制

  • 需求: 分布式环境中, 实时掌握每个节点的状态是必要的, 可以根据节点实时状态做出一些调整.
  • 方案: Zookeeper可以实现实时监控节点状态变化
  • 可将节点信息写入Zookeeper上的一个Znode.
  • 监听这个Znode可获取它的实时状态变化.

InterviewBook - 图113

4.4 集群选主

集群选主使用的是zookeeper的临时节点.

  • 需求: 在集群中, 很多情况下是要区分主从节点的, 一般情况下主节点负责数据写入, 从节点负责数据读取, 那么问题来了, 怎么确定哪一个节点是主节点的, 当一个主节点宕机的时候, 其他从节点怎么再来选出一个主节点呢?
  • 实现:使用Zookeeper的临时节点可以轻松实现这一需求, 我们把上面描述的这个过程称为集群选主的过程, 首先所有的节点都认为是从节点, 都有机会称为主节点, 然后开始选主, 步骤比较简单
    1. - 所有参与选主的主机都去Zookeeper上创建**同一个临时节点**,那么最终一定只有一个客户端请求能够 创建成功。
    2. - 成功创建节点的客户端所在的机器就成为了Master,其他没有成功创建该节点的客户端,成为从节点
    3. - 所有的从节点都会在主节点上注册一个子节点变更的Watcher,用于监控当前主节点是否存活,一旦 发现当前的主节点挂了,那么其他客户端将会重新进行选主。

InterviewBook - 图114

4.5 分布式锁

分布式锁使用的是Zookeeper的临时有序节点

  • 需求: 在分布式系统中, 很容出现多台主机操作同一资源的情况, 比如两台主机同时往一个文件中追加写入文本, 如果不去做任何的控制, 很有可能出现一个写入操作被另一个写入操作覆盖掉的状况.
  • 方案: 此时我们可以来一把锁, 哪个主机获取到了这把锁, 就执行写入, 另一台主机等待; 直到写入操作执行完毕,另一台主机再去获得锁,然后写入.
    这把锁就称为分布式锁, 也就是说:分布式锁是控制分布式系统之间同步访问共享资源的一种方式
  • 实现: 使用Zookeeper的临时有序节点可以轻松实现这一需求.
  1. 所有需要执行操作的主机都去Zookeeper上创建一个临时有序节点.
  2. 然后获取到Zookeeper上创建出来的这些节点进行一个从小到大的排序.
  3. 判断自己创建的节点是不是最小的, 如果是, 自己就获取到了锁; 如果不是, 则对最小的节点注册一个监听.
  4. 如果自己获取到了锁, 就去执行相应的操作. 当执行完毕之后, 连接断开, 节点消失, 锁就被释放了.
  5. 如果自己没有获取到锁, 就等待, 一直监听节点是否消失,锁被释放后, 再重新执行抢夺锁的操作.

InterviewBook - 图115

5. Zookeeper集群高级

5.1 ZK集群介绍

Zookeeper在一个系统中一般会充当一个很重要的角色, 所以一定要保证它的高可用, 这就需要部署Zookeeper的集群. Zookeeper 有三种运行模式: 单机模式, 集群模式和伪集群模式.

  • 单机模式: 使用一台主机不是一个Zookeeper来对外提供服务, 有单点故障问题, 仅适合于开发、测试环境.
  • 集群模式: 使用多台服务器, 每台上部署一个Zookeeper一起对外提供服务, 适合于生产环境.
  • 伪集群模式: 在服务器不够多的情况下, 也可以考虑在一台服务器上部署多个Zookeeper来对外提供服务.

    5.2 数据一致性处理

    ZK是一个分布式协调开源框架, 用于分布式系统中保证数据一致性问题, 那么ZK是如何保证数据一致性的呢?

    5.2.1 集群角色

    Leader: 负责投票的发起和决议, 更新系统状态, 是事务请求(写请求) 的唯一处理者, 一个ZooKeeper同一时刻只会有一个Leader.
    对于create创建/setData修改/delete删除等有写操作的请求, 则需要统一转发给leader 处理, leader 需要决定编号和执行操作, 这个过程称为一个事务.
    Follower: 接收客户端请求, 参与选主投票. 处理客户端非事务(读操作)请求,转发事务请求(写请求)给 Leader;
    Observer: 针对访问量比较大的 zookeeper 集群, 为了增加并发的读请求. 还可新增观察者角色.
    作用: 可以接受客户端请求, 把请求转发给leader, 不参与投票, 只同步leader的状态.
    InterviewBook - 图116

    5.2.2 Zookeeper的特性

    1)Zookeeper: 一个领导者(Leader), 多个跟随者(Follower)组成的集群.
    2)集群中只要有半数以上节点存活, Zookeeper集群就能正常服务.
    3)全局数据一致: 每个Server保存一份相同的数据副本, Client无论连接到哪个Server, 数据都是一致的.
    4)更新请求顺序性: 从同一个客户端发起的事务请求,最终会严格按照顺序被应用到zookeeper中.
    5)数据更新原子性: 一次数据更新要么成功, 要么失败。
    6)实时性,在一定时间范围内,Client能读到最新数据。

    5.2.3 ZAB协议
  • Zookeeper采用ZAB(Zookeeper Atomic Broadcast)协议来保证 分布式数据一致性 。

  • ZAB并不是一种通用的分布式一致性算法,而是一种专为Zookeeper设计的崩溃可恢复的原子消息广播算法。
  • ZAB协议包括两种基本模式: 崩溃恢复 模式和 消息广播 模式:
  • 消息广播模式主要用来进行事务请求的处理
  • 崩溃恢复模式主要用来在集群启动过程,或者Leader服务器崩溃退出后进行新的Leader服务器的选举以及数据同步.
    5.2.4 ZK集群写数据流程

InterviewBook - 图117

  • Client向Zookeeper的Server1上写数据, 发送一个写请求.
  • 如果Server1不是Leader, 那么Server1会把接受的请求进一步转发给Leader, 因为每个Zookeeper的Server里面有一个是Leader. 这个Leader会将写请求广播给各个Server, 比如Server1和Server2, 各个Server会将该写请求加入待写队列, 并向Leader发送成功信息(ack反馈机制).
  • 当Leader收到半数以上Server的成功信息, 说明该写操作可以执行. Leader会向各个Server发送事务提交信息, 各个Server收到信息后会落实队列里面的写请求, 此时写成功.
  • Server1会进一步通知Client数据写成功了, 这是就认为整个写操纵成功.

    5.3 ZK集群选举机制
  • Zookeeper服务器有四个状态:
    looking: 寻找leader状态, 当服务器处于该状态时, 它会认为当前集群中没有leader, 因此需要进入leader选举状态.
    leading: 领导者状态, 表明当前服务器角色是leader.
    following: 跟随者状态, 表明当前服务器角色是follower.
    observing:观察者状态, 表明当前服务器角色是observer。

  • 半数机制: 集群中半数以上机器存活, 集群可用, 所以Zookeeper适合安装奇数台服务器. 集群启动时, 如果当前机器票数超过了总票数一半则为Leader, Leader产生后, 投过票的机器就不能再投票了.
  • Zookeeper虽然在配置文件中没有指定主从节点. 但是, Zookeeper工作时, 是有一个节点Leader, 其他则为Follower, Leader是通过内部的选举机制临时产生的.
    配置文件中会指定每台ZK的myid, 而且不能重复, 通常用1,2,3…区分每台ZK的myid.
    5.3.1 集群启动器的选举机制
    在集群初始化阶段, 当有一台服务器server1启动时, 其单独无法进行和完成leader选举, 当第二台服务器server2启动时, 此时两台机器可以相互通信, 每台机器都试图找到leader, 于是进入leader选举过程.
    InterviewBook - 图118
  1. 服务器 1 启动, 服务器 1 状态保持为looking.
  2. 服务器 2 启动, 发起一次选举. 服务器 1 投票给比自己ID号大的服务器2. 服务器2投票给自己.
    投票结果: 服务器 1 票数 0 票, 服务器 2 票数 2 票, 没有半数以上结果, 选举无法完成, 服务器 1, 2 状态保持looking.
  3. 服务器 3 启动, 发起一次选举. 此时服务器 1 和 2 都会更改选票为服务器 3, 服务器3投票给自己.
    投票结果: 服务器 1 为 0 票, 服务器 2 为 0 票, 服务器 3 为 3 票. 此时服务器 3 的票数已经超过半数,服务器 3 当选 Leader. 服务器 1,2 更改状态为follower,服务器 3 更改状态为leader;
  4. 服务器 4 启动, 发起一次选举. 此时服务器 1,2,3 已经不是looking状态, 不会更改选票信息, 服务器4投票给自己.
    投票结果:服务器 3 为 3 票,服务器 4 为 1 票。此时服务器 4服从多数,更改选票信息为服务器 3,并更改状态为 following;
  5. 服务器 5 启动,同 4 一样当小弟.
    5.3.2 服务器运行时期的Leader选举
    在zk运行期间, leader与非leader服务器各司其职, 即便当有非leader服务器宕机或者新加入, 此时也不会影响leader. 但是一旦leader服务器宕机了, 那么整个集群将会暂停对外服务, 进入新一轮leader选 举, 其过程和启动时期的Leader选举过程基本一致.
    假设正在运行的有server1,server2,server3三台服务器,当前leader是server2,若某一时刻leader挂了, 此时便开始leader选举. 选举过程如下:
  • 变更转态, server1和server3变更为looking状态.
  • 开始投票, 每台服务器投票给比自己myid大的机器, 没有比自己大的就投给自己.
  • 这样server3有2票, server1有1票, server3的票数超过了集群一半, 当选leader, server1变更状态follower.

    6. 为什么zookeeper集群的数目,一般为奇数个?(高薪常问)

    1.容错
    由于在增删改操作中需要半数以上服务器通过,来分析以下情况。
    2台服务器,至少2台正常运行才行(2的半数为1,半数以上最少为2),正常运行1台服务器都不允许挂掉
    3台服务器,至少2台正常运行才行(3的半数为1.5,半数以上最少为2),正常运行可以允许1台服务器挂掉
    4台服务器,至少3台正常运行才行(4的半数为2,半数以上最少为3),正常运行可以允许1台服务器挂掉
    5台服务器,至少3台正常运行才行(5的半数为2.5,半数以上最少为3),正常运行可以允许2台服务器挂掉
    6台服务器,至少3台正常运行才行(6的半数为3,半数以上最少为4),正常运行可以允许2台服务器挂掉
    通过以上可以发现,3台服务器和4台服务器都最多允许1台服务器挂掉,5台服务器和6台服务器都最多允许2台服务器挂掉
    但是明显4台服务器成本高于3台服务器成本,6台服务器成本高于5服务器成本。这是由于半数以上投票通过决定的。
    2.防脑裂
    一个zookeeper集群中,可以有多个follower.observer服务器,但是必需只能有一个leader服务器。
    如果leader服务器挂掉了,剩下的服务器集群会通过半数以上投票选出一个新的leader服务器。
    集群互不通讯情况:
    一个集群3台服务器,全部运行正常,但是其中1台裂开了,和另外2台无法通讯。3台机器里面2台正常运行过半票可以选出一个leader。
    一个集群4台服务器,全部运行正常,但是其中2台裂开了,和另外2台无法通讯。4台机器里面2台正常工作没有过半票以上达到3,无法选出leader正常运行。
    一个集群5台服务器,全部运行正常,但是其中2台裂开了,和另外3台无法通讯。5台机器里面3台正常运行过半票可以选出一个leader。
    一个集群6台服务器,全部运行正常,但是其中3台裂开了,和另外3台无法通讯。6台机器里面3台正常工作没有过半票以上达到4,无法选出leader正常运行。
    通过以上分析可以看出,为什么zookeeper集群数量总是单出现,主要原因还是在于第2点,防脑裂,对于第1点,无非是正本控制,但是不影响集群正常运行。但是出现第2种裂的情况,zookeeper集群就无法正常运行了。

6. SpringCloud

6.1 SOA和微服务的区别?(必会)

谈到SOA和微服务的区别, 那咱们先谈谈架构的演变

  1. 集中式架构

项目功能简单, 一个项目只需一个应用, 将所有功能部署在一起, 这样的架构好处是减少了部署节点和成本.
InterviewBook - 图119
缺点: 代码耦合,开发维护困难, 无法水平扩展, 单点容错率低,并发能力差

  1. 垂直拆分架构

当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
InterviewBook - 图120

优点:

  • 系统拆分实现了流量分担,解决了并发问题
  • 可以针对不同模块进行优化, 方便水平扩展,负载均衡,容错率提高

缺点:

  • 系统间相互独立,会有很多重复开发工作,影响开发效率
  1. 分布式服务

当垂直应用越来越多, 随着项目业务功能越来越复杂, 并非垂直应用这一条线进行数据调用, 应用和应用之间也会互相调用, 也就是完成某一个功能,需要多个应用互相调用, 这就是将功能拆完来完成的分布式架构.
InterviewBook - 图121
优点: 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
缺点: 系统间耦合度变高,调用关系错综复杂,难以维护.

  1. 服务治理架构SOA

SOA :面向服务的架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键, 而最初的服务治理基石是Dubbo服务治理
InterviewBook - 图122
以前分布式服务的问题?

  • 服务越来越多,需要管理每个服务的地址, 调用关系错综复杂,难以理清依赖关系
  • 服务过多,服务状态难以管理,无法根据服务情况动态管理

SOA服务治理架构的优点:

  • 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
  • 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
  • 动态监控服务状态监控报告,人为控制服务状态

SOA服务治理架构的缺点:

  • 服务间依然会有依赖关系,一旦某个环节出错会影响较大(容错机制)
  • 服务关系复杂,运维、测试部署困难,不符合开发-运维一站式维护的思想.
  1. 微服务

前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实缺有一些差别:
微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责.
  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  • 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 自治:自治是说服务间互相独立,互不干扰
  • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
  • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
  • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
  • 数据库分离:每个服务都使用自己的数据源
  • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护. 基于docker容器是开发.

InterviewBook - 图123
目前微服务微服务架构主流的是SpringBoot+Dubbo和SpringBoot+SpringCloud的架构模式.

综上, 无论是SOA还是微服务, 都需要进行服务调度, 目前主流的服务调度室RPC和HTTP两种协议, 而Dubbo基于RPC的远程调度机构, SpringCloud是基于Rest风格(基于http协议实现的)的Spring全家桶微服务服务治理框架. 说到这里也可以继续说下Dubbo和SpringCloud的区别.

6.2 SpringCloud是什么?(了解)

SpringCloud是一系列框架的集合,集成SpringBoot,提供很多优秀服务:服务发现和注册,统一配置中心, 负载均衡,网关, 熔断器等的一个微服务治理框架.

6.3 SpringCloud的优势?(了解)

  • 因为SpringCloud源于Spring,所以它的质量,稳定性,持续性都是可以保证的。
  • SpringCloiud天热支持SpringBoot框架,就可以提高开发效率,能够实现需求。
  • SpringCloud更新很快,后期支持很给力。
  • SpringCloud可以用来开发微服务。

    6.4 SpringCloud有哪些核心组件?(必会)

  • Eureka: 注册中心, 服务注册和发现

  • Ribbon: 负载均衡, 实现服务调用的负载均衡
  • Hystrix: 熔断器
  • Feign: 远程调用
  • Zuul: 网关
  • Spring Cloud Config: 配置中心

(1)Eureka
提供服务注册和发现, 是注册中心. 有两个组件: Eureka服务端和Eureka客户端

  • Eureka服务端: 作为服务的注册中心, 用来提供服务注册, 支持集群部署.
  • Eureka客户端: 是一个java客户端, 将服务注册到服务端, 同事将服务端的信息缓存到本地, 客户端和服务端定时交互.
  1. 原理

InterviewBook - 图124

  • Eureka-Server:就是服务注册中心(可以是一个集群),对外暴露自己的地址。
  • 提供者:启动后向Eureka注册自己信息(地址,服务名称等),并且定期进行服务续约
  • 消费者:服务调用方,会定期去Eureka拉取服务列表,然后使用负载均衡算法选出一个服务进行调用。
  • 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
  1. 服务下线、失效剔除和自我保护
  • 服务的注册和发现都是可控制的,可以关闭也可以开启。默认都是开启
  • 注册后需要心跳,心跳周期默认30秒一次,超过90秒没发心跳认为宕机
  • 服务拉取默认30秒拉取一次.
  • Eureka每个60秒会剔除标记为宕机的服务
  • Eureka会有自我保护,当心跳失败比例超过阈值(默认85%),那么开启自我保护,不再剔除服务。
  • Eureka高可用就是多台Eureka互相注册在对方上.

(2)Ribbon

  • Ribbon是Netflix发布的云中服务开源项目. 给客户端提供负载均衡, 也就是说Ribbon是作用在消费者方的.
  • 简单来说, 它是一个客户端负责均衡器, 它会自动通过某种算法去分配你要连接的机器.
  • SpringCloud认为Ribbon这种功能很好, 就对它进行了封装, 从而完成负载均衡.
  • Ribbon默认负责均衡策略是轮询策略.

(3)Hystrix熔断器

  • 有时候可能是网络问题, 一些其它问题, 导致代码无法正常运行, 这是服务就挂了, 崩溃了. 熔断器就是为了解决无法正常访问服务的时, 提供的一种解决方案.
  • 解决因为一个服务崩溃而引起的一系列问题, 使问题只局限于这个服务中,不会影响其他服务.
  • Hystrix提供了两种功能, 一种是服务降级, 一种是服务熔断.
  1. 服务降级原理
    • Hystrix为每个服务分配了小的线程池, 当用户发请求过来, 会通过线程池创建线程来执行任务, 当创建的线程池已满或者请求超时(这里和多线程线程池不一样,不存在任务队列), 则启动服务降级功能.
    • 降级指的请求故障时, 不会阻塞, 会返回一个友好提示(可以自定义, 例如网站维护中请稍后重试), 也就是说不会影响其他服务的运行.
  2. 服务熔断原理

InterviewBook - 图125
状态机有3个状态:

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。
  • Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求次数最少不低于20次。
  • Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。

(4)Feign: 远程调用组件

  • 后台系统中, 微服务和微服务之间的调用可以通过Feign组件来完成.
  • Feign组件集成了Ribbon负载均衡策略(默认开启的, 使用轮询机制), Hystrix熔断器(默认关闭的, 需要通过配置文件进行设置开启)
  • 被调用的微服务需要提供一个接口, 加上@@FeignClient(“url”)注解
  • 调用方需要在启动类上加上@EnableFeignClients, 开启Feign组件功能.

(5)Zuul: 路由/网关

  • 对于项目后台的微服务系统, 每一个微服务都不会直接暴露给用户来调用的, 但是如果用户知道了某一个服务的ip:端口号:url:访问参数, 就能直接访问你. 如果再是恶意访问,恶意攻击, 就会击垮后台微服务系统.因此, 需要一个看大门的大boss, 来保护我们的后台系统.
  • Zuul类似Nginx, 反向代理功能. 用户访问服务, 首先会到Zuul中心, Zuul去Eureka中拉取服务, 获取服务列表, 获取具体的地址, 再通过具体的地址去访问目标微服务.
  • Zuul提供了过滤和路由两个功能, 过滤可以分配权限, 允许谁可以访问谁不可以访问, 路由则是去Eureka拉取服务提访问地址.
  • Zuul网关也继承了Ribbon负载均衡和Hystix熔断机制.

(6)Spring Cloud Config

  • 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中.
  • 那我们如果想在不重启微服务的情况下更新配置如何来实现呢? 我们使用SpringCloudBus来实现配置的自动更新.
    • 创建Config Server微服务, 用于存放配置文件, 默认使用git存储此项目.
    • 创建Config Client微服务, 用于拉取git上的配置文件, 项目中集成SpringCloudBus对应的消息中间件jar包.
  • 更新配置文件的流程

    • 手动向Mq队列中发送消息,http://127.0.0.1:12000/actuator/bus-refresh(固定地址)
    • Config Client中的MQ监听到消息, 去git服务器上加载新的配置文件, 并向mq中生产一条配置文件变更的消息.
    • 其他被集中管理的微服务也集成了mq,监听到消息, 向Config Client中重新获取最新的配置文件.

      6.5 SpringBoot和SpringCloud的关系(必会)

    • SpringBoot是为了解决Spring配置文件冗余问题, 简化开发的框架.

    • SpringCloud是为了解决微服务之间的协调和配置问题, 还有服务之间的通信, 熔断, 负载均衡远程调度任务框架.
    • SpringCloud需要依赖SpringBoot搭建微服务, SpringBoot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot来实现。
    • SpringBoot不需要依赖SpringCloud就可以独立开发. SpringBoot也可以集成Dubbo进行开发.

      6.6 SpringCloud和Dubbo的区别(高薪常问)

  • SpringCloud和Dubbo都是主流的微服务架构.

  • SpringCloud是Apache下的Spring体系下的微服务解决方案.
  • Dubbo是阿里系统中分布式微服务治理框架.
  • 技术方面对比
  • SpringCloud功能远远超过Dubbo, Dubbo只实现了服务治理(注册和发现). 但是SpringCloud提供了很多功能, 有21个子项目
  • Dubbo可以使用Zookeeper作为注册中心, 实现服务的注册和发现, SpringCloud不仅可以使用Eureka作为注册中心, 也可以使用Zookeeper作为注册中心.
  • Dubbo没有实现网关功能, 只能通过第三方技术去整合. 但是SpringCloud有zuul路由网关, 对请求进行负载均衡和分发. 提供熔断器, 而且和git能完美集成.
  • 性能方面对比
  • 由于Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC。
  • 而SpringCloud是基于Http协议+Rest接口调用远程过程的,相对来说,Http请求会有更大的报文,占的带宽也会更多。
  • 使用Dubbo时, 需要给每个实体类实现序列化接口, 将实体类转化为二进制进行RPC通信调用.而使用SpringCloud时, 实体类就不需要进行序列化.

刚才有提到注册中心不一样,那么Eureka和Zookeeper有什么区别? 我们继续往下说~

6.7 Eureka和Zookeeper的区别(高薪常问)

在谈这个问题前我们先看下CAP原则: C(Consistency)-数据一致性; A(Availability)-服务可用性; P(Partition tolerance)-服务对网络分区故障的容错性, 这三个特性在任何分布式系统中不能同时满足, 最多同时满足两个, 而且P(分区容错性)是一定要满足的.

  • Eureka满足AP(服务可用性和容错性), Zookeeper满足CP(数据一致性和容错性)
  • Zookeeper满足CP, 数据一致性, 服务的容错性. 数据在各个服务间同步完成后才返回用户结果, 而且如果服务出现网络波动导致监听不到服务心跳, 会立即从服务列表中剔除, 服务不可用.
  • Eureka满足AP, 可用性, 容错性. 当因网络故障时, Eureka的自我保护机制不会立即剔除服务, 虽然用户获取到的服务不一定是可用的, 但至少能够获取到服务列表. 用户访问服务列表时还可以利用重试机制, 找到正确的服务. 更服务分布式服务的高可用需求.
  • Eureka集群各节点平等, Zookeeper集群有主从之分.
  1. 如果Zk集群中有服务宕机,会重新进行选举机制,选择出主节点, 因此可能会导致整个集群因为选主而阻塞, 服务不可用.
  2. Eureka集群中有服务宕机,因为是平等的各个服务器,所以其他服务器不受影响.
  • Eureka的服务发现者会主动拉取服务, ZK服务发现者是监听机制
  1. Eureka中获取服务列表后会缓存起来, 每隔30秒重新拉取服务列表.
  2. Zk则是监听节点信息变化, 当服务节点信息变化时, 客户端立即就得到通知.

六、技术点

17 Nginx

1.1 Nginx是什么?

Nginx 是一个高性能的 HTTP 和反向代理服务器,及电子邮件代理服务器,同时也是一个非常高效的反向代理、负载平衡。

1.2 Nginx的作用?

1.反向代理,将多台服务器代理成一台服务器。
2.负载均衡,将多个请求均匀的分配到多台服务器上,减轻每台服务器的压力,提高 服务的吞吐量。
3.动静分离,nginx 可以用作静态文件的缓存服务器,提高访问速度

1.3 Nginx的优势?

  1. 可以高并发连接(5 万并发,实际也能支持 2~4 万并发)。
  2. 内存消耗少。
  3. 成本低廉。
  4. 配置文件非常简单。
  5. 支持 Rewrite 重写。
  6. 内置的健康检查功能。
  7. 节省带宽。
  8. 稳定性高。
  9. 支持热部署。

    1.4 什么是反向代理?

    反向代理是指以代理服务器来接受 internet 上的连接请求,然后将请求,发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
    反向代理总结就一句话:代理端代理的是服务端.

    1.5 什么是正向代理?

    一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
    正向代理总结就一句话:代理端代理的是客户端。

    1.6 什么是负载均衡?

    负载均衡即是代理服务器将接收的请求均衡的分发到各服务器中,负
    载均衡主要解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量,减少后台服务器大并发压力。

    1.7 Nginx是如何处理一个请求的?

    首先,nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip地址,然后在 nginx 的 master 进程里面先初始化好这个监控的 socket,再进行listen,然后再 fork 出多个子进程出来, 子进程会竞争 accept 新的连接。
    此时,客户端就可以向 nginx 发起连接了。当客户端与 nginx 进行三次握手,与 nginx
    建立好一个连接后,此时,某一个子进程会 accept 成功,然后创建 nginx 对连接的封装,即 ngx_connection_t 结构体,接着,根据事件调用相应的事件处理模块,如 http 模块与客户端进行数据的交换。
    最后,nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了。

    1.8 为什么Nginx性能这么高?

    得益于它的事件处理机制:异步非阻塞事件处理机制:运用了 epoll模型,提供了一个队列,排队解决。

    18 Redis

    2.1 Redis是什么?

    Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能非关系型(NoSQL)的(key-value)键值对数据库。可以用作数据库、缓存、消息中间件等。

    2.2 Redis 的存储结构有哪些?

    String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。
    Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和value 的映射表,特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40 多亿)
    List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列边或者尾部(右边)。最多可存储 232 - 1 元素(4294967295, 每个列表可存储 40 亿)
    Set,集合, 是 string 类型的无序集合,最大的成员数为 232 -1(4294967295, 每个集合可存储 40 多亿个成员)。
    Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。

    2.3 Redis 的优点?

    1 因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。Redis 支持事务 、持久化
    2、单线程操作,避免了频繁的上下文切换。
    3、采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流。

    2.4 为什么要用 Redis

    高性能:
    假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

InterviewBook - 图126
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

InterviewBook - 图127

2.5 redis持久化机制

Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(Append Only File)。
RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。
AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。

redis常用数据类型

String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。
Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和value 的映射表,特别适合用于存储对象。每个 hash 可以存储 (40 多亿)键值对
List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列边或者尾部(右边)。最多可存储 (40 多亿)元素(4294967295, 每个列表可存储 40 亿)
Set,集合, 是 string 类型的无序且不重复集合,最大的成员数为 (40 多亿)(4294967295, 每个集合可存储 40 多亿个成员)。
Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。

2.6 Redis 的缺点

2.6.1 缓存和数据库双写一致性问题

一致性的问题很常见,因为加入了缓存之后,请求是先从 redis中查询,如果 redis 中存在数据就不会走数据库了,如果不能保证缓存跟数据库的一致性就会导致请求获取到的数据不是最新的数据。
解决方案:
1、编写删除缓存的接口,在更新数据库的同时,调用删除缓存
的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。
2、消息队列:ActiveMQ,消息通知。

2.6.2缓存的并发竞争问题

并发竞争,指的是同时有多个子系统去 set 同一个 key 值。
解决方案:
1、最简单的方式就是准备一个分布式锁,大家去抢锁,抢到
锁就做 set 操作即可

2.6.3缓存雪崩问题

缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波
请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
1、给缓存的失效时间,加上一个随机值,避免集体失效。
2、使用互斥锁,但是该方案吞吐量明显下降了。
3、搭建 redis 集群。

2.6.4缓存击穿问题

缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,
再去请求数据库。没得到锁,则休眠一段时间重试
2、采用异步更新策略,无论 key 是否取到值,都直接返回,
value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程
去读数据库,更新缓存。

2.7 Redis 集群

2.7.1主从复制

主从复制原理
从服务器连接主服务器,发送 SYNC 命令。主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成RDB 文件并使用缓冲区记录此后执行的所有写命令。主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令。从服务器收到快照文件后丢弃所有旧数据,载入收到的快照。主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令。
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令(从服务器初始化完成)。主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)。

优点
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由 Master 来完成Slave同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据。

缺点
Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP才能恢复。主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP后还会引入数据不一致的问题,降低了系统的可用性。Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

2.7.2 哨兵模式

当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。为此,Redis2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控 Redis 系统的运行状况,它的功能包括以下两个。
1、监控主服务器和从服务器是否正常运行。
2、主服务器出现故障时自动将从服务器转换为主服务器。

哨兵的工作方式
每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从务器以及其他 Sentinel(哨兵)进程发送一个 PING 命令。如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被Sentinel(哨兵)进程标记为主观下线(SDOWN)。如果一个 Master 主服务器被标记为主观下线(SDOWN),则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN),则 Master 主服务器会被标记为客观下线(ODOWN)。
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一169 / 196次的频率向集群中的所有Master主服务器,Slave从服务器发送 INFO命令。当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。若没有足够数量的 Sentinel(哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复,Master 主服务器的主观下线状态就会被移除。
优点
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系统更健壮,可用性更高。
缺点
Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

2.7.3 Redis-Cluster 集群

redis 的哨兵模式基本已经可以实现高可用,读写分离,但是在这种模式下每台 redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0 上加入了 cluster 模式,实现的 redis 的分布式存储,也就是说每台 redis 节点上存储不同的内容。Redis-Cluster 采用无中心结构,它的特点如下:
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与 redis 节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

工作方式
在 redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的取值范围是:0-16383。还有一个就是 cluster,可以理解为是一个集群管理的插件。当我们的存取的 key 到达的时候,redis会根据 crc16 的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。为了保证高可用,redis-cluster 集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与A 通信超时,那么认为主节点 A 宕机了。如果主节点 A 和它的从节点A1 都宕机了,那么该集群就无法再提供服务了。

2.8 Redis的分布式锁

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
1>安全特性:互斥访问,即永远只有一个 client 能拿到锁
2>避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
3>容错性:只要大部分 Redis 节点存活就可以正常提供服务
Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:设置成功,返回 1 。设置失败,返回 0 。
InterviewBook - 图128
使用SETNX完成同步锁的流程及事项如下:
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除

19 RocketMQ

3.1消息中间件的区别?

InterviewBook - 图129

3.2 为什么要使用MQ?

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

InterviewBook - 图130

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

生产者(Producer):负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。
消费者(Consumer):负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。
消息服务器(Broker):是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。
名称服务器(NameServer):用来保存 Broker 相关 Topic 等元信息并给 Producer ,提供 Consumer 查找 Broker 信息。

3.4 RocketMQ消费模式有几种?

消费模型由Consumer决定,消费维度为Topic。
集群消费
1.一条消息只会被同Group中的一个Consumer消费
2.多个Group同时消费一个Topic时,每个Group都会有一个Consumer消费到数据
广播消费
消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。即即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。

3.5 RocketMQ如何做负载均衡?

通过Topic在多Broker中分布式存储实现。
(1)producer端
发送端指定message queue发送消息到相应的broker,来达到写入时的负载均衡

  • 提升写入吞吐量,当多个producer同时向一个broker写入数据的时候,性能会下降
  • 消息分布在多broker中,为负载消费做准备

默认策略是随机选择:

  • producer维护一个index
  • 每次取节点会自增
  • index向所有broker个数取余
  • 自带容错策略

其他实现:

  • SelectMessageQueueByHash
  • hash的是传入的args
  • SelectMessageQueueByRandom
  • SelectMessageQueueByMachineRoom 没有实现

也可以自定义实现MessageQueueSelector接口中的select方法
MessageQueue select(final List mqs, final Message msg, final Object arg);
(2) consumer端
采用的是平均分配算法来进行负载均衡。
其他负载均衡算法
平均分配策略(默认)(AllocateMessageQueueAveragely)
环形分配策略(AllocateMessageQueueAveragelyByCircle)
手动配置分配策略(AllocateMessageQueueByConfig)
机房分配策略(AllocateMessageQueueByMachineRoom)
一致性哈希分配策略(AllocateMessageQueueConsistentHash)
靠近机房策略(AllocateMachineRoomNearby)
追问:当消费负载均衡consumer和queue不对等的时候会发生什么?
Consumer和queue会优先平均分配,如果Consumer少于queue的个数,则会存在部分Consumer消费多个queue的情况,如果Consumer等于queue的个数,那就是一个Consumer消费一个queue,如果Consumer个数大于queue的个数,那么会有部分Consumer空余出来,白白的浪费了。

3.6消息重复消费如何解决?

影响消息正常发送和消费的重要原因是网络的不确定性。
出现原因
正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从queue中剔除
当ack因为网络原因无法发送到broker,broker会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer。
消费模式:在CLUSTERING模式下,消息在broker中会保证相同group的consumer消费一次,但是针对不同group的consumer会推送多次
解决方案

  • 数据库表:处理消息前,使用消息主键在表中带有约束的字段中insert
  • Map:单机时可以使用map做限制,消费时查询当前消息id是不是已经存在
  • Redis:使用分布式锁。

    3.7 如何让RocketMQ保证消息的顺序消费

    你们线上业务用消息中间件的时候,是否需要保证消息的顺序性?
    如果不需要保证消息顺序,为什么不需要?假如我有一个场景要保证消息的顺序,你们应该如何保证?
    首先多个queue只能保证单个queue里的顺序,queue是典型的FIFO,天然顺序。多个queue同时消费是无法绝对保证消息的有序性的。所以总结如下:
    同一topic,同一个QUEUE,发消息的时候一个线程去发送消息,消费的时候 一个线程去消费一个queue里的消息。
    追问:怎么保证消息发到同一个queue?
    Rocket MQ给我们提供了MessageQueueSelector接口,可以自己重写里面的接口,实现自己的算法,举个最简单的例子:判断i % 2 == 0,那就都放到queue1里,否则放到queue2里。

    3.8 RocketMQ如何保证消息不丢失

    首先在如下三个部分都可能会出现丢失消息的情况:
    Producer端
    Broker端
    Consumer端
    3.8.1 Producer端如何保证消息不丢失
    采取send()同步发消息,发送结果是同步感知的。
    发送失败后可以重试,设置重试次数。默认3次。
    producer.setRetryTimesWhenSendFailed(10);
    集群部署,比如发送失败了的原因可能是当前Broker宕机了,重试的时候会发送到其他Broker上。
    3.8.2 Broker端如何保证消息不丢失
    修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。
    flushDiskType = SYNC_FLUSH
    集群部署,主从模式,高可用。
    3.8.3 Consumer端如何保证消息不丢失
    完全消费正常后在进行手动ack确认。

    3.9 RocketMQ的消息堆积如何处理

  1. 如果可以添加消费者解决,就添加消费者的数据量.
    2、如果出现了queue,但是消费者多的情况。可以使用准备一个临时的topic,同时创建一些queue,在临时创建一个消费者来把这些消息转移到topic中,让消费者消费。

    3.10 RocketMQ如何实现分布式事务?

    1、生产者向MQ服务器发送half消息。
    2、half消息发送成功后,MQ服务器返回确认消息给生产者。
    3、生产者开始执行本地事务。
    4、根据本地事务执行的结果(UNKNOW、commit、rollback)向MQ Server发送提交或回滚消息。
    5、如果错过了(可能因为网络异常、生产者突然宕机等导致的异常情况)提交/回滚消息,则MQ服务器将向同一组中的每个生产者发送回查消息以获取事务状态。
    6、回查生产者本地事物状态。
    7、生产者根据本地事务状态发送提交/回滚消息。
    8、MQ服务器将丢弃回滚的消息,但已提交(进行过二次确认的half消息)的消息将投递给消费者进行消费。
    Half Message:预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中
    检查事务状态:Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事务执行状态(提交、回滚、未知),如果是未知,Broker会定时去回调在重新检查。
    超时:如果超过回查次数,默认回滚消息。
    也就是他并未真正进入Topic的queue,而是用了临时queue来放所谓的half message,等提交事务后才会真正的将half message转移到topic下的queue。

    3.11 任何一台Broker突然宕机了怎么办?

    Broker主从架构以及多副本策略。Master收到消息后会同步给Slave,这样一条消息就不止一份了,Master宕机了还有slave中的消息可用,保证了MQ的可靠性和高可用性。而且Rocket MQ4.5.0开始就支持了Dlegder模式,基于raft的,做到了真正意义的HA。

20 RabbitMQ

4.1 什么是 rabbitmq?

采用 AMQP 高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦

4.2 使用 rabbitmq 的场景 ?

1、服务间异步通信
2、顺序消费
3、定时任务
4、请求削峰

4.3 如何确保消息正确地发送至 RabbitMQ? 如何确保消息接收方消费了消息?

采发送方确认模式 将信道设置成 confirm 模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的 ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。
如果 RabbitMQ 发生内部错误从而导致消息丢失,会发送一条 nack(notacknowledged,未确认)消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
接收方确认机制
接收方消息确认机制
消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ 才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ 仅通过 Consumer 的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ 给了 Consumer 足够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊情况
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ 会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)如果消费者接收到消息却没有确认消息,连接也未断开,则 RabbitMQ 认为该消费者繁忙,将不会给该消费者分发更多的消息。

4.4 如何避免消息重复投递或重复消费?

采在消息生产时,MQ 内部针对每条生产者发送的消息生成一个 inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;
在消息消费时,要求消息体中必须要有一个 bizId(对于同一业务全局唯一,如支付 ID、订单 ID、帖子 ID 等)作为去重的依据,避免同一条消息被重复消费。

4.5 Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别?

采对于吞吐量来说kafka和RocketMQ支撑高吞吐,ActiveMQ和RabbitMQ比他们低一个数量级。对于延迟量来说RabbitMQ是最低的。
1.从社区活跃度
按照目前网络上的资料,RabbitMQ 、activeM 、ZeroMQ 三者中,综合来看,RabbitMQ 是首选。
2.持久化消息比较
ActiveMq 和RabbitMq 都支持。持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了,消息不会丢失的机制。
3.综合技术实现
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。
RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。当然ZeroMq 也可以做到,不过自己必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。
4.高并发
毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的erlang 语言。
5.比较关注的比较, RabbitMQ 和 Kafka RabbitMq 比Kafka 成熟,在可用性上,稳定性上,可靠性上, RabbitMq 胜于 Kafka (理论上)。另外,Kafka 的定位主要在日志等方面, 因为Kafka 设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以 如果业务方面还是建议选择 RabbitMq 。还有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出来很多

21 MongoDb

5.1 MongoDB是什么?

mongodb是属于文档型的非关系型数据库,是开源、高性能、高可用、可扩展的
数据逻辑层次关系:文档=>集合=>数据库
在关系型数据库中每一行的数据对应mongodb里是一个文档。mongodb的文档是以BSON(binary json)格式存储的,其格式就是json格式。
InterviewBook - 图131
1>集合
集合是一组文档(即上面的 users 集合)。集合相当于关系数据库中的表,但集合中的文档长度可不同(集合中的文档中的键值对个数可不同)、集合中文档的key可不同。向集合中插入第一个文档时,集合会被自动创建。
2>文档
文档是一组键值对,用{ }表示,字段之间用逗号分隔。相当于关系数据库中的一行(一条记录)。示例:一个文档

1
2
3
4
5
6
{
id: “1”,
name: “张三”,
age: 28,
email: “zhangs@gmail.com”
}

这样写是为了方便看字段,也可以写在一起{id:”1”,name:”张三”,age:28,email:”zhangs@gmail.com”},一样的。
说明:
文档中的键值对是有序的
一个文档中不能有重复的key(对应关系数据库中的一条记录)
以”_”开头的key是保留的,有特殊含义。
3>字段
即一个键值对,key必须是String类型,value可以是任意类型。

5.2 MongoDB和关系型数据库mysql区别

InterviewBook - 图132
上图中,左边的是 MySQL 数据库中 users 表,右边是 MongoDB 中 users 集合。虽然表现形式不同,但是数据内容还是一样的。其中:

test:表示数据库
users:表示集合,类似MySQL中的表
{id:”1”,name:”张三”,age:28,email:”zhangs@gmail.com”}:表示一个文档,类似于MySQL中的记录
id、name、age和email:表示字段
InterviewBook - 图133

5.3 MongoDB有3个数据库

一个MongoDB中可以建立多个数据库,这些数据库是相互独立的,有自己的集合和权限。不同的数据库使用不同的文件存储(不存储在一个文件中)。
MongoDB默认有3个数据库:
admin: 从权限的角度来看,这是”root”数据库。将一个用户添加到这个数据库,这个用户会自动继承所有数据库的权限。一些特定的服务器端命令也只能在这个数据库中运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据库永远不会被复制,里面的数据都是本地的(不会复制到其他MongoDB服务器上),可以用来存储限于本地单台服务器的任意集合
config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

5.4 Mongo中的数据类型

  1. null
    2. false和true
    3. 数值
    4. UTF-8字符串
    5. 日期 new Date()
    6. 正则表达式
    7. 数组
    8. 嵌套文档
    9. 对象ID ObjectId()
    10. 二进制数据
    11. 代码

5.5 MongoDB适用业务场景

网站数据:MongoDB 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性
缓存:由于性能很高,MongoDB 也适合作为信息基础设施的缓存层。在系统重启之后,由 MongoDB 搭建的持久化缓存层可以避免下层的数据源过载
大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储
高伸缩性的场景:MongoDB 非常适合由数十或数百台服务器组成的数据库。MongoDB的路线图中已经包含对 MapReduce 引擎的内置支持
用于对象及 JSON 数据的存储:MongoDB的 BSON 数据格式非常适合文档化格式的存储及查询.

22 FastDFS

6.1 FastDFS是什么?

FastDFS 是一个开源的轻量级分布式文件系统,它可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大 容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、 视频网站等等。

6.2 FastDFS组成

1. Storage server(存储服务器)
Storage server一般都是以组(group)为组织单位,一个组中有多个Storageserver,数据互为备份(意味着每个 Storageserver 的内容是一致的,他们之间没有主从之分),组的存储空间以组内最小的 Storage server 为准,所以为了避 免浪费存储空间最好的话每个 Storage server 的配置最好相同。
2. Tracker server(调度服务器、追踪服务器)
232Tracker server 主要负责管理所有的 Storage server 和 group,每个 storage 在启动后会连接 Tracker,告知自己所属的 group 等信息,并保持周期性的心跳,tracker 根据 storage 的心跳信息,建立 group==>[storage server list]的映射表。

6.3 FastDFS的流程

InterviewBook - 图134
1、选择 tracker server
当集群中不止一个 tracker server 时,由于 tracker 之间是完全对等
的关系,客户端在 upload 文件时可以任意选择一个 trakcer。
2、选择存储的 group
当 tracker 接收到 upload file 的请求时,会为该文件分配一个可以
存储该文件的 group,支持如下选择 group 的规则:
1. Round robin,所有的 group 间轮询。
2. Specified group,指定某一个确定的 group。
3. Load balance,剩余存储空间多多 group 优先。
4. 选择 storage server。
3、选择 storage server
233当选定 group 后,tracker 会在 group 内选择一个 storage server
给客户端,支持如下选择 storage 的规则:
1. Round robin,在 group 内的所有 storage 间轮询。
2. First server ordered by ip,按 ip 排序。
3. First server ordered by priority,按优先级排序(优先级在
storage 上配置)。
4、选择 storage path
当分配好 storage server 后,客户端将向 storage 发送写文件请求,
storage 将会为文件分配一个数据存储目录,支持如下规则:
1. Round robin,多个存储目录间轮询。
2. 剩余存储空间最多的优先。
5、生成 Fileid
选定存储目录之后,storage 会为文件生一个 Fileid,由 storage server ip、文件创建时间、文件大小、文件 crc32 和一个随机数拼接而成,然后将这个二进制串进行 base64 编码,转换为可打印的字符串。
6、选择两级目录
当选定存储目录之后,storage 会为文件分配一个 fileid,每个存储目录下有两级 256256 的子目录,storage 会按文件 fileid 进行两次 hash (猜测),路由到其中一个子目录,然后将文件以 fileid 为文件名存储到该子目录下。
*7、生成文件名

当文件存储到某个子目录后,即认为该文件存储成功,接下来会为234该文件生成一个文件名,文件名由 group、存储目录、两级子目录、fileid、 文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。

6.4 FastDFS 如何现在组内的多个 storage server 的数据同步?

当客户端完成文件写至 group 内一个 storage server 之后即认为文件上传成功,storage server 上传完文件之后,会由后台线程将文件同步至同 group 内其他的 storage server。后台线程同步参考的依据是每个 storageserver 在 写完文件后,同时会写一份 binlog,binlog 中只包含文件名等元信息,storage 会记录向 group 内其他 storage 同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有 server 的时钟保持同步。

23 JWT

JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。
在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。
每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。

7.1组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{“typ”:”JWT”,”alg”:”HS256”}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码(http://base64.xpcha.com/),编码后的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:
1. 标准中注册的声明(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token
2 .公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
3. 私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim, JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。定义一个payload:
{“sub”:”1234567890”,”name”:”John Doe”,”admin”:true}
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRta W4iOnRydWV9
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

7.2 使用场景

1. 一次性验证
比如用户注册后需要发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其它可能的账户……这种场景就和 jwt 的特性非常贴近,jwt 的 payload中固定的参数:iss 签发者和 exp 过期时间正是为其做准备的。
2. restful api 的无状态认证
使用 jwt 来做 restful api 的身份认证也是值得推崇的一种使用方案。客户端和服务端共享 secret;过期时间由服务端校验,客户端定时刷新;签名信息不可被修改……spring security oauth jwt 提供了一套完整的 jwt 认证体系,以笔者的经验来看:使用 oauth2 或 jwt 来做 restful api 的认证都没有大问题,oauth2 功能更多,支持的场景更丰富,后者实现简单。
3.使用 jwt 做单点登录+会话管理(不推荐)

7.3面试问题

  1. JWT token 泄露了怎么办?(常问)

使用 https 加密你的应用,返回 jwt 给客户端时设置 httpOnly=true 并且使用 cookie 而不是 LocalStorage 存储 jwt,这样可以防止 XSS 攻击和 CSRF攻击。

  1. Secret如何设计?

jwt 唯一存储在服务端的只有一个 secret,个人认为这个 secret 应该设计成和用户相关的属性,而不是一个所有用户公用的统一值。这样可以有效的避免一些注销和修改密码时遇到的窘境。

  1. 注销和修改密码?

传统的 session+cookie 方案用户点击注销,服务端清空 session 即可,因为状态保存在服务端。但 jwt 的方案就比较难办了,因为 jwt 是无状态的,服务端通过计算来校验有效性。没有存储起来,所以即使客户端删除了 jwt,但是该 jwt 还是在有效期内,只不过处于一个游离状态。分析下痛点:注销变得复杂的原因在于 jwt 的无状态。提供几个方案,视具体的业务来决定能不能接受:
仅仅清空客户端的 cookie,这样用户访问时就不会携带 jwt,服务端就认为用户需要重新登录。这是一个典型的假注销,对于用户表现出退出的行为,实际上这个时候携带对应的 jwt 依旧可以访问系统。
清空或修改服务端的用户对应的 secret,这样在用户注销后,jwt 本身不变,但是由于 secret 不存在或改变,则无法完成校验。这也是为什么将 secret 设计成和用户相关的原因。
借助第三方存储自己管理 jwt 的状态,可以以 jwt 为 key,实现去 Redis 一类的缓存中间件中去校验存在性。方案设计并不难,但是引入 Redis 之后,就把无状态的 jwt 硬生生变成了有状态了,违背了 jwt 的初衷。实际上这个方案和 session 都差不多了。
修改密码则略微有些不同,假设号被到了,修改密码(是用户密码,不是 jwt 的 secret)之后,盗号者在原 jwt 有效期之内依旧可以继续访问系统,所以仅仅清空 cookie 自然是不够的,这时,需要强制性的修改 secret。

  1. 如何解决续签问题

传统的 cookie 续签方案一般都是框架自带的,session 有效期 30 分钟,30 分钟内如果有访问,session 有效期被刷新至 30 分钟。而 jwt 本身的 payload 之中也有一个 exp 过期时间参数,来代表一个 jwt 的时效性,而 jwt 想延期这个 exp 就有点身不由己了,因为 payload 是参与签名的,一旦过期时间被修改,整个 jwt 串就变了,jwt 的特性天然不支持续签。
解决方案
1. 每次请求刷新 jwt。
jwt 修改 payload 中的 exp 后整个 jwt 串就会发生改变,那就让它变好了,每次请求都返回一个新的 jwt 给客户端。只是这种方案太暴力了,会带来的性能问题。
2.只要快要过期的时候刷新 jwt
此方案是基于上个方案的改造版,只在前一个jwt的最后几分钟返回给客户端一个新的 jwt。这样做,触发刷新 jwt 基本就要看运气了,如果用户恰巧在最后几分钟访问了服务器,触发了刷新,万事大吉。如果用户连续操作了 27 分钟,只有最后的 3 分钟没有操作,导致未刷新 jwt,无疑会令用户抓狂。
3. 完善 refreshToken
借鉴 oauth2 的设计,返回给客户端一个 refreshToken,允许客户端主动刷新 jwt。一般而言,jwt 的过期时间可以设置为数小时,而 refreshToken 的过期时间设置为数天。
4. 使用 Redis 记录独立的过期时间
在 Redis 中单独为每个 jwt 设置了过期时间,每次访问时刷新 jwt 的过期时间,若 jwt 不存在与 Redis 中则认为过期。

  1. 如何防止令牌被盗用?

令牌:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJObzAwMDEiLCJpYXQiOjE1NjkxNTg4MDgsInN1YiI6IuS4u-mimCIsImlzcyI6Ind3dy5pdGhlaW1hLmNvbSIsImV4cCI6MTU2OTE1ODgyMywiYWRkcmVzcyI6IuS4reWbvSIsIm1vbmV5IjoxMDAsImFnZSI6MjV9.lkaOahBKcQ-c8sBPp1Op-siL2k6RiwcEiR17JsZDw98
如果令牌被盗,只要该令牌不过期,任何服务都可以使用该令牌,有可能引起不安全操作。我们可以在每次生成令牌的时候,将用户的客户端信息获取,同时获取用户的IP信息,然后将IP和客户端信息以MD5的方式进行加密,放到令牌中作为载荷的一部分,用户每次访问微服务的时候,要先经过微服务网关,此时我们也获取用户客户端信息,同时获取用户的IP,然后将IP和客户端信息拼接到一起再进行MD5加密,如果MD5值和载荷不一致,说明用户的IP发生了变化或者终端发生了变化,有被盗的嫌疑,此时不让访问即可。这种解决方案比较有效。
当然,还有一些别的方法也能减少令牌被盗用的概率,例如设置令牌超时时间不要太长。

七、黑马头条

一、项目介绍

1.1项目背景

随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻。
InterviewBook - 图135

1.2 项目概述

黑马头条项目是对在线教育平台业务进行大数据统计分析的系统。碎片化、切换频繁、社交化和个性化现如今成为人们阅读行为的标签。黑马头条对海量信息进行搜集,通过系统计算分类,分析用户的兴趣进行推送从而满足用户的需求。
InterviewBook - 图136

1.3 需求说明

1.4 功能架构图

InterviewBook - 图137

1.5 APP主要功能大纲

  • 频道栏:用户可以通过此功能添加自己感兴趣的频道,在添加标签时,系统可依据用户喜好进行推荐
  • 文章列表:需要显示文章标题、文章图片、评论数等信息,且需要监控文章是否在APP端展现的行为
  • 搜索文章:联想用户想搜索的内容,并记录用户的历史搜索信息
  • 查看文章:用户点击文章进入查看文章页面,在此页面上可进行点赞、评论、不喜欢、分享等操作;除此之外还需要收集用户查看文章的时间,是否看我等行为信息
  • 注册登录:登录时,验证内容为手机号登录/注册,通过手机号验证码进行登录/注册,首次登录用户自动注册账号。
  • 实名认证:用户可以进行身份证认证和实名认证,实名认证之后即可成为自媒体人,在平台上发布文章
  • 个人中心:用户可以在其个人中心查看收藏、关注的人、以及系统设置等功能

    1.6 自媒体端功能大纲

  • 内容管理:自媒体用户管理文章页面,可以根据条件进行筛选,文章包含草稿、已发布、未通过、已撤回状态。用户可以对文章进行修改,上/下架操作、查看文章状态等操作

  • 评论管理:管理文章评论页面,显示用户已发布的全部文章,可以查看文章总评论数和粉丝评论数,可以对文章进行关闭评论等操作
  • 素材管理:管理自媒体文章发布的图片,便于用户发布带有多张图片的文章
  • 图文数据:自媒体人发布文章的数据:阅读数、评论数、收藏量、转发量,用户可以查看对应文章的阅读数据
  • 粉丝画像:内容包括:粉丝性别分布、粉丝年龄分布、粉丝终端分布、粉丝喜欢分类分布

    1.7 平台管理端功能大纲

  • 用户管理:系统后台用来维护用户信息,可以对用户进行增删改查操作,对于违规用户可以进行冻结操

  • 用户审核:管理员审核用户信息页面,用户审核分为身份审核和实名审核,身份审核是对用户的身份信息进行审核,包括但不限于工作信息、资质信息、经历信息等;实名认证是对用户实名身份进行认证
  • 内容管理:管理员查询现有文章,并对文章进行新增、删除、修改、置顶等操作
  • 内容审核:管理员审核自媒体人发布的内容,包括但不限于文章文字、图片、敏感信息等
  • 频道管理:管理频道分类界面,可以新增频道,查看频道,新增或修改频道关联的标签
  • 网站统计:统计内容包括:日活用户、访问量、新增用户、访问量趋势、热门搜索、用户地区分布等数据
  • 内容统计:统计内容包括:文章采集量、发布量、阅读量、阅读时间、评论量、转发量、图片量等数据
  • 权限管理:超级管理员对后台管理员账号进行新增或删除角色操作

    1.8 其它需求

    InterviewBook - 图138

    1.9 交互需求

    InterviewBook - 图139

    二.功能介绍

    随着 Restful API、微服务的兴起,基于 Token 的认证现在已经越来越普遍。基于token的用户认证是一种服务端无状态的认证方式,所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。
    当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。
    InterviewBook - 图140

    2.1 admin端-登录实现

    根据用户名和密码登录,验证用户名和密码不能为空。首先查询该用户是否存在。根据名称查询,判断,如果用户是空,数据不存在。然后进行密码比对,与数据库里的信息比,如果不一致,返回密码错误,如果密码正确,返回token,并且返回部分用户信息(隐藏敏感信息)给前端。

    2.2 app端

    2.2.1 APP端用户认证

    全局过滤器实现jwt校验
    InterviewBook - 图141
    通过网关进行授权验证
    首先判断地址中是否包含登录的地址,如果是登陆的地址,不需要token,直接放行,如果不是登陆地址,需要验证token。先获取token(获取请求头对象),判断token是否为空,如果为空返回401,结束请求,不为空需要验证token,验证通过,需要将用户ID放入到请求头中,供后面的微服务使用。

    2.2.2 App端用户认证审核

    18InterviewBook - 图142
    流程说明
    InterviewBook - 图143用户审核功能
    根据ID查询用户,判断审核状态,如果是审核失败,修改用户状态,并且添加失败的原因,并保存并更新后的数据。审核通过,需要创建自媒体用户,构建自媒体用户,通过Feign远程调用接口,判断结果。如果创建自媒体用户成功,那么则继续创建作者,如果作者不为空,需要将authorId更新到自媒体账号中,修改apUserRealname状态,修改apUser中的flag,构建作者信息,通过Feign远程调用接口,判断结果。

    2.2.3 登录功能

    app端登录-需求分析

  • 点击登录可以根据app端的手机号和密码进行登录

  • 点击不登录,先看看可以在无登录状态下进入app

app端登录-思路分析
概念介绍:用户设备,即当前用户所使用的终端设备。
1,用户点击登录

  • 用户输入手机号和密码到后端进行校验,校验成功生成token返给前端
  • 其他请求需要带着token到app网关去校验jwt,校验成功,放行

2,用户点击不登录,先看看

  • 获取用户的设备id到后端根据设备id生成token,设置jwt存储的id为0
  • 其他请求需要带着token到app网关去校验jwt,校验成功,放行

InterviewBook - 图144
端登录-功能实现
用户登陆,查询用户,判断用户的密码是否与传递进来的密码一致,判断用户的密码是否与传递进来的密码一致,密码一致生成token,发送token给前端。

2.2.4 关注作者或取消关注

InterviewBook - 图145如上效果:
当前登录后的用户可以关注作者,也可以取消关注作者
思路分析
一个用户关注了作者,作者是由用户实名认证以后开通的作者权限,才有了作者信息,作者肯定是app中的一个用户。
从用户的角度出发:一个用户可以关注其他多个作者
从作者的角度出发:一个用户(同是作者)也可以拥有很多个粉丝(用户)
实现步骤:
1 前端传递作者id获取作者信息,最终获取到作者在当前app端的账号id
2 如果是关注操作,需要保存数据,用户保存关注的作者,作者保存当前的粉丝
2.1 异步记录关注行为(后面开发,为了推荐做准备)
3 如果是取消关注,删除用户关注的作者,删除作者当前的粉丝
InterviewBook - 图146
app端关注信息表
记录了当前登录用户和关注人(作者)的关系,方便当前用户查看关注的作者

  • app端用户粉丝表
  • 记录了作者与粉丝的关系,方便作者查看自己的粉丝,同时当前作者也是app中的一个用户

InterviewBook - 图147
app用户表与app作者表是一对一关系,只有在用户认证以后才会有作者出现

  • app用户表与app用户关注表是一对多的关系,一个用户可以关注多个作者
  • app用户表与app用户粉丝表是一对多的关系,一个用户可以拥有多个粉丝
  • InterviewBook - 图148InterviewBook - 图149
  • 实现分析
  • 判断当前用户是否是正常登陆(不是设备ID),发送消息,行为处理都是可以记录的,有登录用户,设置登录用户的ID,根据authorId获取userId,获取关注的历史记录,查询该用户对应作者的粉丝信息,判断操作类型,0关注,1取消。

2.2.5 用户行为处理

用户操作行为记录-点赞行为
需求分析
InterviewBook - 图150
当前登录的用户点击了”赞“,就要保存当前行为数据
涉及到的表
InterviewBook - 图151
实现分析
调用微服务查询当前用户点赞记录,更新点赞量时需要判断当前用户是否已经对这篇文章有过点赞数据,没有点赞记录,则更新文章库中文章表的点赞数,行为库中添加点赞记录,如果有点赞记录,不再新增,但是可以修改(取消点赞),获取当前的点赞状态,判断点赞记录的类型与当前是否一致,不一致且操作为取消点赞,如果点赞记录为空,保存点赞记录,取消点赞更新文章表中点赞数,更新行为库记录。

2.2.6 评论系统

文章详情页下方可以查看评论信息,按照点赞数量倒序排列,展示评论内容、评论的作者、点赞数、回复数、时间,默认查看20条评论,如果想查看更多,可以点击加载更多进行分页
评论概述:
登录用户可以进行评论,可以针对当前文章发布评论。
评论内容不为空且不超过140字,评论内容需要做文本反垃圾检测,评论内容需要做敏感词过滤,用户登录后才可以对文章发表评论。
评论话术:
每次用户要评论时,都会进行过滤,通过滤类,确保用户登陆后才能进行评论。
然后识别评论内容,字数判断,140字以内。文本反垃圾检测、阿里云安全审核敏感词。
都通过后,保存用户评论,正式发布评论。
点赞话术:
同样通过过滤器,保证用户登录后才可以对评论点赞。
根据当前用户id和评论id查询是否已有点赞记录,如果已有显示为已点赞。
如果没有点赞记录,新增点赞记录,更新评论的点赞数+1
已有点赞记录,判断点赞记录中的点赞状态是否与当前操作一致
不一致进行点赞数更新和点赞记录更新点赞操作,
根据点赞或取消点赞,评论数进行加减1

评论列表话术:
根据文章id查询评论列表,按照点赞的数量进行排序。
如果用户已经登录,需要判断当前登录用户,对评论列表中的哪些评论已点赞。
评论列表会进行分页查询。

回复话术:
回复功能包括,回复评论、回复列表展示、回复点赞。
当用户点击了评论中的回复时就可以查看所有评论回复的内容。
当用户针对当前评论回复时,需要更新回复数量。
当用户对回复点赞时,保存当前回复的点赞信息。

2.2.7 文章搜索

文章搜索话术:
在APP端正上方有搜索框,可以输入搜索的关键字。
根据用户输入的关键字通过es分词查找文章列表。
文章列表的展示和home页展示一样,当用户点击某一篇文章时,可查看详情。
从es的查询效率远比直接查数据库要快。所以需要把文章相关的数据存储到es中。
搜索时查询es、展示列表,记录当前用户的搜索历史。
查询结果按照发布时间倒序排列。

搜索记录话术:
搜索历史记录会保存之前的5调搜索关键词。用户可以选择手动删除。
在搜索时,会把搜索记录进行保存,存储的是用户实体信息和搜索关键词记录。
在用户再次点击到搜索框时,会查询当前用户的搜索记录列表,展示给用户。
当用户删除记录时,会将状态修改为0,让历史记录无效,便不会展示。

关键词联想:
根据用户输入的关键字进行联想,默认展示5条联想词。
将用户搜索记录经过数据清洗后保存到联想词表,这是一个动态的数据,定时每天抓取搜索较高的1万条数据放到这里,之后根据关键字进行查询
由于每次用户输入都会访问数据库,所以使用redis缓存进行缓解压力。
讲清晰的词分成词组,根据用户输入的在redis中查询出5条,并展示。
数据结构为Trie树

2.3 自媒体文章

2.3.1 自媒体文章列表查询

业务/需求:
InterviewBook - 图152
所属:内容列表
分页:需要进行分页
条件:文章状态、关键字、频道列表、发布日期
可以通过左侧的 “内容列表” 进入 文章查询页面。
筛选条件有:
文章状态: 全部、草稿、待审核、审核通过、审核失败
关键字: 用户输入
频道列表: 通过查询列表并展示
发布日期: 可选择区间
默认查询当前用户的文章,状态:全部,不带有其他筛选信息,分页展示文章列表。

话术:
1.从本地线程获取到当前用户的id,如果id为null则用户未登录,给出”请登录”提示。
确保登陆后,通过用户id查询出,用户的文章列表。
2.其他相关条件查询时,都会先判断是否为空,
确保用户有输入或选择后再加入对应的查询条件。
条件包含:
关键字、所属频道ID、文章状态、发布时间的开始和结束
其中所属频道ID在 ad_channel表中,因为该表id从1开始递增。
如果id值大于0,会根据所属id进行查询,
(频道ID从ad_channel表中查询出所有频道加载到页面即可)
3.对查询的结果进行排序,我们选择根据发布时间倒叙排列。根据实际业务需求可选创建时间、发布时间等进行排序
4.封装好后将其返回,完成文章查询功能。

2.3.2 自媒体文章的发布、修改、保存功能

业务/需求:
InterviewBook - 图153
文章包含:
标题:InterviewBook - 图154
内容:
InterviewBook - 图155
文字内容:点击后可以手动输入需要添加的内容。可以多条。
InterviewBook - 图156
图片内容:可以从素材库选择或本地上传,可以上传多个图片。
InterviewBook - 图157
标签:用户自定义标签InterviewBook - 图158
频道:可以从列表中选择InterviewBook - 图159
定时:设置发布时间,到时间自动发布
InterviewBook - 图160
封面图片:无图、一张图、三张图
跟前端布局有关。一般都是以上三种图片布局
InterviewBook - 图161
话术:
该功能为保存、修改、保存草稿的共有方法,
其中需要注意的是
修改:如果有那么是修改文章,先删除所有素材关联关系,再继续操作。
点击修改按钮,携带文章id,进入编辑页面,可以修改文章的详细信息。
修改的详细信息,都在文章发布功能中实现,如果有ID为修改,无ID为新增。

  1. 封面图片:<br />如果选择是自动需要从内容中截取图片做为封面图片<br />截取规则为:内容图片的个数小于等于2 则为单图截图一张图,内容图片大于等于3,则为多图,截图三张图,内容中没有图片,则为无图<br /> 设置发布和创建时间,通过当前线程获取当前用户,未登录给出提示。<br /> 通过dto对象获取封面图片,并提取内容中的图片,返回一个图片地址urlList集合。<br /> 判断当前发布文章的封面类型 如果是-1为自动。<br /> 关于封面图片选择 “自动” 选项时,我们会提取内容中的图片。<br /> 通过判断typeimage统计数量并获取其值。<br /> 内容中图片数量 0为无图。小于等于2 1 大于2 3图。<br /> 超过3图会取前三个作为封面。<br /> 多张封面图片使用逗号分隔存入。<br /> 另外如果是保存草稿不需要添加素材和文章的关系。否则正常添加关系即可。

2.3.3 自媒体文章的修改

业务/需求:

InterviewBook - 图162
通过点击现有文章进入编辑页面。可以对现有文章进行编辑修改。
话术:
点击修改的时候,就是根据文章id查询,跳转至编辑页面进行展示。
这里只做一个页面跳转,具体修改功能的实现是由之前自媒体文章发布功能实现。

2.3.4 自媒体文章的删除功能

业务/需求:

InterviewBook - 图163
点击现有文章的删除按钮,可以删除文章。
想要删除时,需要几个前提条件

  1. 文章id存在
  2. 文章存在
  3. 状态不是9

然后可以进行文章和素材关系的解除。之后再进行删除文章即可。
话术:
在做文章删除功能时,除了状态为9 已发布的文章不能删除外。
其他状态状态下都可以进行删除。
我们会先把文章和素材的关联关系删除掉,然后再进行对应文章的删除。

2.3.5 自媒体文章的上下架功能

业务/需求:
InterviewBook - 图164
对已发布的文章进行上下架操作
针对当前状态为9(已发布)的文章进行上下架操作,并把数据同步到文章库中。使用kafka实现

话术:
前台页面只有文章状态为9的会出现上下架选项,
先WmNewsdto对象保证不为空,并且id不为空。然后获取文章
确保文章存在,并且状态为9 已发布时,就可以进行上下架操作了。
我们只需要把enable修改一下即可。然后通过Kafka同步到App端
直接发送一个包含上下架信息的主题(Topic),存的是map转换后的json包含enable(上下架状态),和articleId(发布文章库ID)。

2.3.6 自媒体文章审核

1.自媒体文章审核的流程
业务/需求:
自媒体文章可以自由发布,所以文章的内容是否符合规范、安全非常重要。所以我们要细化内容审核的流程,来把控发布的文章。
文章审核分为 人工审核 和 自动审核。
人工审核:
状态为4的进行人工审核。我们只需要把文章分配给审核工作人员即可。
如果人工审核的文章发布时间大于当前时间,即可直接修改状态为8 审核通过(待发布)
否则保存文章相关数据。
自动审核:
状态为1的需要进行自动审核。
我们会调用阿里云文本反垃圾服务,进行文章自动审核。
如果审核不成功或需要人工审核,修改文章状态。
如果当前文章有图片,调用阿里云图片审核服务,同样如果不成功,需要修改文章状态。
自动审核通过
如果文章发布时间大于当前时间,修改自媒体文章状态为8 审核通过(待发布)
否则保存文章相关数据

2.文章审核
业务/需求:
自媒体和文章我们使用 Spring-Cloud的feign进行远程接口调用。
自媒体feign
1 根据文章id查询自媒体文章的数据
2 在审核的过程中,审核失败或者成功需要修改自媒体文章的状态
3 在文章进行保存的时候需要查询作者信息,需要通过自媒体用户关联查询作者信息
文章feign
1 保存文章信息 ap_article
2 保存文章内容 ap_article_content
3 在保存文章的时候需要关联作者,需要根据名称查询作者信息
考虑到发表的文章会随时间增加,数据量巨大。所以使用分布式id,分库进行保存。
所以我们选择使用 雪花算法生成数据库id,Mybaits逆向工程使用ASSIGN_ID生成。
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0

话术:
文章审核:
我们的文章审核功能围绕着状态来实现。首先我们发布的文章初始状态肯定是1(待审核),所以我们会根据文章状态进行如下操作:
状态1
进行自动审核,通过阿里云安全的内容文本检测、图片审核,
DFA敏感词过滤来完成审核。
文本审核结果会有1 审核通过 2 审核失败 3需要人工审核。
自动审核通过后,根据当前时间是否大于发布时间进行判断
是:直接保存到文章库
否:修改状态为8审核通过(待发布)
先保存数据,等发布时间到了再进行发布。
状态8和状态4
8和4为人工审核通过或自动审核通待发布的文章,
所以只需要判断当前时间是否大于发布时间。
是:直接保存到文章库
否:先保存数据,等发布时间到了再进行发布。

2.3.7 自媒体文章自动审核流程

作为内容类产品,内容安全非常重要,所以需要进行对自媒体用户发布的文章进行审核以后才能到app端展示给用户。
审核的流程如下:
1.当自媒体用户提交发布文章之后,会发消息给kafka提交审核,平台运营端监听消息
2.根据自媒体文章id查询文章信息
3.如果当前文章的状态为4(人工审核通过),则无需再进行自动审核,如果发布时间大于当前时间,将状态修改为8,否则保存APP文章相关数据
4.文章状态为8,发布时间小于等于当前时间,则直接保存app文章相关数据
5.文章状态为1,则进行自动审核
5.1 调用阿里云文本反垃圾服务,进行文本审核,如果审核不成功或需要人工审核,修改自媒体文章状态
5.2 如果文章中有图片,调用阿里云图片审核服务,如果审核不通过或需要人工审核,修改自媒体文章状态
5.3 文章内容中是否有自管理的敏感词,如果有则审核不通过,修改自媒体文章状态
5.4 自动审核通过,如果文章发布时间大于当前时间,修改自媒体文章状态为8(审核通过待发布状态)
5.5 自动审核通过,如果文章发布时间小于等于当前时间,保存app相关数据
6.保存app相关数据,修改自媒体文章状态为 9 (已发布)
ap_article 文章
ap_article_content 文章内容
ap_author 文章作者
7.创建索引(为后续app端的搜索功能做数据准备)
表结构

  1. wm_news 自媒体文章表 在自媒体库、

InterviewBook - 图165status字段:0 草稿 1 待审核 2 审核失败 3 人工审核 4 人工审核通过 8 审核通过(待发布) 9 已发布
(2)ap_author 文章作者表 在article库
InterviewBook - 图166
(3)ap_article 文章信息表 在article库
InterviewBook - 图167

  • layout 文章布局 0 无图文章 1 单图文章 2 多图文章
  • flag 文章标记 0 普通文章 1 热点文章 2 置顶文章 3 精品文章 4 大V 文章
  • images 文章图片 多张图片使用逗号分隔

(4)ap_article_content 文章内容表 在article库
InterviewBook - 图168
自媒体文章审核实现
当自媒体用户提交发布文章之后,会发消息给kafka提交审核,平台运营端监听消息,根据自媒体文章id查询文章信息,判断当前文章的状态,丛自媒体文章中提取文本和图片内容,调用阿里云文本反垃圾服务,进行文本审核,如果审核不成功或需要人工审核,修改自媒体文章,调用阿里云图片审核服务,如果审核不通过或需要人工审核,修改自媒体文章状态,文章内容中是否有自管理的敏感词,如果有则审核不通过,修改自媒体文章状态,自媒体文章发布时间大于当前时间,修改自媒体文章状态为8(审核通过待发布状态),审核通过,修改自媒体文章状态为 9 (审核通过),保存app相关数据,如果当前文章的状态为4(人工审核通过),则无需再进行自动审核审核,保存app文章相关数据即可,文章状态为8,发布时间<=当前时间,则修改发布状态,文章状态为1,则进行自动审核,

2.3.8 定时任务扫描待发布文章

业务/需求:
定时任务的作用就是每分钟去扫描那些待发布的文章,如果当前文章的状态为8,并且发布时间小于当前时间的,立刻发布当前文章
流程:

  1. 文章数据准备,远程调用接口自媒体端文章查询功能,得到状态为8且发布时间小于当前时间的文章。
  2. 在任务调度中心创建调度任务,新建一个执行器,策略为轮询,每分钟执行一次。
  3. 将扫描到符合条件的文章进行发布。

话术:
通过自媒体短的文章查询准备好数据后,我在项目中引入了xxl-job调度任务中心,定义一个自媒体文章审核的执行器,路由策略采用轮询,每分钟执行一次扫描,如果发现有当前文章的状态为8,并且发布时间小于当前时间的,立刻发布当前文章。

2.3.8 人工审核文章-admin端

业务/需求:
InterviewBook - 图169
前提:自动审核失败,状态为3的。
自动审核使用的是阿里云和DFA。某些情况自动审核会不通过,比如一些无法识别、无法分辨的信息等。会把文章从自动审核转到人工审核状态,并添加到人工审核列表。审核人员可以从列表中看到需要进行人工审核的文章,并且进行审核后选择 通过或不通过。
平台管理员可以查看待人工审核的文章信息,可以通过(状态改为4)或驳回(状态改为2)也可以通过点击查看按钮,查看文章详细信息,查看详情后可以根据内容判断是否需要通过审核

话术:
我们在人工审核这里主要考虑的是列表的展示、并且增加一些搜索功能,方便找到对应的文章。可以根据文章ID查看文章详情。对详情内容进行详细过审后,可以根据人工结果选择操作通过或不通过。
当审核通过后修改文章状态为4,如果审核不通过 修改状态为2,并添加审核不通过原因。

2.3.9 自媒体登陆

通过网关进行授权验证
首先判断地址中是否包含登录的地址,如果是登陆的地址,不需要token,直接放行,如果不是登陆地址,需要验证token。先获取token(获取请求头对象),判断token是否为空,如果为空返回401,结束请求,不为空需要验证token,验证通过,需要将用户ID放入到请求头中,供后面的微服务使用。

2.4 新热文章计算

业务/需求:
筛选出文章列表中最近5天热度较高的文章在每个频道的首页展示
根据用户的行为(阅读、点赞、评论、收藏)实时计算热点文章
话术:
新热文章计算分为三个部分
1.我们会定时计算热点文章,设置定时任务每日凌晨1点开始,查询前5天的文章。
计算每个文章的分值,其中不同的行为设置不同的权重(阅读:1,点赞:3,评论:5,收藏:8)
按照分值排序,给每个频道找出分值较高的30条数据,存入缓存中
2.实时计算热点文章
- 行为微服务,用户阅读或点赞了某一篇文章,发送消息给kafka
- 文章微服务,接收行为消息,使用kafkastream流式处理进行聚合,发消息给kafka
- 文章微服务,接收聚合之后的消息,计算文章分值(当日分值计算方式,在原有权重的基础上再*3)
- 根据当前文章的频道id查询缓存中的数据
- 当前文章分值与缓存中的数据比较,如果当前分值大于某一条缓存中的数据,则直接替换
- 新数据重新设置到缓存中
3. 查询热点数据
- 判断是否是首页
- 是首页,选择是推荐,channelId值为0,从所有缓存中筛选出分值最高的30条数据返回
- 是首页,选择是具体的频道,channelId是具体的数字,从缓存中获取对应的频道中的数据返回
- 不是,则查询数据库中的数据

2.5 elasticsearch 的倒排索引是什么
面试官:想了解你对基础概念的认知。
解答:通俗解释一下就可以。
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。
而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。
有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率
InterviewBook - 图170
学术的解答方式:
倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。
加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。
lucene 从 4+版本后开始大量使用的数据结构是 FST。FST 有两个优点:
1、空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
2、查询速度快。O(len(str))的查询时间复杂度。

2.6 kafka 如何不消费重复数据?比如扣款,我们不能重复的扣款
其实还是得结合业务来思考,我这里给几个思路:
比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据
这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据

24 技术点

1.1 黑马头条项目中的架构是什么样的?有什么好处?

InterviewBook - 图171
黑马后台是一整套微服务的架构设计
1.错误和故障隔离
当微服务架构隔离功能时,它也会隔离错误。一个微服务中的问题不会关闭整个应用程序,它将包含在该区域中,而其他微服务继续运行。这不仅可以延长正常运行时间,还可以更轻松地查明问题的根源并解决问题。
2.兼容CI / CD和敏捷
微服务架构与软件行业中最有效的过程兼容,包括CI,CD,敏捷和容器方法。团队可以选择最适合他们需求的流程,将微服务集成到他们的开发方法中并使用他们喜欢的任何工具。
3.可扩展
可以从应用程序中轻松提取独立功能,以便在其他应用程序中重用和重新调整用途,并提高可伸缩性。各个开发团队还可以实现和部署他们的代码,而无需考虑更大的IT团队或部门的日程安排。这使得大型组织更容易使用微服务架构来减少可能延迟部署的其他问题。
4.服务独立维护,分工明确
每个微服务都可以交由一个小团队进行开发,测试维护部署,并对整个生命周期负责,当我们将每个微服务都隔离为独立的运行单元之后,任何一个或者多个微服务的失败都将只影响自己或者少量其他微服务,而不会大面积地波及整个服务。

1.2请说说SpringBoot自动配置原理

1) 自动配置
Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是SpringBoot自动完成的。
2) 起步依赖
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
3)自动化配置总结
1、@SpringBootApplication组合注解,包含三个注解
InterviewBook - 图172
@SpringBootConfiguration是组合的注解是对@Configuration封装,也就是理解引导类其实也是一个配置类。

@ComponentScan是Spring注解作用是扫描包其实, 规则是 当前包及其子包所有的注解

2、@EnableAutoConfiguration 是实现Boort自动化配置的注解,给我们内置了大量的约定配置。比如:Tomcat 端口8080, Redis localhost:6379

@AutoConfigurationPackage是自动化配置的包

@Import(AutoConfigurationImportSelector.class):初始化Bean并且存储到IOC容器中,但是并不是所有的Bean对象都会被实例化。我们需要根据@ConditionalOnXXX来去初始化。

通过AutoConfigurationImportSelector 的getAutoConfigurationEntry 获取所有的自动化配置的全类名存到List集合中,等待被初始化。

InterviewBook - 图173


3、以XXXAutoConfiigureation为例
InterviewBook - 图174
判断当前类是否生效,如果生效就开始实例化bean,找 @EnableConfigurationProperties(XXXProperties.class)里面的默认的配置数据。 获取成功之后就会去使用这些默认信息初始化Bean对象,存到IOC容器中。

1.3项目中用到了nacos,它与eureka有什么区别?

对比:
InterviewBook - 图175
nacos:
同时支持AP和CP模式,根据服务注册选择临时和永久来决定走AP模式还是CP模式,同时nacos也支持配置中心

1.4项目开发的过程中,与前端人员是如何配合的?接口联调如何做?

前后端分离开发的流程

  • 需求分析
  • 接口定义
  • 前后端同时开发
  • 联调

swagger
随着springboot、springcloud等微服务的流行,在微服务的设计下,小公司微服务小的几十,大公司大的几百上万的微服务。这么多的微服务必定产生了大量的接口调用。而接口的调用就必定要写接口文档。在微服务的盛行下,成千上万的接口文档编写,不可能靠人力来编写,故swagger就产生了,它采用自动化实现并解决了人力编写接口文档的问题;它通过在接口及实体上添加几个注解的方式就能在项目启动后自动化生成接口文档,
Swagger 提供了一个全新的维护 API 文档的方式,有4大优点:
自动生成文档:只需要少量的注解,Swagger 就可以根据代码自动生成 API 文档,很好的保证了文档的时效性。
跨语言性,支持 40 多种语言。
Swagger UI 呈现出来的是一份可交互式的 API 文档,我们可以直接在文档页面尝试 API 的调用,省去了准备复杂的调用参数的过程。
还可以将文档规范导入相关的工具(例如 SoapUI), 这些工具将会为我们自动地创建自动化测试。
Yapi工具
https://hellosean1025.github.io/yapi/index.html

1.5项目中的异常是如何处理?

全局异常处理器的流程
InterviewBook - 图176
主要是通过,springmvc提供的一个全局异常处理器拦截异常信息。
黑马头条项目中的异常处理流程:
InterviewBook - 图177
异常主要分了两种情况:
不可预知异常(系统异常):统一返回错误信息
可预知异常(业务异常),正常返回错误信息

1.6如何解决分布式项目中的分布式事务?

seata
InterviewBook - 图178
Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
一个典型的分布式事务过程:
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
XID 在微服务调用链路的上下文中传播。
RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
TM 向 TC 发起针对 XID 的全局提交或回滚决议。
TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
AT模式机制
基于两阶段提交协议的演变。
一阶段:
业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
InterviewBook - 图179

1.7为什么要使用Elasticsearch?

MySQL弊端:1)查询性能,模糊查询 like 全表查询2)搜索精确度低 分词 我要买一部手机
ElasticSearch是一个基于Lucene的搜索服务器,是一个分布式、高扩展、高实时的搜索与数据分析引擎
基于RESTful web接口,用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎
应用场景
搜索:海量数据的查询
日志数据分析
实时数据分析

1.8为什么要使用mongodb?

项目中在存在文章的评论内容的时候,使用的mongodb,主要是当前mongodb的特点来决定使用它。
评论内容的特点:
数据量的很大的,
查询或新增的比较频繁
数据价值比较低,对事务要求不高。
所以最终采用的当前技术解决。
mongodb特点:
(1)高性能:
MongoDB提供高性能的数据持久性。特别是,
对嵌入式数据模型的支持减少了数据库系统上的I/O活动。
索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。
(2)高可用性:
MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。
(3)高扩展性:
MongoDB提供了水平可扩展性作为其核心功能的一部分。
分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)
(4)丰富的查询支持:
MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。
(5)其他特点:如无模式(动态模式)、灵活的文档模型

1.9在使用kafka的时候,生产者的消息确认机制有哪些,各有什么特点?

生产者执行流程
InterviewBook - 图180
消息发送确认机制
acks=0
生产者在成功写入消息之前不会等待任何来自服务器的响应,也就是说,如果当中出现了问题,导致服务器没有收到消息,那么生产者就无从得知,消息也就丢失了。不过,因为生产者不需要等待服务器的响应,所以它可以以网络能够支持的最大速度发送消息,从而达到很高的吞吐量。
acks=1(默认)
只要集群首领节点收到消息,生产者就会收到一个来自服务器的成功响应,如果消息无法到达首领节点,生产者会收到一个错误响应,为了避免数据丢失,生产者会重发消息。
acks=all
只有当所有参与赋值的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应,这种模式是最安全的,它可以保证不止一个服务器收到消息,就算有服务器发生崩溃,整个集群仍然可以运行。不过他的延迟比acks=1时更高。

2.0为什么使用jenkins? jenkins的优势是什么?

Jenkins的特征:
开源的 Java语言开发持续集成工具,支持持续集成,持续部署。
易于安装部署配置:可通过 yum安装,或下载war包以及通过docker容器等快速实现安装部署,可方便web界面配置管理。
消息通知及测试报告:集成 RSS/E-mail通过RSS发布构建结果或当构建完成时通过e-mail通知,生成JUnit/TestNG测试报告。
分布式构建:支持 Jenkins能够让多台计算机一起构建/测试。
文件识别: Jenkins能够跟踪哪次构建生成哪些jar,哪次构建使用哪个版本的jar等。
丰富的插件支持:支持扩展插件,你可以开发适合自己团队使用的工具,如 git,svn,maven,docker等。

2.1项目中是如何解决高并发的问题的?


前端处理
采用前后端分离的模式,前端项目单独部署到服务器上面
前端加入CDN 加速服务(静态资源的缓存,就近缓存)
前端引入Nginx,如果不够,加入集群Nginx

后端处理
服务单一原则,如果某一个压力较大,可根据实际情况增加单个服务集群数量(单点扩容)
尽量使用缓存技术来做(redis,es,mongoDB,MQ等)
限流(nginx,网关)
降级(hystrix)
熔断(hystrix)

八、探花交友

一、项目介绍

探花交友是一个陌生人在线交友平台,在该平台中可以搜索附近的人,查看好友动态,平台提供大数据分析,通过后台推荐系统帮我们快速匹配自己的“意中人”。项目主要分为交友,圈子,消息,视频,我的五大块。项目的亮点是使用MongoDB 技术实现海量数据的存储;使用FastDFS实现图片的上传和下载;使用百度AI技术实现非人脸无法上传。项目采用前后端分离的方式开发,采用的是YApi在线文档进行数据交互管理。

1.1 开发技术

前端:
- flutter + android + 环信SDK + redux + shared_preferences + connectivity + iconfont + webview + sqflite
后端:
- Spring Boot + SpringMVC + Mybatis + MybatisPlus + Dubbo
- MongoDB geo 实现地理位置查询
- MongoDB 实现海量数据的存储
- Redis 数据的缓存
- Spark + MLlib 实现智能推荐
- 第三方服务 环信即时通讯
- 第三方服务 阿里云 OSS 、 短信服务

1.2 技术架构

InterviewBook - 图181

1.3 开发方式

探花交友项目采用前后端分离的方式开发,就是前端由前端团队负责开发,后端负责接口的开发,这种开发方式有2点好处:
扬长避短,每个团队做自己擅长的事情
前后端可以并行开发,事先约定好接口地址以及各种参数、响应数据结构等。
InterviewBook - 图182
我们在项目中使用YApi 在线接口文档来进行项目开发。YApi是一个开源的接口定义、管理、提供mock数据的管理平台。具体操作如下:
InterviewBook - 图183
接口定义:
InterviewBook - 图184

二、功能介绍

探花交友平台,涵盖了主流常用的一些功能,如:交友、聊天、动态等。具体功能如列表所述:

24.1 功能列表

功能 说明 备注
注册、登录 用户无需单独注册,直接通过手机号登录即可 首次登录成功后需要完善个人信息
交友 主要功能有:测灵魂、桃花传音、搜附近、探花等 通过不同的趣味模式让我们找到自己更熟悉的人
圈子 类似微信朋友圈,用户可以发动态、查看好友动态等
消息 通知类消息 + 即时通讯消息
小视频 类似抖音,用户可以发小视频,评论等 显示小视频列表需要进行推荐算法计算后进行展现。
我的 我的动态、关注数、粉丝数、通用设置等

2.2 注册登录

用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。
InterviewBook - 图185InterviewBook - 图186InterviewBook - 图187InterviewBook - 图188

2.2.1 技术要点:

阿里大于 + Redis + JWT + OSS云存储 + 百度AI

2.2.2 涉及的表:
tb_user(用户表)
tb_info(用户详细信息表)

2.2.3 实现分析:

实现用户登录我们要处理四个接口来进行实现,分别是:
发送手机验证码(阿里大于)
- 阿里云平台短信发送
校验用户登录(Redis)
- 后台需要验证手机号与验证码是否正确
- 校验成功之后,需要按照JWT规范进行返回响应
首次登录完善个人信息(百度AI + OSS)
校验token是否有效(Redis + JWT)
- 校验存储到Redis中的token是否有效

2.2.3.1 发送手机验证码流程:

InterviewBook - 图189
用户在客户端输入手机号,点击发送验证码,向后台tanhua-server进行请求,调用后台阿里云短信平台进行短信发送。tanhua-server服务会会携带用户给发过来的两个参数(用户的手机号和后台生成的随机6位数)通过调用阿里大于第三方平台进行接口的响应。如果阿里云短信平台在接受响应成功后,会把验证码发送给客户,并把这个验证码保存到Redis中,并给这个验证码设置有效期为5分钟。

2.2.3.2校验用户登录流程:

InterviewBook - 图190
当用户在手机上接受到验证码之后,开始输入验证码校验。当用户输入完6位验证码之后,会自动触发后台tanhua-server服务进行校验功能。对用户传输过来的手机号和验证码进行验证。如果校验通过会根据客户的手机号生成一个token,给客户返回一份,方便用户在App上访问其他微服务的时候进行身份的识别。同时在通过RocketMQ响应给JWT的鉴权中心通知其他微服务。在用户通过校验之后,我们会做3个工作:

  1. 我们会根据用户的手机号给用户创建一个账户并存入mysql的用户表中。
  2. 我们会调用RocketMQtemplate发送一条消息给消息中间件,用来给后台做log日志的统计,便于后台管理系统展示平台用户日活量,注册量等等。
  3. 把创建的新用户id注册到环信通讯中,来实现即时通信的功能。
    2.2.3.3用户登录
    InterviewBook - 图191
    考虑到我们项目采用的是前后端分离架构,我们采用JWT生成token的方式来给用户进行身份识别,当我们登录成功后我们会根据用户的id和手机号生成一个token返回给用户并保存到Redis中。这样用户在访问受保护的资源的时候,我们会在后台进行校验客户端发过来的token,如果校验通过则进行放行。
    在这块我们考虑到三个问题:
    1.是token续签;2. token的安全性; 3. 如何统一处理token;
    在解决token续签的问题上,我们这里采取的是在请求头获取token时判断token是否存在,如果不存在就创建token并保存到Redis中,如果存在我们就重新从Redis中获取token并进行续签(Duration.ofHours(1))。在安全问题上,我们这里采用加密加盐(SignatureAlgorithm.HS256,secret)的方式来解决。为了实现token统一校验,我们这里采用的是springmvc拦截机制+Threadlocal局部线程的方式来解决。这里我们自定义了一个TokenInterceptor拦截器,实现preHandle() 方法,这样的话就可以在用户请求进入controller层之前进行拦截,通过token获取了用户对象,并将用户对象存储到Threadlocal中,这样就实现了token统一校验的功能。具体的实现过程如下图:
    InterviewBook - 图192
    2.2.3.4首次登录完善个人信息
    用户在完成登录操作之后,如果是第一次登录,用户会跳到完善个人信息页面,这块我们需要输入用户昵称,生日,所在地,设置头像。其中,头像数据需要做图片上传,这里采用阿里云的OSS服务作为我们的图片存储服务器。并且对头像要做人脸识别,非人脸照片不得上传,这里我们调用的是百度AI的人脸识别接口进行校验。我们需要在本地创建一个ossTemplate模板和aipFaceTemplate模板,把百度AI和阿里OSS给我们提供的SDK进行修改。我会在yml中创建以上两个模板所需要的参数。

    2.3 通用功能的实现

    InterviewBook - 图193
    2.3.1 涉及的表:
    | tb_settings(通用设置) | | —- | | tb_question(陌生人问题表) | | tb_black_list(黑名单) |

2.3.2 实现分析:

通用设置,包含探花交友APP基本的软件设置功能。主要功能有设置陌生人问题,通用设置,黑名单等。设置陌生人问题这块主要为了在添加你的时候需要回答问题,根据用户回答的问题,进行确认关系。我们这块主要做问题的添加和问题查看。这块就是根据用户的id查询用户的问题,问题查看就是如果没有问题那么就设置问题,如果有就update重新更新一下。通用设置这块我们主要做的接口是给用户推送喜欢通知,推送评论通知,推送平台公告通知,这块我们根据用户的id去通用设置表里边保存或者更新用户通知情况。黑名单这块我们做的是黑名单列表的查询和黑名单的移除。主要查询用户表和黑名单表,如果黑名单移除,那么直接在黑名单里边根据用户的id删除。这块主要做了黑名单列表分页的问题,因为随着我们往下拉取,后台会根据用户的id动态展示黑名单列表。这块就使用到mybatisPlus的分页Ipage。通过创建Page对象,传入当前页,和每页条数,调用findPage(page,XXX条件)来实现;

2.4 今日佳人

在用户登录成功后,就会进入首页,首页中有今日佳人、推荐好友、探花、搜附近等功能。今日佳人,是根据用户的行为数据统计出来匹配值最高的人。缘分值的计算是由用户的行为进行打分,如:点击、点赞、评论、学历、婚姻状态等信息组合而成的。

InterviewBook - 图194

2.4.1 技术要点:

MongoDB

2.4.2 涉及的表:

recommend_user(推荐用户表)

2.4.3 实现流程:

InterviewBook - 图195

2.4.4 实现分析:

当我们进入首页之后,上边会有一个滑动区域,有今日佳人,探花,公告等模块。我在这块负责今日佳人模块实现,主要根据用户的id查找对应的推荐佳人表,根据缘分值进行排序,获取第一个进行展示。缘分值是我们通过用户平时浏览平台动态和浏览小视频,对浏览的内容进行点赞、评论的情况以及用户本身的学历、婚姻状态等信息进行计算的。我们会把计算的最终值存储到MongoDB中方便用户进行快速查询。当用户对圈子,视频的动态执行操作时,如:发布、浏览、点赞、喜欢等,平台就会通过RocketMQ给推荐系统发送消息。推荐系统接收消息,并且处理消息数据,处理之后将结果数据写入到MongoDB中,Spark系统拉取数据,然后进行推荐计算,计算之后的结果数据写入到Redis中,为每个用户都进行个性化推荐,如果有用户没有数据的,查询MongoDB中的默认数据。我们这块动态计分规则是:浏览 +1,点赞 +5,喜欢 +8,评论 + 10,文字长度:50以内1分,50~100之间2分,100以上3分。

2.5 公告

主要用于接收平台发布的一些消息和福利,比如:新增功能通知,活动通知,版本迭代通知。
InterviewBook - 图196InterviewBook - 图197

2.5.1 涉及的表:

Tb_nnouncement(公告表)

2.5.2 实现分析:

这块我们主要做两个事情,一个是公告查询分页,一个是公告对象转换VO。分页的话我们这里使用的是MybatisPlus的分页功能,公告对象转化VO主要是为了解耦和不做代码污染,我们通过创建一个list的方式把公告对象遍历到我们创建的AnnouncementVO对象中,在这里我们要做一个时间的校验,如果创建时间不存在我们需要给他生成个时间在进行加入。

2.6 圈子

类似微信朋友圈,我们可以发布图片,文字;可以对圈子里边的内容进行点赞,评论,喜欢操作。和微信朋友圈不一样的功能是微信只能查看好友的圈子,但是我们这个平台圈子推荐的不仅仅是好友还有系统智能推荐用户点赞喜欢的好友。
InterviewBook - 图198InterviewBook - 图199

2.6.1 技术要点:

MongoDB + Redis + MongoDB geo + rocketMQ + 华为云内容审核 + Spark + Mllib

2.6.2 涉及的表:
quanzi_publish(发布表)
quanzialbum{userId}(相册表)
quanzitime_line{userId}(时间线表)
quanzi_comment(评论表)
tanhua_users(好友表)
tb_user(用户表)

2.6.3 实现分析:

2.6.3.1 通过页面分析,剖析如下:

1、推荐频道为根据问卷及喜好推荐相似用户动态(RocketMQ+Spark + MLlib)
2、显示内容为用户头像、用户昵称、用户性别、用户年龄、用户标签和用户发布动态
3、图片最多不超过6张或发布一个小视频(FastDFS)
4、动态下方显示发布时间距离当时时间,例如10分钟前、3小时前、2天前,显示时间进行取整。(MongoDB Geo)
5、动态下方显示距离为发布动态地与本地距离
6、显示用户浏览量
7、显示点赞数、评论数 转发数

InterviewBook - 图200

2.6.3.2 发布流程:

InterviewBook - 图201
- 用户发布动态,首先将动态内容写入到发布表。
- 然后,将发布的指向写入到自己的相册表中。
- 最后,将发布的指向写入到好友的时间线中。

2.6.3.3 查看流程:

InterviewBook - 图202
- 用户查看动态,如果查看自己的动态,直接查询相册表即可
- 如果查看好友动态,查询时间线表即可
- 如果查看推荐动态,查看推荐表即可
由此可见,查看动态的成本较低,可以快速的查询到动态数据。

2.6.3.4 实现话术:

我负责项目中的圈子功能的实现,类似微信的朋友圈,基本的功能为:发布动态、浏览好友动态、浏览推荐动态、点赞、评论、喜欢等功能。我们这里采用spark + mllib实现智能推荐,这样的话就会根据我们自身的条件,喜好进行匹配推送用户感兴趣的动态。针对用户上传的动态我们采用rocketMQ发送消息调用华为云内容审核进行自动处理,考虑到需要处理海量的动态信息数据,我们这里选用了MongoDB+Redis的形式来进行数据的存储,其中MongoDB负责存储,Redis负责缓存。
首先是查询好友动态接口的实现,用户点击好友动态之后,服务器会从ThreadLocal中获取用户id,根据用户id从该用户对应的好友动态时间线表中查询出所有好友动态id以及对应的好友id,再根据动态id从总动态表查询动态详细信息,根据动态发布的时间倒序排序。再根据好友id从用户详情表中查询好友用户详细信息,再将这些信息构造成vo对象返回给前端进行分页展示。
接下来说一下用户发布动态接口的实现,用户在客户端写好文本,上传好图片,确定好位置点击发布按钮之后,请求会携带一系列参数,文本内容,图片文件,地理位置的经纬度等,后端代码先创建对象,设置相应的属性,保存到MongoDB动态表中,此时动态的状态属性默认设置为0,表示待审核。然后通过rocketMQ向消息中间件发送一条消息,包含动态id,消息消费者接收到消息之后会调用华为云的内容审核服务,对文字信息和图片信息进行审核,审核通过后将动态的状态属性改为1,表示已通过,不然就改为2,表示驳回。然后前端在展示动态的时候只会查询状态为已通过的动态。动态发布成功之后也会像消息中间件发送消息,消息内容包括动态id(objectId类型)、用户id、操作类型、Long类型的pid。消息消费者会以此生成一条该用户对该动态的一个评分记录保存到MongoDB的动态评分表中,作为大数据推荐的依据。
然后说一下用户对动态的一系列操作的接口的实现。我们是将用户的点赞、评论、喜欢操作记录到MongoDB的一个comment表中,用type字段来区分具体的操作类型,比如1代表点赞,2代表评论,3代表喜欢。用户进行操作之后服务器就会生成相应操作类型的comment记录保存到MongoDB的comment表中。保存成功后向消息中间件发送消息,消息内容包括动态id(objectId类型)、用户id、操作类型、Long类型的pid。消息消费者会以此生成一条该用户对该动态的一个评分记录保存到MongoDB的动态评分表中,作为大数据推荐的依据。
最后是推荐频道的动态展示接口的实现,服务器使用Spark + Mllib技术,根据用户的动态评分表,基于用户userCF算法进行推荐,将推荐的结果以pid组成的字符串形式存入Redis。用户进入推荐频道之后,服务器去查找Redis有没有推荐结果,如果没有的话,就从MongoDB中查询默认的推荐动态数据展示,如果有推荐结果,就获取字符串,转换成pid的数组,再根据这些pid从MongoDB的总动态表中查找对应动态信息,构造vo对象的分页结果,返回给前端展示给用户。

2.7 小视频

用户可以上传小视频,也可以查看小视频列表,并且可以对推荐的视频进行点赞操作。通过页面分析我们需要获取视频展示出来的视频封页,好友的头像,昵称,点赞数量。
InterviewBook - 图203

2.7.1 技术要点:

FastDFS + CDN

2.7.2 涉及的表:
video(小视频表)
quanzi_comment(评论表)
quanzi_publish(发布表)
tb_user(用户表)

2.7.3 实现分析:

小视频功能类似于抖音、快手小视频的应用,用户可以上传小视频进行分享,也可以浏览查看别人分享的视频,并且可以对视频评论和点赞操作。对于存储而言,小视频的存储量以及容量都是非常巨大的,所以我们选择自己搭建分布式存储系统 FastDFS进行存储。对于推荐算法,我们将采用多种权重的计算方式进行计算,对于加载速度,除了提升服务器带宽外可以通过CDN的方式进行加速。发布视频的流程是:客户通过点击发布视频,会向后台传递3个数据,一个是视频封面图片,一个是视频,一个是文字。我们在后台会把视频封面图片保存到阿里的OSS图片存储服务器上会给我们返回一个图片的存储链接,把视频上传到本地部署的FastDFS上边会给我们返回一个视频的存储链接,之后我们这边构造一个Video对象,把需要的参数(文字,图片URL,视频URL,用户ID)通过调用VideoAPI进行保存到MongoDB中,之后构造返回值给用户进行显示发布成功。接下来用户会跳转到小视频列表页面。分页查询视频列表是根据后台推荐系统进行智能推荐的,这个根据用户浏览视频的时候点赞,喜欢,评论的情况进行分数积累,把对应的分数值存储到推荐表中,当用户下拉刷新的时候,会从MongoDB中查询出推荐的视频。

2.8 探花

左划喜欢,右划不喜欢,每天限量不超过50个,开通会员可增加限额。双方互相喜欢则配对成功,可以互加好友。
通过页面分析:我们需要展示用户的信息,包括:头像,昵称,年龄,是否单身,学历等。
实现:数据来源推荐系统计算后的结果。

InterviewBook - 图204

2.8.1 技术要点:

MongoDB + OSS + spark + 环信

2.8.2 涉及的表:
recommend_quanzi(推荐表)
tanhua_users(好友表)
tb_user(用户表)

2.8.3 实现分析:

当用户的通过点击探花进入时,会根据用户的id去MongoDB中查询10条推荐的好友信息,推荐依据是来源于推荐系统通过spak + Mallib 根据用户平时喜欢,点赞,评论进行生成。当用户选择喜欢的时候我们会把用户和喜欢好友的id写入到环信通讯中,再重新存入到MongoDB中,方便用户做选择。当用户选择不喜欢时候,我们要清除当前数据,不予以推荐。我们这块实现主要做了三个接口,分别是展示卡片列表接口,右滑喜欢接口,左滑不喜欢接口。展示列表我们根据获取的用户id去推荐表中查询默认的推荐好友。如果选择不喜欢会根据当前用户的id和推荐人的id调用接口将该用户的从推荐表中删除,如果选择喜欢,会根据当前用户的id和推荐人的id通过调用之前的一个喜欢接口生成一个实例保存到mongoDB数据库中,并且判断所喜欢的用户是否为自己的粉丝,如果是的话则将两人添加到好友关系表中,并且将环信中的两人绑定为好友关系,这样在好友列表中可以查询到对方并且可以进行聊天了。

2.9 测试灵魂

测灵魂是我们项目的一大亮点。平台会提供对应的初级灵魂,中级灵魂,高级灵魂对应的答卷。通过用户答题,我们会在后台进行用户行为数据分析,给用户发送一份信息报告,同时也会推荐和用户本人灵魂属性相符的人。我们这块会为每个灵魂等级设置10个问题,测试题为顺序回答,回答出初级题,我们才可以点击中级灵魂测试。

InterviewBook - 图205InterviewBook - 图206InterviewBook - 图207

2.9.1 技术要点:

MySQL + MongoDB

2.9.2 涉及的表:

tb_questionnaire(调查问卷)
tb_test_question(问卷问题)
tb_option(问题选项)
tb_conclusion(性格信息) Mysql中的表
testSoul_Questionnaire (调查问卷) MongoDB中的表
testsoul_user_score(用户得分)
testsoul_report

2.9.3 实现分析:

灵魂测试是通过用户答题的方式来进行分类,寻找同一类型或者内心深处的朋友。我们这块主要采用的技术有:MongoDB +Sprak+Mllib 当用户点击灵魂测试,我们会去后台加载用户的灵魂信息。如果是新用户我们会停留在初级灵魂题,后边的中级灵魂,高级灵魂则需解锁上一级才能进行答题。如果是老用户我们会加载用户之前测试灵魂的结果进行展示。如果之前他测评通过的是高级灵魂,那么就可以查看初级,中级,高级灵魂的测评。每一级的测评灵魂题都会有10道题,测评结束之后会根据用户的回答生成一个测试报告,通过点击我们可以查看属于自己的唯一一份灵魂报告。生成的报告主要根据用户的属性外向,判断,抽象,理性组成。我们可以把自己的报告进行分享或者选择重做。当进行报告查看时候我们还可以查看系统给我推荐的测试结果相同的好友。通过点击好友头像,我们可以通过回答陌生人问题来进行确认好友,如果对方回复并通过就可以进行正常的交友啦。

2.10 桃花传音

InterviewBook - 图208InterviewBook - 图209InterviewBook - 图210InterviewBook - 图211

2.10.1 技术要点:

MongoDB + FastDFS + Quartz

2.10.2 涉及的表:
user_sound(用户语音)
user_soundTime(语音剩余次数)
tb_user(用户表)

2.10.3 实现分析:

语音匹配类似漂流瓶类似,可以发送匿名语音,可以收听匿名语音。收听完匿名语音可以选择喜欢或不喜欢。如果双方互相喜欢那么可以配对成功互相关注,一个人每天只能接收8次,自己接收过语音,其他人不能再使用。如果双方都点击喜欢,就可以添加为好友。这块我们采用的是MongoDB做数据存储,使用Dubbo做分布式服务的调用,使用FastDFS存储语音。用户可以发布一段语音文件,上传到fastDFS中,并且将返回的地址值,与当前用户的一些信息封装成实例保存到mongoDB中,其他用户会进行接收操作时,随机在数据库中获取一条语音信息,然后进行选择打招呼,和扔掉,打招呼的话就可以进行调用环信的发送一条信息,如果对方回复的话则成为好友,同时该语音文件会在数据库中删除,如果扔掉的话则不会再数据库中消失,继续等待被获取。

九、Git

1、git的作用

代码回溯
版本控制
多人协作
远程备份

2、git的工作流程

Git工作组成:本地版本库、远程版本库、工作区、暂存区
工作区《=》暂存区《=》本地版本库《=》远程版本库
本地版本库:本地的.git隐藏文件夹就是本地版本库,版本库中存储了很多配置信息、日志信息和文件版本信息等
远程版本库:在远程代码托管服务器上管理版本库。用于协同共享。
工作区:包含.git文件夹的目录就是工作区,也称为工作目录,主要用于存放开发的代码
暂存区:.git文件夹中有很多文件,其中有一个index文件就是暂存区,也可以叫做stage。暂存区是一个临时保存修改文件的地方
图1
InterviewBook - 图212
图2
InterviewBook - 图213

3、git的本地仓库操作命令

git status 查看文件状态
git add 将文件的修改加入暂存区
git reset 将暂存区的文件取消暂存或者是切换到指定版本
git commit 将暂存区的文件修改提交到版本库
git log 查看日志

4、git的远程仓库操作命令

InterviewBook - 图214

5、git的分支操作命令

InterviewBook - 图215

十、前端

1、Html

1.1 html的作用

Hyper Text Markup language 超文本标记语言。用于布局标签数据。

1.2 html有哪些常用标签

InterviewBook - 图216
InterviewBook - 图217
InterviewBook - 图218
InterviewBook - 图219

1.3 div与span的区别

InterviewBook - 图220

1.4 表格标签都有哪些?作用是什么?

InterviewBook - 图221

1.5 表单的作用和常用表单元素

InterviewBook - 图222

2、Javascript

2.1 javascript的作用

Javascript是运行在网页上的逻辑小脚本语言,可以在网页上进行逻辑控制、浏览器对象操作、网页标签数据操作管理等开发。

2.2 javascript的BOM对象有哪些?

InterviewBook - 图223

2.3 javascript的DOM对象如何获取?

InterviewBook - 图224

2.4 javascript有哪些常用的事件?

InterviewBook - 图225

2.5 javascript 内置对象有哪些?

Array 数组对象
RegExp 正则表达式定义对象
String 字符串对象
Date 日期对象

3、Vue和ElementUI

3.1 vue的是什么

Vue 是一套前端框架,免除原生JavaScript中的DOM操作,简化书写
基于MVVM(Model-View-ViewModel)思想,实现数据的双向绑定,将编程的关注点放在数据上

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架
与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

3.2 什么是MVVM编程模式?

M Model 数据模型对象,用于存储数据
V View 视图,用于展现数据
VM ViewModel 用于实现双向数据绑定。
当Model数据改变了,VM操作Model将数据给到View绑定展现。如果view数据改变了,VM操作view数据绑定到Model对象中。

一 概述

  • View是视图层,也就是用户界面。前端主要由HTML和CSS来构成,为了更方便地展现ViewModel或者Model层的数据。
  • Model是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的接口规则。
  • ViewModel由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者从后端获取得到Model数据进行转换出来,做二次封装,以生成符合View层使用预期的视图数据模型。视图状态和行为都封装在ViewModel里。这样的封装使得ViewModel可以完整地去描述View层。

二 什么是MVVM
MVVM(Model-View-ViewModel)是一种软件架构设计模式,它是一种简化用户界面的事件驱动编程方式。

image.png

在MVVM架构中,是不允许数据和视图直接通信的,只能通过ViewModel来通信,而ViewModel就是定义了一个Observer观察者。ViewModel是连接View和Model的中间件。

  • ViewModel能够观察到数据的变化,并对视图对应的内容进行更新。
  • ViewModel能够监听到视图的变化,并能够通知数据发生变化。

到此,我们就明白了,Vue.js就是一个MVVM的实现者,它的核心就是实现了DOM监听与数据绑定。
MVVM源自于经典的MVC(Model-View-Controller)模式。MVVM的核心是ViewModel层,负责转换Model中的数据对象来让数据变得更容易管理和使用,其作用如下:
该层向上与视图层进行双向数据绑定。
向下与Model层通过接口请求进行数据交互。

image.png

MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流向的MVVM框架有Vue.js、AugularJS等。
三 为什么要使用MVVM

  • 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候,View也可以不变。
  • 可复用:可以把一些视图逻辑放到一个ViewModel里面,让很多View重用这段视图逻辑。
  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
  • 可测试:界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

四 MVVM的组成部分

image.png

五 MVVM实现者

  • Vue.js
  • AngularJS
  • 微信小程序

3.3 vue的常用指令有哪些?

vue常用指令有:v-once指令、v-show指令、v-if指令、v-else指令、v-else-if指令、v-for指令、v-html指令、v-text指令、v-bind指令、v-on指令、v-model指令等等

v-once
能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定

v-show
和v-if一样 区别是if是注释掉 v-show是给一个display:none的属性 让它不显示! 用法 参考下一个v-if指令.
v-show和v-if的区别:
v-if 是真实的条件渲染,因为它会确保条件块在切换当中适当地销毁与重建条件块内的事件监听器和子组件; v-show 则只是简单地基于 CSS 切换。
v-if 有更高的切换消耗而 v-show 有更高的初始渲染消耗。因此,如果需要频繁切换使用 v-show 较好,如果在运行时条件不大可能改变则使用 v-if 较好。

v-else
必须和v-if一起用才可以 不能单独用 而且必须在v-if下面 中间有别的标签也会报错

v-for
基于数据渲染一个列表,类似于JS中的遍历。其数据类型可以是 Array | Object | number | string
该指令之值,必须使用特定的语法(item, index) in items, index是索引,可以省略。item是 为当前遍历元素提供别名(可以自己随便起名字) 。v-for的优先级别高于v-if之类的其他指令

v-html
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 而且给一个标签加了v-html 里面包含的标签都会被覆盖。
注意v-html要慎用 因为会出现安全问题 官网解释为:你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。

v-model
v-model是一个指令,限制在