写在前面

临时接到面试通知,很仓促,所以在这里速过一下可能会被面到的问题。

自我介绍

首先表示感谢,给了我面试的机会,这么晚还在工作,谢谢你。面试官你好,我叫胡雅宁,就读于湖南科技大学,专业是数据科学与大数据技术。我大学三年来,学习比较勤奋,专业排名在前百分之二十,在2020年秋,便通过了六级考试,在2021年秋,取得了软件设计师资格证书。同年初,我通过hexo框架搭建了自己的个人博客,从此便养成了撰写技术博文的习惯,一直坚持到今天,博客上已经积累了一百二十余篇的技术博文,这让我养成了很好的整理文档的习惯,我有能力写出一份条理清晰、逻辑严谨、通俗易懂的文档。在这三年的时间里,我接触过一些简单的Java开发的项目,比如外卖后台管理系统、科大图书借阅系统,整个系统的数据库设计、接口设计、功能模块开发都是我一个人独立完成的(选做),目前在校内实习的项目也和Java后端开发有关,所以我有开发项目的经验,基于这个基础,我能更快的学会并更好的掌握当下公司项目所需的主流技术。我今天来求职的岗位是(IT开发)。之所以来应聘这个岗位,是因为想进入到(新能源)这个行业,我了解到贵公司在新能源方面是行业的标杆,技术上的积累非常出色。基于我的学习能力和几年来的专业学习,我有信心胜任这个岗位。

项目

基于SpringBoot前后端分离的外卖习题

登录模块

登录逻辑:

  • 将页面提交的密码password进行md5加密处理,得到加密后的字符串
  • 根据页面提交的用户名 username 查询数据库中员工数据信息
  • 如果没有查询到,则返回登陆失败的结果
  • 密码比对,如果不一致,就返回登陆失败的结果
  • 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
  • 登陆成功,将员工id存入Session,并返回登陆成功的结果

技术点说明:

A. 根据需求分析,我们看到前端发起的请求为post请求,所以服务端要使用注解@PostMapping

B. 由于前端传递的请求参数为json格式的数据,这里使用Employee对象接收,但是将json格式数据封装到实体类中,在形参前需要加注解@RequestBody

登录模块的完善:

用户如果不登陆,直接访问系统首页,照样可以正常访问。 这一块可以使用过滤器来解决。在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有则返回提示信息,跳转到登陆页面。

过滤器具体的处理逻辑:

A. 获获取本次请求的URI

B. 判断本次请求,是否需要登录,才可以访问

C. 如果不需要,则直接放行

D. 判断登陆状态,如果已登录,则直接放行

E.如果未登录,则返回未登录结果

需要在引导类上, 加上 Servlet 组件扫描的注解, 来扫描过滤器配置的@WebFilter 注解, 扫描上之后, 过滤器在运行时就生效了。

算法思想

二分查找

编写二分查找代码:

  • 有已经排好序的数组
  • 定义左边界L,右边界R,确定搜索范围,循环执行以下的两步
  • 获取中间索引:M = Floor((L+R)/2)
  • 中间索引的值A[M]与待搜索值进行比较:
    • A[M]==T表示找到,返回中间索引
    • A[M]>T。中间值右侧的元素都大于T,无需比较,中间索引左边去找,M-1设为右边界,重新查找
    • A[M]<T。中间值左侧的其它元素都小于T,无需比较,中间索引去右边找,M+1设为左边界,重新查找
  • 当L>R时,表示没找到,应结束循环。

解决整数溢出问题

  • 问题关键在于M=(R+L)/2;我们对它进行一个拆分,变成,M=L+(R-L)/2;
  • 第二种方法就是使用无符号右移,将整体(L+R)>>> 1 右移一位.

做题技巧

如果数组里面的元素是奇数个,二分查找取中间值。

如果是偶数个,那么就取偏左的。

冒泡排序

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BubbleSort{
public static void main(String[] args){
int[] a = {5,9,7,4,1,3,2,8};
bubble(a);
}


public static void bubble(int[] a){
for(int j =0;j<a.length-1;j++){
for(int i =0;i<a.length-1-j;i++){
if(a[i]>a[j]){
swap(a,i,i+1);
}
}
}
}

public static void swap(int[] a,int i,int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}

优化方式

每轮冒泡,最后一次交换的索引可以作为下一次冒泡的比较次数。如果这值为零,那就证明已经有序,无需再排。

选择排序

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SelectionSort{

public static void main(String[] args){
int[] a = {5,3,7,2,1,9,8,4};
selection(a);
}

private static void selection(int[] a){
for(int i = 0;i<a.length-1;i++){
//i代表每轮选择的最小元素,到i索引
int s = i;//代表最小元素的索引
for(int j = s+1;j<a.length-1;j++){
if(a[s]>a[j]){
s = j;
}
swap(a,s,i);
}
}
}
}

总结

冒泡排序属于稳定算法,选择排序是不稳定算法。选择排序并不适合于有序度高的数组。

Java基础(重点)

Java中常用的注解有哪些?

@Override

@Deprecated

什么是反射?

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。

什么是封装?

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。

怎么打破一个类的封装?

通过继承

怎样得到封装后对象的方法属性?

通过内置函数来访问。任何要访问类中私有成员变量的类都要通过这些getter和setter方法。

抽象类和接口区别和共同点?

区别:

  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。

共同点:

  • 都可以包含抽象方法。
  • 都不能被实例化。

什么是继承?

举个例子,小明小红都是科大的学生,他们共享学生的特性(班级,学号),我们就可以把这部分共同点抽象出一个父类来,班级和学号这种共同的属性就在父类中。小明和小红通过继承父类,就可以拥有班级和学号的属性。而小明和小红也有不同的地方,比如小明每天早餐都吃包子,小红每天早餐都吃烧麦,这两个不同的行为可以分别在子类中体现。这就是继承。

集成的特点:

  • 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  • 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  • 子类可以用自己的方式实现父类的方法。

做项目的时候遇见的一些异常

  • JDBC错误。看报错日志,如果出现这几个字,基本上是因为没连接数据库。
  • 500错误。Tomcat没有配置相应的lib,用maven导jar包的话,也需要在tomcat上lib文件里导入相同的jar包。
  • 400错误。前端传递的数据的字段名和后端实体类无法匹配,无法封装。
  • 坑爹的500错误。这个错误折磨我很多次了,反复横跳。最多的原因在于,如果后端传递的值不是对象,而是单个的数据,那么传递的参数名必须要和实体类的属性名匹配,由于Spring托管,如果名字不匹配根本找不到对应的Service操作。

为什么使用单例模式,单例模式的优点

Java Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,

有利于Java垃圾回收(garbage collection)。

单例模式:它的核心结构只包含一个被称为单例的特殊类。它的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点

优点:

  • 控制资源的使用,通过线程同步来控制资源的并发访问;
  • 控制实例产生的数量,达到节约资源的目的。
  • 作为通信媒介使用,也就是数据共享。

ArrayList的底层 得到ArrayList的某个值

  • ArrayList 底层使用的是 Object 数组

  • 通过get(index)方法来获取,index是索引

For的迭代器中,删除添加,ArrayList的某个索引会出现问题?

在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。

什么是内存泄漏?

  • 不再会被使用的对象的内存不能被回收,就是内存泄露。
  • 如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露

ArrayList字符串进行字符串反转方法

1
2
3
4
5
6
7
8
ArrayList aList = new ArrayList();
//Add elements to ArrayList object
aList.add("1");
aList.add("2");
aList.add("3");
aList.add("4");
aList.add("5");
Collections.reverse(aList);

Java代理的几种实现方式

  • 静态代理:只能静态代理某些类或者某些方法,不推荐使用,编码简单
  • 动态代理:包含Proxy代理和CGLIB动态代理

ArraryList与LinkedList的区别(重点)(中水数建)

底层的数据结构有区别,ArraryList是由数组实现的,而LinkedList是基于链表实现的。

链表和数组的在于,数组的内存空间是连续的,根据索引就可以实现随机存取,而链表只能从头遍历。所以对于查找效率来讲,ArrayList要比LinkedList高效。但在插入和删除数据的时候,LinkedList要比ArrayList高效。

HashMap和HashTable的区别是什么(重点)

  • HashTable是线程同步的,HashMap不是线程同步的
  • HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值(key有一个,value可以有多个)
  • HashTable还支持Enumeration,HashMap使用Iterator
  • HashTable与HashMap继承的父类不同,但是实现了相同的接口

HashMap的底层原理是什么?(中水数建)

数组+链表+红黑树

HashMap有哪些线程安全的方式(重点)

  • 通过Collections.synchronizedMap()返回一个新的Map,这个新的Map就是线程安全的。
  • 重写改写HashMap,具体看java.util.concurrent.ConcurrentHashMap

Java类加载器有哪些?(重点)

  • Bootstrap类加载器:它负责将JAVA_HOME/lib路径下的核心类库加载到内存中

  • Extention类加载器:负责加载JAVA_HOME/lib/ext目录下的的类库

  • Application类加载器:负责加载系统java -classpath路径下的类库

如何避免ABA问题(重点)

可以通过加版本号或者时间戳解决,或者保证单项递增或递减就不会出现此类问题。

atomic包下AtomicStampedReference类:其conpareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。

CLASS初始化的过程是什么?(类加载过程)

类加载的过程大致分为:加载–验证–准备–解析–初始化–使用–卸载 相当于 加载–连接–初始化–使用–卸载

类的初始化阶段,是真正开始执行类中定义的Java程序代码(字节码)并按程序员的意图去初始化类变量的过程。更直接地说,初始化阶段是执行类构造器()方法的过程。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序决定,重点就是类变量和静态代码块按源文件中的定义顺序决定执行顺序。

如何实现一个IOC容器?

主要分为四步:

  • 在配置文件中,配置包的扫描路径
  • 根据扫描路径,递归包路径,取出所有的.class文件
  • 反射这些.class文件,确定哪些.class文件需要交给IOC容器管理,把这些类找出来
  • 对需要注入的类进行依赖注入

JVM(重点)

什么是守护线程?

在Java中有两类线程:User Thread和Daemon Thread。任何一个守护线程都是整个JVM中所有非守护线程的保姆。

只要当前JVM实例尚存在任何一个非守护线程没有结束,守护线程就继续工作。比如GC。

设置守护线程要注意,thread.setDaemon(true)必须在thread.start()之前

双亲委派(重点)

如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类还存在其父类加载器,则进一步向上委托,依次递归,直至达到顶层。如果父类加载器可以完成任务,就成功返回,倘若父类加载器无法完成此任务,子加载器才会尝试自己去加载。

双亲委派的优点:

  • 每一个类只被加载了一次,避免了重复加载
  • 有效避免了某些恶意类的加载

为什么要使用线程池?

Java线程的创建非常昂贵,需要JVM和OS配合完成大量的工作

  • 必须为线程堆栈分配和初始化大量内存块,其中包括至少1MB栈内存
  • 需要进行系统调用,以便在OS中创建和注册本地线程

Java高并发应用频繁创建和销毁线程是非常低效的,而且是不被编程规范所允许的。

线程池主要解决了两个问题:

  • 提升性能:线程池能独立负责线程的创建、维护和分配。在执行大量异步任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大限度地对已经创建的线程进行复用。
  • 线程管理:每个Java线程池会保持基本的线程统计信息

JVM8为什么要增加元空间?

持久代

持久代中包含了虚拟机中所有可通过反射获取到的数据,比如Class和Method对象。JVM用于描述应用程序中用到的类和方法的元数据也存储在持久代中除此之外,Java SE库中的类和方法也都存储在这里。

如果JVM发现有的类已经不需要了,他就回去回收这些类。

持久代的大小

他的上限是MaxPermSize,默认是64MB.持久代用完后,会抛出OOM,异常。需要多大的持久代取决于类的数量,方法的大小以及常量池的大小。

元空间

取代了持久代,元空间使用本地内存。

元空间的优点:

  • 类及相关的元数据的生命周期与类加载器保持一致
  • 每个加载器有专门的存储空间
  • 只进行线性分配

JVM内存划分(重点)

JVM内存分为:堆、虚拟机栈、本地方法栈、程序计数器、方法区5部分

JDK1.7与JDK1.8最大的区别是:元数据区取代了永久代(持久代)。元空间本质和持久类似。都是对JVM规范中方法区的实现。不过元空间与持久代最大的不同在于:元数据空间并不在虚拟机中,而是使用本地内存。

新生代:eden、from、to

老年代:old

持久代:perm

元空间:meta

synchronized和lock有哪些区别?(重点)

  • lock是一个接口,synchronized是一个关键字。
  • synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁。而lock发生异常的时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁。
  • lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断。
  • Lock可以通过tryLock来知道有没有获取锁,而synchronized不能

有哪些垃圾回收算法?

  • 标记-清除算法:标记,清除,产生碎片,低效
  • 复制算法:标记,复制到另一边,清除,代价是内存比原来少了一半
  • 标记-整理算法:标记,清楚,整理到一起。费时间
  • 分代收集法:根据对象存活周期,将内存划分为几块。一般是把Java堆分成新生代和老年代,根据各个年代采用合适的算法。

如何预防死锁

死锁发生的四个必要条件:

  • 互斥
  • 非抢占
  • 占有且等待
  • 循环等待

预防死锁的方式就是破坏这四个必要条件。

volatile的可见性和禁止指令重排序怎么实现?(重点)

volatile的功能就是被修饰的变量在被修改后可以立即同步到主内存,被修饰的变量在每次是用之前都从主内存刷新。

可见性本质是通过内存屏障来实现的,禁止指令重排序也是通过内存屏障实现的。

内存屏障:

  • 他确保指令重排序时不会把其后面的指令拍到内存屏障之前的位置,也不会把前面的指令拍到内存屏障后面的位置。在执行到内存屏障这句指令时,在它前面的操作已全部完成。
  • 他会强制将对缓存的修改操作立即写入主存
  • 如果是写操作,它会导致其他CPU中对应的缓存行无效。

禁止指令重排序

程序开多少线程合适?(重点)

CPU密集型:线程数等于核心数cpu

IO密集型:等待时间占比越多,线程开的越多。cpu运行占比越多,线程越少。

线程创建的方式(重点)(中水数建)

  • 继承Thread类创建线程
  • 实现Runnable接口创建线程
  • 使用线程池继承Thread类创建线程
  • 使用Callable和Future创建线程

什么是字节码?采用字节码的好处是什么?

在Java中,供虚拟机理解的代码叫做字节码(即.class文件),它不面向任何的处理器,只面向虚拟机。

每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。

Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行比较高效。而且,由于字节码并不专针对一种机器,因此,Java程序无需重新编译便可在多种不同的计算机上运行。

ThreadLocal的原理及使用场景

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,他存储本线程中所有ThreadLocal对象及其对应的值。

ThreadLocalMap对对象由一个一个Entry对象构成

ThreadLocal内存泄漏

ThreadLocal内存泄露的根本原因是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法:

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。

GC是如何判断对象可以被回收的?(重点)

  • 引用计数法:每一个对象有一个引用属性,新增一个引用时加一,引用释放减一,计数为0的时候可以回收。但是这种方法有一个致命的问题,无法解决循环引用的问题。
  • 可达性分析算法:
    • 从Gc root开始向下搜索,搜索所走过的路径被成为引用链,当一个对象到GcRoot没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就可以判定收回。
    • Gc Root有:
      • 虚拟机栈中的对象
      • 方法区中静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中引用的对象

不同的引用类型

  • 强引用:通过关键字new的对象就是强引用对象,强引用对象任何时候都不会被收回,宁愿OOM

  • 软引用:如果一个对象持有软引用,那么当JVM空间不足时,会被回收。一个类的软引用可以通过java.lang.ref.SoftReferece持有

  • 弱引用:如果一个对象持有弱引用,那么在GC时,只要发现弱引用对象,就会被回收。一个类的弱引用可以通过java.lang.ref.WeakReference持有

  • 虚引用:几乎和没有一样,随时可以被回收。通过PhantomReference持有。

MySQL(重点)

何为索引?有什么作用?(中水数建)

索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。

InnoDB索引的数据结构(中水数建)

  • InnoDB索引采用了B-Tree的数据结构,数据存储在叶子节点上,每个叶子节点默认的大小是16KB。

  • 当新记录插入到InnoDB聚簇索引中时,如果按顺序插入索引记录(升序或降序),当达到叶子节点最大的容量时,下一条记录就会写到新的的页中。

MySQL的B+树的原理(中水数建)

  • B+树只有叶子节点存放 key 和 data,其他内节点只存放 key
  • B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
  • B+树的检索效率很稳定,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。

MySQL的隔离级别有哪些?(中水数建)

  1. 读取未提交内容(脏读)
  2. 读取提交内容(不可重复读)
  3. 可重复读(MySQL默认隔离级别)(可能会幻读)
  4. 可串行化

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

索引存在于磁盘中。存储引擎是基于表的,而不是数据库。

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

innodb既有聚簇索引,也有非聚簇索引。而myisam中只有非聚簇索引。

MySQL为什么需要主从同步?

  • 在业务复杂的系统中,有这么一个情景,有一句sql需要锁表,导致暂时不能使用读服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样即使主库出了锁表的情景,通过从库也可以保证业务的正常运行。
  • 做数据的热备份
  • 业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。

什么是MySQL的主从复制?

业务量比较大的时候,一台服务器承载不住那么大的压力。于是需要多台服务器。这些服务器需要同步信息,以此来缓解压力。MySQL采用异步复制的方式,没必要都复制,效率太低。

什么是MVCC?

MVCC,多版本并发控制,MVCC是一种并发控制的方法。在进行多线程并发读写的时候,不会进行加锁操作,既能保证数据安全,又能保证并发效率。

  • 当前读:update、insert、delete这些操作都是一种当前读,他读取的是最新的纪录版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录加锁。
  • 快照读:不加锁的select就是快照读。快照读的前提是隔离级别不能是串行级别。

Spring

SpringBoot和Spring MVC的区别是什么?

  • Spring Boot有内嵌的Web容器,可以直接打成jar包运行。而Spring MVC需要Web容器才能运行
  • SpringBoot提供了自动装配机制,省去了非常麻烦的配置工作,能够让我更专心于业务逻辑

Spring提供了哪些AOP的方式(中水数建)

1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面
3.纯POJO切面
4.注入式AspectJ切面

AOP的实现原理(中水数建)

Spring中AOP有两种实现方式:

  • JDK动态代理
  • Cglib动态代理

谈谈自己对于 AOP 的了解(中水数建)

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

将一个类声明为 Bean 的注解有哪些?

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

什么是 Spring Bean?

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

谈谈自己对于 Spring IoC 的了解(中水数建)

IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

什么是Bean的自动装配?

之前都是在XML文件中进行手动装备配的,在Spring的XML配置文件中,通过的autowire属性可以为bean指定自动装配的形式。

  • no
  • ByName
  • ByType
  • constructor
  • autodetect

Spring的优势

  • Spring通过DI、和AOP来简化企业级Java开发
  • Spring框架之外还存在一个构建在核心框架之上的庞大生态圈,它将Spring扩展到不同领域,如Web服务、REST、移动开发以及NoSQL
  • Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问

事务的基本特征是什么

- 原子性
- 一致性
- 隔离性
- 持久性

项目

你的前端登陆页面用的什么?(中水数建)

jsp

你的后端用的什么做的交互?(中水数建)

在Web应用程序中,我们经常要跟踪用户身份。当一个用户登录成功后,如果他继续访问其他页面,Web程序如何才能识别出该用户身份?

因为HTTP协议是一个无状态协议,即Web应用程序无法区分收到的两个HTTP请求是否是同一个浏览器发出的。为了跟踪用户状态,服务器可以向浏览器分配一个唯一ID,并以Cookie的形式发送到浏览器,浏览器在后续访问时总是附带此Cookie,这样,服务器就可以识别用户身份。

Session

我们把这种基于唯一ID识别用户身份的机制称为Session。每个用户第一次访问服务器后,会自动获得一个Session ID。如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID。

Session和ModelAndView区别是什么?

在写后台的过程中,往往会遇到单点登录等问题,单点登录就意味着前端的一次请求参数我们会重复使用。这里我们就可以用到Session这个方法。然而ModelAndView方法是将数据处在一个model中,让前端直接获取。

Session中的信息不仅可以在后台各个接口中重复使用,也可以在前端传参的时候重复使用Session中的信息来传递参数,ModelAndView中的信息就是为前端需要的数据提供一个存放空间,不同的时候我们需要用到这两个不同的空间,但是也要把他们区分开来。

Redis

Redis的实现原理?(中水数建)

与传统数据库不同的是 Redis 的数据是存在内存中的(内存数据库),读写速度非常快,被广泛应用于缓存方向。并且,Redis 存储的是 KV 键值对数据。

缓存数据的处理流程是怎样的?(中水数建)

  1. 如果用户请求的数据在缓存中就直接返回。
  2. 缓存中不存在的话就看数据库中是否存在。
  3. 数据库中存在的话就更新缓存中的数据。
  4. 数据库中不存在的话就返回空数据。

Redis 常用的数据结构有哪些?(中水数建)

  • 5 种基础数据结构 :String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
  • 3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。

缓存击穿、缓存穿透、缓存雪崩?(中水数建)

缓存击穿

  • 缓存里没有key,数据库里有数据
  • 某一个key失效,通常是热点数据,导致高并发情况下,请求直接打到DB上,DB直接崩掉(若是大量的key同时失效则变成了缓存雪崩)
    • 解决方法:在查询第一个数据请求上使用一个互斥锁来锁住它。其他线程走到这一步拿不到锁就等着,等到第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

缓存穿透

  • 缓存里没有数据
  • 大量不存在的id去查询数据,会产生大量的请求到DB查询,可能会导致DB由于压力过大而宕机
    • 解决办法:
      • 对null缓存:如果一个查询结果返回为空(不管数据是否存在),我们仍把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
      • Bloom Filter:在缓存之前再加一层Bloom Filter,在查询的时候先去Bloom Filter去查询key是否存在,如果不存在就直接返回,存在再走缓存->查DB

缓存雪崩

  • key对应的数据存在,但在redis中过期,此时若有大量的并发请求过来,这些请求发现缓存过期一般都会从DB加载数据并返回缓存,此时若有大并发的请求可能会瞬间把后端DB压垮。
    • 解决方法:
      • 构建多级缓存:nginx缓存+redis缓存+其他缓存(ehcache等)
      • 将缓存失效时间分散开 :比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
      • 互斥锁:
        • 在第一个请求去查询数据库的时候对他加一个互斥锁,其余查询的请求就会被阻塞住,直到锁被释放,从而保护数据库。

Kafka

Kakfa是一个分布式的基于发布/订阅模式的消息队列(message queue)

Spark

什么是RDD?

  • RDD是一个弹性分布式的数据集合
  • 数据可以保存到内存中也可以保存到磁盘中
  • 他是分布式计算的一个数据集合
  • RDD有三个特征分区、不可变、并行操作
  • RDD是由很多分区组成,操作RDD的时候,对RDD里面的每一个分区进行操作
  • RDD使用算子进行操作
  • 算子又分为转换算子和行动算子

MapReduce的Shuffle过程

MapReduce的Shuffle:

  • 分区
  • 排序
  • 局部聚合
  • 分组

Spark的shuffle过程

Spark使用一些涉及到Shuffle的算子就会进行Shuffle

宽依赖与窄依赖?

  • 窄依赖是指父RDD的每个分区只被子RDD的一个分区所使用,子RDD分区通常对应常数个父RDD分区
  • 宽依赖是指父RDD每个分区都可能被多个子RDD分区所使用,子RDD分区通常对应所有的父RDD分区

SparkStreaming消费kafka数据的两种方式

  • receiver:通过调用kafka的高阶API来获取kafka中的数据,读取到kafka数据后,会先加载到sparkexecutor中的内存中,然后SparkStreaming启动job就会处理这批数据
  • direct:定时去读取kafka中的topic+partition中最新的offset,通过调用kafka的低阶API指定offset来获取数据

Spark中的广播变量是什么?

广播变量是一个只读变量,通过它我们可以将一些共享数据集或者大变量缓存在Spark集群中的各个机器上而不用每个task都需要copy一个副本,后续计算可以重复使用,减少了数据传输时网络带宽的使用,提高效率。相比于Hadoop的分布式缓存,广播的内容可以跨作业共享。

Spark性能调优方法

1,首先调整任务并行度,并调整partition分区。

2,尝试定位可能的重复计算,并优化之。

3,尝试定位数据倾斜问题或者计算倾斜问题并优化之。

4,如果shuffle过程提示堆外内存不足,考虑调高堆外内存。

5,如果发生OOM或者GC耗时过长,考虑提高executor-memory或降低executor-core。

Spark 数据倾斜及其解决方案

『不患多而患不均』,这是分布式环境下最大的问题。意味着计算能力不是线性扩展的,而是存在短板效应: 一个 Stage 所耗费的时间,是由最慢的那个 Task 决定。

由于同一个 Stage 内的所有 task 执行相同的计算,在排除不同计算节点计算能力差异的前提下,不同 task 之间耗时的差异主要由该 task 所处理的数据量决定。所以,要想发挥分布式系统并行计算的优势,就必须解决数据倾斜问题。

数据倾斜的原因

在进行 shuffle 的时候,必须将各个节点上相同的 key 拉取到某个节点上的一个 task 来进行处理,比如按照 key 进行聚合或 join 等操作。此时如果某个 key 对应的数据量特别大的话,就会发生数据倾斜。比如大部分 key 对应10条数据,但是个别 key 却对应了100万条数据,那么大部分 task 可能就只会分配到10条数据,然后1秒钟就运行完了;但是个别 task 可能分配到了100万数据,要运行一两个小时。

因此出现数据倾斜的时候,Spark 作业看起来会运行得非常缓慢,甚至可能因为某个 task 处理的数据量过大导致内存溢出。

Debug:

  • 通过WebUI看那个阶段卡住了,然后再联想一下代码中设计的Shuffle算子
  • 如果报异常了,则在yarn的日志中可以定位到具体的代码行

解决方案:

  • 对聚合类算子进行两次操作,第一次给key加上一个随机数,然后聚合一次,第二次将加上的随机数取消在聚合一次
  • 将reduce Join转换成map Join
  • 核心思想就是将key均匀分布到不同的分区中,并行去处理这些数据

HDFS的读写流程?

HDFS写流程:

  1. Client客户端发送上传请求,通过RPC与NameNode建立通信,NameNode检查该用户是否有上传权限,以及上传的文件是否在HDFS对应的目录下重名,如果这两者有任意一个不满足,则直接报错,如果两者都满足,则返回给客户端一个可以上传的信息;

  2. Client根据文件的大小进行切分,默认128M一块,切分完成之后给NameNode发送请求第一个block块上传到哪些服务器上;

  3. NameNode收到请求之后,根据网络拓扑和机架感知以及副本机制进行文件分配,返回可用的DataNode的地址;

注:Hadoop在设计时考虑到数据的安全与高效, 数据文件默认在HDFS上存放三份, 存储策略为本地一份,同机架内其它某一节点上一份, 不同机架的某一节点上一份。

  1. 客户端收到地址之后与服务器地址列表中的一个节点如A进行通信,本质上就是RPC调用,建立pipeline,A收到请求后会继续调用B,B在调用C,将整个pipeline建立完成,逐级返回Client;

  2. Client开始向A上发送第一个block(先从磁盘读取数据然后放到本地内存缓存),以packet(数据包,64kb)为单位,A收到一个packet就会发送给B,然后B发送给C,A每传完一个packet就会放入一个应答队列等待应答;

  3. 数据被分割成一个个的packet数据包在pipeline上依次传输,在pipeline反向传输中,逐个发送ack(命令正确应答),最终由pipeline中第一个DataNode节点A将pipelineack发送给Client;

  4. 当一个block传输完成之后, Client再次请求NameNode上传第二个block,NameNode重新选择三台DataNode给Client。

HDFS读流程:

  1. Client向NameNode发送RPC请求。请求文件block的位置;

  2. NameNode收到请求之后会检查用户权限以及是否有这个文件,如果都符合,则会视情况返回部分或全部的block列表,对于每个block,NameNode都会返回含有该block副本的DataNode地址;这些返回的DataNode地址,会按照集群拓扑结构得出DataNode与客户端的距离,然后进行排序,排序两个规则:网络拓扑结构中距离 Client 近的排靠前;心跳机制中超时汇报的DataNode状态为STALE,这样的排靠后;

  3. Client选取排序靠前的DataNode来读取block,如果客户端本身就是DataNode,那么将从本地直接获取数据(短路读取特性);

  4. 底层上本质是建立Socket Stream(FSDataInputStream),重复的调用父类DataInputStream的read方法,直到这个块上的数据读取完毕;

  5. 当读完列表的block后,若文件读取还没有结束,客户端会继续向NameNode 获取下一批的block列表;

  6. 读取完一个block都会进行checksum验证,如果读取DataNode时出现错误,客户端会通知NameNode,然后再从下一个拥有该block副本的DataNode 继续读;

  7. read方法是并行的读取block信息,不是一块一块的读取;NameNode只是返回Client请求包含块的DataNode地址,并不是返回请求块的数据;

  8. 最终读取来所有的block会合并成一个完整的最终文件;

Zookeeper

Zookeeper 是一个分布式协调服务的开源框架。 主要用来解决分布式集群中应用系统的一致性问题,例如怎样避免同时操作同一数据造成脏读的问题。

ZooKeeper 本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。 诸如: 统一命名服务(dubbo)、分布式配置管理(solr 的配置集中管理)、分布式消息队列(sub/pub)、分布式锁、分布式协调等功能。

zk 架构图:

img

Leader:

Zookeeper 集群工作的核心;
事务请求(写操作) 的唯一调度和处理者,保证集群事务处理的顺序性;
集群内部各个服务器的调度者。
对于 create, setData, delete 等有写操作的请求,则需要统一转发给 leader 处理, leader 需要决定编号、执行操作,这个过程称为一个事务。

Follower:

处理客户端非事务(读操作) 请求,
转发事务请求给 Leader;
参与集群 Leader 选举投票 2n-1 台可以做集群投票。
此外,针对访问量比较大的 zookeeper 集群, 还可新增观察者角色。

Observer:

观察者角色,观察 Zookeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给 Leader 服务器进行处理。
不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务 处理能力的前提下提升集群的非事务处理能力。
简答:说白了就是增加并发的读请求

ZAB 协议全称:Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)。

ZAB 协议是专门为 zookeeper 实现分布式协调功能而设计。zookeeper 主要是根据 ZAB 协议是实现分布式系统数据一致性。

zookeeper 根据 ZAB 协议建立了主备模型完成 zookeeper 集群中数据的同步。这里所说的主备系统架构模型是指,在 zookeeper 集群中,只有一台 leader 负责处理外部客户端的事物请求(或写操作),然后 leader 服务器将客户端的写操作数据同步到所有的 follower 节点中。

Hive