该面试题的答案不保证一定对
一、Java基础
1、多线程开发的时候,有哪些并发的容器?或者说哪些是线程安全的?
StringBuffer、List中的Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、hashtable、Atomicxxx-包装类的线程安全类、Concurrentxxx
2、hashmap的原理
通过 HashSet 存储对象,会根据对象的 hashCode() 生成 hash 值,根据 hash 生成索引,根据索引找到hash 表中的位置;判断位置上是否有对象,没有对象直接插入,有对象则通过 equals() 判断内容是否相同,如果相同则不插入,不同则根据链表往下检索,直到重复或者链表没有下一个对象,准备插入最后的时候判断链表是否超过 8 个对象,当链表超过 8 个对象时,总存储的对象大于等于 64 则把链表转换成红黑树当 hash 表总存储的对象大于负载因子值(0.75),则会进行数组的扩容,扩容为原本的 2 倍。
3、线程池的最大的线程数如何设计
江湖谣传是:
IO 密集型: 2 * CPU核心数
计算密集型: CPU核心数 + 1
但私以为下面这个更可信:
理论公式:cpu核心数 * cpu利用率 * (1+ 线程等待时间 / 线程计算时间)
但是理论公式仅供参考,实际使用还需要不断的细微调节。
4、Java死锁怎么排查?
1、使用 jconsole。这个是 java 自带的简单可视化工具,用来监控和管理 jvm,通过这个可以链接运行中的 java 进程,并且查看到里面的线程信息,包括里面是否有死锁。

2.使用 jstack。通过这个工具去捕获java 中线程堆栈信息,并去检查是否存在死锁。通过 jstack -l 进程id,去查看堆栈跟踪信息。

3、阿里巴巴提供的arthas 工具。通过 thread -b 找出目前阻塞的线程,在通过 thread 线程id 进而分析是否发生死锁。
5、乐观锁和悲观锁的区别
乐观锁:假设并发冲突不会频繁发生,因此处理数据的时候并不会直接锁定数据,而是更新数据的时候判断有没有其他线程修改过数据,如果存在,那么就撤销当前更新操作;如果不存在冲突,就继续执行当前更新操作。
乐观锁适用于读多写少的场景。
悲观锁:假设最坏的情况发生总是认为并发冲突会发生,所以会锁定操作过程中涉及到的数据,避免其他线程操作,例如数据库中行锁表锁就算是悲观锁。java代码中synchronized 关键字或者 lock接口或者实现类,比如ReentrantLock等等来实现悲观锁。
悲观锁适用于写操作频繁的场景。可以保证数据的一致性,但是会牺牲一部分性能。
6、synchronized 和 ReentrantLock 有什么区别?
1.来源不同。synchronized 是java 内置的关键字,用来给对象或者方法加锁,依赖于 jvm 解释器来锁定的。而 ReentrantLock 是 java5 里面新增的类。是 jdk 实现的。
2、释放不同。synchronized 发生异常的时候,会自动释放线程占用的锁,因此不会导致死锁的发生。而ReentrantLock 发生异常的时候如果没有调用 unlock() 释放锁,可能造成死锁,因此使用 ReentrantLock 锁的时候需要在 finally 代码块里面释放锁。
3、锁等待时是否可中断不一样。synchronized 是不可中断锁,等待锁的线程会一直等待不能中断。而ReentrantLock 就可以中断等待,等待锁的线程能够响应中断,从而停止等待。
4、公平性不同。synchronized 是非公平锁,线程获取到锁的顺序并不是按照线程加锁的顺序来的,有可能后加锁的线程比先加锁的线程更早获得锁。而ReentrantLock 可以是公平锁也可以是非公平锁,在构造函数里面传入 true 就是公平锁。
如果需要更灵活的控制锁的行为就考虑使用 ReentrantLock ,如果只是简单同步的需求就考虑synchronized 。
7、线程池拒绝策略有哪些?
2、DiscardPolicy:表示当线程池无法处理新任务的时候,那么新任务会直接丢弃,而且不会给任何通知。这种策略会存在一定风险,可能发生数据丢失。
3、DiscardOldestPolicy:这种策略表示当线程池无法处理新任务的时候,会丢弃队列中等待时间最久的任务,然后将新任务加入到队列里面,然后尝试再次提交。这种方式同样存在数据丢失的风险,但是可以确保新任务得到处理。
4、CallerRunsPolicy:这种策略表示线程池无法处理新任务的时候,新任务会由提交任务的线程自己执行,这种策略不会造成业务损失但是提交业务的线程会被占用,进而导致任务提交速度变慢。
如果内置的四种策略不满足使用,可以通过实现RejectedExecutionHandler 接口来自定义拒绝策略。
8、信号量(Semaphore)和互斥信号量(也叫互斥锁:Mutex)有何区别?
都是用来控制对共享资源访问的。
区别:
1、使用场景。信号量一般用来控制对有限资源的并发访问数量,通过信号量可以指定访问资源的线程数量,达到上限的时候,其他线程就需要等待,这个经典场景就如数据库连接池,线程池,限流等
互斥信号量主要用于保护对临界资源的访问,确保一次只有一个线程可以访问目标资源,经典使用场景如保护数据一致性和防止竞态条件。
2、信号量内部维护了一组虚拟许可,许可数量可以通过构造函数去指定,当线程获取信号量的时候,许可数量就减一,释放信号量的时候,许可数量就加一。而互斥锁确保一次只有一个线程可以访问临界资源,所以就不需要许可数量。
3、信号量可以配置公平模式或者非公平模式,而互斥锁也是一样的,两种模式都支持。具体到 java代码的时候,Semaphore 和 ReentrantLock 分别用来实现信号量和互斥信号量。Semaphore提供了计数信号量的实现,而ReentrantLock 提供了可重入的互斥锁实现。
9、Hash值相等,对象一定相等吗?
不一定。hahsmap 底层是 数组+链表+红黑树,链表+红黑树就是为了解决 hash 冲突问题,hash冲突就是不同对象产生了同样的hash 值,解决办法很多种,hashmap使用了拉链法,也就是不同对象产生相同hash 值把这些对象组织成一个链表,挂在数组上面,达到一定条件会转成红黑树用来加快搜索效率。
判断两对象是否相等还需要调用 equals()方法做进一步判断。
10、线程池有哪些队列
2、LinkedBlockingQueue:基于链表结构的阻塞队列,同样是先进先出原则排序,但是容量可选不指定容量就是Integer.Max_VAULE ,也就是2的32次方-1 ,这种队列更适用于任务数量多的情况,可以有效缓冲大量任务。
3、SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待相应的删除操作,反之亦然。这种可以保证任务不会被拒绝,但是可能导致线程饥饿,静态工厂方法Executors.newCachedThreadPool 就是使用了这种队列。
4、PriorityBlockingQueue:基于优先级堆的无界阻塞队列,可以按照元素优先级进行出队操作,意味着优先级高的任务会优先被执行。
5、DelayQueue:延迟队列,内部元素必须实现Delayed 接口,只有在延迟期条件满足的时候才会取出元素去执行。
11、线程池有哪些状态?
问的是线程池状态不是线程状态,不要搞混!

1、running:最正常的状态,初始状态。可接受新任务,处理等待队列里面的任务。
2、shutdown:这个状态下不接受新任务提交,会继续处理等待队列里面的任务。
3、stop:这个状态下不仅不接受新任务提交,而且也不再处理等待队列里面的任务,同时还会中断正在执行任务的线程。
4、tidying:所有任务已经完成并且 ctl 记录任务数为 0 的时候,线程池会进入到此状态,这个状态下会执行钩子方法 terminated() 。
5、terminated:线程池彻底终止。
12、Java有哪些类加载器?
主要有四种。
1、启动类加载器 Bootstrap Classloader:负责加载jre/lib/rt.jar 里面的类,也就是核心的 java api,比如java.lang 包里面的类。这个加载器用 C++编写。
2、扩展类加载器 Extension Classloader:负责加载 jre/lib/ext 里面jar 包的类。是java.lang.ClassLoader 的子类,主要用于加载java的扩展库。
3、系统类加载器 System.Classloader(也被称为应用程序类加载器Application Classloader):负责加载 classpath 下或者java命令指定路径下的类,也是 java.lang.ClassLoader 的子类,这个是最常用的。
4、自定义类加载器 custom Classloader :通过继承 classloader 类来创建自定义类加载器,这样可以实现自己的类的加载方式。
13、什么是双亲委派机制?
这是个类加载的常见问题,java存在哪些类加载器看上个问题。
双亲委派也就是当需要加载一个 class 的时候,首先是AppClassLoader,但是不会自己去加载这个类,而是把这个类加载请求委派给父类加载器 ExtClassLoader 去完成,接下来就到 ExtClassLoader 他自己也不会去加载这个类,而是把这个类加载请求委派给 BootStrapClassLoader 去完成,接着到 BootStrapClassLoader ,因为BootStrapClassLoader 没有parent了 ,所以会自己去加载,会到自己的加载目录里面去找这个类,找到了就加载,没找到就通过ExtClassLoader 来加载,ExtClassLoader 也是去加载目录里面加载,找到了就加载,没找到就使用 AppClassLoader 加载,如果 AppClassLoader 也没有加载到,就会抛出一个 ClassNotFountException 异常,这个就是双亲委派。
2、安全性的考虑。双休委派机制,有利于维护java核心类库的一个安全性,因为Java的核心类库是由启动类加载器加载的,那么其他类加载器我没有办法加载或者修改这些核心类库,这样就可以防止恶意代码的话,通过自定义类加载器来篡改核心类库,进而增强 Java运营环境的安全性。
3、模块化开发的一个支持。在实际应用中的话,我们可能需要在一个应用程序中使用多个第三方库,而这些库之间可能存在同名的类,那么通过创新委派机制不同的类加载器只会加载自己的类这样就可以避免类名称的一个冲突的问题,然后使得这个模块化开发也更加便捷。
14、什么是CAS?
cas全称 compare-and-swap ,翻译就是对比和交换,cas是java 中用于实现无锁数据结构的一种原子操作,cas操作需要输入两个数值,一个旧值也就是期望值,还有一个是新值,操作的时候先比较旧值,有无发生变化,如果没有就交换新值,发生变化就不交换,因为cas操作是原子性的,所以多线程并发使用cas更新数据的时候,就不需要加锁,cas实现是基于硬件平台汇编指令,cas其实是靠硬件来实现的,jvm只是封装了汇编的调用,像AtomicInteger 类就是使用了封装后的接口,在大多数处理器下这种操作比直接加锁开销更小,性能更高,像 juc atomic包下的类都是基于cas 实现的。cas其实就是乐观锁的一种思想落地。
15、什么是ABA问题,怎么解决?
多线程环境下相比直接加锁,cas性能更好,但是也有问题,最主要问题就是aba问题,cas操作值的时候需要检查值有没有发生变化,aba问题也就是一个值是a变成b又变成a,使用cas进行检查的时候,看起来没有变化,但实际已经变化了两次;要解决这个问题可以通过加版本号来解决,直接在变量前面加上版本号,每次变量更新版本号+1,aba就会变成 1a 2b 3a,这样就能区分值的变化。
从 jdk1.5开始,juc atomic包里面提供了一个叫 AtomicStampedReference 的类来解决aba问题,这个类本质上也是通过版本号来解决问题,内部维护了一个叫 pair 的数据结构,用 volatile 来修饰确保可见性,这个 pair将数据对象和版本号打包到一起,进行比较,通过这种方式解决aba问题。
16、如何确保Bean的加载顺序?
是初始化顺序而不是执行的先后顺序,如果是执行的先后顺序,可以通过 @Order 注解控制相同类型bean 的执行顺序,或者实现Ordered接口。
那么如何调整bean 的加载顺序,确保先加载 b 后加载 a呢?
1、使用 @DependsOn 注解,比如:

2、利用 BeanFactoryPostProcessor 处理器,因为这个处理器在其他的 bean 初始化之前就被触发了,

所以可以在这个处理器里面手动调用 getBean() 方法,把 bean 初始化提前:

17、缓存预热有哪些方案?
主要考察对系统启动是否熟悉,能否找到系统启动过程中预留的钩子函数。
1、SpringBoot预留的两个系统启动任务CommandLineRunner 和 ApplicationRunner :
CommandLineRunner :

2、事件监听机制:SpringBoot启动过程中,不同阶段会发布相应的事件,通过对这些事件的监听,就可完成缓存预热:

还有 bean 初始化过程中预留的钩子函数完成预热:
1.自定义 bean 实现 InitializingBean 接口 并且重写 afterPropertiesSet 方法:

也可利用 @PostConstruct注解在bean 初始化过程中执行指定的方法进行缓存预热:

18、FactoryBean 和 BeanFactory 有什么区别
BeanFactory 是Spring 框架的基础容器接口,也就是常说的 Bean 工厂,用来生产 Bean 对象,本质上就是 Ioc 容器,负责管理和实例化应用程序里面的 bean 对象,平时使用的 getBean 方法也是该工厂提供。
二、Jvm相关
1、JVM垃圾回收器有哪些?
Serial收集器(新生代、复制算法)
ParNew收集器(新生代、复制算法)
Parallel Scavenge收集器(新生代、复制算法)
Serial Old收集器(老年代、整理算法)
Parallel Old收集器(老年代、整理算法)
CMS收集器(Concurrent Mark Sweep)
G1回收器(Garbage First)
2、JVM的优化配置哪些参数
参考网址:https://blog.csdn.net/wyn_365/article/details/
-Xms10g :JVM启动时申请的初始堆内存值
-Xmx20G :JVM可申请的最大堆内存值
-Xmn3g : 新生代大小,一般设置为堆空间的1/3 1/4左右,新生代大则老年代小
-Xss :Java每个线程的栈内存大小
-XX:PermSize :持久代(方法区)的初始内存大小
-XX:MaxPermSize : 持久代(方法区)的最大内存大小
-XX:SurvivorRatio : 设置新生代eden空间和from/to空间的比例关系,关系(eden/from=eden/to)
-XX:NewRatio : 设置新生代和老年代的比例老年代/新生代
3、JVM怎么判断对象已经死去(垃圾回收)?
也就是判断是否可以被垃圾回收,有两种方式。引用计数器法和可达性分析算法(目前jvm主流)。
1.引用计数算法:给对象添加一个引用计数器,每当有一个地方引用了这个对象的时候计数器就加1,引用失效就减一,任何时刻只要计数器为0就表示对象不再被使用可以回收了。但是这种方式有致命问题,很难处理循环引用的情况比如 a引用b,b引用a,然后 ab没有被其他对象引用,这时候按理说应该被回收了,但是因为 ab 引用计数器都不为 0 所以无法回收。
2、可达性分析算法:基本思路是通过一系列称作 GC Roots的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径叫做引用链,当一个对象到任何 GC Roots都没有引用链的时候,用图论的话来说就是 GC Roots 到这个对象不可达,这个说明对象是不可用的,可以回收了。
java中可以作为 GC Roots 对象主要有6种:
虚拟机栈里面引用的对象、
方法区里面类静态属性引用的对象、
方法区里面 final 常量引用的对象、
本地方法栈里面 JNI 所引用的对象、
java虚拟机内部的引用(比如基本数据类型对应的Class对象,一些常驻的异常对象如空指针等)、
被同步锁 synchronized 持有的对象,
这六种可以作为 GC Roots。
JVM 回收的时候还需要判断对象是否真正死去,因为可能存在不可达但是仍然有必要存活的情况,比如对象可能被 finalize 的方法复活(注意这个 finalize 方法只会被调用一次):

三、Spring相关
1、spring里面除了单例,工厂,代理模式,哪些功能还用到了什么模式?
a、工厂设计模式(Spring 框架中 BeanFactory 和 ApplicationContext 类使用工厂模式创建 Bean 对象)
(1)BeanFactory:延迟注入,即使用到某个Bean时才会进行注入,和 ApplicationContext 相比会占用更少的内存,程序启动速度更快。
(2)ApplicationContext:容器启动时就创建所有的Bean,和 BeanFactory 相比 ,BeanFactory 仅提供了最基本的依赖注入支持 . ApplicationContext 扩展了 BeanFactory, 除了 BeanFactory 的功能外还包含其余更多的功能,通常使用ApplicationContext 创建 Bean。
(3)ApplicationContext的三个实现类:
ClassPathXmlApplication: 将上下文文件作为类路径资源。
FileSystemXmlApplication: 从文件系统中的 XML 文件中载入上下文定义信息。
XmlWebApplicationContext: 从Web系统中的 XML 文件中载入上下文定义信息。
b、单例设计模式(Spring 中的 Bean 的作用域默认就是单例 Singleton 的)
Spring底层通过ConcurrentHashMap实现单例注册表来实现单例模式。
c、模板方法模式
模板方法模式是一种行为型模式,基于继承的代码复用、定义一个操作的算法骨架,将一些实现步骤延迟到子类中、模板方法使得子类可以不改变一个算法结构的情况下即可重新定义算法的某些特定步骤的实现方式
Spring 中以 Template 结尾的类,比如 jdbcTemplate 等,都是使用了模板方法模式。
1、通常情况下,都是使用继承来实现模板模式。
2、在 Spring 中,使用了 Callback 与模板方法相结合的方式,既达到了代码复用的效果,又增加了系统的灵活性
d、观察者模式
观察者模式表示的是一种对象和对象之间具有依赖关系,当一个对象发生改变,依赖于这个对象的对象也会发生改变。
Spring事件驱动模型就是基于观察者模式实现的,包含三种角色:事件Event角色、事件监听者Listener角色、事件发布者Publisher角色
(1)事件角色Event:
讯享网Spring的事件发布流程:
定义一个事件(实现一个继承自ApplicationEvent的事件类,并写出相应的构造函数)——>
定义一个事件监听者(实现ApplicationListener接口、重写onApplicationEvent()方法)——>
使用事件发布者发布消息(使用ApplicationEventPublisher的publishEvent() 方法、重写publishEvent() 方法发布消息)
e、AOP中的适配器模式
适配器模式:将一个接口转换为调用方需要的接口,适配器使得接口不兼容的类之间可以一起工作.适配器又被称为包装器Wrapper
Spring AOP中的增强和通知Advice使用了适配器模式,接口是AdvisorAdapter。
每个通知Advice都有对应的拦截器:
BeforeAdvice - MethodBeforeAdviceInterceptor
AfterAdvice - MethodAfterAdviceInterceptor
AfterReturningAdvice - MethodAfterReturningAdviceInterceptor
Spring中预定义的通知要通过对应的适配器,适配成为MethodInterceptor接口类型的对象
f、Spring MVC中的适配器模式
Spring MVC中 ,DispatchServlet 根据请求信息调用 HanlderMapping, 解析请求对应的 Handler, 解析到对应的Handler 后,开始由 HandlerAdapter 适配器进行处理。
HandlerAdapter 作为期望接口,具体的适配器实现类对具体目标类进行适配 。controller 作为需要适配的类。
通过使用适配器 AdapterHandler 可以对 Spring MVC 中众多类型的 Controller 通过不同的方法对请求进行处理。
g、装饰器模式
装饰器模式:动态地给对象添加一些额外的属性或者行为,和继承相比,装饰器模式更加灵活
使用场景:当需要修改原有的功能,但是不想直接修改原有的代码,就可以设计一个装饰器 Decorator 类在原有的代码的外面,这样可以在不修改原有的类的基础上扩展新的功能。
2、spring中事务没有回滚(或者说失效)是什么原因造成的?
a.方法访问权限不是 public(因为如果你的方法不是public,就没有办法去给你生成动态代理,那事务当然就没有办法生效) 。
b.方法被 final 修饰,这种情况代理类无法重写该方法,无法添加事务功能、static 也是。
c.方法自调用(同一个类里面的方法调用:因为Spring里边是通过动态代理去处理的@Transactional注解的,A方法直接调用B方法隐藏的含义就是A调用了当前对象的B方法,那么当前对象又不是代理对象,他就是个普通对象,所以事务就不会生效。)
d.未被 Spring 管理(事务注解不生效,spring没办法生成动态代理)
e.多线程调用:可能存在两个方法不在同一个线程中、获取到的数据库链接不一样,从而是两个不同的事务。
f.不正确的异常捕获会导致事务失效(如果方法上面添加了@Transactional注解,同时又在方法里面去捕获了异常,那么事务注解就不会生效,因为事务注解本质上是通过什么Aop去处理的,那么异常捕获之后 AOP就感知不到你方法出错了,那么失误当然也就不会生效了。)
g.数据库不支持事务
h.rollbackFor可能配置有问题(这个属性是用来去配置有哪些异常需要回滚的,默认情况下是RuntimeException 这种异常需要回滚。)
3、Bean的初始化流程


4、Spring如何解决循环依赖的
循环依赖就是:多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A。
a、三级缓存名词解释
第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象。
第二级缓存:earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存:Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂。
b、解决循环依赖具体说明
(1)A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B。
(2)B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。
(3)B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A放到一级缓存中。
c、为什么使用三级缓存?二级缓存能解决循环依赖吗?
如果没有 AOP 代理,二级缓存可以解决问题,但是有 AOP 代理的情况下,只用二级缓存就意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则,Spring 在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。
d、B 中提前注入了一个没有经过初始化的 A 类型对象不会有问题吗?
虽然在创建 B 时会提前给 B 注入了一个还未初始化的 A 对象,但是在创建 A 的流程中一直使用的是注入到 B 中的 A 对象的引用,之后会根据这个引用对 A 进行初始化,所以这是没有问题的。
5、AOP 的增强过程


6、BeanFactory 和 ApplicationContext 的区别
BeanFactory 和 ApplicationContext 都是接口,并且 ApplicationContext 是 BeanFactory 的子接口。
BeanFactory 是 Spring 中最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。而ApplicationContext 是 Spring 的一个更高级的容器,提供了更多的有用的功能。
ApplicationContext 提供的额外的功能:国际化的功能、消息发送、响应机制、统一加载资源的功能、强大的事件机制、对 Web 应用的支持等等。
加载方式的区别:BeanFactory 采用的是延迟加载的形式来注入 Bean;ApplicationContext 则相反的,它是在 IOC 启动时就一次性创建所有的 Bean,好处是可以马上发现 Spring 配置文件中的错误,坏处是造成浪费。
7、@Resource和@Autowired的区别(五个)
1、装配方式不同:
@Resource 默认是按照名称(id)来装配注入的,只有当找不到与名称匹配的 bean 才会按照类型(class)来装配注入。(指定了name就先按照name,指定了类型就按照类型(type),如果两个都指定,那么就查找两个都同时满足的bean。如果都没有指定,就先按照name,再按照type)
@Autowired 默认是按照类型装配注入的,如果找到多个bean,继续以变量名作为 name,根据name 去查找这个 bean,此时若还是找到多个 bean,这时才会报异常。如果想按照名称来注入,则需要结合 @Qualifier 一起使用。
2、优先级:@Autowired支持优先级配置,可以添加@Primary注解标记bean的优先级。这个能力@Resource不支持。
3、来源不同:@Resource 遵循JSR-250规范,是由 J2EE(JAkArtAEE) 提供;而 @Autowired 是由 Spring 框架提供,在 Spring-bean依赖下面;@Resource 理论上可以搭配任何框架使用。
4、使用范围不一样:@Autowired可以用在很多地方,比如构造器,方法,方法的参数,成员变量,注解都能使用。@Resource只能用在 类,方法,成员变量上面。
5、参数不一样:@Autowired只有一个参数required,表示是否开启自动注入。@Resource有7个参数,最为熟悉的就是 name 和 type 这两个参数。
8、Spring中的bean是否存在线程安全
关于bean线程安全的问题,首先从作用域考虑,非单例的bean不存在安全问题。
其次从bean的状态考虑,bean的状态分为两种,第一种是无状态(一般只有查询,不涉及修改);第二种就是有状态的bean(指的是多线程环境,操作bean的成员变量的时候,既去查询他,又去修改他),有状态的bean就会存在线程安全问题。
如何解决 spring 中 bean 的线程安全:
1.作用域方面入手(改成prototype)
2.避免 bean 里面定义需要修改的成员变量
3.使用 Threadlocal 保存 Bean 的成员变量。
9、Spring中 Bean 的生命周期
也就是从创建到销毁的整个过程。
五个阶段
1:bean的实例化。 根据开发者的配置信息,通过反射或者工厂方法创建 bean 的实例,
2:属性赋值。 实例化后就给bean 的成员变量注入属性值。比如@Autowired 注解,@Value 注解等等。这个过程也就是依赖注入。
3:初始化。 属性赋值完成后,Spring容器会调用 bean 的一些初始化方法,比如 init-method,afterPropertiesSet,Aware接口的一些回调,BeanPostProcessor的一些前置处理器,后置处理器方法等等。
4:使用阶段。 初始化完成后bean已经可以使用了。
5:销毁。 当程序不需要使用bean 的时候就会被Spring容器负责销毁,这个过程可以实现DisposableBean这个接口或者配置文件里面配置 destory-method 方法来实现。
10、Spring中 bean 的作用域
下面四个跟 WEB 环境相关:
3、request:在一个 http 请求里面一个 bean 只会被实例化一次。意味着不同的 http 请求可能是多例的。
4、session会话:在一个 http 会话里面,这个 bean 只会被实例化一次。不同会话 bean 可能是多例的。
5、application:在一个 servletContext 里面,bean 只会被实例化一次。在一个 web 应用的生命周期里面,这个 bean 是单例的。
6、websocket:在一个 webSocket 会话里面,bean 只会被实例化一次。
四、SpringBoot相关
1、Spring Boot 怎么完成自动化配置的?
结论: SpringBoot不需要写配置文件的原因是,SpringBoot所有配置都是在启动的时候进行扫描并加载,SpringBoot的所有自动配置类都在Spring.factories里面,但是不一定会生效,生效前要判断条件是否成立,只要导入了对应的start,就有对应的启动器,有了启动器就能帮我们进行自动配置类
以前我们需要自己配置的东西,自动配置类帮我们做了:
1、SpringBoot在启动的时候从类路径下的 META-INF/spring.factories 中获取 EnableAutoConfiguration 指定的值。
2、将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
3、整个J2EE的整体解决方案和自动配置都在 springboot-autoconfigure 的 jar 包中;
4、它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
Spring的自动装配原理:
Spring Boot启动的时候会通过 @EnableAutoConfiguration 注解找到 META-INF/spring.factories 配置文件中的所有自动配置类,并对其进行加载,这些自动配置类都是以 AutoConfiguration 结尾来命名的,它实际上就是一个 JavaConfig 形式的 Spring 容器配置类,通过 @Bean 导入到 Spring 容器中,以 Properties 结尾命名的类是和配置文件进行绑定的。它能通过这些以 Properties 结尾命名的类中取得在全局配置文件中配置的属性,我们可以通过修改配置文件对应的属性来修改自动配置的默认值,来完成自定义配置。
额外说一句:
run 方法的作用:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
2、SpringBoot 初始化时的启动扩展点
a、监听容器刷新完成扩展点 ApplicationListener
容器刷新成功意味着所有的 Bean 初始化已经完成,当容器刷新之后 Spring 将会调用容器内所有实现了ApplicationListener 的 Bean 的 onApplicationEvent 方法,应用程序可以以此达到监听容器初始化完成事件的目的。
易错的点这个扩展点用在 web 容器中的时候需要额外注意,在 web 项目中(例如 spring mvc),系统会存在两个容器,一个是 root application context,另一个就是我们自己的 context(作为 root application context 的子容器)。如果按照上面这种写法,就会造成 onApplicationEvent 方法被执行两次。
先自定义一个事件,注册一个事件监听器,发布事件,执行单元测试可以看到邮件的地址和内容都被打印出来了。
b、SpringBoot 的 CommandLineRunner 接口
c、@PostConstruct 注解
@PostConstruct 注解一般放在 Bean 的方法上,被 @PostConstruct 修饰的方法会在 Bean 初始化后马上调用。
d、InitializingBean 接口
InitializingBean 的用法基本上与 @PostConstruct 一致,只不过相应的 Bean 需要实现 afterPropertiesSet 方法
e、@Bean 注解中定义初始化方法(销毁方法也一样)
bean 的销毁方法时在容器关闭的时候被调用的,applicationContext.close()。
bean对象的初始化方法调用的时机:
对象创建完成,如果对象中存在一些属性,并且这些属性也都已经赋值了,那么就会调用bean的初始化方法。对于单实例bean来说,在Spring容器创建完成后,Spring容器会自动调用bean的初始化方法;对应多实例bean来说,在每次获取bean对象的时候,调用bean的初始化方法。
bean对象的销毁方法调用的时机:
对于单实例bean来说,在容器关闭的时候,会调用bean的销毁方法;对于多实例bean来说,Spring容器只负责创建bean不会管理这些bean,也就不会自动调用这个bean的销毁方法了。小伙伴只能手动调用多实例bean的销毁方法了。
3、如何优雅关闭 SpringBoot
1、利用 actuator(监控)提供的接口 shutdown 接口,调用这个接口就可以关闭,这个接口很敏感,默认情况下不开启。通过配置文件开启后,通过 /actuator/shutdown 地址调用接口,post请求。
2、调用应用程序的上下文的 close 方法。

这种方案跟第一种一样敏感,需要处理好认证和权限。
3、调用 SpringApplication 里面的 exit 方法,调用这个方法会触发SpringBoot程序,执行关闭的钩子函数,还有bean 销毁的钩子函数。能关闭是因为每一个 SpringApplication 都向JVM 注册一个关闭钩子,这个关闭钩子确保ApplicationContext 在退出的时候能优雅的关闭。
4、直接杀进程kill,这个也是可以正常关闭的。
五、SpringCloud(微服务)相关
1、分布式和集群有什么区别呢?
a、集群是个物理形态,分布式是个工作方式。集群一般是物理集中、统一管理的;一个程序或系统,只要运行在不同的机器上,就可以叫分布式。
b、分布式是相对中心化而来,强调的是任务在多个物理隔离的节点上进行,除了解决部分中心化问题,也倾向于分散负载,但分布式会带来很多的其他问题,最主要的就是一致性。集群就是逻辑上处理同一任务的机器集合。
java基础实战_摸底项目
c、分布式:一个业务分拆多个子业务,部署在不同的服务器上;集群:同一个业务,部署在多个服务器上。
d、集群是解决高可用的;而分布式是解决高性能、高并发的。
2、nacos里面要配置 namespace 和 group id 这两个的作用是什么?
group 是分组,namespace 是命名空间。一般 namespace 区分环境,group区分项目。

3、nacos 的配置变化了是服务器主动推送的还是微服务每次循环去拉取的?
(1)Nacos 采用的是客户端主动拉 pull 模型,应用长轮询(Long Polling)的方式来获取配置数据。
(2)Nacos 获取配置数据的逻辑比较简单,先取本地快照文件中的配置,如果本地文件不存在或者内容为空,则再通过HTTP 请求从远端拉取对应 dataId 配置数据,并保存到本地快照中,请求默认重试3次,超时时间 3s。获取配置有getConfig() 和 getConfigAndSignListener() 这两个接口,但 getConfig() 只是发送普通的 HTTP 请求,而getConfigAndSignListener() 则多了发起长轮询和对dataId数据变更注册监听的操作addTenantListenersWithContent()。
(4)变更通知:
客户端又是如何感知服务端数据已变更呢?
我们还是从头看,NacosConfigService 类的构造器中初始化了一个 ClientWorker,而在 ClientWorker 类的构造器中又启动了一个线程池来轮询 cacheMap。而在 executeConfigListen() 方法中有这么一段逻辑,检查 cacheMap 中 dataId的 CacheData 对象内,MD5 字段与注册的监听 listener 内的l astCallMd5 值,不相同表示配置数据变更则触发safeNotifyListener 方法,发送数据变更通知。safeNotifyListener() 方法单独起线程,向所有对 dataId 注册过监听的客户端推送变更后的数据内容。客户端接收通知,直接实现 receiveConfigInfo() 方法接收回调数据,处理自身业务就可以了。
4、RestTemplate 和 Feign 的区别
讯享网
5、docker怎么解决容器间的通信
docker network 来创建一个桥接网络,在 docker run 的时候将容器指定到新创建的桥接网络中,这样同一桥接网络中的容器就可以通过这个桥接网络互相访问。
六、MySQL 相关
1、mybatis的缓存(一级缓存二级缓存那些)
a. 一级缓存
b.二级缓存
mapper 级别的缓存,同一个 namespace 公用这一个缓存,所以对 SqlSession 是共享的。二级缓存需要我们手动开启。(全局级别)二级缓存是 mapper 级别的缓存,多个 sqlSession 去操作同一个Mapper 的 sql 语句,多个 sqlSession可以共用二级缓存,二级缓存是跨 sqlSession 的。二级缓存是基于映射文件的缓存(namespace),缓存范围比一级缓存更大,不同的 SQLSession 可以访问二级缓存的内容。哪些数据放入二级缓存需要自己指定
c.一级缓存原理
(1)查询数据时,会先到缓存中查询是否有,如果没有则到数据库中查找,找到后存到缓存中。
(2)如果 sqlSession 去执行 commit 操作(插入、更新、删除),清空 sqlSession 中的一级缓存,保证缓存中始终保存的是最新的信息,避免脏读。
(3)两次查询须在同一个 sqlsession 中完成,否则将不会走 mybatis 的一级缓存。在 mybatis 与 spring 进行整合开发时,事务控制在 service 中进行,重复调用两次 servcie 将不会走一级缓存,因为在第二次调用时 session 方法结束,SqlSession 就关闭了
d.二级缓存原理
(1)二级缓存与一级缓存原理相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper ( Namespace ),并且可自定义存储源,如 Ehcache。作用域为 namespance 是指对该 namespance 对应的配置文件中所有的 select 操作结果都缓存,这样不同线程之间就可以共用二级缓存。
e.二级缓存设置对象策略
f.二级缓存注意事项
g.开启二级缓存
通过application.yml配置二级缓存开启:mybatis.configuration.cache-enabled=true
2、mysql数据库的 innodb 和 myisam 的区别?
a、InnoDB支持事务,MyISAM不支持
b、MyISAM适合纯读或者纯写为主,InnoDB适合频繁修改以及涉及到安全性较高的应用
c、InnoDB支持外键,MyISAM不支持
d、从MySQL5.5.5以后,InnoDB是默认引擎
e、InnoDB不支持FULLTEXT类型的索引
g、InnoDB中不保存表的行数,如select count() from table时,InnoDB需要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。但是当count()语句包含where条件时MyISAM也需要扫描整个表。
h、对于自增长的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中可以和其他字段一起建立联合索引。
i、清空整个表时,InnoDB是一行一行的删除,效率非常慢。MyISAM则会重建表。
j、innodb默认表锁,使用索引检索条件时是行锁,而myisam是表锁(每次更新增加删除都会锁住表)。
k、innodb和myisam的索引都是基于b+树,但他们具体实现不一样,innodb的b+树的叶子节点是存放数据的,myisam的b+树的叶子节点是存放指针的。
l、innodb是聚簇索引,必须要有主键,一定会基于主键查询,但是辅助索引就会查询两次,myisam是非聚簇索引,索引和数据是分离的,索引里保存的是数据地址的指针,主键索引和辅助索引是分开的。
3、innodb支持几种索引?
参考网址:https://www.cnblogs.com/aluna/p/15850620.html
主键索引(B+树)、普通索引、唯一索引、全文索引(5.6.4版本以后。char、varchar、text的列才支持)、哈希索引
4、对sql优化的理解
讯享网
5、你说说死锁怎么解决
死锁(DeadLock)指的是两个或两个以上的运算单元(进程、线程或协程),都在等待对方释放资源,但没有一方提起释放资源,从而造成了一种阻塞的现象就称为死锁。
2、请求与保持:一个进程要请求新的资源,但同时对已获得的资源不释放,要等待其他进程释放资源;
3、循环等待:若干进程都要申请资源,但是都对已获得的资源不释放,都要等待其他进程释放资源,若干进程陷入循环等待资源;
4、不可剥夺:进程已获得的资源,在未使用完之前,不能被强行剥夺。
6、resultMap 标签干什么用的?
sql代码的复用,类似于java的继承。
7、一个 sql 查询变得很慢,你会怎么去分析这个 sql
1、检查 SQL 是否走索引,或者检查是否出现索引失效的情况。如果是单条 sql,则使用 explain 进行相关分析。
2、单表数据量数据过多,导致查询瓶颈:可以考虑对表进行切分,或者分库。
3、网络原因或者机器负载过高:可以进行读写分离。
8、什么样的情况下不会走索引
1、用 != 或者 <> 导致索引失效
2、字段编码类型不一致进行关联查询不走索引;如:a表和b表,一个utf8编码,一个utf8mb4编码
3、函数导致的索引失效
4、运算符导致的索引失效
5、OR引起的索引失效。比如:or链接同一个字段走索引,不同就不走索引。或者两个字段都有索引才走索引

比如 name 有索引,email 没有索引。
6、模糊搜索导致的索引失效,如 like的百分号放左边不走索引,或者两边都有。

7、NOT IN、NOT EXISTS导致索引失效
8、is null,is not null也无法使用索引,不走索引!
9、数据类型隐式转换,字符串列与数字直接比较,不走索引
10.可能某张表存在可用的索引,但是数据量少或者索引列选择性不高,mysql优化器可能选择全表扫描,可能判断次数全表扫描更快。
9、mybatis的加载流程是怎样的?
启动程序,通过 springfactoryBuilder 去加载 mybatis 全局配置文件,生成 sqlSessionFactory,通过这个去生成 sqlSession,sqlSession 代表的是一个会话,通过这个去获取 mapper,获取到代理类执行即可找到对应的 mapper.xml文件,扫描 mapper 接口。生成 mapperProxyFactory 生成代理类,再执行具体的增删改查。
10、哪些情况会导致MySQL索引失效?
1、索引列上使用函数或者表达式

2、查询条件中数据类型和索引列中数据类型不匹配的时候,会发生隐式类型转换

比如user_id 是整数,这里用的是字符串。
3、使用 or
11、非聚集索引一定会回表吗?
不一定。
1、比如ab组成复合索引,根据a查询b,这时候不需要回表。
2、如果在非聚集索引中查询的是主键值,就不需要。
12、怎么定位慢SQL?
最常用方法就是开启慢查询日志。在mysql配置文件里面配置。
13、MySQL中有哪些锁?
1、读锁(共享锁,S锁):当一个事务加上读锁,其他事务只能只能对这条数据加读锁,而不能加写锁。直到这条数据所有读锁都释放之后,其他事务才能加写锁进来。
2、写锁(排他锁,X锁):当一个事务为数据加上写锁,其他事务不能对这条数据加任何锁。
3、意向锁:是一种表级锁,可继续细分为意向共享锁和意向排他锁,作用是想获取表锁的时候,不用去遍历表的每一行数据添加什么锁,可以直接判断表是否存在意向锁就可以了。
4、记录锁:是行级锁的实现方式,这种锁允许对数据库中特定的行进行加锁,这种细粒度可以提高并发性能。
5、间隙锁:对索引记录之间的空隙进行锁定,以及对第一个索引记录之前或者最后一个索引记录之后的空隙进行锁定,这种锁机制可以防止其他事务在这些缝隙里面插入新数据,这个锁用来解决幻读问题。
6、Next-key Lock:记录锁+间隙锁。
7、元数据锁:特殊的锁机制,主要用来保护元数据对象,比如表结构,索引,触发器等等的一致性和完整性。多个并发事务对元数据锁对象修改的时候,能够保证只有一个事务可以成功修改。
14、如何防止SQL注入?
使用 PrepareedStatement,最有效方法,因为sql语句是固定的,用户输入只是参数,传递给sql,并不会把用户输入拼接到 sql 语句里面,这样就可以确保用户输入被正确转义,可以防止 sql 注入。
七、Redis 相关
1、redis的数据类型以及你们公司具体的应用
2、redission的分布式锁原理
参考网址:https://www.cnblogs.com/crazymakercircle/p/14731826.html#autoid-h3-5-0-0
watchdog 的具体思路是:加锁时,默认加锁 30秒,每10秒钟检查一次,如果存在就重新设置 过期时间为30秒。
底层主要是通过 lua 脚本去完成原子性的命令操作。
注意:
(1)watchDog 只有在未显示指定加锁时间时才会生效。
(2)lockWatchdogTimeout设定的时间不要太小 ,比如我之前设置的是 100毫秒,由于网络直接导致加锁完后,watchdog去延期时,这个key在redis中已经被删除了。
3、redis为什么这么快(为什么被设计为单线程)?
2、单线程设计避免多线程设计的复杂问题,比如锁,并发控制。
3、避免锁带来的额外性能开销(获取锁释放锁甚至死锁问题)。
4、充分利用了cpu的缓存,cpu通常有多级缓存功能,用来加速数据访问,多线程环境由于不同线程同时访问不同数据,导致缓存的命中率下降,影响性能。
5、易于拓展和部署,单线程设计使得扩展和部署更加简单方便,不需要考虑线程同步和并发控制问题,非常容易实现集群和高可用的特点。
4、redis 是单线程还是多线程(6.0之前以及6.0之后)
redis6.0中的变化导致有些人的一些误解。
6.0之前: 在6.0之前主要是单线程,这个单线程指的是键值对的读写,还有网络 io 的操作,是由一个线程来完成的。redis在处理客户端请求的时候,包括请求数据的读取,数据的解析,命令的执行,内容的返回,这些操作是由一个顺序串行的主线程去处理的。
但是redis 的其他功能,比如持久化,异步删除,集群数据的同步等等,是由额外的线程去处理的,这些额外的线程一般用来处理一些后台任务,防止主线程被阻塞,从而确保redis 的性能还有稳定性。
6.0之后: 6.0开始redis 引入了一个新的多线程处理场景,这个多线程主要处理网络请求处理这块,6.0提出的东西叫 Threaded IO,也就是网络IO处理方面的多线程,引入这东西后在网络请求过程中,redis 会采用多线程的操作,提高网络传输的性格。但是执行命令的核心模块还是 单线程 。
5、Redis内存淘汰策略有哪些?
大概8种,内存空间不足的时候自动清除一部分已有数据。大致分为两大类:
第一类,在设置了过期时间的数据中进行淘汰:
1.volatile-random:从已经设置的过期时间数据中随机选择数据进行淘汰
2.volatile-ttl:优先淘汰更早过期的key
3.volatile-lru:在所有设置了过期时间的key里面,淘汰最长时间没有使用的key——redis3.0之前默认方案
4.volatile-lfu:在所有设置了过期时间的key里面,淘汰最少使用的key——redis4.0后引入的新的淘汰策略
第二类,在所有数据范围内进行淘汰:
1.allkeys-random:随机淘汰任意key
2.allkeys-lru:淘汰整个键值中,最久没有使用的key
3.allkeys–lfu:淘汰整个键值中最少使用的key
特殊策略(默认策略):noevication:当运行内存超过设置的最大内存时,不淘汰任何数据,直接报错。
6、如何发现Redis中的热点Key?
1.通过业务预估,比如今天某个商品做促销,那么这个商品大概率是热点key。
2.客户端收集。操作redis会用到客户端工具,比如 jedis, Spring Data redis,对这些工具进行封装,再去对操作key进行统计。
3.利用 redis monitor,redis cli里面有个命令,叫 monitor 命令,这个命令可以实时查看redis 实例的执行情况,通过分析输出的日志就能找到访问频率比较高的热点 key。
4.redis-stat。这是个实时监控 redis实例的一个工具,这个工具可以展示包括命令的执行次数,内存的使用情况等一系列的指标。
5.redis 的4.0.3版本里面添加了一个 hotkeys 查找特性,可以用过 redis-cli --hotkeys去获取当前keyspace 里面的热点 key,这种方式不需要二次开发,是现成的就可以实现,但是因为要扫描整个 keyspace,所以在实时性上面差点意思。
7、RedLock有哪些优点?
RedLock 是 redis 官方提出的基于 redis 分布式锁的方法。这种方法比原先单节点方法更安全,主要解决了分布式系统中如何保证多个客户端对共享资源互斥访问的问题。
特性和有优点:
1、互斥访问:通过 RedLock 可以确保永远只有一个客户端能拿到锁。从而实现对共享资源的互斥访问。
2、避免死锁:RedLock在设计的时候就考虑了各种可能得异常情况,比如锁定资源的服务崩溃或者网络分区。
3、容错性:只要有一半以上的 redis 节点存活,RedLock 就能正常的提供服务。
RedLock通过从多节点获取锁,解决主节点可能宕机时导致的锁丢失问题。 但是也有多节点挂机导致的性能安全问题,追求可靠性的话也可以用比如Zookeeper实现
8、redis 如何实现延时队列
专业的事情交给专业的工具来做,redis能做,但最好还是交给MQ来做。
通过 zset 来实现延时队列,zset里面有个 score,存储任务的时候 score 设置为 任务的执行时间戳,消费的时候客户端通过 zrangebyscore 的命令去查找和当前时间相符合的任务,找到就执行,没找到就休息一秒继续通过 zrangebyscore 的命令去查找任务,这个过程中如果有涉及原子性的操作通过 lua 脚本去实现。 这种延时队列并没有很大价值,因为没有消息重试机制,也没有ack,消息可靠性得不到保障。
9、Redis怎么实现消息队列
专业的事情交给专业的工具来做,redis能做,但最好还是交给MQ来做。
1、发布/订阅模式
2、redis 中的 list 数据类型,然后使用 lpush 操作实现入队,brpop操作实现出队,这种支持阻塞式获取消息,不过没有消息广播功能,也没有ack机制,
3、redis的 zset 数据类型,利用 zset 的 score 做时间戳,可以实现延迟消息队列(看上题)
4、从 redis5开始新增了一个叫 stream 的数据类型,这是对消息队列的完善,支持消息广播,消息持久化,也可以阻塞式获取消息,也支持并发消费,提供了ack机制,但是受到本身限制没办法做到消息百分百可靠。超过队列长度的消息也没有很好的办法去处理
10、Redis6为什么要引入多线程
是对 redis6之前的单线程操作的补充,核心还是单线程。redis数据放到内存里边,内存的响应时间是 100纳秒左右,对于小数据包 redis服务器可以处理 8W——10W 的qps,对于绝大多数公司已经够用,但是互联网公司动辄上亿的交易量就不够看了,需要更大的 qps。
redis作者发现读写网络系统的调用占用了 redis 的执行期间大部分cpu时间,redis瓶颈主要在于 io 网络消耗,所以redis6开始处理网络请求的时候引入多线程,提高网络的性能。
八、消息队列相关
九、Linux 相关
1、linux查看端口命令
2、linux常用指令
cp
midir
cat xxx | grep “xx” | more
tail -f
kill
docker
sh xx
倒叙日志:tac
等等
3、linux杀死进程的指令、-9跟-15的区别
-9是强制杀死进程,立马停止;-15是通知程序安全干净的退出。
4、线上服务器CPU飙升如何定位问题
四个步骤:
1.使用 top 命令去找到 cpu 里面占用过高的一个进程,获取进程 id:

2.使用 ps -mp 命令找到这个进程下面占用 cpu 比较高的线程 id,这时候拿到的线程 id 是十进制的一个数,不能直接使用,要转成16进制

3.使用 printf 命令把十进制转换成 16 进制,这样一来就知道哪个线程导致 cpu 过高

4.通过 jstack 命令输出线程的运行日志,根据日志的提示检查代码里面的问题。

5、Nginx负载均衡策略有哪些?
2、加权轮询。在轮询的基础上,给不同的后端服务器设置不同的权重,权重越高,能获得更多请求。这种方式可以根据服务器的性能还有负载情况去分配请求,能更好的利用服务器资源。
3、IP Hash。根据客户端的ip地址进行哈希运算,把相同的ip地址请求始终发到同一个后端服务器上,这样可以保证来自同一客户端的请求始终被同一台后台服务器去处理。这种策略适用于需要进行会话保持或者缓存一致性的场景。
4、最少链接。这种策略会把请求发给当前连接数最少得后台服务器,nginx会动态统计每个服务器的连接数,然后把请求分配给连接数最少得服务器。适用于对于长连接和短连接混合的场景。
十、场景题
1、有没有遇到过超卖的情况,怎么解决?
参考网站:http://www.pingtaimeng.com/article/detail/id/
商城设计的过程中,必然会考虑到一个库存扣除的问题,超卖将会带来一定的损失和麻烦,在某一个时间段的瞬间的流量会造成库存的并发性操作带来库存超额扣除。
1、流量较少时,可以采用锁定最后库存+乐观锁解决方案。但是缺点就是操作的时间内只能有一个用户操作成功,如果执行一个200ms,则其他用户等待时间会很长。
2、redis的队列来实现。将要促销的商品数量以队列的方式存入redis中,每当用户抢到一件促销商品则从队列中删除一个数据,确保商品不会超卖。这个操作起来很方便,而且效率极高。
3、分阶段排队下单方案(将提交操作变成两段式):
第一阶段申请,申请预减减库,申请成功之后,进入消息队列;
第二阶段确认,从消息队列消费申请令牌,然后完成下单操作。 查库存 -> 创建订单 ->扣减库存。通过分段锁保障解决多个provider实例并发下单产生的超卖问题。
但是下单阶段的性能比较差,如何提升性能呢?
可以使用Redis 分布式锁。为了达到每秒600个订单,可以将锁分成 600 /5 =120 个段,每个段负责5个订单,600个订单,在第二个阶段1秒钟下单完成。
2、sku跟spu的区别
计量单位不同、描述特性不同、产品分类不同。
SKU=stock keeping unit(库存量单位),SKU即库存进出计量的单位。
SKU的计量可以是以件、盒、托盘等为单体,就是物理上不可分割的最小存货单元。
3、产品分类不同:
SPU描述的就是属性值、特性相同的商品。例如:iphone4就是一个SPU,与商家,与颜色、款式、套餐都无关。
SKU在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。例如:纺织品中一个SKU通常表示:规格、颜色、款式。
3、假设现在需要调用第三方接口,这个接口限制每秒请求5次,我们的系统需求是对这个接口调用每秒100次,你怎么解决这个问题?
redis令牌桶或者mq队列
4、假设现在有一万个数,你要对这些数进行累加,每个加法耗时0.5秒,你怎么样以最小的时间把累加的值算出来?
这里考的是线程如何分配最优
多线程,由于是cpu计算密集型,cpu核心数 * cpu利用率 * (1+ 线程等待时间/线程计算时间)
5、假设接到一个项目,某些部分的接口,比如 controller,在 jar 包里面,我们只有class,没有其他,要对他原有的功能进行修改,你怎么做?
通过反编译软件 jd-gui 反编译得到反编译文件,然后去修改文件。
6、春节抢红包怎么设计随机金额?
一般来说,红包基本分配了两种不同的算法,第一种的话叫做金额随机法,第二种叫做二倍均值法。
a、金额随机法(不太公平,极端情况差异大,但是刺激)
那么金额随机法的话可能是最容易想到的一种方式,这种方式比较简单也比较直观,在这个方法里面。里面随机生成的,比如说我们的范围是0-100对吧?然后用红包你就在这个区间里面去生成随机红包的金额,这个方法比较简单,直接把红包金额都是随机生成的;这种方式有一个缺点就是特别不公平,比如说我现在想发100块钱发10个红包,那么宣传第一个红包的时候我的取值在0.01-99.9之间,或者是0.0~100块钱举个例子当取到100了对吧?后面9个就没有了。
b、二倍均值法(相对更公平)
首先确定红包的总金额,还有红包的一个数量,然后的话去计算每个红包的一个平均金额,就是总金额除以数量就是平均金额。
然后把每个红包的金额范围都设定在一个平均值的两倍以内。这样的话每个红包金额在0.01到平均金额的两倍之间的话就随机生成。
比如还是100块钱发10个红包,那么平均数就是10,乘以2也就是20,也就是红包金额在0.1~20之间取一个随机数,假设第一次生成了一个12,那么剩下的就是88,还有9个人,那么此时平均数就是 88/9*2 = 19,那么随机数就是0.01-19之间生成,后面依次类推。
两种方法无所谓好坏,看需求,是需要刺激还是需要公平。
7、如何设计订单自动超时
主流使用消息中间件的方案,以 rabbitMQ 为例:
1.死信队列和死信交换机。
2.用rabbitmq 的插件 rabbitmq-delayed-message-exchange ,有了这个插件就可以发送消息的时候设置延迟时间。
8、如何解决幂等性问题
由于用户重复提交或者网络抖动,恶意攻击等原因导致请求多次重复发送,服务器要识别出这种请求并且只解决依次,这就是解决幂等性。方法有五种
1.利用数据库唯一主键,或者唯一索引处理,插入数据前检查是否已经存在这个主键或者索引,已经存在就再执行插入操作。
2.业务状态的校验,比如支付系统的订单状态,如果订单状态已经处于已支付/支付成功,再次支付就应该拒绝或者返回错误。
3.通过分布式锁,分布式系统通过分布式锁来保证幂等性,执行操作之前先获取锁,如果获取成功就执行操作,否则说明这个操作已经被其他节点操作过了。这个可以利用 redis 的 setnx 来实现。
4.token机制,可以在请求里面加全局唯一的token,服务器收到请求先检查token是否存在,一般token可以存在redis里面,如果存在说明请求已经被处理过了。
5.乐观锁,通过版本号或者时间戳等方式去实现乐观锁。执行操作前先检查版本号或者时间戳是否一致,如果不一致说明数据已经被其他节点修改过。
在解决幂等性问题时要注意不要产生性能下降或者系统复杂度增加。
9、生产服务器变卡,怎么排查?
四个维度考虑:网络、CPU利用率,IO效率,内存瓶颈
首先的话可以先通过 netstat 还有 iftop 等这样一些工具去查看网络流量和网络连接的情况,检查一下是否存在网络拥塞丢包等等一些问题,然后根据你检查结果来进行优化;
(第二步具体可查看目录第九的第4题)第二步的话我们可以通过Top命令的话去定位一些占用的CPU过高的一些进程,然后进一步定位到进程里面比较活跃的一些线程,然后通过这些命令输出现成的一个运行日志,再根据这些日志的话去排查问题,好这是第二步。
第三步的话我们要去检查一下磁盘IO 是否导致了卡顿,这个话我们可以使用iostat,iotop的一些工具去查看磁盘的一个读写情况,检查磁盘是否负载过高,如果磁盘负载过高的话,我们可以使用一些缓存系统,或者优化的一些策略,减少随机写入等等方式去进行一些优化。
第四步的话就是我们检查内存这块是否存在瓶颈,因为如果内存使用过高的话,可能会导致频繁的垃圾回收,频繁的垃圾回收的话,肯定会影响到服务器的一个性能的,这个话我们可以使用dump命令去导出来 jvm 的堆内存,然后用 idea 或者是 mat 工具,进行分析,找出来内存占用过高的对象,同时排插是否存在内存泄露的问题。
如果dump 出来的堆内存是正常的,继续借助pmap命令,检查进程的内存分配是否正常。如果都正常,需要进一步开启 GC日志,通过 jstack 命令,分析用户线程暂停的时间和各部分区域 GC 的次数,检查问题是否出在GC上面,去进行参数调优。
10、如何平滑系统突发流量?
三种
1、滑动窗口算法:顾名思义实际上就是允许窗口在时间轴上面去滑动这种算法,它会把一个时间窗口的时间分成很多的时间片,然后它把每个时间片内的请求进行累加累加之后再去计算当前时间点向前一个时间窗口的内的请求总数,这几个总数如果超过预设的阈值的话,后续的就会被拒绝相比第一种的话这种方案会比较好的去解决突刺的问题,因为窗口被滑动了,但是这种算法的缺点的话就实现起来比较麻烦一些。
2、漏斗算法:这种算法像是大家平时常见的漏斗,一边大一边小,请求从大口进,从小出口出,如果都满了你就会被拒绝,出口的速率就相对于恒定的,这种方式就很好的去平滑系统突发流量的,因为不管入口流量有多少,出口流量都是恒定的。
3、redis令牌桶算法。
这三种算法无所谓好坏,看实际需求。一般用令牌桶会多一些。
11、get 和 post 这两种请求有何不同?
1、使用场景不同。get 请求通常用来获取或者查询资源。post用于创建和修改资源。
2、幂等性不一样。get请求幂等,post不幂等。
3、请求参数格式不一样。get请求参数通常放在 url 里面。post 请求参数通常放在请求体里面。因此get请求相对更容易泄露敏感数据。
4、因为 get 请求幂等,所以可以被浏览器或者其他中间节点比如网关或者代理给缓存起来,可以提高性能和效率。post请求就不适合。
12、Getaway 和 nginx 反向代理的区别?
这两个不是一个东西,但还是有人面试问到,遂记录。
这两个都可以做代理。
1、应用场景不同。getaway 适用于微服务架构里面的服务间通信,关注的是服务之间的交互效率,还有可靠性。nginx 主要用于 web 服务器、反向代理服务器还有负载均衡服务器,可以代理客户端和服务端之间的一个请求,提高网站访问的速度还有可靠性。
2、底层实现不同。nginx使用 C语言编写,性能优化和扩展主要依赖C语言的优势,getaway 用 java 语言编写,所以 getaway 更好的对微服务实现扩展功能,比如安全控制,异常统一处理,性能监控等等。
3、功能特性不同。nginx有强大的并发处理能力,最高支持5万个并发。getaway主要具有路由,断言,过滤器等等这些功能。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/9854.html