栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java高频面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java高频面试题

J2SE

1.String和StringBuilder和StringBuffer区别

String是不可变字符串,底层是基于字符数组,且有final修饰,因此每次对String的操作都会在内存中开辟空间,生成新的对象

StringBuilder和StringBuffer是可变字符串,修改不会生成新的对象

StringBuffer是线程安全的,方法有synchronized修饰,性能较低

StringBuilder是线程不安全的,方法没有synchronized修饰,性能较高

2.ArrayList和linkedList区别,你一般用哪一个,为什么

ArrayList是基于数组实现的,根据索引随机访问元素性能高,但是插入和删除元素性能差,因为这会涉及到移位操作

linkedList是基于双链表实现的,不支持索引,随机访问元素需要从头查找,因此性能差,但是添加删除性能高因为不涉及移位操作,但是linkedList容易造成内存的碎片化,增加内存管理难度。

根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用linkedList

3.创建线程是几种方式

方式一:继承Thread类,覆写run方法,创建实例对象,调用该对象的start方法启动线程 方式二:创建Runnable接口的实现类,类中覆写run方法,再将实例作为此参数传递给Thread类有参构造创建线程对象,调用start方法启动

方式三:创建Callable接口的实现类,类中覆写call方法,创建实例对象,将其作为参数传递给FutureTask类有参构造创建FutureTask对象,再将FutureTask对象传递给Thread类的有参构造创建线程对象,调用start方法启动

方式一有单继承的局限性

方式二和方式三避免了单继承的局限,使用更广泛。而方式二适用于无需返回值的场景,方式三使用于有返回值的场景

4.直接调用线程的start方法和run方法有什么区别

start是开启新线程,而调用run方法是一个普通方法调用,还是在主线程里执行

5.ArrayList和Vector区别

ArrayList是线程不安全的,Vector是线程安全的,方法有synchronized修饰

ArrayList底层数组容量不足时,会自动扩容0.5倍,Vector会自动扩容1倍

6.说一下面向对象的几大特征

继承:对类的抽象,将公共部分抽取出来,子类可以拥有父类的属性和行为,意义在于提高代码的复用性

封装:对外隐藏实现细节,提供公共访问的方式,意义在于提高安全性

多态:对象的多种形态,当编译时类型和运行时类型不一样,就是多态,意义在于屏蔽子类差异

7.接口和抽象类

定义接口使用interface,定义抽象类使用abstract class

接口由全局常量,抽象方法,(java8后:静态方法,默认方法)

抽象类由构造方法,抽象方法,普通方法

接口和类是实现关系,抽象类和类是继承关系

8.Equals和==的区别

equals()方法,适用于引用数据类型,覆写Object中的equals()方法,判断对象中的内容是否相同

==是比较运算符,适用于基本数据类型和引用数据类型,基本数据类型时比较的是值,引用数据类型比较的是地址

9.int和Integer的区别

int是基本数据类型,变量中直接存放数值,变量初始化时值是0

Integer是引用数据类型,变量中存放的是该对象的引用,变量初始化时值时null

Integer是int类型的包装类,将int封装成Integer,符合java面向对象的特性,可以使用各种方法比如和其他数据类型间的转换

Integer和int的深入对比:

1.两个通过new生成的Integer对象,由于在堆中地址不同,所以永远不相等

2.int和Integer比较时,只要数值相等,结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int

3.通过new生成的Integer对象和非通过new生成的Integer对象相比较时,由于前者存放在堆中,后者存放在Java常量池中,所以永远不相等

4.两个非通过new生成的Integer对象比较时,如果两个变量的数值相等且在-128到127之间,结果就相等。这是因为给Integer对象赋一个int值,java在编译时,会自动调用静态方法valueOf(),根据java api中对Integer类型的valueOf的定义,对于-128到127之间的整数,会进行缓存,如果下次再赋相同的值会直接从缓存中取,即享元模式

问?Integer 和 int用 == 比较结果一定是true吗?

11.说一下Java中的集合体系,以及他们的特点

我们按照两种接口来分类

Collection接口

List:

ArrayList:底层数据结构是数组,查询性能高,增删性能低

Vector:底层数据结构是数组,查询性能高,增删性能低

linkedList:底层数据结构是双向链表,查询性能低,增删性能高

Set:

HashSet:无序不重复的,底层数据结构是数组+链表+红黑树,判断重复依据是hashCode()和equals()

TreeSet:有序不重复的,底层数据结构是数组+链表+红黑树,排序方式分为自然排序,比较器排序

Map接口

HashMap:key的值没有顺序,线程不安全

TreeMap:key的值可以自然排序,线程不安全

HashTable:它的key和value都不允许为null,线程安全

Properties:它的key和value都是String类型的,线程安全

12.HashMap和HashTable的区别在多线程中又要保证线程安全,又要保证性能,应该用哪种Map?

HashMap和HashTable都是实现了Map接口的集合框架,他们的区别

第一,HashTable是线程安全的,它的实现方法都加了synchronized关键字,因此它的性能较低

HashMap是线程不安全的,它实现方法没有加synchronized,因此它的性能较高

第二,HashMap的key和value都允许为null,HashTable中的key和value都不能为null

如果不考虑线程安全,建议使用HashMap,如果需要考虑线程安全的高并发实现,建议使用ConcurrentHashMap

13.Synchronized 和 lock的区别

他们都是用来解决并发编程中的线程安全问题的,不同的是

synchronized是一个关键字,依靠Jvm内置语言实现,底层是依靠指令码来实现;Lock是一个接口,它基于CAS乐观锁来实现的

synchronized在线程发生异常时,会自动释放锁,不会发生异常死锁,Lock在异常时不会自动释放锁,我们需要在finally中释放锁

synchronized是可重入,不可判断,非公平锁,Lock是可重入,可判断的,可手动指定公平锁或者非公平锁

14.悲观锁和乐观锁

悲观锁和乐观锁,指的是看待并发同步问题的角度

悲观锁认为,对同一个数据的并发操作,一定是会被其他线程同时修改的。所以在每次操作数据的时候,都会上锁,这样别人就拿不到这个数据。如果不加锁,并发操作一定会出问题。用阳间的话说,就是总有刁民想害朕

乐观锁认为,对同一个数据的并发操作,是不会有其他线程同时修改的。它不会使用加锁的形式来操作数据,而是在提交更新数据的时候,判断一下在操作期间有没有其他线程修改了这个数据

悲观锁一般用于并发小,对数据安全要求高的场景

乐观锁一般用于高并发,多读少写的场景,但需要注意ABA问题,通常使用版本号控制,或者时间戳来解决

15.线程的几种状态

新建状态:线程刚创建,还没有调用start方法之前

就绪状态:也叫临时阻塞状态,当调用了start方法后,具备cpu的执行资格,等待cpu调度器轮询的状态

运行状态:就绪状态的线程,获得了cpu的时间片,真正运行的状态

冻结状态:也叫阻塞状态,指的是该线程因某种原因放弃了cpu的执行资格,暂时停止运行的状态,比如调用了wait,sleep方法

死亡状态:线程执行结束了,比如调用了stop方法

16.sleep 和 wait的区别

第一,sleep方法是Thread类的静态方法,wait方法是Object类的方法

第二:sleep方法不会释放对象锁,wait方法会释放对象锁

第三:sleep方法必须捕获异常,wait方法不需要捕获异常

17.Synchronized 加在方法上 锁的对象是什么,加在静态方法上锁住的对象是什么?Synchronized(this) 和 Synchronized (User.class) 的区别

实例方法上的锁,锁住的是这个对象实例,它不会被实例共享,也叫做对象锁

静态方法上的锁,锁住的是这个类的字节码对象,它会被所有实例共享,也叫做类锁

Synchronized(this) 中,this代表的是该对象实例,不会被所有实例共享

Synchronized (User.class),代表的是对类加锁,会被所有实例共享

18.Synchronized 和 volatitle 关键字的区别

这两个关键字都是用来解决并发编程中的线程安全问题的,不同点主要有以下几点

第一:volatile的实现原理,是在每次使用变量时都必须重主存中加载,修改变量后都必须立马同步到主存;

synchronized的实现原理,则是锁定当前变量,让其他线程处于阻塞状态

第二:volatile只能修饰变量,synchronized用在修饰方法和同步代码块中

第三:volatile修饰的变量,不会被编译器进行指令重排序,synchronized不会限制指令重排序

第四:volatile不会造成线程阻塞,高并发时性能更高,synchronized会造成线程阻塞,高并发效率低

第五:volatile不能保证操作的原子性,因此它不能保证线程的安全,synchronized能保证操作的原子性,保证线程的安全

19.创建线程的3种方式 三种线程的区别? start() 和 run()方法区别

方式一:继承Thread类,覆写run方法,创建实例对象,调用该对象的start方法启动线程 ​ 方式二:创建Runnable接口的实现类,类中覆写run方法,再将实例作为此参数传递给Thread类有参构造创建线程对象,调用start方法启动

方式三:创建Callable接口的实现类,类中覆写call方法,创建实例对象,将其作为参数传递给FutureTask类有参构造创建FutureTask对象,再将FutureTask对象传递给Thread类的有参构造创建线程对象,调用start方法启动

方式一有单继承的局限性

方式二和方式三避免了单继承的局限,使用更广泛。而方式二适用于无需返回值的场景,方式三使用于有返回值的场景

start才是开启新线程,而调用run方法是一个普通方法调用,还是在主线程里执行

20.Synchronized和Lock的区别

他们都是用来解决并发编程中的线程安全问题的,不同的是

synchronized是一个关键字,依靠Jvm内置语言实现,底层是依靠指令码来实现;Lock是一个接口,它基于CAS乐观锁来实现的

synchronized在线程发生异常时,会自动释放锁,不会发生异常死锁,Lock在异常时不会自动释放锁,我们需要在finally中释放锁

synchronized是可重入,不可判断,非公平锁,Lock是可重入,可判断的,可手动指定公平锁或者非公平锁

21.线程的生命周期 sleep 和 wait区别

新建状态:线程刚创建,还没有调用start方法之前

就绪状态:也叫临时阻塞状态,当调用了start方法后,具备cpu的执行资格,等待cpu调度器轮询的状态

运行状态:就绪状态的线程,获得了cpu的时间片,真正运行的状态

冻结状态:也叫阻塞状态,指的是该线程因某种原因放弃了cpu的执行资格,暂时停止运行的状态,比如调用了wait,sleep方法

死亡状态:线程执行结束了,比如调用了stop方法

sleep 和 wait区别

第一,sleep方法是Thread类的静态方法,wait方法是Object类的方法

第二:sleep方法不会释放对象锁,wait方法会释放对象锁,需要notify唤醒

第三:sleep方法必须捕获异常,wait方法不需要捕获异常

22.synchronized 锁的原理,JDK1.6之后做了什么样的优化?

在字节码层面:

synchronized在修饰方法时,通过ACC_SYNCHRONIZED关键字来标识对方法进行加锁,当线程要执行该方法时,需要获得锁才能执行

synchronized在修饰同步代码块时,通过monitorenter和monitorexit指令来执行加锁,当线程执行到monitorenter指令时,需要获取monitor的所有权才能执行,当执行到monitorexit的时候就要释放锁

在JVM层面:

无论时同步方法,还是同步代码块,这两种实现方式都是为了获取Monitor对象,这个对象在JVM中是基于C++实现的,叫做ObjectMonitor,当获取锁执行enter方法,释放锁执行exit方法。当某个线程获取到对象的monitor时,会把owner变量设置为当前线程,同时计数器count加1,也就意味着当前线程获得了对象锁

在操作系统层面:

monitor依靠的是操作系统老大哥的大锁,Mutex lock来实现的,这把大锁太重,会让整个程序性能变得很低

因此在JDK1.6及以后的版本中,增加了锁升级的过程,依次为无锁,偏向锁,轻量级锁,重量级锁。而且还增加了锁粗化,锁消除等策略,这就节省了锁操作的开销,提高了性能

23.乐观锁的使用场景(数据库,ES)

场景一:ES中对version的控制

场景二:Mysql中版本号的控制

场景三:原子类中的CompareAndSwap操作

24.什么是CAS

CompareAndSwap,比较和交换,是一种乐观锁的实现

举个栗子,线程t1需要将内存中的变量A=1,修改为A=2

第一步,将主存中A的值A=1拷贝一份到工作内存中

第二步,修改工作内存中变量A的值为A=2

第三步,再次判断主存中A的值,如果此时A=1,就将A=2的值写入主存中

25.AtomicInterger怎么保证并发安全性的

通过CAS操作原理来实现的,就可见性和原子性两个方面来说

它的value值使用了volatile关键字修饰,也就保证了多线程操作时内存的可见性

volatile跳过内存,直接在主存中操作

Unsafe这个类是一个很神奇的类,而compareAndSwapInt这个方法可以直接操作内存,依靠的是C++来实现的,它调用的是Atomic类的cmpxchg函数。而这个函数的实现是跟操作系统有关的,比如在X86的实现就利用汇编语言的CPU指令lock cmpxchg,它在执行后面的指令时,会锁定一个北桥信号,最终来保证操作的原子性

26.什么是乐观锁,什么是悲观锁,什么是重入锁,什么是自旋锁,怎么是阻塞。

悲观锁和乐观锁,指的是看待并发同步问题的角度

悲观锁认为,对同一个数据的并发操作,一定是会被其他线程同时修改的。所以在每次操作数据的时候,都会上锁,这样别人就拿不到这个数据。如果不加锁,并发操作一定会出问题。用阳间的话说,就是总有刁民想害朕

乐观锁认为,对同一个数据的并发操作,是不会有其他线程同时修改的。它不会使用加锁的形式来操作数据,而是在提交更新数据的时候,判断一下在操作期间有没有其他线程修改了这个数据

可重入锁是指允许同一个线程多次获取同一把锁,比如一个递归函数里有加锁操作

自旋锁不是锁,而是一种状态,当一个线程尝试获取一把锁的时候,如果这个锁已经被占用了,该线程就处于等待状态,并间隔一段时间后再次尝试获取的状态,就叫自旋

阻塞,指的是当一个线程尝试获取锁失败了,线程就就进行阻塞,这是需要操作系统切换CPU状态的

27.你用过JUC中的类吗,说几个:

Lock锁

ConcurrentHashMap

AtomicInteger

。。。

28.ThreadLocal的作用和原理? ThreadLocal的使用场景

ThreadLocal,翻译成中国话,叫做线程本地变量,它是为了解决线程安全问题的,它通过为每个线程提供一个独立的变量副本,来解决并发访问冲突问题

在SpringMVC中将数据与线程进行绑定达到线程隔离的效果

原理:ThredLocal是和当前线程有关系的,每个线程内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它用来存储每个线程中的变量副本,key就是ThreadLocal变量,value就是变量副本。

当我们调用get方法时,就会在当前线程里的threadLocals中查找,它会以当前ThreadLocal变量为key获取当前线程的变量副本

它的使用场景比如在spring security中,我们使用SecurityContextHolder来获取SecurityContext,

比如在springMVC中,我们通过RequestContextHolder来获取当前请求,

比如在 zuul中,我们通过ContextHolder来获取当前请求

29.线程池的作用,常用的线程池有几种,分别是什么意思?

线程池它和连接池类似的,主要作用是控制并发数量,同时可以实现线程的复用,也能管理线程的生命周期

常见的线程池有四种:

CachedThreadPool:可缓存的线程池

它在创建的时候,没有核心线程,线程最大数量是Integer最大值,最大空闲时间是60S

FixedThreadPool:固定长度的线程池

它的最大线程数等于核心线程数,此时没有最大空闲时长为0

SingleThreadPool:单个线程的线程池

它的核心线程和最大线程数都是1,也就是说所有任务都串行的执行

ScheduledThreadPool:可调度的线程池

它的最大线程数是Integer的最大值,默认最长等待时间是10S,它是一个由延迟执行和周期执行的线程池

30.线程池的执行流程

当一个任务来临时,如果有空闲的核心线程,直接使用

如果没有空闲的线程了,就将任务移入队列,等待执行

如果队列也满了,就新建线程来执行任务

如果新线程加上核心线程已经达到了最大线程数,那接下来的任务就会执行拒绝策略

31.解释一下线程池构造器的7个参数

CorePoolSize:核心线程数,它是不会被销毁的

MaximumPoolSize :最大线程数,核心线程数+非核心线程数的总和

KeepAliveTime:非核心线程的最大空闲时间,到了这个空闲时间没被使用,非核心线程销毁

Unit:空闲时间单位

WorkQueue:是一个BlockingQueue阻塞队列,超过核心线程数的任务会进入队列排队

ThreadFactory:它是一个创建新线程的工厂

Handler:拒绝策略,任务超过最大线程数+队列排队数 ,多出来的任务该如何处理取决于Handler

32.EurekaClient拉取注册表&心跳续约用到了什么技术来实现?

它用到了一个带有周期性任务的线程池ScheduledThreadPoolExecutor来实现的

33.数据结构有哪几种分类?

按照逻辑结构分:

集合:没有相互关系的一堆数据

线性结构:元素存在一对一的相互关系

树形结构:元素存在一对多的相互关系

图形结构:元素存在多对多的相互关系

按照物理结构分:

顺序存储结构:用一组地址连续的存储空间依次存储线性表的数据元素,也叫顺序存储结构,比如数组

链接存储结构:用一组任意的存储空间来存储线性表中的数据元素,不要求相邻元素在物理位置上也相邻,比如链表

数据索引存储结构:建立附加的索引来标识节点的地址,通过索引,可以很快检索数据

数据散列存储结构:将数据元素的存储位置与关键字之间建立确定的对应关系,加快查找的速度,又叫hash存储

34.数组和链表在内存中的存储结构有什么区别?

数组在内存中是一组连续的存储空间,它随机存取元素性能很高,但是插入和删除操作,需要移动其他元素,因此性能很低

链表在内存中的存储空间可以是不连续的,而在每一个元素中都保存相邻节点的指针,因此它的存储密度相对较小,查找的性能低,因为需要从第一个元素依次遍历,但是它的插入和删除操作性能很高,因为它不需要移动节点,只需要改变相邻节点指针就行了,同时它更容易造成内存的碎片化

35.说一下散列存储(Hash存储) , 什么是Hash冲突 , 有什么解决方案

散列存储,它通过把关键码的值映射到表中的一个位置,来提高查询的速度。而这个映射函数叫做散列函数。

哈希冲突,也叫哈希碰撞,指的是两个不同的值,计算出了相同的hash,也就是两个不同的数据计算出同一个下标,通常解决方案有:

拉链法,把哈希碰撞的元素指向一个链表

开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突

再散列法,就是换一种哈希算法重来一次

建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表

36.举例说说时间复杂度,比如:数组,链表,循环,嵌套循环 , 2个非嵌套循环 , 在什么情况下时间复杂度是什么?

时间复杂度是用来度量算法执行的时间长短,通常我们用O(f(n))渐进时间复杂度来衡量,比如说

要在 hash 表中找到一个元素就是 O(1)

要在无序数组中找到一个元素就是 O(n)

访问数组的第 n 个元素是 O(1)

二分搜索的时间复杂度最好的情况是 O(1),最坏情况(平均情况)下 O(log n)

访问链表的第 n 个元素是 O(n)

一个For循环是O(n)

两个For循环嵌套是O(n2)

三个Foreach嵌套是O(n3)

37.JDK中线性结构的集合有哪些?

数组:按照顺序物理结构存储,ArrayList

链表:按照链式物理结构存储,linkedList

栈:LIFO后进先出的线性存储结构,分为用数组实现的顺序栈,用链表实现的链栈

队列:FIFO先进先出的线性存储结构,分为顺序队列和链式队列

串:特殊的线性存储结构,String,StringBuffer,StringBuilder

38.你说一下树形结构和线性结构的优势?

线性结构,对于大量的输入数据,访问时间很长,效率很低

树形结构的优势在于它查找数据性能很高

39.说一下树的分类,以及你对它们的理解(二叉查找树的优缺点,平衡树的优缺点,红黑树的优缺点,B-树的优缺点 ,B+树的优缺点)平衡树 ,红黑树

二叉树:树中任意节点最多只有两个分叉的树,它又分为二叉排序树,平衡二叉树,赫夫曼树,红黑树

二叉排序树,它是一个有序的二叉树,优势在于查找插入数据的性能很高,但是可能会出现倾斜而变成数组

平衡二叉树,二叉排序树进化形态,要求任何节点的两颗子树高度差不大于1。它的查询性能很高,但是每次增删元素,会重排序导致性能低

红黑树,自平衡二叉树,要求根节点和叶子节点是黑色,其他节点红黑交替,在任何一个子树中,从根节点向下走到空姐点的路径经过的黑节点数相同。从而保证了平衡。它的查询性能比平衡二叉树稍低,插入和删除元素的性能大幅提高。

赫夫曼树,它是根据数据出现的次数来构建二叉树,以权值作为根节点,构建N个二叉树,组成森林。它主要用来编码,比如压缩技术的实现

多叉树:解决二叉树存储大规模数据时,深度过大而导致IO性能低,查询效率低的问题,常见有B树和B+树,字典树,后缀树等等

B树,自平衡的树,适用于读写相对大的数据块,比如文件系统,数据库索引

B+树,B树升级版,它的内部节点只存储key,不存储具体数据,叶子节点存放key和具体数据。这就使得每个节点可以存更多的key,树的高度更低,查询更快,同时它每次查询都会到叶子节点,查询速度更稳定。并且所有的叶子节点会组成一个有序链表,方便区间查询

40.有是二叉树为什么要出现多叉树

因为二叉树在大规模的数据存储中,树会高的没谱,这会导致IO读写过于频繁,查询效率低下

多叉树可以解决这个问题,它每层可以存放更多的数据,因此能大幅度降低树的深度,提高查询性能

41.b-tree和b+tree的区别?

B树每个节点都可以存放key,存放数据,而B+树所有内部节点只存放key,叶子节点存放key和数据,因此它能存放更多数据,降低树高,查询性能更快

M阶的B树,节点中存放元素的个数是M-1个,而M阶B+树,节点中存放的元素就是M个

B+树所有的叶子节点会构成一个链表结构,方便区间查找和排序

42.说一下ES用到了什么数据结构?

ES是使用了数据索引存储结构,它是通过为关键字建立索引,通过索引找到对应的数据,这种索引也叫倒排索引,可以实现快速检索

43.常见的算法了解几个? 手写一个冒泡,手写一个二分查找

散列算法,将任意长度的输入,通过散列算法变成固定长度的输出。可以对节点快速检索

递归,一个方法自己调用自己

排序,常见排序方法有,插入排序,希尔排序,冒泡排序,比较排序。。。

查找,常见查找方法有,顺序查找,二分查找,插值查找,哈希查找。。。

44.一个User的List集合,如何实现根据年龄排序?

第一种方式,让User类实现Comparable接口,覆写compareTo方法,方法中自定义根据年龄比较的算法

第二种方式,调用Collections.sort方法,传入一个比较器,覆写compare方法,方法中自定义根据年龄比较的算法

45.HashMap底层用到了那些数据结构?

JDK1.7及其之前:数组,链表

JDK1.8开始:数组,链表,红黑树

46.为什么要用到链表结构?

当我们向HashMap中添加元素时,会先根据key尽心哈希运算,把hash值模与数组长度得到一个下标,然后将该元素添加进去。

但是如果产生了哈希碰撞,也就是不同的key计算出了相同的hash值,这就出问题了,因此它采用了拉链法来解决这个问题,将产生hash碰撞的元素,挂载到链表中

47.为什么要用到红黑树?

当HashMap中同一个索引位置出现哈希碰撞的元素多了,链表会变得越来越长,查询效率会变得越来越慢。因此在JDK1.8之后,当链表长度超过8个,会将链表转坏为红黑树来提高查询

——————

tips 为啥子是红黑树:虽然平衡二叉树的查询性能高,但它在每次增删元素后,为了维护自身平衡需要重新排序,这是很耗性能的;而红黑树在每次增删元素后,只需要改变节点颜色就可以了,因此它的增删性更好

综合考虑一下,为了保证性能,选择了红黑树

48.链表和红黑树在什么情况下转换的?

当链表的长度大于等于8,同时数组的长度大于64,链表会自动转化为红黑树

当树中的节点数小于等于6,红黑树会自动转化为链表

——————

tips treeify的阈值为啥子是8,不是7,不是9:泊松分布

untreeify的阈值为啥子是6,不是7,不是8:防止链表和树频繁转换

49.HashMap在什么情况下扩容?HashMap如何扩容的?

HashMap的数组初始容量是16,负载因子是0.75,也就是说当数组中的元素个数大于12个,会成倍扩容

——————

tips 为啥子是0.75:负载因子过小容易浪费空间,过大容易造成更多的哈希碰撞,产生更多的链表和树,因此折衷考虑采用了0.75

为啥子是成倍扩容:需要保证数组的长度是2的整数次幂

为嘛数组的长度必须是2的整数次幂:我们在存储元素到数组中的时候,是通过hash值模与数组的长度,计算出下标的。但是由于计算机的运算效率,加减法>乘法>除法>取模,取模的效率是最低的。开发者们为了让你用的开心,也是呕心沥血。将取模运算转化成了与运算,即数组长度减1的值和hash值的与运算,以此来优化性能。但是这个转化有一个前提,就是数组的长度必须为2的整数次幂

50.HashMap是如何Put一个元素的?

首先,将key进行hash运算,将这个hash值与上当前数组长度减1的值,计算出索引。此时判断该索引位置是否已经有元素了,如果没有,就直接放到这个位置

如果这个位置已经有元素了,也就是产生了哈希碰撞,那么判断旧元素的key和新元素的key的hash值是否相同,并且将他们进行equals比较,如果相同证明是同一个key,就覆盖旧数据,并将旧数据返回,如果不相同的话

再判断当前桶是链表还是红黑树,如果是红黑树,就按红黑树的方式,写入该数据,

如果是链表,就依次遍历并比较当前节点的key和新元素的key是否相同,如果相同就覆盖,如果不同就接着往下找,直到找到空节点并把数据封装成新节点挂到链表尾部。然后需要判断,当前链表的长度是否大于转化红黑树的阈值,如果大于就转化红黑树,最后判断数组长度是否需要扩容。

51.HashMap是如何Get一个元素的?

首先将key进行哈希运算,计算出数组中的索引位置,判断该索引位置是否有元素,如果没有,就返回null

如果有值,判断该数据的key是否为查询的key,如果是就返回当前值的value

如果第一个元素的key不匹配,判断是红黑树还是链表

如果是红黑树,就就按照红黑树的查询方式查找元素并返回

如果是链表,就遍历并匹配key,让后返回value值

52.什么是Hash冲突

哈希冲突,也叫哈希碰撞,指的是两个不同的值,计算出了相同的hash,也就是两个不同的数据计算出同一个下标,通常解决方案有:

拉链法,把哈希碰撞的元素指向一个链表

开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突

再散列法,就是换一种哈希算法重来一次

建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表

53.HashMap是如何解决Hash冲突的?

采用拉链法,将哈希碰撞的元素,转化为链表

从JDK1.8开始,增加了红黑树,当链表的长度大于8,就将链表转化为红黑树,来优化查询性能

54.还有哪些解决Hash冲突的方式?

开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突

再散列法,就是换一种哈希算法重来一次

建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表

55.你知道HahsMap死循环问题吗?

HashMap在扩容数组的时候,会将旧数据迁徙到新数组中,这个操作会将原来链表中的数据颠倒,比如a->b->null,转换成b->a->null

这个过程单线程是没有问题的,但是在多线程环境,就可能会出现a->b->a->b....,这就是死循环

在JDK1.8后,做了改进保证了转换后链表顺序一致,死循环问题得到了解决。但还是会出现高并发时数据丢失的问题,因此在多线程情况下还是建议使用ConcurrentHashMap来保证线程安全问题

56.介绍一下Java中的集合体系

Collection接口

List:

ArrayList:底层数据结构是数组,查询性能高,增删性能低

Vector:底层数据结构是数组,查询性能高,增删性能低

linkedList:底层数据结构是双向链表,查询性能低,增删性能高

Set:

HashSet:无序不重复的,底层数据结构是数组+链表+红黑树,判断重复依据是hashCode()和equals()

TreeSet:有序不重复的,底层数据结构是数组+链表+红黑树,排序方式分为自然排序,比较器排序

Map接口

HashMap:key的值没有顺序,线程不安全

TreeMap:key的值可以自然排序,线程不安全

HashTable:它的key和value都不允许为null,线程安全

Properties:它的key和value都是String类型的,线程安全

57.如果需要从一个List集合中频繁的删除和添加元素,是选择ArrayList还是linkedList?为什么?

使用linkedList

因为ArrayList在底层使用的是数组,物理结构属于顺序存储结构,在内存中使用连续的存储空间,它随机访问元素性能很高,但是在删除和添加元素后需要移动其他元素,因此他的插入删除性能比较低。

而linkedList在底层使用的是链表,物理结构属于链式存储结构,它不要求在内存中的存储空间连续,只需要保存相邻节点的指针。它查询元素性能比较低,因为需要从头遍历,但是在删除和添加元素的性能很高,因为这不涉及移位操作,只需要改变相邻节点的指针就可以了

总体来说,在实际开发过程中,如果增删多,就使用linkedList,如果查询多,就使用ArrayList

58.HashMap是否是线程安全的,如果不是,那么在多线程环境中我们应该使用哪个集合?

HashMap是线程不安全的,在多并发环境可能会出现并发问题:数组扩容重排序时产生死循环,同时写数据导致数据丢失等情况

如果需要考虑线程安全的高并发实现,建议使用ConcurrentHashMap

59.HashMap扩容机制

HashMap的数组初始容量是16,负载因子是0.75,也就是说当数组中的元素个数大于12个,会成倍扩容

60常见的设计模式说一下,以及你用过的框架中哪儿用到这些设计模式

单例模式:一个类只能有一个实例,分为饿汉模式(迫切加载)和懒汉模式(延迟加载)和枚举。

在Spring的IOC容器中管理的bean默认都是单例的

工厂模式:隐藏了产品的复杂创建过程,实现生产功能的复用,让产品生产更加高效。分为简单工厂(需要来回切换生产线),工厂方法(开设新的生产线),抽象工厂(制定创建产品的接口,让子工厂选择创建哪种产品)

在Spring中各种的BeanFactory创建bean都用到了

模板模式:定义一个算法骨架,而将某个或多个具体的实现延迟到子类中,比如考试中所有考生的试卷都一样,答案由每个考生自己完成

比如RedisTemplate实现了RedisOperations,ElasticSearchTemplate实现了ElasticsearchOperations

代理模式:不直接使用实际对象,通过调用代理对象间接调用实际对象,主要用作对实际对象的增强,分为静态代理,JDK动态代理,CGLIB动态代理

比如Spring的AOP原理就是动态代理,当目标对象实现了接口会使用JDK动态代理,没有实现接口会使用CGLIB动态代理

适配器模式:将不兼容的接口转换为可兼容的接口的中间类

比如HandlerInterceptorAdapter ,我们定义拦截器时不需要覆写HandlerInterceptor中的所有方法,因为适配器类帮我们做了空实现。但JDK1.8之后,给接口中增加了默认方法,可以有方法体,因此这些适配器类已经失去作用了

观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

比如Spring中的ApplicationListener

61.什么是单例,如何实现

一个类只能有一个实例,主要用于需要频繁使用的对象避免频繁初始化和销毁来提高性能,或者资源需要相互通信的环境。比如在Spring中的bean默认都是单例的

主要实现方式有,饿汉模式,懒汉模式,枚举,静态内部类

饿汉模式,是在类加载过程中就将这个单例对象实例化,需要将构造方法私有化,定义一个成员变量并new一个该类的实例作为初始值,提供一个公共的静态方法获取这个实例

懒汉模式,是在使用时才创建这个单例对象,需要将构造方法私有化,定义一个该类的成员变量不赋初始值,提供一个获取实例的公共静态方法。特别注意这个方法需要保证多线程环境下的并发安全性,可以通过DCL加volatile关键字来解决

枚举,直接在枚举中定义字段,它就是单例并且线程安全的

静态内部类,在类中搞一个静态内部类,在静态内部类中搞一个目标类的静态成员变量并且new一个实例作为初始值。然后在目标类中定义一个获取实例的静态方法,方法返回的就是静态内部类中的成员变量。这种方式能保证线程安全,也能实现延迟加载。缺点是这种方式传参不太方便

——————

tips ★DCL是啥:双重检查锁,第一次检

查为了提高效率,第二次检查保证线程安全

★为啥要加volatile:因为new 这个实例的过程不是原子性的,它分为,在堆内存中开辟空间,初始化,将堆内存中对象的地址赋值给栈中变量。但是在实际运行时候,会指令重排序,极端情况下可能会首先给变量赋值,然后再将对象初始化。若此时另一个线程访问,可能会得到初始化一半的实例,导致空指针或者成员变量未初始化的情况,后果很严重,需要加上volatile限制指令重排序

★为啥字加了volatile就能保证不会指令重排序:

☆字节码层级:加上ACC_VOLATILE标识符

☆jvm层级:volatile读写操作的前后,都加上内存屏障,jsr规定,屏障前后的操作禁止换顺序

☆hotspot层实现:没有使用sfence(写屏障),mfence(全屏障),lfence(读屏障)等系统原语,而是用的llock指令

☆硬件层级:锁定一个北桥信号(说到这里就没有意义了,你懂了就性)

★静态内部类如何实现延迟加载:jsr规定了5种类加载时机,也叫做主动引用,除此之外都叫被动引用,而静态内部类恰好就属于被动引用,因此它只在我们调用了获取实例的方法时候,才会被初始化

☆new一个对象 / 读取或者设置静态字段 / 调用一个类的静态方法

☆对类进行反射调用的时候,如果类没有初始化,需要先调用初始化方法进行初始化

☆初始化一个类时,如果父类没有初始化,先对父类初始化

☆虚拟机启动时,对main方法所在的类初始化

☆还有一种没见过。。。

★静态内部类如何保证线程安全:在编译时静态变量会被生成static{}方法,在类初始化阶段,JVM会保证同一个类的static{}方法只被执行一次

62.模板模式的作用

定义一个算法骨架,而将某个或多个具体的实现延迟到子类中,使得子类可以在不修改当前算法的结构情况下,重新定义当前算法的某些特定步骤

比如考试中所有考生的试卷都一样,答案由每个考生自己完成

63.什么是适配器模式

将不兼容的接口转换为可兼容的接口的中间类

比如HandlerInterceptorAdapter ,我们定义拦截器时不需要覆写HandlerInterceptor中的所有方法,因为适配器类帮我们做了空实现。但JDK1.8之后,给接口中增加了默认方法,可以有方法体,因此这些适配器类已经失去作用了

64.什么是代理模式?有几种代理?

不直接使用实际对象,通过调用代理对象间接调用实际对象,主要用作对实际对象的增强,分为静态代理,JDK动态代理,CGLIB动态代理

65.Spring使用的是哪种代理模式?

当目标对象实现了接口会默认使用JDK动态代理,他会实现目标类相同的接口,保证和目标类有相同的方法,当然我们也可以指定强制使用CGLIB动态代理

如果目标对象没有实现接口会使用CGLIB动态代理,他会继承目标类,从而获得目标类相同的方法

66.JDK动态代理和CGLIB动态代理的区别?

JDK动态代理是jdk提供的,我们可以直接使用,而CGLIB需要导入第三方库

JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用目标方法前调用InvokeHandler来处理

CGLIB动态代理是先加载目标类的class文件,然后修改其字节码生成子类来实现的

1.介绍一下Spring

Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理

2.什么是Spirng的IOC

IOC控制反转,把对象的创建,属性设置,初始化,销毁等工作交给Spirng的IOC容器去管理,解放程序员的劳动力。

对象被注册到Spring的IOC容器中,使用的时候从容器中获取即可,非常方便。

它通过依赖注入,将需要的外部资源注入到组件中,使用IOC使得对象之间的耦合度降低,资源变得容易管理,从而使得代码更加优雅

3.IOC的启动流程有了解过吗?(或 IOC是如何管理Bean的)

当Spring启动时,IOC容器会加载Spring的配置文件,包括XML配置或者注解,然后解析这些Bean并把相关定义信息封装成BeanDefinition对象,通过Bean注册器BeanDefinitionRegistry注册到IOC容器,也就是一个ConcurrentHashMap中

此时会找出所有的单例且没有配置为多例模式的bean,根据其BeanDefinition进行Bean的实例化,它会判断如果bean中有方法覆盖,就使用JDK反射创建Bean,否则使用CGLIB方式生成代理。然后把实例化好的Bean缓存到一个ConcurrentHashMap中

4.Bean的生命周期讲一下

实例化:如果是单例且迫切加载的bean,在Spring容器启动时就会根据BeanDefinition进行实例化,如果时设置了懒加载或者多例模式的bean,在用的时候才会实例化

属性赋值:通过BeanDeifinition找到当前Bean所依赖的其他Bean,如果容器中有就直接拿过来,如果没有就根据创建流程区创建依赖的bean,然后通过反射给依赖的字段注入值

初始化,如果我们指定了bean的init-method方法,此时会调用执行

销毁,如果我们指定了bean的destroy-method方法,此时会调用执行

5.IOC容器是如何保证Bean的单例的?

IOC容器会将单例模式的bean放入一个ConcurrentHashMap中,需要这个bean时直接到这个map中获取,如果没有找到才会实例化这个bean。而ConcurrentHashMap本身时线程安全的,也就保证了Bean是单例的

6.Spring如何解决Bean的循环依赖

循环依赖分为三种,构造器注入循环依赖 ,setter方式注入循环依赖,多例模式Bean的循环依赖。而Spring解决了单例bean的setter注入循环依赖

setter循环依赖的解决主要使用了三级缓存

一级缓存,用来缓存已经实例化好的bean

二级缓存,用来缓存正在创建的bean

三级缓存,用来缓存创建bean的实例工厂

假设有两个bean,A依赖B,B依赖A

当创建A实例时,会先将正在创建的A实例,放入二级缓存,同时将A的实例工厂放入三级缓存

然后A属性注入时发现依赖了B,此时B的实例还没有创建,就会去创建B的实例,同样的也会把正在创建的B的实例放入二级缓存,B的实例工厂放入三级缓存。

此时发现B依赖了A,就会去三级缓存中,找到A的创建工厂,通过反射的方式调用A的无参构造创建A实例,并注入到B中。此时B就初始化好了,然后将B实例放入一级缓存。

最后将B实例注入到A中,A也就创建好了

——————

构造注入不能解决循环依赖的原因是:如果A的构造其中依赖了B B的构造器中又依赖了A 在getSingleton中三级缓存需要调用getObject()构造器,来构造提早暴露但未设置属性的bean,此时就会产生无限递归创建

多例模式下Bean是不做缓存的,所以就没法暴露ObjectFactory,也就没办法解决循环依赖

7.说几个Spring的IOC的容器工厂类

BeanFactory:IOC容器顶层接口,提供了Bean获取的基础方法

DefaultListableBeanFactory:是整个 bean 加载的核心部分,Spring 注册及加载Bean 的默认实现

ApplicationContext:除了实现IOC基本功能外,还扩展了国际化支持,资源访问,事件发布

ClasspathXmlApplicationContext:从classpath中获取XML配置

8.你知道@Autowaire自动注入的实现原理吗?

自动注入是通过BeanPostProcessor 后置处理器完成的

容器刷新过程中,向容器注册了后置处理器。在Bean实例化过程中,触发了AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues方法的调用执行,它就会扫描当前类中是否有@Autowired注解,然后通过反射得到自动注入的字段所以来的bean,调用BeanFactory得到依赖的bean实例,最后使用反射进行字段赋值

9.你知道@Transcational注解的实现原理吗?

首先TxNamespaceHandler中注册了AnnotationDrivenBeanDefinitionParser,它用来解析事务注解配置

在bean实例化的过程中,解析bean是否有@Transactional注解,从而判断是否需要创建代理

然后会判断切点,是否需要对这个bean做增强,并解析方法上的@Transactional,如果找到了就用一个map缓存起来,如果没有找到就到类上面找,或者父类的方法和类上找,如果都没找到,就说明这个类不需要事务增强

当我们调用开启了事务的目标方法时,会通过TransactionInterceptor来拦截请求,并实现事务增强

10.你知道AOP的实现原理吗?

AOP的实现原理是基于动态代理,动态代理就是在运行时期动态的为原生类生成代理类以达到代码增强的目的,且代理类是持有原生类的,可以在代理类中调用原生类以及做一些增强业务。

动态代理分为JDK动态代理和CGLIB代理,CGLIB代理需要导入相关的jar包

两者的区别是JDK动态代理要求目标类需要实现至少一个接口。而CGLIB则是基于继承进行代理,原生类可以不实现任何接口

Spring中默认采用JDK动态代理,如果原生类没有实现任何接口,Spring会选择CGLIB代理,或者你可以在配置文件中强制指定使用CGLIB代理

11.SpringBoot自动配置原理

在启动类上我们会打上: @SpringBootApplication 注解,它是一个组合标签,包括:

  • SpringBootConfiguration ,本质是一个 Configuration ,代表Spring的配置类。

  • IOC自动扫描的注解 ,ComponentScan 会去扫描类上是否有:@Component ,@Respository ,@Service @Controller ,如果有,就会把这个类自动注册到Spring容器中。

  • EnableAutoConfiguration :就是启动SpringBoot自动配置的注解

  • 在 @EnableAutoConfiguration 注解中,注册了一个选择器,其中有一个方法会去返回很多的自动配置的的全限定名,这些类会自动注册到Spring容器中,

    那它是怎么去找到这些所谓的自动配置类的呢?

    他会通过Spring的SPI接口,也就是通过一个SpringFactoryLoader去扫描 classpath中的所有的jar包中的 MET-INF/spring.factories 中的自动配置类,比如: DispatchServlert就对应了DispatchServlertAutoConfiguration自动配置类 , 它通过@Bean+方法的方式注册了一个 DispatchServlert 到Spring容器中了

    12.SpringBoot启动流程

    1.开启秒表计时

    2.starting监听器,

    3.处理应用参数

    4.加载环境对象

    5.打印横幅

    6.创建Spring容器对象:AnnotationConfigApplicationContext

    7.容器刷新的前置工作

    8.刷新容器

    9.刷新容器后置工作

    10.秒表停止

    11.started事件

    12.调用runner去启动程序

    13.running.listeners.

    13.Eureka是如何实现服务发现和服务续约的? 14.说一下 Ribbon的工作原理 15.说一下Feign的工作原理 16.说一下Zuul的执行原理?

    1.哪些因素可能会造成数据库性能问题?

    不合理的商业需求,比如实时更新总注册人数,总交易额等等,应该考虑不要实时

    对于热点数据的查询并发太高,应该考虑用缓存

    数据库结构设计不合理,比如几十个字段集中在一张表,应该考虑分表

    SQL语句有问题,比如太多JOIN,很多不需要的字段也要全部查询出来,应该考虑优化SQL

    硬件和网络方面的影响

    2.Mysql的执行流程是怎么样的?

    客户端发起SQL查询,首先通过连接器,它会检查用户的身份,包括校验账户密码,权限

    然后会查询缓存,如果缓存命中直接返回,如果没有命中再执行后续操作,但是MySQL8.0之后已经删除了缓存功能

    接下来到达分析器,主要检查语法词法,比如SQL有没有写错,总共有多少关键字,要查询哪些东西

    然后到达优化器,他会以自己的方式优化我们的SQL

    最后到达执行器,调用存储引擎执行SQL并返回结果

    2.页面上发起的一个查询很慢,你怎么去优化

    首先看一下硬件和网络层面,有没有什么异常

    然后分析代码有没有什么问题,算法有没有什么缺陷,比如多层嵌套循环

    最后我们再定位到慢SQL,比如

    通过德鲁伊连接池的内置监控来定位慢SQL

    通过MySQL的慢查询日志查看慢SQL

    通过show processlist,查看当前数据库SQL执行情况来定位慢SQL

    定位到慢SQL再考虑优化该SQL,比如说

    不需要的字段就不要查询出来

    小结果集驱动大结果集,将能过率更多数据的条件写到前面

    in和not in尽量不要用,会导致索引失效

    避免在where中使用or链接条件,这会导致索引失效

    考虑如果不需要事务,并且主要查询的化,可以考虑使用MyISAM存储引擎

    如果优化SQL后还是很慢,可以考虑给查询字段建索引来提升效率

    如果建立索引了还是慢,看一下是不是数据量太庞大了,应该考虑分表了

    3.优化SQL你采用什么样的优化流程?

    不需要的字段就不要查询出来

    小结果集驱动大结果集,将能过滤更多数据的条件写到前面

    in和not in尽量不要用,会导致索引失效

    避免在where中使用or链接条件,这会导致索引失效

    给经常要查询的字段建立索引

    考虑如果不需要事务,并且主要查询的化,可以考虑使用MyISAM存储引擎

    如果表数据量实在太庞大了,考虑分表

    4.如何去定位慢SQL

    通过德鲁伊连接池的内置监控来定位慢SQL

    通过MySQL的慢查询日志查看慢SQL

    通过show processlist,查看当前数据库SQL执行情况来定位慢SQL

    5.定位到慢SQL你如何优化?

    尽量不要有null值,可以用0代替

    不需要的字段就不要查询出来

    小结果集驱动大结果集,将能过滤更多数据的条件写到前面

    in和not in尽量不要用,会导致索引失效

    避免在where中使用or链接条件,这会导致索引失效

    给经常要查询的字段建立索引

    考虑如果不需要事务,并且主要查询的化,可以考虑使用MyISAM存储引擎

    6.你如何看SQL有没有命中索引?

    在SQL语句前加上explain,结果中的key就是实际用到的索引

    7.mysql存储引擎有哪些,有什么区别,如何选择?

    主要有innodb,memory,myisam

    innodb支持事务,速度相对较慢,支持外键,不支持全文索引

    myisam 速度相对较快,支持全文索引,不支持外键,不支持事务,

    memory不支持事务,基于内存读写,速度快,支持全文索引

    如果对事务要求不高,而且是查询为主,考虑用myisam

    如果对事务要求高,保存的都是重要的数据,建议使用innodb,它也是默认的存储引擎

    如果数据频繁变化的,不需要持久化,可以使用memory

    8.一个sql : select sum(amount) from recharge ,来查询总充值,recharge 表数据量达到了上千万,怎么优化?

    可以考虑建个汇总表来统计总充值,总订单数,总人数等等等

    或者采用日报表,月报表,年报表的方式来统计

    如果非得要在这个上千万的表中来查询,那就采用分表

    9.什么是索引?

    用来高效获取数据的存储结构,比如字典的目录

    10.Mysql索引有哪些类型?索引方式有哪些?

    索引类型有

    普通索引:允许重复的值

    唯一索引:不允许有重复的值

    主键索引:数据库自动为我们的主键创建索引,如果我们没有指定主键,它会根据没有null的唯一索引创建主键索引,否则会默认根据一个隐藏的rowId作为主键索引

    全文索引,用来对文本域进行索引,比如text,varchar,只针对MyISAM有效

    索引方式有

    B+树和hash,Myisam和innodb都不支持hash

    11.Mysql的索引结构原理?为什么用B+tree

    采用了B+树的数据结构

    其他的数据结构,如数组,查询性能很高,但是插入和删除会移动其他元素因而性能差

    如链表的删除和插入的性能高,但是查询需要从头遍历性能差

    如二叉树的查询性能会因为树的高度增加而使得IO次数变多而查询边慢

    如B树能降低树的高度增加查询性能,但还是不够优化

    采用B+树的原因,B+树每次查询都要走到叶子节点, 查询性能更稳定,同时它的非叶子节点只存储key,因此每个节点能存储更多的key,树的高度变的更低,查询性能更快,而且它的叶子节点能够形成一个链表,支持范围查询,排序

    因此最终选择了B+树

    13.InnoDB的索引结构和MyIsam的索引结构有什么区别

    他们都是用的B+树,不同的是

    innodb的叶子节点存放的是数据,myisam的叶子节点存放的是数据的地址

    innodb中辅助索引的叶子节点存放的是主键索引的键值,myisam中辅助索引的叶子节点存放的也是数据的地址

    innodb的索引和数据都存放到一个文件中,myisam的索引和数据分别存放到不同的文件中

    14.哪些列不适合创建索引

    不经常查询的列不适合创建索引

    不出现在where中的字段不适合创建索引

    离散度太低的字段不适合创建索引,比如性别

    更新非常频繁的字段不适合创建索引

    15.哪些因素会造成索引失效

    模糊查询时,通配符放到左边的时候,会导致索引失效 比如 like ''%keyword%''

    列是字符串类型,查询条件没有用引号,会导致索引失效

    使用了or,in,not in,not exist, !=等,会导致索引失效

    查询null值,会导致索引失效

    还有mySQL认为全表扫描会比索引查找快,就不会使用索引,比如表里只有一条记录

    16.InnoDB辅助索引的叶子节点也存数据吗?

    InnoDB辅助索引的叶子节点存放的是,主键索引的键值

    因此辅助索引扫描完还会扫描主键索引,也叫回表

    但是如果查询的列恰好包含在辅助索引的键值中,就不会再回表了,这也叫覆盖索引

    17.组合索引的匹配原则 , 你优选使用单列索引和是组合索引,为什么?

    优先选择组合索引,因为对覆盖索引命中率更高,查询性能更高

    但是应该考虑列的顺序,因为组合索引会向左匹配

    22.什么是辅助索引?什么是覆盖索引

    除了主键索引之外的其他索引都叫辅助索引,也叫二级检索。

    辅助索引的叶子节点存储的是主键索引的键值,因此辅助索引扫描完之后还会扫描主键索引,这也叫回表

    但是如果查询的列恰好包含在辅助索引的键值中,就不会再回表了,这也叫覆盖索引

    23.索引创建的原则有哪些?

    查询较频繁的列应该考虑创建索引

    不经常查询的列不适合创建索引

    不出现在where中的字段不适合创建索引

    离散度太低的字段不适合创建索引,比如性别

    更新非常频繁的字段不适合创建索引

    J2EE

    1.项目开发流程

    项目启动

    需求调研

    系统设计

    程序开发

    测试

    试用、培训、维护

    2.maven的打包和安装用什么命令

    mvn compile 编译

    mvn clean 清理

    mvn test 测试

    mvn package 打包

    mvn install 打包并安装到本地仓库

    3.多表JOIN查询,完整查询SQL中的关键字的定义顺序

    SELECt 列名 FROM 表1 JOIN 表2 ON 条件 WHERe 条件 GROUP BY 列名 HAVINg 条件 ORDER BY 列名 LIMIT

    4.完整的多表JOIN查询,SQL中关键字的执行顺序

    FROM --> ON --> JOIN --> WHERe --> GROUP BY --> HAVINg --> ORDER BY --> LIMIT

    5.MyBatis结果集映射,如果是单个关联对象用什么映射?如果是一个集合的查询用什么映射?

    单个关联对象用associate ,适用于多对一的关联查询,使用javaType来定义实体类型

    集合用collection,适用于一对多的关联查询,使用ofType来定义集合的泛型类型

    6.MyBatis中${}取值和#{}取值的区别

    {}能够防止SQL注入,因为底层使用PreparedStatement对象,预编译,性能较高

    ${}不能防止SQL注入,因为底层使用Statement对象,不会预编译而是拼接字符串,性能较低

    能使用#{}时尽量使用#{},但如果直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY时只能使用${}

    7.MyBatis关联查询中,延迟加载和饥饿加载的区别

    延迟加载,是先从单表查询,需要使用关联数据的时候才发起关联查询,不用的时候不查询关联的数据,又叫懒加载

    饥饿加载,是在查询时将关联的数据立即查询出来加载进内存,不管用不用

    8.MyBatis一级缓存和二级缓存的区别

    缓存,是指将从数据库查询出的数据存放在缓存中,下次使用相同查询时不必再从数据库查询,而是直接从缓存中读取,从而减轻数据库查询的压力,提高性能

    mybaits中的一级缓存,是SqlSession级别,默认开启,使用同一个SqlSession发送相同的SQL时命中;它的生命周期和SqlSession一致,当调用SqlSession.close()方法时会释放缓存

    mybatis中的二级缓存,是SqlSessionFactory级别,默认不开启,使用同一个SqlSessionFactory,发送相同的SQL时命中;它的生命周期是程序结束

    当SQL中执行了update()、delete()、insert()操作,则缓存中的数据都会清空

    9.MyBaits的Mapper接口没有实现类为社么可以用@Autowired直接注入

    赋值给mapper接口引用的对象其实是一个代理对象,这个代理对象是由 JDK 动态代理创建的。在解析mapper的时候,mybatis会通过java反射,获取到接口所有的方法

    当调用接口中方法时,将通过接口全限定名+方法名对应找到映射文件中namespace和id匹配的sql,然后将执行结果返回

    10.你对IOC的理解

    IOC,Inversion Of Control 英文首字母缩写,意为控制反转,是Spring的核心思想之一

    控制,指的是对向创建、实例化、管理、销毁等等权力

    反转,指的是将控制权交给Spring框架,IOC容器

    使用依赖注入的方式,将需要的外部资源注入到组件中,使用IOC使得对象之间的耦合度降低,资源变得容易管理,从而使得代码更加优雅

    11.你对AOP的理解,AOP的使用场景是什么?实现原理是什么?

    AOP,Aspect Oriented Programming 英文首字母缩写,意为面向切面编程,是Spring的核心思想之一

    AOP是对OOP(面向对象编程)的一种补充,能够做到很多面向对象无法做到的事情,比如需要在所有方法执行前开启事务,打印日志,如果使用面向对象来编程,将会产生大量重复代码,而使用AOP,可以将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,一次解决这些问题。而这些重复的代码,一般统称为横切逻辑代码

    使用AOP,在不改变原有业务逻辑的情况下,实现解耦合,避免横切逻辑代码重复

    AOP的使用场景包括日志记录,性能统计,安全控制,事务处理,异常处理等等

    它是基于动态代理实现的,分为JDK动态代理和CGLIB动态代理。JDK动态代理只支持实现了接口的类 ,CGLIB支持没有实现接口的类。Spring默认使用JDK动态代理,如果被代理类没有实现接口,会选择CGLIB动态代理

    12.说一下切面相关的注解,分别是什么意思

    @Aspect:定义切面

    @Pointcut:定义切点 = cn.itsource.service.*

    @Before:前置通知,在目标方法运行之前运行

    @After:后置通知,在目标方法运行结束之后运行(无论方法正常结束还是异常结束)

    @AfterReturning:返回通知,在目标方法正常返回之后运行

    @AfterThrowing:异常通知,在目标方法出现异常以后运行

    @Around:动态代理,手动推进目标方法运行

    13.Bean的四种注册方式

    方式一:普通注册方式,直接通过class注册

    方式二:简单静态工厂方式注册

    方式三:简单实例工厂方式注册

    方式四:FactoryBean方式注册

    14.注册Bean的注解有哪些

    @Controller/@RestController 一般用于定义控制层的类

    @Service 一般用于定义服务层的类

    @Repository 一般用于定义持久层类

    @Component 定义一般类

    @Configuration 定义配置类

    15.SpringBoot和Spring有什么区别

    Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效

    它大量简化maven依赖,管理了大量的基础依赖

    基于注解配置(JavaConfig),无需xml配置

    内嵌Tomcat,部署流程简单

    打包和部署更加灵活,允许独立运行

    16.@SpringBootApplication注解的含义

    @SpringBootApplication是SprnigBoot项目的核心注解,目的是开启自动配置,并表示该类为主启动类。它包含三个子标签

    @ComponentScan注解:开启ioc自动扫描注解,默认扫描当前包及其子包中@Controller,@Service等,并把这些bean加载到ioc器中

    @EnableAutoConfiguration注解:启用springboot自动配置,自动所有扫描classpath目录下面所有jar中的spring.factories文件实现配置类批量注册

    @SpringBootConfiguration注解:标志该类为springboot配置类

    17.spring-boot-starter-parent的作用

    这是SpringBoot的父工程,它的作用是帮我们管理了很多的基础jar包,同时它继承了spring-boot-dependencies,在spring-boot- dependencies项目中通过管理了大量的依赖,同时通过维护了这些依赖的版本号

    但是在项目中,还需要通过 去导入具体的依赖才能使用

    18.spring-boot-starter-web的作用

    此项目是Springboot和Springmvc整个的jar包,构建了web项目的基本环境,集成了日志,tomcat,springmvc,json支持等等

    19.SpringBoot中如何读取配置

    方式一:使用@Value读取配置文件

    方式二:使用@ConfigurationProperties读取配置文件

    20.SpringBoot中日志的level有哪些

    日志级别从低到高分别为:

    TRACE < DEBUG

    如果设置为 WARN,则低于 WARN 的信息都不会输出

    Spring中默认使用INFO级别输出到控制台

    21.单例多例的区别

    单例和多例属于对象模式,单例模式指对象在整个系统中只存在一份,多例模式则可以有多个实例。

    在spring的ioc容器中的bean默认都是单例的,如果需要使用多例,可以通过修改scope属性:scope="prototype"

    如果一个bean是单例模式的,在处理多次请求的时候,在ioc容器中只实例化一个bean,这个对象会被保存在一个map中,当有请求来的时候,会先从map中查看,如果有就直接使用这个对象,没有才会实例化新的对象。

    如果是多例(prototype)模式的bean,每次请求来的时候,会直接实例化新的bean,没有map缓存的过程。

    22.BeanFactory和ApplicationContext有什么区别

    BeanFactory接口是IOC容器的核心接口,定义了管理bean的最基本方法,比如实例化,配置,管理,获取bean的方法

    ApplicationContext接口是BeanFactory接口的子接口,除了继承BeanFactory中所有管理bean的方法,还拥有环境、国际化、资源、事件等服务相关的接口

    BeanFactory是延迟加载,ApplicationContext是迫切加载

    23.BeanFactory和FactoryBean的区别

    BeanFactory接口是IOC容器的核心接口,定义了管理bean的最基本方法,比如实例化,配置,管理,获取bean的方法

    FactoryBean是IOC容器创建bean的一种形式,可以通过实现此接口来创建实例化过程比较复杂的bean

    24.介绍一下Spring框架的组成

    CoreContain核心容器模块:

    spring-core:提供框架的基本组成部分,包括 IoC 和依赖注入功能

    spring-beans:提供 BeanFactory,工厂模式

    context:提供国际化,事件传播,资源加载等功能

    spring-expressionLanguage:提供表达式语言

    Web模块

    Web:提供面向web的基本功能和面向web的应用上下文

    Web-MVC:为web应用提供模型视图控制(MVC)

    Web-Socket:在 web 应用程序中提供客户端和服务器端之间通信的方式

    Web-Portlet:模块提供了用于Portlet环境的MVC实现

    数据/集成模块

    JDBC:包含了Spring对JDBC数据访问进行封装的所有类

    ORM:为对象-关系映射提供交互层

    OXM:提供对Object/XML映射实现的抽象层

    JMS:主要包含了一些制造、消费和消息的功能

    Transaction:为实现特殊接口类以及所有的 POJO 支持编程式和声明式的事务管理

    其他模块

    AOP:提供了面向切面编程相关实现

    Aspects:模块提供了与AspectJ的集成,是一个功能强大的AOP框架

    Instrumentation:提供了class instrumentation 的支持和类加载器classloader的实现

    Messaging:为 STOMP 提供支持

    Test:支持使用JUnit和TestNG对Spring组件进行测试

    25.Spring的Bean懒加载和非懒加载有什么区别

    懒加载:需要使用对象的时候才创建,节省资源,但不利于提前发现错误

    非懒加载,也叫迫切加载,容器启动时就创建对象,消耗资源,但有利于提前发现错误

    spring中默认时迫切加载,即在项目启动时,spring会扫描符合条件的所有bean并将其初始化

    如果需要懒加载,可以使用@Lazy注释或者xml中配置属性default-lazy-init="true"

    26.Spring的依赖注入方式有哪些?

    方式一:setter方式注入,通过反射调用无参构造方法生成对象,再通过对于的setter方法注入配置的值,支持注解和xml两种实现方式

    方式二:构造器方式注入,通过反射调用有参构造方法生成对象,支持注解和xml两种实现方式

    注解实现方式:@Autowired,它是默认按类型匹配的、@Resource,它是默认按名字匹配的

    27.SpringBoot中如何管理事务 ?

    事务(transaction)是指业务逻辑上对数据库进行的一系列持久化操作,要么全部成功,要么全部失败。

    在Springboot中,可以通过xml配置和注解配置

    xml方式通过配置DataSourceTransactionManager和transactionManager实现

    注解方式配置通过在主启动类上加上@EnableTransactionManagement开启事务管理器,在具体的实现层service类上加上@Transactional 实现事务

    28.什么是RBAC , 相关表怎么设计的?

    RBAC:Role-based Access Control首字母缩写,意为基于角色的访问控制。基本思想是对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。

    将权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

    实现RBAC,需要将用户对权限的多对多关系,转化为用户对角色,角色对权限的多对多关系,因此在数据库中,需要在用户,角色,权限中分别加入中间表,即用户表,用户和角色关系表,角色表,角色和权限关系表,权限表

    29.在VUE中,什么是MVVM

    MVVM,Model–View–ViewModel首字母缩写,是一种软件架构模式。

    其中Model指的是模型,包括数据和一些基本操作

    View指的是视图,页面渲染结果

    ViewModel指的是模型与视图间的双向操作

    MVVM的思想就是数据模型和视图的双向绑定,只要数据变化,视图会跟着变化,只要视图被修改,数据也会跟者变化

    30.讲几个VUE的指令

    v-text:给元素填充纯文本内容

    v-html:给元素填充内容,与v-text的区别是它会把内容的html符号进行渲染

    v-for:遍历数字,字符串,数组,对象

    v-bind:将data中的数据绑定到标签上,作为标签的属性

    v-model:创建双向绑定,表单的值被修改时会自动修改data中的数据,data中的值变化时页面也会被修改

    v-show:根据表达式的真假值,切换元素的css属性

    v-if:根据表达式的真假值,销毁或重建元素

    v-on:绑定事件

    31.Node你们用的哪个版本,vue你们用的哪个版本,npm你们用的哪个版本

    nodejs版本:v10.16.0

    vue版本:2.6.13

    npm版本:6.9.0

    32.webpack的作用

    首先,它可以将ES6等高级语法,编译成各个浏览器都认识的语法

    其次,它可以将相互依赖的许多散碎文件搞成一个整体,提高网页访问的效率

    再次,它可以将代码压缩,减小代码体积

    33.Vue中定义组件分为几种,有什么区别

    组件是一种自定义的元素标签,可以对功能封装,提高代码复用性,分为全局组件和局部组件两种

    全局组件,是在所有vue挂载的标签中都有效,

    局部组件,只在当前vue所挂载的标签中有效

    34.前后端分离的好处

    第一,专人干专事,前后端同时开发,效率更高

    第二,责任分离,避免了前后端相互踢皮球的现象

    第三,前后端解耦合,一套后端可以处理不同的前端,包括app端,浏览器端

    第四,分开部署,减轻了服务器压力

    第五,页面显示东西再多也不怕,数据都是异步加载,就算后端服务器挂了,前端页面也能访问,虽然没有数据

    35.你们这个前后端分离项目是怎么部署的

    前后端分开部署,前端使用Nginx部署,

    后端使用Springboot内嵌的tomcat部署,

    分开部署后,通过cors代理解决前后端域名不一致的跨域问题

    36.你们这个前后端分离项目的技术栈是怎么样的

    前端门户系统:HTML + JQuery + CSS

    前端管理系统:VUE + ElementUI

    后端系统:基于SpringBoot框架的SpringMVC + MyBatis + Redis

    37.讲一下你用过ElementUI的哪些组件

    基础组件,比如按钮Button,图标Icon

    表单组件:比如表单Form,单选框Radio,多选框Checkbox,输入框Input,选择器Select,级联选择器Cascader

    其他组件:比如Dialog对话框,消息提示Message

    38.你们用什么做项目代码管理的

    使用主流的Git管理项目

    39.讲讲Git相对于SVN的区别

    第一。Git是每个攻城狮都有自己的版本库,可以在自己的库上任意操作提交代码

    第二。Git在每个工程只产生一个.git目录,而SVN会在每个目录下都生成.svn目录

    第三。Git能快速切换分支,且合并文件的速度比SVN快

    第四。Git采用分布式版本库,内容完整性更好

    40.讲几个Git的命令

    git clone:从远程仓库克隆项目到本地

    git add:添加代码到本地仓库管理

    git commit:提交add后的代码到本地仓库

    git push:推送本地仓库文件到远程仓库

    git pull:拉取远程仓库中的代码到本地仓库

    41.讲一下你理解的Redis,为什么Redis很快

    Redis是一种高性能的,开源的,C语言编写的非关系型数据库,可以对关系型数据库起到补充作用,同时支持持久化,可以将数据同步保存到磁盘

    说Redis很快是相对于关系型数据库如mysql来说的,主要有以下因素

    第一,数据结构简单,所以速度快

    第二,直接在内存中读写数据,所以速度快

    第三,采用多路IO复用模型,减少网络IO的时间消耗,避免大量的无用操作,所以速度快

    第四,单线程避免了线程切换和上下文切换产生的消耗,所以速度快

    42.你常用的Redis的数据存储结构有哪些,他们的使用场景分别是什么

    Redis存储形式是键值对,支持value形式包括String,List,Set,ZSet,Hash。

    String可以用作缓存,计数器,防攻击,验证码、登录过期等,List可以用来做队列,秒杀等,Set可以用来去重,zset可以用来排序

    43.每种存储结构说 4 个命令吧

    1.String

    set key value 设置值

    get key 取值

    mset key value key value... 设置多个值

    mget key key 获取多个值

    incr key 将key中的值自增1

    decre key 将key中的值自减1

    2.List

    lpush key value value... 从最左边设置值

    rpush key value value... 从最右边设置值

    lrange key start stop 查询key中指定区间的元素

    lpop key 移出并返回key中最左边的元素

    rpop key 移出并返回key中最右边的元素

    3.Set

    sadd key value value 添加元素

    smembers key 返回集合key中的所有元素

    srem key member 删除集合key中member元素

    scard key 查询集合key中的元素数量

    4.ZSet

    zadd key score value (score value)... 添加元素

    zcard key 查询集合key中元素数量

    zcount key min max 返回有序集合key中score 在min和max之间的元素

    zrange key start stop 返回有序集合key中索引在start和stop之间的元素

    5.Hash

    hset key field value 添加元素

    hget key field 获取key集合中field键对应的值

    hmset key field value (field value)... 添加元素并批量添加子键值对

    hmget key field field 获取key集合中所有的子键值对

    44.你们项目是怎么用Redis的?

    使用的是Springboot整合的redis,主要用来解决前后端分离后前后端会话问题,以及验证码的问题

    45.怎么防止Redis宕机数据丢失问题

    通过对Redis持久化,把内存中的数据和命令,保存一份到磁盘中做备份,当Redis发生宕机,重启服务器的时候,会从磁盘重新加载备份的数据,从而解决数据丢失问题

    46.Redis持久化是什么?有几种方式

    将内存中的数据备份到磁盘的过程,就叫作持久化

    Redis持久化主要有两种方式,RDB和AOF,可以通过修改redis.conf进行配置

    RDB是记录数据快照,而AOF是记录写命令的

    47.Redis有了AOF持久化为什么还要RDB?

    AOF和RDB各有所长

    RDB是记录数据快照,它的优点是只产生一个持久化文件,体积相对较小,启动恢复速度快,备份方便,它的缺点是没办法做到数据百分百不丢失,因为它是每隔一定时间保存一次

    AOF是记录写命令,它的优点是格式清晰,容易理解,数据更安全,采用append模式即使持久化过程中宕机,也不影响已经保存的数据,它的缺点是文件体积较大,恢复速度慢

    根据实际需要来选择,通常二者可以结合来使用

    48.Redis内存不够了怎么办?

    方式一:增加物理内存

    方式二:使用淘汰策略,删掉一些老旧数据

    方式三:集群

    49.淘汰策略有哪些?你们用的哪种

    volatile-lru :从已设置过期时间的数据集中挑选最近最少使用的数据淘汰

    volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰

    volatile-random:从已设置过期时间的数据集中任意选择数据淘汰

    ★ allkeys-lru:从数据集中挑选最近最少使用的数据淘汰

    allkeys-random:从数据集中任意选择数据淘汰

    no-enviction:不使用淘汰

    50.Redis事务和Mysql事务的区别

    Mysql的事务是基于日志,记录修改数据前后的状态来实现的,而Redis的事务是基于队列实现的

    Mysql中的事务满足原子性:即一组操作要么同时成功,要么同时失败,

    Redis中的事务不满足原子性,即一组操作中某些命令执行失败了,其他操作不会回滚

    因此对于比较重要的数据,应该存放在mysql中

    51.使用Redis如何实现消息广播

    Redis是使用发布订阅来实现广播的

    订阅者通过 SUBSCRIBE channel命令订阅某个频道 , 发布者通过 PUBLISH channel message向该频道发布消息,该频道的所有订阅者都可以收到消息

    52.讲一下你们宠物这个项目中店铺的入驻流程

    首先店铺需进行注册,将店铺的信息和管理员账号信息分别保存

    然后平台对店铺进行审核,如果审核通过,发送激活邮件,店铺在有效期内点击邮件中的链接进行激活,此时店铺就可以正常登录

    如果审核失败,也会发送驳回信息的邮件,店铺需重新申请

    53.如果有人用脚本刷你们的短信接口怎么办?

    首先,可以设置图形验证码,流量错峰

    其次,可以获取请求的ip地址,手机号,发送时间,并保存到发送短信记录的日志中,对于短时间多次请求的ip地址,手机号,可以拦截不执行发送手机验证码

    再次,可以设置单位时间内发送短信的总数量,比如设定1秒最多只发送10条验证码。但这种方式会降低并发性

    54.讲一下Session的工作原理

    当用户端第一次发起请求时,服务器端保存session同时会生成一个sessionid

    服务器将生成的sessionid返回给用户端

    用户端收到sessionid并将sessionid保存在cookie中,此次会话的中所有对服务器端的访问,都会携带这个sessionid

    服务端收到sessionid,就能找到对应的session数据

    55.讲一下你们的登录实现方案

    当用户第一次发起登录请求,后台生成一个token保存到Redis中

    将生成的token返回给用户端

    用户端使用用浏览器中的localStorage保存token

    通过axios的拦截器,给每次请求的请求头都加上token

    服务端收到token,就能在Redis中找到对应的数据

    56.你们是怎么处理登录信息过期的?有什么更好方案吗?

    给保存在Redis中的token设置过期时间来处理登录过期的

    为了防止已登录用户在访问后台时突然遭遇登录过期的情况,我们在后台接收到用户访问时,重新设置token的过期时间写入Redis,则用户访问期间就不会突然过期了

    57.你们Redis用在哪些业务上?用的什么存储结构?

    主要用在校验验证码实现注册流程,保存token实现登录流程

    都是使用String类型的存储结构,并且使用了Json格式进行序列化

    58.三方登录流程讲一下

    1.用户发起微信登录请求

    2.后端获取请求二维码的连接,重定向到扫码界面

    3.用户使用微信扫一扫并同意授权

    4.后端回调获取授权码,并将授权码作为参数,重定向到前端跳转页面

    5.前端将授权码返回后端,后端根据授权码获取token

    6.后端根据token获取openId

    7.根据openId查询微信用户表

    如果查到有用户信息,且已关联本地账户,就默认登录

    如果有查到用户信息,但没有关联本地账户,就跳转本地账户绑定页面,

    如果没有查到用户信息,就向微信平台发起请求查询用户基本信息,添加到微信用户信息表,再跳转本地账户绑定页面

    8.执行绑定逻辑时,根据手机号判断是否有本地账户,如果有就直接绑定,如果没有就自动注册再绑定,绑定成功后就默认登录

    59.Oauth2的优势

    oauth协议是一个安全的开放授权标准,

    与传统的授权方式相比,它不会使第三方触及到用户的账号信息,比如用户名,密码。

    60.Oauth2的四种授权模式

    一、授权码模式,它是功能最完整,流程最严密的授权模式

    二、简化模式,直接从前端渠道获取token,容易受安全攻击

    三、用户名密码模式,使用用户名和密码登录的应用,比如桌面APP

    四、客户端凭证模式,用户直接向客户端认证,客户端以自己的名义向第三方索取服务

    61.介绍一下寻主模块的业务流程

    1.用户发起寻主需求,可以根据需要设定收购费用

    2.后台进行寻主需求的审核,审核通过,自动匹配商家,匹配失败则进入寻主池,任意商家可接单

    3.如果自动匹配商家,商家可以接单,也可以拒单,拒单后寻主需求会返回寻主池

    4.商家如果接单了,可以分配给工作人员上门收购

    5.工作人员上门收购,录入宠物信息到系统,选择支付方式,则完成收购,商家后续可以上架宠物进行销售

    62.讲一下宠物领养下单以及支付的总体流程

    1.商家上架宠物,门户网站可以浏览到所有上架的宠物列表

    2.点击宠物进入详情页面,可以直接购买

    3.用户点击购买,发出下单申请,需要选择收货地址和支付方式

    4.确定订单后,后台保存领养订单包括对应的地址,以及一个支付账单

    63.讲一下什么是非对称加密,什么是数字签名,数字签名的作用是什么?

    非对称加密是一种算法,指的是加密和解密时使用不同的密钥,其中私钥不可公开,公钥可以公开。

    数字签名就是在非对称加密的基础上,使用私钥加密,公钥解密,主要用来防止数据被篡改,实现安全传输的目的

    64.你们项目中有哪些订单?

    1.账户充值,提现订单,通过银行或其他支付平台向用户账户充值,或从用户账户中取出到银行卡中

    2.寻主收购订单,商家支付费用给宠物主任

    3.宠物服务订单,比如宠物洗澡,宠物按摩的服务费用

    4.宠物领养订单,用户在平台购买宠物

    5.宠物商品订单,比如狗粮,猫砂等支付订单

    65.要求每天早上 1点统计前一天的平台注册人数,怎么做?

    使用qurtz或者springboot定时任务。任务为每天凌晨1点扫描表中前一天的人数统计出来,使用where条件

    66.使用定时任务做订单超时关单,那并发多的时候不就会产生很多的定时任务吗?性能不会很差吗?你怎么优化

    对于我们的小型项目,并发在1000以下可以使用quartz定时器,使用起来也很简单方便

    但如果是高并发,比如秒杀等业务,可以使用RabbitMQ的延迟队列来实现,也可以使用Redis监听key过期事件来处理

    67.讲一下你做过的比较复杂的业务,或者遇到过的比较难的问题。你是如何解决的?

    比如说使用微信作为第三方登录,公司之前是只使用了本地账户管理,所有新客户都需要注册而填一大堆注册信息

    后来我有幸接手并重新设计了这个模块,采用了时下最流行的第三方登录,考虑到微信用户量庞大,能快速引流,新客户也只需要扫码就能快速注册并登录,因此我最终选择了微信登录。

    当时我也大量查询了网上各种方案,结合微信开放平台的API,确定了最终的方案。也就是通过在数据库维护了微信用户表,同时关联一个本地账户表来实现,最终的实现效果也很理想

    68.说说preparedStatement和Statement的区别

    statement的sql语句使用字符串拼接,很容易出错,而preparedStatement使用?作为占位符,不容易出错易于维护

    statement不对sql语句作处理,直接交给数据库,而preparedStatement支持预编译,事先将编译好的sql语句放到数据库端,相当于缓存,因此效率更高

    statement有sql注入风险,preparedStatement没有sql注入风险

    69.为什么要使用连接池,数据库连接池的原理

    对数据库的操作都需要取得连接,使用完都需要关闭连接,如果每次操作需要打开关闭连接,这样系统性能很低下。连接池就可以动态的管理这些连接的申请,使用和释放,我们操作数据库只需要在连接池里获取连接,使用完放回连接池,这样大大节省了内存,提高效率。

    数据库连接池的原理主要分为三部分

    第一,连接池的建立,在系统初始化时建立几个连接对象以便使用。

    第二,连接池的管理,客户请求连接数据库时,首先查看连接池中是否有空闲连接,如果有直接分配,如果没有就等待,直到超出最大等待时间,抛出异常

    第三,连接池的关闭,当系统关闭时,连接池中所有连接关闭

    70.Session和cookie有什么区别

    session和cookie都是为了弥补http协议的无状态特性,解决会话问题

    session是以ConcurrentHashMap结构存储在服务器端,同时生成一个sessionid返回客户端并存放到cookie中

    cookie是将数据存储在客户浏览器端

    session占用服务器的性能,但安全性较高,使用cookie减轻服务器的压力,但有被用户篡改风险因此安全性较低

    71.请求转发和重定向的区别?

    转发是一次请求,可以共享同一组request和response,重定向是多次请求,不能共享同一组request和response

    转发地址栏不会发生变化,重定向地址栏会发生变化

    转发不能到外部应用,重定向可以到尾部应用

    如果我们需要数据共享,使用转发,如果需要访问内部资源(WEB-INF),使用转发,如果需要跨域到外部资源,必须使用重定向

    72.get和post请求的区别

    最直观的区别,get把参数包含在url中,post是把参数放到request body中

    post相对于get更安全

    post发送的数据更大,get有url的长度限制

    post更发送更多的数据类型,get只能发送ASCII字符

    在restful中,get一般用户查询搜索数据,post一般用户添加或者修改数据

    73.JSP的原理

    jsp的本质就是servlet,静态部分是标准的html,动态部分是java程序

    74.Mybatis的Mapper接口为什么可以@Autowire直接注入

    赋值给mapper接口引用的对象其实是一个代理对象,这个代理对象是由 JDK 动态代理创建的。在解析mapper的时候,mybatis会通过java反射,获取到接口所有的方法

    当调用接口中方法时,将通过接口全限定名+方法名对应找到映射文件中namespace和id匹配的sql,然后将执行结果返回

    75.在MyBatis如何动态修改SQL(在代码执行的过程中修改SQL)

    使用Mybatis的拦截器可以做到

    76.MyBatis的动态SQL标签有哪些?

    if标签:条件判断

    choose、when、otherwise标签:选择结构,类似java中的switch

    trim标签:对包含的内容加上前缀,后缀

    where标签:主要是用来简化SQL语句中where条件判断的,能智能的处理and or,不必担心多余导致语法错误

    foreach标签:遍历元素

    77.Mybatis的mapper如何传递多个参数?

    方式一,可以使用map进行传参,SQL中使用map的key来引用取值

    方式二,可以在SQL中使用#{param1},#{param2}...来引用取值,它是根据mapper接口对应方法中形参的顺序进行匹配的,不管接口方法的参数名字叫个啥,SQL都只能使用param1,param2,等来取值

    方式三,可以使用@Param注解,给mapper接口方法的参数命名,在SQL中直接使用取的名字来引用

    78.嵌套子查询和JOIN有什么区别?

    嵌套子查询,指的是在查询一个主对象的时候,使用单表查询,在resultmap中额外发送一个子sql查询关联对象,然后映射给主对象

    连表join查询,指的是查询一个主对象的时候,使用join连表的方式把主对象和关联对象的数据一次性查出来,用resultmap映射结果

    他们的区别,join连表查询只发一条sql就能把数据查询出来,嵌套子查询会有一个n+1的问题,就是说如果主查询出来n条数据,那么会额外发送n条子sql去查询对应的关联对象,加上主查询那1次,也就是n+1次,因此它的性能相对较低的,一般我们会使用join连表查询

    79.在ResultMap中多对一关联对象查询用什么?一对多关联查询用什么?

    单个关联对象用associate ,适用于多对一的关联查询,使用javaType来定义实体类型

    集合用collection,适用于一对多的关联查询,使用ofType来定义集合的泛型类型

    80.Spring的Bean被指定为prototype以及singleton有什么区别

    这两者分别指的是多例和单例模式,singleton即单例模式,指对象在整个系统中只存在一份;prototype即多例模式系统中可以有多个实例。

    如果一个bean是单例模式的,在处理多次请求的时候,在ioc容器中只实例化一个bean,这个对象会被保存在一个map中,当有请求来的时候,会先从map中查看,如果有就直接使用这个对象,没有才会实例化新的对象。

    如果是多例模式的bean,每次请求来的时候,会直接实例化新的bean,没有map缓存的过程。

    在spring的ioc容器中的bean默认都是单例的,如果需要使用多例,可以指定scope属性:scope="prototype"

    81.说一下Sping的Bean的生命周期

    Bean的生命周期包括实例化、属性赋值、初始化、销毁四个阶段

    实例化,指的是通过构造函数创建bean

    属性赋值,指的是为bean设置相关书信和依赖

    初始化,是指spring容器调用指定的初始化方法,如果我们指定了bean的init-method方法,此时会调用执行

    销毁,是指spring容器调用指定的销毁方法,如果我们指定了bean的destroy-method方法,此时会调用执行

    82.RequestMapping 和 GetMapping有什么区别?

    @Getmapping是一个组合注解,即是@RequestMapping(method = RequestMethod.GET)的缩写,意思是只接收get请求的方法

    @Requestmapping如果没有指定请求方式,可以接收get,put等各种类型的请求

    83.SpringMVC怎么样设定重定向和转发的

    重定向是指将用户从当前请求重新定向到一个视图页面,或者是一个handler处理请求,以前的request域中信息全部失效,同时地址栏会 发生变化,它是客户端行为

    转发是指将用户从当前请求转发给另一个视图页面或者handler处理请求,以前的request域可以共享,地址栏不会发生变化,它是服务器 行为

    springmvc默认是使用转发方式跳转的,且会默认经过视图解析器

    我们也可以通过指定,转发时在返回值前面加"forward:",重定向时在返回值前面加"redirect:",且此时就不会再经过视图解析器了

    84.SpringMVC如何对时间格式的参数进行格式化?有哪些方式?

    第一种需求,后台接收前台页面返回的string类型时间,要转换成的Date类型数据,可以使用@DateTimeFormat注解来接收参数

    第二种需求,后台将Date类型数据返回给前台页面,默认是返回时间戳,如果想要优雅的格式,可以在模型的Date字段或get方法上使用@JsonFormat注解

    85.SpringMVC常用的注解有哪些

    @Controller:用来标识一个类是控制器类

    @RequestMapping:用来映射请求路径和参数

    @ResponseBody:将返回值放到responsebody中,通常返回json或者xml格式数据

    @RequestBody:将前台请求参数转换成对象

    @PathVariable:接收路径参数,通常用在restful接口中

    @RestController:@Controller和@ResponseBody的组合注解

    @ControllerAdvice:运用aop的思想,对全局做一些处理,比如结合@ExceptionHandler做全局异常捕获

    86.如何定义SpringMVC的拦截器

    SpringMVC 的拦截器主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、判断登录等功能上

    第1步,定义拦截器:可以实现 HandlerInterceptor 接口来自定义拦截器,接口定义了三个方法,preHandler方法是在请求到达处理器之前执行,postHandler方法是在请求经过处理器之后、解析试图之前执行,afterCompletion方法是在视图渲染之后、返回客户端之前执行

    第2步,配置拦截器:在springmvc的配置文件xml中,配置所有拦截路径,以及需要放行的路径

    87.HandlerInterceptor和HandlerInterceptorAdapter的区别

    HandlerInterceptor是接口,我们可以实现该接口来定义拦截器,

    HandlerInterceptorAdapter是抽象类,它实现了HandlerInterceptor接口的子接口AsyncHandlerInterceptor,我们可以继承该类来定义拦截器,它简化拦截器的实现,默认preHandler返回true

    88.springMVC的执行原理

    1.Http请求:客户端请求提交到DispatcherServlet-前端控制器

    2.寻找处理器:由DispatcherServlet调用HandlerMapping-处理器映射器,根据url找到对应的的Handler

    3.调用处理器:DispatcherServlet指定HandlerAdapter-处理器适配器去调用Handler

    4.调用业务处理和返回结果:Handler调用业务逻辑处理完成后,返回ModelAndView

    5.处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler-视图解析器,找到ModelAndView指定的视图

    6.Http响应:将结果显示到客户端

    89.SpringMVC的Controller是单例还是多例,有没有并发安全问题,如何解决?

    在spring中,bean默认都是单例的,controller也是交给spring容器管理的一个bean,因此它也是单例的。

    单例的好处是减少了创建对象和垃圾回收的时间,节省了内存资源,但同时单例会造成线程不安全的问题,因为当所有请求访问同一个controller实例,controller中的成员变量是所有线程公用的,某个线程如果修改了这个变量,别的请求再来拿这个变量就编程修改后的值了

    要解决这个问题,最直接有效的方式就是不要在controller中定义成员变量,如果你非要定义成员变量,两种方式

    第一种,可以给controller上加注解@Scope("prototype"),将controller设置为多例模式,每次请求都重新实例化一个controller

    第二种,使用ThreadLocal变量,让每一个线程都有自己独立的变量

    90.Spring Boot有哪些优点

    Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效

    它大量简化maven依赖,管理了大量的基础依赖

    基于注解配置(JavaConfig),无需xml配置

    内嵌Tomcat,部署流程简单

    打包和部署更加灵活,允许独立运行

    91.SpringBoot如何做全局异常处理?

    可以使用@ControllerAdvice注解,编写一个全局异常处理类,再自定义一个方法使用@ExceptionHandler来捕获具体的异常并作相应的处理

    通常情况下后台向前台返回结果时,会把结果封装成包含有错误码,错误信息以及数据本身的json数据,因此我们可以使用自定义异常类,自定义枚举错误码,在捕获全局异常后,向前台返回一个包含错误码的信息

    92.RabbitMQ的使用场景

    rabbitMQ消息队列可以用来

    做任务的异步处理,提高程序的相应时间

    提高系统稳定性,通过手动确认机制,当消费者出现故障,只要没有确认签收,请求的数据都不会丢失可以再次处理

    服务解耦,生产者通过MQ与消费者交互

    消除峰值,通过异步处理,消息传到MQ直接返回,接着等待排队处理,避免了线路拥堵

    93.RabbitMQ如何防止消息丢失

    首先,RabbitMQ的消息确认机制,默认是自动签收,也就是说消息一旦被消费者接收,就自动签收,消息就从队列里清除了。因此对于重要的消息,不容丢失的数据,我们需要设置在消费完成后手动签收

    其次,我们可以将消息持久化,避免消息在消费前MQ宕机,网络问题等造成的消息丢失

    94.RabbitMQ的交换机有哪几种

    Fanout:广播,将消息交给所有绑定到交换机的队列

    Direct:定向,把消息交给符合指定routing key的队列

    Topic:通配符,把消息交给符合routing pattern的队列

    95.消息是如何从发送者到达消费者的(RabbitMQ工作流程)

    分为消息发送和消息接收两个步骤

    消息发送:生产者和Broker建立TCP连接,创建信道。通过信道将消息发送给Broker,由Exchange将消息进行转发到指定的队列

    消息接收:消费者和Broker建立TCP连接 ,创建信道 ,然后监听指定的队列,当有消息到达队列时,Broker默认将消息推送给消费者,消费者就能接收到消息

    96.如何防止消息重复消费?

    重复消费,一般时由于消费者消费成功后,在给MQ确认签收的时候出现了网络波动,MQ没有接到确认,就会继续给消费者投递之前的消息,造成消费者接收到了两条一样的消息。

    我们可以通过实现消息的幂等性来避免这种情况,比如说让生产者给每个消息携带一个唯一的id,消费者获取消息后根据这个id去查询数据库,如果不存在就正常消费,如果存在了就证明该消息被消费过,直接丢弃

    97.Lucene创建索引原理

    Lucene是基于倒排索引原理来实现的

    首先,将原文档进行分词处理,形成一个个单独的单词,

    然后取出标点符号以及停词,形成词元,

    再将词元做一些语言相关的处理,比如变成小写,转换时态,单复数形式等等,

    将得到的词创建一个字典,按照字母顺序排序,合并相同的词,最终生成一个倒排索引文档

    98.ES的优势

    ES是基于Lucene的开源搜索引擎,它解决了原生Lucene使用的不足,优化了Lucene的调用方式

    分布式的实时文件存储,每个字段都被索引并可被搜索 ​ 支持实时分析搜索 ​ 可以扩展到上百台服务器,处理PB级结构化或非结构化数据 ​ 通过简单的 RESTful API、可以跟各种语言的客户端甚至命令行进行交互 ​ 上手非常容易,只需很少的学习就可以在生产环境中使用

    99.Lucene/ES为什么那么快(ES用到什么数据结构)

    传统搜索比如mysql的like关键字查询,它的搜索方式就是全文扫表,查询性能很低

    ES是基于Lucene的全文检索引擎,它采用的是倒排索引结构,在存储时先对文档进行分词,再做一些标点符号去除,大小写时态转换等优化处理,最后按照字母顺序去重排序,形成一个倒排索引文档,我们在检索时,就可以通过二分查找的方式找到目标值

    100.ES的分层结构,index下面是什么?

    Index:索引库,包含有一堆相似结构的文档数据,类比Mysql中的数据库

    Type:类型,它是index中的一个逻辑数据分类,类比Mysql中的表

    document:文档:是ES中的最小数据单元,通常用json结构标识,类比Mysql中的一行数据

    Field:字段:类比Mysql中的一个列

    从ES7.0开始,Type被干掉了,从此库表合一即一个Index中只有一个默认的Type

    101.讲几个ES中的查询对象:比如TermQuery

    TermQuery:匹配关键字查询(关键词不分词)

    MatchQuery:匹配关键字查询(关键字分词后)

    BooleanQuery:按条件查询

    matchAllQuery:匹配所有文档查询

    rangeQuery:查询指定范围内的数据

    102.你怎么判断一个字段要不要创建索引

    需要经常关键字查询的字段需要创建索引

    103.你简单描述一下DSL语法

    DSL是一种以json形式标识的,由ES提供的一种查询语言,它由两部分组成,DSL查询和DSL过滤。

    DSL查询类似于模糊查询,DSL过滤类似于精确查询

    104.你说一下 match和term的区别?

    term:不会对搜索词进行分词处理,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到

    match:会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到

    105.项目并发高处理过不过来怎么办

    前端优化:

    使用页面静态化技术由Nginx实现动静分离、CDN加速加快响应速度、使用验证码使流量错峰等手段最大限度的降低并发

    后端优化:

    从架构上使用分布式、集群分散并发量,

    从数据结构上使用缓存如Redis减少数据读写时间,

    从处理方式上采用如RabitMQ队列实现异步响应,

    资源隔离比如使用Hystrix的信号量隔离来限流,同时做好备用方案比如Hystrix的熔断降级策略等等

    106.什么是集群

    集群使将应用复制成多个相同的应用,一起来工作,从而提高工作能力。即将多个应用程序分散在不同的服务器,每个服务器都独立运行相同的代码。可以分散服务器压力解决高并发的问题,同时也能预防单节点故障,即一台服务器故障不影响其他服务器正常运行,但没有解决单体应用代码臃肿,业务复杂,维护性差等等问题

    107.什么是负载均衡

    使用了集群后,解决高并发同时有一个新的问题,就是客户端的请求如何分配到多台服务。因此需要通过负载均衡器,比如Nginx,使用负载均衡算法比如轮询、权重、随机等等将请求路由到不同的服务器

    108.什么是分布式

    分布式是将应用按照业务类型拆分成多个子应用,每个子应用部署在不同的服务器上单独运行,子应用之间通过API相互调用。

    可以分散服务器压力解决高并发问题,同时可以解决单体应用代码臃肿、业务复杂、维护性差等等问题,但是不能防止单节点故障,比如一个子应用故障,整个应用就能不完整运行

    109.集群和分布式的区别,分别解决什么问题

    集群是将一个应用程序复制多份,部署在多台服务器上,每个服务器中的程序都是完整的,可以独立运行

    分布式是将一个应用程序拆分成多个子程序,分别部署在多台服务器上,每个服务器中的程序都是不完整的,所有服务器需要相互通信相互协调才能完成最终的业务

    集群能解决高并发问题,同时能防止单节点故障,即一台服务器宕机不影响其他服务器的正常运行

    分布式也能解决高并发问题,但不能防止单节点故障,即一台服务器宕机了,整体业务就无法完成

    集群无法解决项目本身的代码臃肿、业务复杂等等问题,分布式能降低模块之间的耦合

    实际应用中,我们可以将分布式和集群相结合,比如分布式某个子程序的负载很高,可以单独对这个子程序做集群

    110.说一下你理解的微服务

    微服务也是一个分布式系统,它将单体应用进行细粒度拆分,形成多个微服务,每个服务独立运行,每个服务也都可以有自己的数据库,服务之间使用HTTP通信,互相协调完成整个系统的业务。

    它的优点是服务之间解耦合,不同的服务可以有不同的编程语言,技术选型多元化,支持敏捷开发

    他的缺点是分布式事务很复杂,部署麻烦,技术成本高,服务间通信对性能也有一定的损耗

    111.讲一下你们公司微服务解决方案

    我司正在使用的是第一代微服务方案,Springcloud Netflix全家桶。

    它是使用Eureka做服务注册与发现,也就是解决服务之间通信问题,

    使用Ribbon/OpenFeign做客户端的负载均衡,也就是解决将请求路由到微服务集群的问题,

    使用Hystrix断路器的熔断、降级来解决单节点故障,

    使用Zuul做服务网关,将它作为整个微服务的大门,来实现登录、权限检查等业务,

    使用Config分布式配置中心,来统一管理配置所有微服务的配置文件,

    使用Bus消息总线给各个微服务广播消息,可以实现各个微服务配置的自动刷新,

    使用Sleuth链路追踪,来实时监控各个微服务建的调用关系,快速定位故障节点

    112.说一说Spring Cloud有哪些常用组件,分别有什么用?

    Eureka:做服务注册与发现,用来解决服务之间通信问题,

    Ribbon/OpenFeign:用做客户端的负载均衡,也就是解决将请求路由到微服务集群的问题,

    Hystrix:断路器,它的熔断、降级策略用来解决单节点故障,

    Zuul:做服务网关,它是整个微服务的大门,可以用来实现登录、权限检查等业务,

    Config:分布式配置中心,用来统一管理配置所有微服务的配置文件,

    Bus:消息总线,用来给各个微服务广播消息,可以实现各个微服务配置的自动刷新,

    Sleuth:链路追踪,用来实时监控各个微服务建的调用关系,快速定位故障节点

    113.Spring Cloud的优缺点?

    微服务相对单体应用来说

    优点

    服务之间无耦合,代码简单方便开发维护,服务之间升级维护互不影响 ​ 轻量级HTTP通信机制,不同的服务可以采用不同的编程语言 ​ 有极强的扩展能力,业务量大的服务可以再次拆分服务,或者也可以集群部署 ​ 支持时下流行的敏捷开发并做了优化

    缺点

    分布式事务繁琐

    部署麻烦,开发人员的学习成本高

    技术成本高,开发人员需要花更多的时间学习相关技术

    微服务间的通信存在对性能的损耗问题

    114.什么是服务注册

    Eureka是一个服务组测与发现的组件,翻译成人话就是管理所有微服务的通讯录的组件。它包含注册中心,客户端两部分组成。客户端在启动的时候会向注册中心发送一条自我介绍信息,比如端口,ip等等,在注册中心就会保存一张所有微服务的通讯录。这就叫服务注册

    115.什么是服务发现

    微服务会定期的从客户端拉取一份微服务通讯录,到本地缓存起来,默认是30s一次。当一个微服务向另一个微服务发起调用,直接根据本地的通讯录找到对方的服务名,发送HTTP请求。这个就叫服务发现

    116.什么是服务续约

    微服务会定时(默认30s)发送心跳请求,告诉注册中心,自己还处于存活状态,那么服务中心就不会将其从清单中删除,否则,当微服务宕机或者网络故障等因素,没有在规定时间(默认90s)内提交心跳请求,注册中心就会将它从通讯录中删除。

    117.如果服务挂了,注册中心要等到90s后剔除,那么在剔除前的这段时间内,挂掉的服务有可能还是会被调用,怎么处理?

    第一,可以修改注册中心剔除服务时间,同时加快服务续约心跳请求的频率

    第二,可以使用Hystrix的熔断降级机制,当某个服务不可访问,快速失败,并返回托底数据

    第三。重试

    118.你知道EurekaClient服务发现和服务续约每隔30s做一次请求是用什么技术实现的吗?

    使用了ScheduledThreadPoolExecutor线程池定时任务来实现

    服务发现是先判断是否开启了服务发现功能(默认是开启的),获取定时任务的间隔时间(默认是30s),然后初始化服务发现的定时任务,间隔时间可以在yml中修改

    服务续约是先判断是否开启服务注册功能(默认是开启的),获取定时任务间隔时间(默认是30s),然后初始化心跳请求的定时任务,间隔时间可以在yml中修改

    119.Ribbon是什么,Ribbon的工作原理讲一下

    Ribbon是一个负债均衡器,它可以按照负债均衡算法,向多个服务发起调用。当一个微服务有多个集群时,就可以使用它做请求的分发

    当我们需要调用一组集群服务时,ribbon会根据服务名,在本地缓存的通讯地址里找到这一组服务的通讯地址,然后按照负债均衡算法(默认是轮询),选择其中的一个通讯地址,发起http调用服务

    120.Ribbon有哪些负载均衡算法,怎么配置

    RoundRobinRule:简单轮询,ribbon默认规则

    AvailabilityFilteringRule:忽略短路状态和并发过高的服务器

    WeightedResponseTimeRule:根据服务器响应时间作为权重,响应时间越长权重越小

    ZoneAvoidanceRule:根据区域选择

    BestAvailableRule:忽略短路的服务器,选择并发较低的服务器

    RandomRule:随机选择一个可用服务器

    Retry:重试机制的选择逻辑

    121.OpengFiegn的工作流程

    首先,当程序启动时,使用了@FeignClient注解的接口会被扫描并交给Spring容器管理。

    当发起请求时,会使用jdk动态代理,并为每个方法都生成相应的RequestTemplate,同时封装http信息,包括url和请求参数等,

    最后,由RestTemplate生成request请求,使用ribbon的负载均衡发起调用

    122.为什么Feign的客户端接口没有写实现类也可以直接被依赖注入

    自动注入的实例其实是一个jdk动态代理对象,Feign会为每个方法生成相应的requestTemplate,它根据服务名找到对应的服务,根据返回值类型、形参列表匹配相应的接口,然后封装url、请求参数,最后生成request请求,使用Ribbon负载均衡发起调用

    123.介绍一下Hystrix

    Hystrix意为熔断器,它可以将出现故障的服务,通过熔断、降级等手段隔离开,这样不影响整个系统的主业务。它可以防止由单节点异常导致整个微服务故障,如果遇到故障时,快速失败,熔断的同时可以返回兜底数据达到服务降级的目的

    124.什么是熔断,什么是降级

    熔断,是对服务链路的一种保护机制,当链路上的某个服务不可访问时,服务就会触发降级返回拖地数据,同时当失败率到达一个阈值,就标记该服务为短路状态,当请求访问时直接熔断。直到检查到该服务能正常访问时,就快速恢复

    降级,是当某个服务不可访问时,我们返回一些事先准备好的数据给客户端,比如说,友情提示服务暂不可用,请骚后重试,这样用户体验就上去了

    125.什么是资源隔离?

    指的是限制某一个分布式服务的资源使用,可以理解为限流,也就是限制某个服务的请求数量。它包括线程池隔离和信号量隔离

    线程池隔离,是指用一个线程池来存储当前请求,可以通过设置线程池最大线程数和最大排队队列数来限制请求数量

    信号量隔离:是指用一个计数器来记录当前有多少个线程在运行,请求进来计数器就增加1,超过最大信号量,就直接返回

    126.资源隔离:信号量和线程池的区别

    线程池方式是异步处理,它与调用线程不是同一个线程

    信号量方式是同步处理,与调用线程是同一个线程

    线程池方式由于需要排队,调度,线程切换,因此开销较大,信号量方式无需切换线程,开销较小

    127.对于CAP理论,Eureka选择的是AP还是CP?它保证了一致性还是可用性?

    CAP理论指的是,一个分布式系统中,一致性,可用性,分区容错性,三个要素只能同时实现两点。Eureka选择的是AP,它是弱一致性的,保证了可用性和分区容错性,放弃了数据一致性。也就是说当多个Eureka之间不可通信时,需要保证服务可用,正常提供服务注册发现功能,但是网络恢复后最终还是会同步的。

    128.说一下Eureka的自我保护

    为了防止服务被误删除,Eureka不会立即删除过时的服务数据。这种机制可能会导致客户端从注册中心获取到已经下线的服务并发起调用而导致错误,因此在开发阶段我们可以关闭自我保护机制。在生产环境中,我们需要打开自我保护,因为它可以防止因为网络波动,服务没有及时续约而造成的服务误删除问题。

    129.你们项目是如何做服务降级的?

    比如在秒杀业务中,需要实时从redis中查询库存,通过设置hystrix的最大信号量,以此来防止redis雪崩。当并发过高,请求数超过最大信号量,触发降级,直接向客户端返回兜底数据:”活动太火爆啦,请骚后重试“

    130.Zuul有哪几类Filter,他们的执行顺序是怎么样的?

    zuul按照执行顺序,分为pre前置过滤,route路由过滤,post后置过滤,error异常后过滤

    正常流程是请求先经过前置过滤器,到达路由过滤器进行路由,路由到各种微服务执行请求,返回结果后经过后置过滤,返回用户

    异常流程,如果再整个过程中出现异常,都会进入error异常过滤器,处理完毕后经过post过滤器返回用户,如果error自己出现异常,最终也会通过post过滤器返回用户,如果post过滤器出现异常,也会跳转到error过滤器,然后直接返回用户

    131.在Zuul中做登录检查如何实现?

    可以通过继承ZuulFilter抽象类,自定义pre类型的过滤器,shouldFilter方法中可以定义需要放行的资源,run方法中检查请求头中的token信息,如果没有token,就响应到客户端未登录的信息,并组织filter继续往后执行

    132.在Zuul中如何做限流?

    方式一:可以通过继承ZuulFilter抽象类自定义pre过滤器,加上限流算法,来实现

    方式二:可以通过hystrix的资源隔离模式,设置线程池最大连接数或者最大信号量来实现

    方式三:常用,Ratelimit,使用令牌桶算法。。。

    133.了解SpringCloud源码吗?

    。。。

    134.配置中心解决什么问题?

    在分布式系统中,服务数量很多,而每个服务都有自己的配置文件,管理起来很麻烦。配置中心是个好东西,可以帮我们集中管理配置文件,它支持本地配置文件,也支持将配置文件放到远程仓库如git集中管理。

    135.EureakServer的搭建流程

    第一步,导入eureka-server依赖,以及springboot的web环境依赖。

    第二布,主启动类上打注解,@EnableEurekaServer,开启eureka服务端功能

    第三步,yml配置文件中,配置注册中心的端口号,主机名,注册中心地址

    136.Ribbon的整合流程

    第一步,导入ribbon依赖

    第二部,给RestTemplate的Bean定义方法上,加上注解@LoadBalanced,让这个restTemplate有负载均衡的功能

    第三步,修改restTemplate调用服务的url,将目标主机名换成目标服务名

    137.Feign的整合流程

    第一步,导入openfeign依赖

    第二部,主配置类加注解,@EnableFeignClients,开启feign支持

    第三步,定义feign客户端接口,并加上注释@FeignClient("目标服务名"),接口中定义方法,该方法与目标服务的对应方法的方法名,返回值类型,形参列表,url路径要一致

    138.Hystrix的整合流程

    ribbon整合:

    第一步,导入hystrix依赖

    第二部,主启动类加注解,@EnableCircuitBreaker,开启熔断功能

    第三步,在需要开启熔断功能的方法上,加注解@HystrixCommand(fallbackMethod="xxx"),xxx是降级方法

    第四步,定义降级方法,方法名需要和fallbackMethod的值一致,形参列表和返回值类型需要和目标方法一致

    feign整合:

    第一步,yml中配置,feign.hystrix.enable=true,开启hystrix功能

    第二部,@FeignClient标签中,定义fallback或者fallbackFactory,指定降级类

    第三步,

    如果是fallback,就实现feign接口,并覆写接口中的方法作为降级方法

    如果是fallbackFactory,就实现FallbackFactory接口,同时指定泛型为feign接口,覆写create方法,返回一个feign接口的匿名内部类,类中写降级方法

    139.Zuul的整合流程

    第一步,导入zuul依赖

    第二步,主启动类上加注解@EnableZuulProxy,开启zuul功能

    第三步,yml中配置,统一访问前缀prefix,禁用通过服务名方式访问服务ignoredServices,配置路由routes指定某个服务使用某个路径来访问

    140.ConfigServer的整合流程

    配置中心服务端配置:

    第一步,导入config-server依赖

    第二步,主启动类加注解,@EnableConfigServer,开启配置中心

    第三步,配置文件中,配置远程仓库地址,仓库账号密码

    客户端配置:

    第一步,导入config-client依赖

    第二步,创建bootstrap.yml配置文件,配置中心地址config.uri,要拉取的配置文件名name,环境名profile

    141.你们微服务项目的技术栈描述一下

    前端门户系统:HTML + JQuery + CSS

    前端管理系统:VUE + ElementUI

    后端系统:基于SpringCloud微服务框架(Eureka+OpenFeign+Hystrix+Zuul+Config)

    +MyBatisPlus+SpringMVC+Redis+ElasticSearch+RabbitMQ+AlicloudOSS

    142.浏览器发起一个请求,在你的微服务项目中的怎么去执行的?

    浏览器发起的所有请求首先通过Nginx,通过负载均衡算法,路由给zuul集群,然后通过zuul前置过滤,作登录校验后,它会从配置中心拉取的通讯地址中,根据url匹配到对应的服务,然后使用ribbon发起restful调用。微服务间也可以通过feign相互调用,最终执行完任务,返回浏览器

    143.为什么要使用Redis做缓存

    一个字,快。

    缓存它指的是将数据库的数据同步到内存中,客户端获取数据直接从内存中获取。由于内存读写速度大于磁盘,而使用缓存能减少磁盘读取,大大提高查询性能。

    我们一般会将经常查询的,不会经常改变的热点数据,保存到缓存中,提高响应速度

    144.缓存的执行流程

    1.客户端发起查询请求

    2.判断缓存中是否有数据

    如果有,直接返回

    如果没有,就从数据库查询,再把数据同步到缓存

    3.返回数据给客户端

    145.SpringCache常用注解

    @EnableCaching:打在主启动类上,开启缓存功能

    @Cacheable:打在方法上,表示该方法会开启缓存,打在类上,表示类中所有的方法都开启缓存

    @CacheEvict:搭载类或者方法上,会将缓存清除

    @CachePut:更新缓存

    @Caching:组合操作,要应用于方法的多个缓存操作

    @CacheConfig:打在类上,共享的一些常见缓存设置

    146.你们怎么保证Redis和Mysql的一致性

    第一,对redis的操作,采用直接删除,因为redis存储的数据包括了数据库中的多个表,更新的性能比删除的性能低

    第二,操作顺序,我们的业务对一致性要求不是很高,因此采用了先操作mysql,后删除redis,redis中缓存的数据对key都设置了过期时间,就算有缓存有脏数据,最终也会一致。

    147.京东的首页的商品分类,让你设计表,你怎么设计

    首先可以看出表的结构是自关联,三层的树状结构,分类表的字段可以有主键id,商品名,创建时间,修改时间,上架时间,下架时间,商品数量,排序,图标,父级id

    148.如何查询出树状结构的课程分类数据

    首先,在entity中加入子分类字段children

    查询方式有四种

    第一,使用嵌套for循环,循环体内查询每一层级的数据,并关联到children。当然这也可以使用递归函数来实现

    第二,使用mybatis的嵌套查询,也就是主查询加额外子sql查询的方式

    第三,使用mybatis的嵌套结果,也就是join连表查询的方式

    第四,只使用一次查询,将所有数据查询出来,通过一种算法来实现:除了第一级,其他所有数据都关联到自己的父级分类,结果返回第一级数据就可以

    第一,第二种方式,当层级多的时候查询性能极低,第三种方式一般只能查询两层结构,第四种方式性能最高,适用于数据量本身并不大但层级很多的场景

    所有课程的数据本身体量小,层级多,因此采用了第四种方式。

    149.你们系统使用Redis缓存了哪些东西?用Redis的什么结构去存储的?

    登录信息login,使用的是String结构存储

    手机验证码code,使用的是String结构

    课程分类course_type ,使用的是String结构

    购物车保存,使用的是Hash结构

    150.课程发布流程讲一下

    发布课程两大步

    第一步,将课程的状态改为上线并保存到数据库中,

    第二步,将课程信息保存到ES中,方便门户网站展示

    151.你们课程相关的表是怎么设计的?主要的字段说一下

    我们按照字段的使用频次,垂直分表来设计,分为课程主表,课程详情表,课程类型表,课程市场详情表。

    课程主表,包括主键id,课程名称,课程类型id,课程上下线状态,适用人群,课程等级,课程所属机构等,并且冗余了课程类型名,课程价格字段来提高前台的查询性能

    课程详情表,包括课程简介,课程详情

    课程市场详情表,包括课程价格,促销活动,活动过期时间

    课程类型表,包括主键id,类型名,创建修改时间,课程数量,父级id

    其中课程主表和课程详情表、课程主表和课程市场详情表,都是一对一的关系,他们采用相同的主键id来相互关联。课程主表和课程类型表是多对一的关系,在课程主表添加类型id来相互关联

    152.讲一下你们这个项目的主线业务

    我们项目分为两大版图,

    入驻我们平台的培训机构,可以发布相关课程,入驻平台的企业,可以发布相关的就业招聘信息

    门户网站的大众用户,可以选择培训机构发布的课程来进行学习,可以选择企业发布的招聘信息来就业

    153.RabbitMQ消息投递失败,你们怎么处理

    我们可以设置/confirm/i回调和 returned 回调

    比如说,可以在发送消息的时候,把消息详情包括交换机名,路由键,都保存到一个表中,状态设置为发送中,如果在/confirm/i方法中ack为false,代表发送到交换机失败 ,就把这个记录状态修改为发送失败

    然后我们创建一个定时任务定时扫表,去读取发送失败的数据并重新发送,为了优化性能,我们设置重试次数3次,如果3次都失败了,我们可以采取人工干预

    154.为什么要使用Eureka 为什么要使用Ribbon 为什么要使用config配置中心

    在微服务系统中,各个服务之间是需要进行网络通信的,那么他们相互调用就得知道对方的通信地址。eureka就是专门来做做服务注册与发现,解决服务之间通信问题的

    当一个微服务做了集群,也就是同一个服务名会对应多个地址,那么我们在调用的时候,应该调用哪一个就成了问题,Ribbon是一个负债均衡器,它可以按照负债均衡算法,向多个服务发起调用。当一个微服务有多个集群时,就可以使用它做请求的分发

    在微服务系统中,服务数量很多,而每个服务都有自己的配置文件,管理起来很麻烦。用了配置中心就可以帮我们集中管理配置文件,它支持本地配置文件,也支持将配置文件放到远程仓库如git集中管理

    155.你们项目最大并发是多少

    俺们项目是按照最高2000 QPS设计的,实际并发数运维在统计,俺也不太清楚

    156.你们项目最大表数量是多少

    俺们项目都有分库分表,按服务拆分多个数据库,对于有些数据量大的表,我们也是按照字段的使用频率,拆分成多个表,比如课程表拆分成课程主表,课程详情表,课程分类表等等。

    但是有些表比如日志,流水相关的表,数据量还是很大的

    157.说一下security中的常用filter

    SecurityContextPersistenceFilter:请求开始会从SecurityContextRepository中获取SecurityContext对象并设置给SecurityContextHolder,在请求完处理成后将SecurityContextHolder持有的SecurityContext再保存到配置好的SecurityContextRepository中,同时清除SecurityContextHolder中的SecurityContext

    UsernamePasswordAuthenticationFilter:默认拦截“/login”登录请求,将请求中的认证信息包括用户名,密码封装成UsernamePasswordAuthenticationToken,然后调用AuthenticationManager的认证方法进行认证

    BasicAuthenticationFilter:处理 HTTP 请求的 BASIC 授权标头,如果身份验证成功,就把生成的Authentication对象放入SecurityContextHolder。如果设置了记住我,下次访问就不会走这里来了

    RememberAuthenticationFilter:记 住我,调用RememberMeServices的autoLogin方法自动登录

    AnonymousAuthenticationFilter:匿名filter,检测SecurityContextHolder有没有Authentication对象,如果没有,就会创建一个AnonymousAuthenticationToken并保存到SecurityContextHolder

    ExceptionTranslationFilter:处理filter链中的所有AccessDeniedException和AuthenticationException

    FilterSecurityInterceptor:继承自AbstractSecurityInterceptor,通过调用AccessDecisionManager.decide方法进行授权

    158.说一下security的认证原理

    首先,请求会经过UsernamePasswordAuthenticationFilter拦截,请求的用户名密码会封装成UsernamePasswordAuthenticationToken,过滤器将token提交给认证管理器AuthenticationManager进行认证

    然后,认证管理器调用AuthenticationProvider进行认证,AuthenticationProvider再调用UserDetailsService获取到数据库中存储的用户信息UserDetails,然后调用密码编码器对密码进行比较,认证成功后封装Authentication

    再后来,请求回到UsernamePasswordAuthenticationFilter,调用SecurityContextHolder将Authentication对象封装成SecurityContext并保存到SecurityContextHolder中

    最后,请求回到SecurityContextPersistenceFilter,它会调用SecurityContextRepository将SecurityContext对象存储起来,再清理掉SecurityContextHolder中的信息

    159.Oauth2授权有几种方式,你们项目用的哪种

    授权码模式:它是功能最完整、流程最严密的授权模式

    简化模式:跳过授权码,直接再浏览器端申请令牌

    用户名密码模式:客户向客户端提供用户名密码,建立在用户对客户端高度信赖的基础上

    客户端模式:客户端以自己的名义,要求服务提供商提供服务,你仔细一品,这其实也不存在授权一说了

    我们项目中,用户登录使用的是用户名密码模式,服务内部临时调用使用的是客户端模式

    160.说一下security授权原理

    认证后的用户在访问受保护的资源,会经过FilterSecurityInterceptor,它会调用父类AbstractSecurityInterceptor的beforeInvocation方法开始授权

    首先通过SecuritymetadataSource.getAttributes(object),获取该资源所需要的权限

    然后通过SecurityContextHolder.getContext().getAuthentication(),获取当前用户的权限

    最后通过调用AccessDecisionManager.decide进行授权,它使用的是投票机制来决定用户是否有访问权限,投票通过,就能访问请求的资源。

    161.说一下你们课程搜索的那个业务方法的大致逻辑。

    首先,课程在发布的时候,就同时将课程信息存放到ES中,信息中包括了需要查询的字段,如课程标题,课程分类,课程等级,机构名,销量,浏览量,上线时间,价格等等

    接下来,根据用户在前台发送的查询条件,在ES中搜索对应的课程,并作关键字高亮处理,聚合与排序和分页处理,然后返回前台

    162.你使用过ES的哪些聚合查询?

    指标聚合,比如求和,求最大值,最小值,平均数

    数量统计聚合,计算满足条件数据的总条数,相当于sql中的count

    去重聚合,它会计算非重复的数据个数,相当于sql中的distinct

    桶聚合,它会将某个field的每个唯一值当成一个桶,并计算每个桶内的文档个数,相当于sql中的group by,我们使用的是桶聚合

    最高权值聚合,它会匹配每组前n条数据,相当于sql中的group by后取出前n条

    163.ES高亮怎么做的?

    使用HighlightBuilder对关键字作高亮处理,由于我们项目使用的是SpringBoot整合ES的jar包,结果没有进行高亮处理,我们使用ElasticsearchTemplate的queryForPage方法来获取结果,再手动进行分页封装返回前台

    164.说一下你对ConcurrentHashMap的理解

    ConcurrentHashMap,它是HashMap的线程安全,支持高并发的版本

    在jdk1.7中,它是通过分段锁的方式来实现线程安全的。意思是将哈希表分成许多片段Segment,而Segment本质是一个可重入的互斥锁,所以叫做分段锁。

    在jdk1.8中,它是采用了CAS操作和synchronized来实现的,而且每个Node节点的value和next都用了volatile关键字修饰,保证了可见性

    165.讲一下你们的微服务授权方案 你还知道有哪些方案吗?

    我们使用的是SpringSecurity+Oauth2+JWT,认证服务器负责颁发token,资源服务器负责认证和授权

    或者也可以将认证工作交给网关zuul,资源服务器只负责授权工作。

    另外常见的授权方案还有,单点登录,用户只用在某个服务上登录,访问其他服务时就不需要登录了,这就要求每个面向用户的服务都必须于认证服务交互,会产生大量重复的工作

    分布式会话,它是将用户认证信息存储在共享容器比如redis中,通常会以会话作为key,当用户访问微服务时,就从redis中获取认证信息。这对安全存储有较高的要求,复杂度高

    166.讲一下你们微服务认证授权的整体流程

    客户端访问认证服务器,认证服务器验证用户名密码,然后颁发token

    客户端保存token,并且每次访问服务时都携带token

    资源服务器接收到客户端请求,会验证token信息,认证通过后返回资源

    167.你们为啥要用JWT

    一个字,安全

    我们做了认证授权后,每次客户端访问资源服务器,都需要远程调用认证服务器进行token的校验和授权,才能访问到资源。这是很好性能的,因此我们考虑将签名信息直接保存到客户端,那就不需要每次都向认证服务器认证授权了。

    但是这有有一个新的问题,这些敏感数据赤裸裸的存到客户端不安全!而JWT就能解决这个问题。它支持非对称加密算法对信息加密,保证了信息安全

    另外,JWT以json对象的形式传递信息,解析更方便

    可以再令牌中定义内容,方便扩展

    168.Oauth2的授权模式有哪些,分别使用在什么场景?

    授权码模式:它是功能最完整、流程最严密的授权模式

    简化模式:跳过授权码,直接再浏览器端申请令牌

    用户名密码模式:客户向客户端提供用户名密码,建立在用户对客户端高度信赖的基础上

    客户端模式:客户端以自己的名义,要求服务提供商提供服务

    169.Oauth2认证,如果Token过期了你们是怎么处理的

    首先,我们会在前端设置axios后置拦截,检查是否是token过期,判断一下如果返回401,就代表token过期了

    然后从localStorage中获取刷新refresh_token,并发送请求获取新的token

    后台接收到前台的刷新token请求,拼接完整的刷新token的url,发送http请求获取到新的token并返回客户端

    客户端收到新的token就把旧的token覆盖掉,最后把之前的请求再重新发送一次

    170.Oauth2认证,如果Token被盗了怎么办?

    首先,我们需要对token设置过期时间,这个时间可以根据需要设置短一点

    然后,可以在token中加入客户身份标识,比如客户的ip地址,如果短时间内ip地址频繁变动,就标记为异常状态,并给用户发送信息,提示账户有风险

    171.说一下Eureka的自我保护

    为了防止服务被误删除,Eureka不会立即删除过时的服务数据。这种机制可能会导致客户端从注册中心获取到已经下线的服务并发起调用而导致错误,因此在开发阶段我们可以关闭自我保护机制。在生产环境中,我们需要打开自我保护,因为它可以防止因为网络波动,服务没有及时续约而造成的服务误删除问题。

    172.说下Ribbon和Feign的区别呢?

    Ribbon和Feign都是SpringCloud Netflix中实现负载均衡的组件,不同点在于

    Ribbon是需要我们手动构建http请求,根据目标服务名通过负载均衡算法直接调用目标服务,

    Feign是采用接口的方式,将需要调用的目标服务方法定义成抽象方法,路径,服务名,形参列表,返回值类型需要保持一致。我们只需要调用接口中的方法就可以了。它会自动帮我们生成jdk动态代理,为每个方法生成RequestTemplate并封装url和请求参数,使用负载均衡算法发起调用

    Ribbon的实现方式,一般配合RestTemplate发起http请求,我们需要在注册RestTemplate的Bean的方法上加@LoadBalanced,使它具有负载均衡的能力

    Feign的实现方式,是在主启动类上加@EnableFeignClients,在客户端接口上加注解@FeignClient

    173.Spring,SpringBoot和SpringCloud的关系以及区别

    Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理。

    Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效。比如它大量简化maven依赖,基于注解配置(JavaConfig)无需XML,内嵌Tomcat,部署流程简单,打包和部署更加灵活,允许独立运行

    SpringCloud是基于SpringBoot实现的,用于微服务架构中管理和协调服务的,它是一系列框架的有序集合,它为开发者提供了一系列工具,例如服务发现与注册,配置中心,网关,负载均衡,熔断器,链路追踪等等,让微服务架构落地变得更简单

    174.ES的keyword和text区别

    keyword:不分词,直接建立索引,支持模糊查询,精确查询,聚合查询

    text:分词后建立索引,支持模糊查询,精确查询,不支持聚合查询

    keyword通常用于通常用于存储年龄,性别,邮编,邮箱号码等等,直接将完整数据保存的场景

    text通常存储全文搜索的数据,例如地址,文章内容的保存

    175.常见Http状态码

    200 成功返回状态

    301 永久重定向,被请求的资源永久移动到新位置

    302 临时重定向,被请求的资源临时移动到新的位置,项目中使用了oauth2,对目标资源访问无权限时就会见到,它是会重定向到授权地址

    401 无权限访问

    403 禁止访问,服务器已经接收到请求,但拒绝执行

    404 找不到该资源

    500 服务器内部错误 zuul找不到服务名就会见到

    503 服务器内部错误 服务器维护或者过载

    504 网关超时

    176.什么是事务

    一组对数据库的操作,要么全部成功,要么全部失败

    举个栗子,比如A向B转账,A账户的钱少了,B账户的钱就应该对应增加,这就转账成功了,如果A账户的钱少了,由于网络波动等因素转账失败了,B账户的钱没有增加,那么A账户就应该恢复成原先的状态

    177.事务的四大特性

    原子性:指的是一个事务应该是一个最小的无法分割的单元,不允许部分成功部分失败,只能同时成功,或者同时失败

    持久性:一旦提交事务,那么数据就应该持久化,保证数据不会丢失

    隔离性:两个事务修改同一个数据,必须按顺序执行,并且前一个事务如果未完成,那么中间状态对另一个事务不可见

    一致性:要求任何写到数据库的数据都必须满足预先定义的规则,它基于其他三个特性实现的

    178.InnoDB如何保证原子性和持久性的

    通过undo log 保证事务的原子性,redo log保证事务的持久性

    undo log是回滚日志,记录的是回滚需要的信息,redo log记录的是新数据的备份

    当事务开始时,会先保存一个undo log,再执行修改,并保存一个redo log,最后再提交事务。如果系统崩溃数据保存失败了,可以根据redo log中的内容,从新恢复到最新状态,如果事务需要回滚,就根据undo log 回滚到之前的状态

    179事务并发问题有哪些

    脏读:事务A读到了事务B修改还未提交的数据

    幻读,也叫虚读:事务A两次读取相同条件的数据,两次查询到的数据条数不一致,是由于事务B再这两次查询中插入或删除了数据造成的

    不可重复读:事务A两次读取相同条件的数据,结果读取出不同的结果,是由于事务B再这两次查询中修改了数据造成的

    第一类丢失更新:也叫回滚丢失,事务A和事务B更新同一条数据,事务B先完成了修改,此时事务A异常终止,回滚后造成事务B的更新也丢失了

    第二类丢失更新:也叫覆盖丢失,事务A和事务B更新同一条数据,事务B先完成了修改,事务A再次修改并提交,把事务B提交的数据给覆盖了

    180.事务隔离级别有哪些,分别能解决什么问题

    读未提交:事务读不阻塞其他事务的读和写,事务写阻塞其他事务的写但不阻塞读,能解决第一类丢失更新的问题,

    读已提交:事务读不会阻塞其他事务读和写,事务写会阻塞其他事务的读和写,能解决第一类丢失更新,脏读的问题

    可重复读:事务读会阻塞其他事务的写但不阻塞读,事务写会阻塞其他事务读和写,能解决第一类丢失更新,脏读,不可重复读,第二类丢失更新问题

    串行化:使用表级锁,让事务一个一个的按顺序执行,能解决以上所有并发安全问题

    181.MySql的InnoDB是如何保证原子性的

    利用了undo log实现的

    undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,就可以利用undo log中的信息将数据回滚到修改之前的样子

    182.MySql的InnoDB是如何保证持久性的

    利用了redo log实现的

    redo log记录的是新数据的备份,在事务提交前,需要将Redo Log持久化,当系统崩溃时,可以根据redo Log的内容,将所有数据恢复到最新的状态

    183.说一下事务的执行流程(Undolog+Redolog)

    假设有A=1,B=2,两个数据,现在有个事务把A修改为3,B修改为4,那么事务的执行流程:

    当事务开始时,会首先记录A=1到undo log,记录A=3到redo log,和记录B=2到undo log,记录B=4到redo log,然后再将redo log写入磁盘,最终事务提交

    184.解释一下事务并发丢失更新问题,·如何解决

    第一类丢失更新:也叫回滚丢失,事务A和事务B更新同一条数据,事务B先完成了修改,此时事务A异常终止,回滚后造成事务B的更新也丢失了

    第二类丢失更新:也叫覆盖丢失,事务A和事务B更新同一条数据,事务B先完成了修改,事务A再次修改并提交,把事务B提交的数据给覆盖了

    SQL标准中的四种隔离级别,读未提交,读已提交,可重复读,串行化,都能解决第一类数据更新丢失问题

    对于第二类丢失更新问题,可以使用悲观锁也就是串行化来解决,也可以使用乐观锁的方式,比如加一个版本号管理来解决

    185.InnoDB事务隔离的实现原理是什么

    隔离的实现主要利用了读写锁和MVCC机制

    读写锁,要求在每次读操作时需要获取一个共享锁,写操作时需要获取一个写锁。共享锁之间不会产生互斥,共享锁和写锁,写锁与写锁之间会产生互斥。当产生锁竞争时,需要等一个操作的锁释放,另一个操作才能获得锁

    MVCC,多版本并发控制,它是在读取数据时通过一种类似快照的方式将数据保存下来,不同的事务看到的快照版本是不一样的,即使其他事务修改了数据,但是对本事务仍然是不可见的,它只会看到第一次查询到的数据

    可重复读是只在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照

    186.什么是分布式事务, 分布式事务你知道哪些解决方案? 这些方案如何选型

    分布式事务,指的是在分布式环境中,一个请求可能涉及到对多个数据库的写操作,要保证多数据库的一致性就需要用到分布式事务

    常见的分布式事务解决方案,2PC,TCC,可靠消息最终一致性,最大努力通知

    2PC,它将整个事务流程分为两个阶段,P指的是准备阶段,C指的是提交阶段。它是一个阻塞协议,不适用于并发较高,事务生命周期长的分布式事务。

    TCC,它是基于补偿性事务的AP系统的一种实现,补偿也就是说先按照预定方案执行,如果失败了就走补偿方案。它可以自己定义数据操作的粒度,但是对应用的侵入性强,可以用在登录送积分,送优惠券等等场景

    可靠消息最终一致性,指的是当事务发起方执行完本地事务后,就发出一条消息通知其他参与方,并且他们一定能接收到消息并处理事务。适合执行周期长,并且实时性要求不高的场景

    最大努力通知,是在不影响主业务的情况下,尽可能的保证数据的一致性,它适用于一些最终一致性敏感度低的业务,比如支付结果通知

    187.什么是2pc,基于什么协议,有什么缺点?

    2PC,是将整个事务流程分为两个阶段,P指的是准备阶段,C指的是提交阶段。它常见的标准有XA,JTA,Seata

    由DTP模型定义事务管理器TM和资源管理器RM之间通讯的接口规范叫做XA,它规定的交互方式是酱紫的:应用程序AP通过TM提交和回滚事务,TM通过XA接口来通知RM数据库事务的开始,结束,提交,回滚

    2PC能保证分布式事务的原子性,但是也有很多缺陷

    比如,在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好

    比如,在第二阶段,如果事务协调者发出提交事务指令后宕机,一部分参与者收到消息提交了事务,另一部分没有收到消息没有提交事务,这就会导致数据不一致

    再比如,在第二阶段,如果事务协调者发出提交事务指令后宕机,收到指令的参与者也宕机了,我们就不能确定事务的执行结果,究竟有没有提交

    188.Seata相比传统2PC有什么区别,以及优点?

    Seata是由阿里中间件团队发起的开源项目Fescar更名而来,是一个开源的分布式事务框架,它通过对本地关系数据库的分支事务协调,来驱动完成全局事务

    Seata的主要优点是性能好,不会长时间占用链接资源,对业务零入侵

    与传统的2PC的区别主要两方面

    在架构层次方面,传统的2PC方案的RM本质就是数据库自身,而Seata的RM是以jar包形式作为中间件层部署在应用程序上

    在两阶段提交上方面,传统2PC方案是在第二阶段完成才释放资源,而Seata是在第一阶段就将本地事务提交,提高了效率

    189.Seata的TC,TM,RM的含义,以及作用?

    TC:事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚

    TM:事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令

    RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支事务的提交和回滚

    190.你知道TCC吗,它有什么样的优缺点?

    TCC是基于补偿型事务的AP系统的一种实现。补偿指的先按照事先预定的方案去执行,如果失败了就走补偿方案

    它的优点是异步执行效率高,它能对分布式事务中的各个资源分别锁定,分别提交与释放

    它的缺点是对应用的侵入性强,改动成本高,实现难度大

    191.解释一下Seata的工作原理

    假设有服务A需要调用服务B,且两个服务都需要修改各自的数据库

    A服务的TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID

    A服务的RM向TC注册分支事务,并将其纳入XID对应全局事务的管辖

    A服务执行分支事务

    当调用B服务时,B服务的RM向TC注册分支事务,该分支事务执行,并将其纳入XID对应全局事务的管辖

    B服务执行分支事务,执行完毕后,返回A服务

    192.你能简单描述一下你在项目中是如何集成Seata的吗

    事务协调器:安装并启动Seata客户端

    主业务端:

    第一步,导入Seata依赖

    第二步,yml中配置事务组名,同时需要添加配置文件file.conf,registry.conf,需要注意yml中事务组名与file.comf中的事务组名一致

    第三步,配置DataSource,需要适用Seata对DataSource进行代理

    第四步,数据库中添加undo log日志表

    第五步,业务方法上加注解@GlobalTransactional(rollbackFor = Exception.class)注解

    事务参与者:

    前四步与主业务端相同,第五步不需要了

    193.你说一下什么是分布式锁

    锁,解决多线程并发造成的一系列数据安全问题.

    在分布式环境,微服务环境,集群环境中解决多进程多线程高并发的安全问题需要用到分布式锁

    分布式锁需要具备的特点:

    1.保证线程的安全

    2.可重入锁(防止死锁)

    3.根据业务需求,阻塞锁

    4.根据业务需求,公平锁

    5.获取和释放锁的性能要好并且高可用

    194.分布式锁有哪些解决方案

    常用的三种方案

    基于数据库

    1.1 行锁(悲观锁)

    共享锁(读取), 排他锁(修改)

    for update:

    select * from table where xx = yy for update => 读取也变成排他锁

    1.2 乐观锁

    添加版本号

    1.3 数据库主键

    1.4 数据库唯一索引(类似于主键)

    基于Redis

    2.1 setnx 命令

    setnx(lock_stock, 1) 设置一个锁名和一个value

    基于zookeeper

    195.Redis如何实现分布式锁,用什么命令

    使用的是setnx命令实现的nx获取锁和ex设置过期时间

    设置redis里面值得时候key没有值得时候就设置值为value,没有值的时候说明里面已经有锁被占用,操作完数据之后再删除锁,这样就实现了分布式锁

    但是存在几种特殊的情况,例如在使用setnx命令获取锁的时候如果服务突然出现了异常这样的话就没有设置过期的时间导致产生了死锁,解决的方案就是使用set命令解决获取锁和设置过期时间的原子性问题

    还有问题就是在释放锁的时候要删除锁的时候设置的到期时间到了这就导致锁没有被删除就丢失锁,另外一个线程进来操作数据删除锁的时候就删除的不是自己的锁造成锁的误删,可以使用我们可以在删除锁的时候判断是不是自己的锁,比如可以在创建锁的时候锁的值加上一个uuid,在删除锁的时候判断是否是自己创建的锁,如果不是自己的锁就不进行操作

    但是还是有特殊情况,例如在判断锁是否是自己的时候,在删除锁中间不是原子性,可能在判断所之后进行删除锁操作的时候时间到期了,造成锁的误删,我们可以使用一段Lua脚本解决获取锁,判断所,删除锁操作的原子性

    196.Redis实现分布式锁可能会出现什么问题,如何解决

    在使用setnx命令获取锁的时候如果服务突然出现了异常这样的话就没有设置过期的时间导致产生了死锁,解决的方案就是使用set命令解决获取锁和设置过期时间的原子性问题

    还有问题就是在释放锁 要删除锁的时候设置的到期时间到了这就导致锁没有被删除就丢失锁,另外一个线程进来操作数据删除锁的时候就删除的不是自己的锁造成锁的误删,可以使用我们可以在删除锁的时候判断是不是自己的锁,比如可以在创建锁的时候锁的值加上一个uuid,在删除锁的时候判断是否是自己创建的锁,如果不是自己的锁就不进行操作

    但是还是有特殊情况,例如在判断锁是否是自己的时候,在删除锁中间不是原子性,可能在判断所之后进行删除锁操作的时候时间到期了,造成锁的误删,我们可以使用一段Lua脚本解决获取锁,判断锁,删除锁操作的原子性

    197.你项目中怎么使用分布式锁的

    我的项目中使用的是redisson实现的分布式锁基于的就是java对于redis

    使用的是senx命令实现分布式锁,例如有三个线程需要操作同一资源,就去竞争锁,当获取到的key里面没有值,就是空闲的锁,就是用setnx命令获取锁,当key的value中存在值,就说明当前的锁已经被占用

    198.了解Redission的看门狗原理吗?

    了解,看门狗的原理是基于redisson实现的

    主要就是锁的续约的实现,当代码执行到了设置的过期时间还没有执行完成就自动为其加时间,直到自删除锁

    看门狗 十秒续约一次每次续约三十秒

    199.你知道zookeeper实现分布式锁吗?

    知道的,zookeeper主要维护的是一个关系型的树

    里面分为持久顺序节点,持久节点,临时节点,临时顺序节点四种

    一般使用临时顺序节点实现分布式锁,实现的是一种公平锁

    实现的方式就是当第一个节点获取到了锁其他节点就获取不到锁

    其他节点就监听上一个节点的锁的删除当上一个节点锁删除之后就变成第一个节点然后就获得了锁

    200.你在项目中如果使用ZK实现分布式锁的?

    我们在项目中没有实现zookeeper实现分布式锁,但是我还是对其有点点了解,

    zookeeper实现分布式锁的话主要是通过官方封装的curtor里面封装的就是临时顺序节点

    201.如果没有Redis和zookeeper怎么实现分布式锁?

    使用数据库实现分布式锁,但是数据库实现分布式锁的话性能比较差,而且容易造成等待超时

    主要的实现方案有

    inindb行锁和表锁,其中对数据库的读操作为共享锁,写操作为排他锁,排他锁会于其他的锁产生竞争的关系

    乐观锁实现方案:CAS,通过版本号进行控制,每次操作之前都判断是否为刚刚操作之前的版本号

    主键的实现方案:每次操作之前都在数据库的操作表中生成和操作数据相同主键的数据,操作完成之后就删除操作表中的数据,其他线程来获取数据进行控制的时候也要设置生成主键数据但是主键是唯一的所以就生成失败导致获取不到操作权限从而实现分布式的锁

    使用唯一索引实现分布式的锁

    202.你们数据库和Redis的数据一致性怎么做的

    每次修改数据库的时候同时修改redis中的数据就可以保证一致性

    203.了解缓存击穿,穿透,雪崩吗?怎么处理?

    缓存击穿:缓存中没有,数据库中有的数据,由于某种原因比如缓存过期了,同时并发用户特别多,一时间都往数据库中读取数据

    解决方案:加互斥锁,只能允许一个线程访问数据库,然后其他线程就可以往内存中拿

    缓存穿透:缓存和数据库中都没有数据

    解决方案:布隆过滤器来判断数据库中有没有这个key

    缓存雪崩:缓存重启,或者大量key失效

    解决方案:为key设置不同的过期时间

    204.你们ES和数据库的数据一致性怎么做的?

    205.秒杀的整体流程详细说一下

    首先再后台添加秒杀的课程并且设置好唯一的秒杀码,设置定时任务定时发布秒杀课程

    每五秒定时扫描一次秒杀列表当离秒杀的时间只有两天的时候直接将秒杀使用hash结构课程添加到redis中,用户就可以再前端页面可以看到秒杀课程

    用户在秒杀时间到了之后执行秒杀操作,后端的话直接先从redis中将信号量预减库存,防止超卖

    创建订单号,并且保存好预创订单保存到redis,并且 发送消息到rabbitMQ,用来设置超时下单(这里有一个延迟队列和死信队列可以扯一扯)

    返回订单号到前端,前端在规定的时间点击确认订单,带上订单号发送到后端,后端从redis拿到预创单,创建订单保存到数据库并且创建一个支付单到数据库删除预创单,发送延迟消息到rabbitMQ,用作支付超时处理,同时创建一个支付单保存到数据库,

    前端拿着点单号调用支付接口进行支付

    支付结果的异步回调我们需要修改支付单的状态,保存支付的流水,修改订单号的状态,保存课程和用户的关系

    PS:其中订单超时的编写的延迟队列,首先到死信队列收到消息的时候就是说明订单的已经到确认超时的时间了,就去redis里面找预创单,如果找到了,就说明用户没有确认订单,就删除预创单,并且回退库存也就是信号量

    对于支付超时的延迟队列,首先当死信队列收到消息就是说明订单支付的时间到了,如果支付单的状态还是待支付就信号量回退,远程调用支付接口将支付单的状态该为支付超时

    我们可以设置死信队列的超时时间稍微大于正常的过期时间,然用户操作有一个缓冲区不至于用户在确认订单的时候和支付的时候延迟队列过期时间到了,造成数据的不一致性

    206.你们这个接口QPS是多少

  • 本地单机压测 : 单机秒杀服务,2千+(6个核 , 16G内存)

  • 线上:LVS+Nginx集群(限流)+秒杀服务集群 : 要求 1W QPS

  • 207.如果流量更高,比如:每秒10W请求,应该怎么处理

  • LVS+Nginx集群(限流)+秒杀服务集群+分流(CDN)

  • 208.说一下支付超时处理方案?延迟队列和死信队列是什么意思?

  • 延迟队列:设置了过期时间的队列,消息在这个队列中超过过期时间就会被丢弃或者转移到其他队列

  • 死信队列:用来保存过期了的消息,这种消息叫死信消息,该队列就叫死信队列

  • 死信交换机:用来转发已经过期了的消息到死信队列的交换机,就叫死信交换机

  • 209.整个秒杀流程你用了几个队列,分别用来存储什么东西

  • 2个延迟队列,用作下单超时和支付超时处理

  • 下单之后要:减去库存,推送消息,赠送积分,这里用到了MQ异步

  • 支付结果:把支付结果放到MQ中,其他的服务来做出相应的处理,比如:订单服务修改状态 。

  • 210.秒杀成功,返回给用户的数据是什么?

  • 订单号,我们使用多少预创订单,用户秒杀成功,带着订单号去确认订单,然后把预创订单保存到数据库

  • 211.你们怎么处理超卖,少卖的?

  • 信号量可以防止库存减到负数,防止超卖,它本身是原子性的,我们使用的是预减库存方案。

  • 延迟队列,如果规定时间内支付超时会退回库存

  • 2.用户如何支付,它怎么知道该支付哪个订单

  • 用户带着订单号去支付,就可以

  • 3.用户进入支付宝支付页面在最后一步输支付密码停住,这时如果订单z自动超时关单,然后用户输入支付密码支付,请问这种问题怎么处理?

  • 可以在支付结果异步回调中,调用退款接口把这一步支付退给用户

  • 可以在平台的订单超时的业务中,调用支付宝的取消订单接口,用户就支付不了了。

  • 5.如果支付宝的N次异步通知都通知失败了(掉单),你平台怎么处理?

  • 根据支付单号调用支付宝接口去查询支付状态就可以了

  • 6.你们平台使用了支付宝的哪些接口?

  • 支付接口

  • 查询支付状态接口

  • 取消订单接口

  • 退款接口

  • 对账接口

  • 7.你们这个秒杀怎么防刷

  • 地址隐藏&地址加密 + 秒杀码

  • Nginx做防刷:ip/1次/1s ; userId/1次/1s ; 秒杀防重

  • 黑名单 :从多方面收集用户的信息,加入黑名单

  • 212.你知道BIO,NIO,AIO么?讲一下你的理解

    BIO (Blocking I/O):同步阻塞I/O 模式,以流的方式处理数据,数据的读取写入必须阻塞在一个线程内等待其完成。适用于连接数目比较小且固定的架构

    NIO (New I/O):同时支持阻塞与非阻塞模式,以块的方式处理数据,适用于连接数目多且连接比较短(轻操作)的架构,比如聊天器

    AIO ( Asynchronous I/O):异步非阻塞I/O 模型,适用于连接数目多且连接比较长(重操作)的架构

    213.如何提高接口的qps

    qps是吞吐量

    一方面:提高并发数

    1.多线程,尽量用线程池 (线程个数:CPU核数 / (1 - 阻塞系数(IO密集型接近1,计算密集型接近0)))

    2.适当调整连接数(Tomcat,Redis,Mysql等连接数)

    3.集群

    二方面:提高接口响应速度

    1.减少和数据库交互,使用Redis代替

    2.使用异步方案,比如MQ

    3.使用并发编程,多个线程同时工作

    4.减少服务的调用链

    5.实在要连数据库,考虑数据库优化

    1.什么是CAP理论 , 哪些技术用到AP,哪些用到CP

    CAP理论指的是,在一个分布式系统中,一致性,可用性,分区容错性,三个要素最多只能同时实现两点。

    分区容错性是分布式系统的内在要求,因此我们通常会在一致性和可用性之间做取舍。

    满足CP,也就是满足一致性和容错性,舍弃可用性,如果系统允许有段时间失效就可以考虑。常见的如Redis,Nacos,ZooKeeper

    满足AP,也就是满足可用性和容错性,舍弃一致性,如果系统允许出现短暂时间的不一致可以考虑。常见的如MySQL,Eureka

    2.什么是强一致性和最终一致性

    对于关系型数据库,要求更新过的数据能实时的被后续的访问看到,就是强一致性

    如果能容忍后续的请求短时间内访问不到,则是弱一致性

    如果经过一段时间后要求能访问到更新后的数据,则是最终一致性

    通常情况下我们在选择了AP模式,牺牲了强一致性,会采用最终一致性

    3.什么是base理论

    base指的是基本可用,软状态,最终一致性。它是对CAP中的AP的扩展,意思是说当出现故障部分服务不可用时,要保证核心功能可用,允许在一段时间内数据不一致,但最终要保证一致性。满足base理论的事务也叫柔性事务

    4.Mysql主从解决什么问题,不能解决什么问题?

    MySQL主从同步,主负责写,从负责读,使用一主多从,能减轻读的压力

    但是这不能解决写的压力和主库的单点故障,如果主库的写并发高,可以做成多个主库

    5.MySql主从复制原理?

    主要依靠binlog来实现的,它记录的是所有的DDL,DML,TCL操作

    当主库的数据发生改变时,会将改变记录保存到binlog中

    主库新开一个线程将binlog内容发送到从库

    从库会发起一个I/O线程请求主库的binlog,并保存到中继日志中

    从库新开一个SQL线程,读取中继日志并解析成具体操作,从而将主库更新的内容写到了从库中

    6.MySql主从配置步骤?

    安装mySQL主从客户端,并配置my.ini

    主库需要配置授权从库使用的账号和权限,启动后可以通过show 主库名 status查看状态,我们需要记录File和Position的值,File是对应的binlog文件名,position是当前同步数据的最新行

    从库需要配置主库链接信息,包括账号密码和binlog文件名和最新行,然后启动。通过show 从库名 status 检查同步状态,Slave_IO_Running 和 Slave_SQL_Running 的值都为YES,说明大功告成了

    7.什么是垂直分表,垂直分库,水平分表,水平分库

    垂直分表,可以理解为按列分表,如果一个表的字段太多了,可以按照使用频率分成不同的表,优化查询性能。比如商品表可以分为商品类型表,商品详情表,商品促销表等等

    垂直分库,为了减轻单个数据库压力,我们可以按照业务类型,拆分成多个数据库,比如分布式架构,不同的模块可以有不同的数据库

    水平分表,可以理解为按行分表,如果一个表的数据有千万行,查询性能太低,可以拆分成10张小表,每张表保存一百万行数据

    水平分库,我们做了水平分表后,表数量太多了也会影响数据库查询效率,我们可以将这些表分到多个数据库中

    8.分库分表后会出现哪些问题?怎么解决

    会产生分布式事务,以前本地事务就能结局的问题现在要用上Seata分布式事务

    垂直分库后跨库查询会导致一个查询结果来源于两个库,可能要用到多线程调用多个库查询

    水平分库后一个分页查询的某一页可能来自两个库,可以将两个库的数据合并之后再执行SQL

    水平分表后不同的表出现主键重复,可以通过雪花算法来解决

    两个库都用到同一个表,那这个公共表的维护可能要用到MySQL主从同步

    9.你们公司使用的是什么技术来水平分表?还可以有什么技术?有什么区别?

    使用的是sharding-jdbc来实现的,它是由java开发的关系型数据库中间件,读写分离,分库分表操作简单

    TDDL,淘宝业务框架,复杂而且分库分表的部分还没有开源

    Mycat,要安装额外的环境,不稳定用起来复杂

    MySQL官方提供的中间件,不支持大数据量的分不分表,性能较差

    10.你们使用什么规则来分库分表的?还有哪些规则?

    垂直分库,按照业务进行垂直分库,比如课程表和用户表放到不同数据库

    垂直分表,把多字段表拆分少量字段表,比如将课程表分为课程类型表,课程详情表,课程促销表等

    水平分表,把海量数据表拆分为多个小表

    把商品业务进行水平分库,可以对水平分库后每一个数据库服务器进行集群

    11.你说一下数据量从 100 到 1000万 到 上亿,你按照什么样的顺序去优化你的数据库?

    首先应该考虑垂直分库,不同的业务使用不同的数据库

    然后进行垂直分表,按照使用频率把字段多的表拆分成若干个表

    对经常查询的列建立索引,提高查询效率

    设计冗余字段,减少join表的次数

    SQL优化,比如尽量使用索引查询

    对热点数据应该考虑做缓存,比如首页展示汇总数据

    从海量数据中查询数据应该考虑用全文检索

    如果查询并发高,可以对mySQL做集群

    如果数据量实在太大了,可以考虑水平分表,

    水平分表后,表数量还是太多了,可以考虑水平分库

    1.Mysql的集群有哪些模式?

    一主一从

    一主多从

    双主

    环形多主

    级联同步

    2.单机优化到极致了,可以怎么优化?

    可以考虑做集群,比如一主多从模式,然后对应用做读写分离

    3.多机优化有哪些方式?

    分表,分库,主从同步

    4.解释一下分库分表的含义?

    垂直分表,可以理解为按列分表,如果一个表的字段太多了,可以按照使用频率分成不同的表,优化查询性能。比如商品表可以分为商品类型表,商品详情表,商品促销表等等

    垂直分库,为了减轻单个数据库压力,我们可以按照业务类型,拆分成多个数据库,比如分布式架构,不同的模块可以有不同的数据库

    水平分表,可以理解为按行分表,如果一个表的数据有千万行,查询性能太低,可以拆分成10张小表,每张表保存一百万行数据

    水平分库,我们做了水平分表后,表数量太多了也会影响数据库查询效率,我们可以将这些表分到多个数据库中

    5.水平分表有哪些分表规则?

    按照区间范围分表,比如把用户按照年龄分为新生代表,青年代表,老年代表

    按照时间分表,比如按照年来分表,比如登录日志,分成今年的表,去年的表。。

    hash分表,通过将某一列的值比如id,通过一定的hash算法来算出对应那张表

    雪花算法,通过雪花算法生成id,根据id来算出对应那张表

    8.能简单说一下你怎么使用shardingjdbc做读写分离的嘛

    首先导入相关的依赖

    然后在配置文件中配置datasource,包括主从数据库的名字,主从数据库的连接信息,配置负载均衡

    项目中就可以正常使用datasource了,自动做读写分离

    9.能简单说一下你怎么使用shardingjdbc做读分库分表的嘛

    首先,要改造数据库,比如水平分表,水平分库

    在配置文件中,需要做如下配置

    datasource名字,多个数据源就配多个datasource

    分库策略,比如按照哪一列分库,分库规则

    分表策略,比如哪些库下面的哪些表,按照那一列分表,分表规则

    配置公共的表

    然后项目中就可以正常使用了

    10.Redis内存不够了,怎么办?

    第一,可以加钱换大内存的云服务器,或者加内存条

    第二,可以做集群,比如Clurse集群

    第三,使用淘汰策略,比如淘汰一些过期的数据

    11.Redis的主从有什么优点,和缺点?

    优点是读写分离,分担了读的压力,同时能起到备份作用,防止数据丢失

    缺点是不能分担写的压力,主的单点故障没有解决,存储没有得到扩容

    12.解释一下Redis的哨兵模式。哨兵的不足?

    当主服务器中断服务后,可以将一个从服务器升级为主服务器 ,以便继续提供服务

    哨兵就是用来监控主从服务器,实现故障恢复功能的。它会不断的检查主服务器和从服务器的健康状态,当某个服务器出现问题时,可以向管理员发起通知。如果主服务器不可用时,会自动选择一个从服务器作为新的主服务器,并让其他的从服务器从新的主服务器复制数据

    哨兵也是主从模式,没有解决写的压力,只减轻了读的压力,而且存储也得不到扩容

    13.Redis的cluster集群怎么存储数据的?

    Redis Cluster集群采用哈希槽 (hash slot)的方式来分配的。它默认分配了16384个槽位,当我们set一个key 时,会用CRC16算法得到所属的槽位,然后将这个key 分到对应区间的节点上

    14.什么情况下Redis集群不可用?

    Redis Cluster有一个容错机制,如果半数以上的主节点与故障节点通信都超时了,就会认为该节点故障了,自动触发故障转移操作,故障节点对应的从节点升级为主节点。

    但是如果某个主节点挂了,又没有从节点可以使用,那么整个Redis集群就不可用了、

    15.Redis五大基本存储结构

    Redis存储形式是键值对,支持value形式包括

    String

    List

    Set

    ZSet

    Hash

    16.Redis存储结构底层有没有了解?什么是SDS

    简单动态字符串,是Redis自己封装的字符串结构。它记录了字节数组buf,字节数组中用到的字节数len,以及未使用的字节数free。

    为了解决二进制安全问题,定义了len来表示已有字符串长度

    为了防止缓冲区溢出,在分配内存的时候做了预留空间free

    内存惰性释放,多余的内存加入free做预留,优化了内存频繁分配

    针对不同的String长度定制了不同的SDS结构

    SDS就是动态字符串

    17.Redis如何模拟队列和栈,用什么命令

    list控制同一边进,同一边出就是栈

    list控制一边进,另一边出就是队列

    18.Redis存储单个对象怎么存,存储对象集合怎么存

    单个对象可以使用String,也可以使用hash

    集合对象可以使用hash,以便可以快速的通过field来取值

    19.你们Redis用来做什么?使用的什么结构?

    登录信息login,使用的是String结构存储

    手机验证码code,使用的是String结构

    课程分类course_type ,使用的是String结构

    购物车保存,使用的是Hash结构

    20.统计全国高考前20名用什么?

    Zrevrangebyscore

    21.从100个VIP用户中随机抽取5名怎么做?

    Srandmember

    1.你们用什么工具监控JVM

    jmap

    2.JVM类加载流程

    loading加载:class文件从磁盘加载到内存中

    linking:

    1.verification验证:校验class文件,包括字节码验证,元数据验证,符号引用验证等等

    2.preparation准备:静态变量赋默认值,只有final会赋初始值

    3.resolution解析:常量池中符号引用,转换成直接访问的地址

    initializing初始化:静态变量赋初始值

    3.JVM类加载器有几种类型,分别加载什么东西,用到什么设计模式?

    启动类加载器,加载lib下的类

    扩展类加载器,加载libext下的类

    应用程序类加载器,加载Classpath下的类

    自定义类加载器

    用到了模板模式

    4.JVM组成,以及他们的作用

    类加载器子系统:使用双亲委派模式来加载类

    运行时数据区:

    堆:存放对象的区域,所有线程共享

    虚拟机栈:对应一个方法,线程私有的,存放局部变量表,操作数栈,动态链接等等

    本地方法栈:对应的是本地方法,在hotspot中虚拟机栈和本地方法栈是合为一体的

    程序计数器:确定指令的执行顺序

    方法区:存放虚拟机加载的类的信息,常量,静态变量等等,JDK1.8后,改为元空间

    执行引擎:

    即时编译器,用来将热点代码编译成机器码(编译执行)

    垃圾收集,将没用的对象清理掉

    本地方法库:融合不同的编程语言为java所用

    6.在JVM层面,一个线程是如何执行的?

    7.程序内存溢出了,如何定位问题出在哪儿?

    8.垃圾标记算法, 垃圾回收算法

    9.说说分带回收算法,(新生代,老年代)

    10.JVM优化的目的是什么?

    11.堆怎么调,栈怎么调f

  • 转载请注明:文章转载自 www.mshxw.com
    本文地址:https://www.mshxw.com/it/786410.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号