关注
中高级Java程序员,你不得不掌握的基本功,挑战20k+

文章目录

1 前言

工作久了就会发现,基础知识忘得差不多了。为了复习下基础的知识,同时为以后找工作做准备,这里简单总结一些常见的可能会被问到的问题。

2 自我介绍

自己根据实际情况发挥就行

3 Java SE

3.1 Java访问控制修饰符

Java访问控制修饰符

3.2 Java中抽象类与接口有何区别?

Java中抽象类与接口

3.3 Java中super关键字与final关键字

Java中super关键字与final关键字

3.4 final修饰的对象,有几种初始化方式

final修饰的对象,有几种初始化方式

3.5 Java中方法的重载(Overload)与方法的覆盖(Override)

Java中方法的重载与方法的覆盖

3.6 Java基础知识总结,干货来啦

Java基础知识总结,干货来啦

3.7 Java基础面试题干货系列(一)

Java基础面试题干货系列(一)

3.8 Java中的String是不可变的,Why

Java中的String是不可变的,Why?

3.9 Java反射基础入门,一篇就够啦

Java反射基础入门,一篇就够啦

3.10 面向对象与面向过程

面向对象与面向过程

3.11 Java基础面试题干货系列(二)

Java基础面试题干货系列(二)

3.12 理解Java中的多态机制,一篇就够啦

理解Java中的多态机制,一篇就够啦

3.13 Java中参数传递(值传递还是引用传递)

Java中参数传递(值传递还是引用传递)

3.14 Java编程思想之高内聚低耦合

Java编程思想之高内聚低耦合

3.15 Java基础面试题干货系列(三)

Java基础面试题干货系列(三)

3.16 Java中的异常(Exception)

Java中的异常(Exception)

3.17 Java中设计模式的七大基本原则

Java中设计模式的七大基本原则

3.18 Java中File类,你知道有哪些api方法吗?

Java中File类,你知道有哪些api方法吗?

3.19 计算机中字节、字、位、bai比特等单位之间的换算关系

计算机中字节、字、位、bai比特等单位之间的换算关系

3.20 HTTP请求状态码对照表

HTTP请求状态码对照表

3.21 集合

3.21.1 集合顶层的接口类有哪些?集合常见的有哪几种?都有啥区别?

集合类图:
在这里插入图片描述

  • Collection接口
    在这里插入图片描述
  • Map接口
    在这里插入图片描述

3.21.2 集合顶层的接口类有哪些?

集合的顶层接口,常见的主要有: Collection接口Map接口

3.21.3 集合常见的有哪几种?

和Collection相关的接口主要有: CollectionListSet接口

  • Collection接口
    Collection是一个抽象出来的接口,定义如下:

    public interface Collection<E> extends Iterable<E> {}
    

    其中包括了集合的基本操作,包括: 删除添加遍历大小

  • List接口
    List接口继承自Collection,List中的元素的有序且允许重复的。定义如下:

    public interface List<E> extends Collection<E> {}
    
  • Set接口
    Set接口继承自Collection,Set是数学中定义的集合,元素无需不允许重复。定义如下:
    public interface Set extends Collection {}

Map接口,也是顶层接口,Map保存的是键值对映射,映射关系可以是一对一或者一对多。

参考资料: https://blog.csdn.net/weixin_34176694/article/details/88708182

3.21.4 请说明Collection和Collections的区别

Collection是集合类的顶级接口,继承与他的接口主要有List和Set。而Collections是针对集合类的一个工具类,它提供了一系列的静态方法实现对各种集合的搜索、排序、线程安全化等操作。

3.21.5 ArrayList和Vector以及LinkedList三者有啥区别?

  • ArrayList 和Vector底层都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快

  • 请说明ArrayList和LinkedList的区别?
    ArrayList和LinkedList都实现了List接口,ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。而LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。LinkedList的插入,添加,删除操作比ArrayList速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

3.21.6 HashMap和HashTable有何区别?

  • HashTable
    底层数组+链表实现,无论key还是value都不能为null,线程安全,适合于多线程环境,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化

    初始size为11,扩容:newsize = olesize*2+1

    计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

  • HashMap
    底层数组+链表实现,可以存储null键和null值,线程不安全,更适合于单线程环境

    初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂

    扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)

    当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀

    计算index方法:index = hash & (tab.length – 1)

注:
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

更详细的介绍,可参考我的另一篇博客: HashMap、Hashtable、ConcurrentHashMap的区别和原理浅析

3.21.7 HashTable和ConcurrentHashMap有何区别?

  • 相同点
    Hashtable和ConcurrentHashMap都实现了Map接口,两者都是线程安全的

  • 不同点
    Java5提供了ConcurrentHashMap,它是HashTable的替代,ConcurrentHashMap比HashTable的扩展性更好,性能也大大提升了

  • 为什么说ConcurrentHashMap性能大大提高了?

    简单来说,ConcurrentHashMap底层有分段锁,类似于mysql中的行级锁,而Hashtable是锁整个hash表,类似于mysql中的表级锁

    ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作,而ConcurrentHashMap是使用了锁分段技术来保证线程安全的,是一次锁住一个桶。

    ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

    锁分段技术: 首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

3.21.8 ConcurrentHashMap底层实现原理

  • 底层采用分段的数组+链表实现,是线程安全的

  • 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)

  • Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术

  • 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁

  • 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,可以有效避免无效扩容

4 Java EE

4.1 UML类和类之间的关系详解

UML类和类之间的关系详解

4.2 UML图使用详解

UML图使用详解

4.3 SpringBoot和Spring Cloud有什么区别?

4.3.1 什么是SpringBoot?什么是Spring Cloud?

  • SpringBoot
    是一个快速开发框架,通过用Maven依赖的继承方式,帮助我们快速整合第三方常用框架。比起传统的Spring,SpringBoot采用注解化的方式(使用注解方式启动SpringMVC),简化Xml配置,内置HTTP服务器(Tomcat,Jetty),最终以Java应用程序进行执行

  • SpringCloud
    SpringCloud是一套目前比较完整的微服务框架,是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的结合体,俗称为微服务全家桶

    它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再次封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂易部署易维护分布式系统开发工具包。它基于SpringBoot提供了一套微服务(microservices)解决方案,包括服务注册与发现配置中心服务网关全链路监控负载均衡熔断断路器等组件,都可以用SpringBoot的开发风格做到一键启动和部署。

4.3.2 SpringBoot和Spring Cloud区别

  • SpringBoot只是一个快速开发框架,使用注解大大简化了Spring的Xml配置,内置了Servlet容器,以Java应用程序进行执行。

  • SpringBoot专注于开发单应用微服务,而SpringCloud是一系列框架的集合,SpringCloud是微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来

  • SpringBoot可以离开SpringCloud单独使用,而Spring Cloud很大的一部分是基于SpringBoot来实现的,所以说SpringCloud离不开SpringBoot

4.4 什么是Spring

Spring 是 个 java 企 业 级 应 用 的 开 源 开 发 框 架 。 Spring 主 要 用 来 开 发 Java 应 用 , 但 是 有 些 扩 展 是 针 对 构 建 J2EE 平 台 的 web 应用。Spring 框 架 目 标 是 简 化 Java 企 业 级 应 用 开 发 , 并 通 过 POJO 为 基 础 的 编 程 模 型 促 进 良 好 的 编 程 习 惯 。

4.5 使用Spring框架的好处是什么?

  • 轻量: Spring 是轻量的,基本的版本大约 2MB
  • 控制反转: Spring 通过控制反转实现了松散耦合,对象们给出它们的依 赖,而不是创建或查找依赖的对象们
  • 面向切面的编程(AOP): Spring 支持面向切面的编程,并且把应用业务 逻辑和系统服务分开
  • 容器: Spring 包含并管理应用中对象的生命周期和配置
  • MVC 框架: Spring 的 WEB 框架是个精心设计的框架,是 Web 框架的 一个很好的替代品
  • 事务管理: Spring 提供一个持续的事务管理接口,可以扩展到上至本地 事务下至全局事务(JTA)
  • 异常处理: Spring 提供方便的 API 把具体技术相关的异常(比如由 JDBC, Hibernate or JDO 抛出的)转化为一致的 unchecked 异常

4.6 Spring 事务隔离级别和传播行为有哪些?都有什么区别?

Transactional 注解的属性:

  • name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器
  • propagation 事务的传播行为,默认值为 REQUIRED
  • isolation 事务的隔离级别,默认值采用 DEFAULT
  • timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务
  • read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true
  • rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔
  • no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务

4.6.1 Spring事务的隔离级别有哪些?

Spring在TransactionDefinition接口中定义这些属性,定义了五个不同的事务隔离级别:

Isolation 属性一共支持五种事务设置:

  • DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别
  • READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )
  • READ_COMMITTED 会出现不可重复读、幻读问题(锁定正在读取的行)
  • REPEATABLE_READ 会出幻读(锁定所读取的所有行)
  • SERIALIZABLE 保证所有的情况不会发生(锁表)

表格总结如下:

类型

解释

ISOLATION_DEFAULT

这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别,另外四个与JDBC的隔离级别相对应

ISOLATION_READ_UNCOMMITTED

这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读

ISOLATION_READ_COMMITTED

保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读

ISOLATION_REPEATABLE_READ

这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读

ISOLATION_SERIALIZABLE

这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行,除了防止脏读,不可重复读外,还避免了幻像读

4.6.2 什么是事务的传播行为?

事务传播行为指的是: 两个有事务的方法进行相互调用的时候,它的事务如何进行传播

4.6.3 Spring事务的传播行为有哪些?

在TransactionDefinition接口中定义了七个事务传播行为:

  • PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
  • PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起
  • PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则以非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同
  • PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务
  • PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
  • PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
  • PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

propagation 属性(事务传播性)表格整理如下:

类型

解释

REQUIRED

默认的传播行为,REQUIRED 支持当前已经存在的事务,如果还没有事务,就创建一个新事务

REQUIRES_NEW

如果当前有事务,挂起当前事务,创建一个新事务,如果还没有事务,就简单地创建一个新事务

SUPPORTS

支持当前事务,有事务就加入事务,如果没有事务,那么就以非事务的方式运行

NOT_SUPPORTED

强制不在事务中运行,如果当前存在一个事务,则挂起该事务

NEVER

强制要求不在事务中运行,如果当前存在一个事务,则抛出异常

MANDATORY

支持当前已经存在的事务,如果还没有事务,就抛出一个异常

NESTED

在当前事务中创建一个嵌套事务,如果还没有事务,那么就简单地创建一个新事务

4.6.4 Spring事务的实现原理?

4.6.4.1 Spring事务的使用

Spring支持编程式事务管理声明式事务管理两种方式

  • 编程式事务
    编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,Spring推荐使用TransactionTemplate

  • 声明式事务
    声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

    声明式事务管理使业务代码不受污染,显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。通常,使用注解方式通过@Transactional来使用事务。我们可以使用@EnableTransactionManagement注解来启用事务管理功能,该注解可以加在启动类上或者单独加个配置类来处理。@Transactional 可以加在方法上,表示对当前方法配置事务。也可以添加到类级别上,当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。

4.6.4.2 Spring事务的实现原理

项目中是使用注解方式实现的事务,这里以这种场景进行说明:
Spring事务是使用了@Transactional注解,底层产生CGLIB动态代理对象来进行事物的管理。如果一个service类没有产生动态代理对象的时候,那么它不会对事务进行管理。

声明式事务是建立在AOP之上的,通过注解方式或者是在xml文件中进行配置。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务

参考资料: https://www.cnblogs.com/kingsonfu/p/10413154.html

4.7 说一下Spring MVC执行流程?

在这里插入图片描述

  • 1、向服务器发送HTTP请求,请求被前端控制器DispatcherServlet捕获
  • 2、DispatcherServlet根据xxx-servlet.xml中的配置对请求的URL进行解析,得到请求资源标识符(URI),然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回
  • 3、DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
  • 4、提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    数据转换: 对请求消息进行数据转换,如String转换成Integer、Double等
    数据格式化: 对请求消息进行数据格式化,如将字符串转换成格式化数字或格式化日期等
    数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  • 5、Handler(Controller)执行完成后,向DispatcherServlet返回一个ModelAndView对象
  • 6、根据返回的ModelAndView,选择一个适合的视图解析器ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet
  • 7、ViewResolver结合Model和View,来渲染视图
  • 8、视图负责将渲染结果返回给客户端

4.8 请你说说对Spring IOC和Spring AOP的理解?

4.8.1 Spring IOC

4.8.2 Spring AOP

4.8.2.1 什么是 AOP?

AOP(Aspect-OrientedProgramming),即面向切面编程。它与OOP(Object-OrientedProgramming,面向对象编程)相辅相成,提供了与OOP不同的抽象软件结构的视角。在OOP中,我们以类(class)作为我们的基本单元,而AOP中的基本单元是Aspect(切面)。

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在SpringAOP中,切面通过带有@Aspect注解的类实现。

4.8.2.2 什么是 Aspect?

Aspect由pointcount切入点advice通知组成,它既包含了横切逻辑的定义,也包括了连接点的定义。SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑编织到切面所指定的连接点中。AOP的工作重心在于如何将增强编织到目标对象的连接点上这里包含两个工作:

  • 如何通过pointcut和advice定位到特定的joinpoint上
  • 如何在advice中编写切面代码

可以简单地认为,使用@Aspect注解的类就是切面

4.8.2.3 连接点

连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。

比如我在我的业务方法上加入我的切面注解,进行日志切面处理:

/**
 * <p>
 * 自定义日志存储注解
 * <p/>
 *
 * @author smilehappiness
 * @Date 2021/8/15 10:08
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {

    /**
     * 日志操作信息
     */
    String value() default "";
}


@OperateLog("切面日志处理")
@PostMapping(value = "/path1")
public Result<Object> path1(@RequestBody Dto dto) {
    // 省略业务代码....
    return 结果;
}

4.8.2.4 什么是JoinPoint切点

切入点JoinPoint是一个或一组连接点,是程序运行中的一些时间点,通知将在这些位置执行,可以通过表达式或匹配的方式指明切入点,例如一个方法的执行,或者是一个异常的处理。在Spring AOP中,joinpoint总是方法的执行点。

例如:

 /**
     * 使用@Pointcut定义切点,在加注解的位置切入代码
     */
    @Pointcut("@annotation(com.smilehappiness.aspect.operate.OperateLog)")
    public void logPointCut() {

    }

4.8.2.5 什么是Advice通知?有哪些类型的Advice通知?

  • Advice概念
    特定JoinPoint处的Aspect所采取的动作称为Advice,通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。。SpringAOP使用一个Advice作为拦截器,在JoinPoint“周围”维护一系列的拦截器。

  • 有哪些类型的通知(Advice)
    Spring切面可以应用五种类型的通知:

    切面通知类型

    解释

    Before

    前置通知,在一个方法执行前被调用,该类型的Advice在joinpoint方法之前执行,使用@Before注解标记进行配置

    After

    后置通知,在连接点方法执行之后调用的通知,无论方法执行是否成功,使用@After注解标记进行配置

    Around

    环绕通知,在连接点方法执行之前和之后调用的通知,使用@Around注解标记进行配置。目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象

    AfterReturning

    返回通知,在连接点方法正常执行后执行,仅当方法成功完成后执行的通知,使用@AfterReturning注解标记进行配置

    AfterThrowing

    异常通知,仅在joinpoint方法抛出异常退出时执行的通知,使用@AfterThrowing注解标记配置时执行

4.8.2.6 指出在Spring AOP中关注点(concern)和横切关注点(cross-cuttingconcern)的不同之处。

关注点-concern是我们想要在应用程序的特定模块中定义的行为,一个关注点可能会被定义成一个我们想要实现的一个功能。横切关注点-cross-cuttingconcern是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用程序,比如日志安全性数据传输,是应用程序几乎每个模块都需要关注的问题,因此它们是跨领域的问题,这些都属于横切关注点。

4.8.2.7 什么是引入 什么是目标对象 什么是代理 有几种不同类型的自动代理?

  • 引入
    引入允许我们在已存在的类中增加新的方法和属性
  • 目标对象
    被一个或者多个切面所通知的对象,它通常是一个代理对象,也指被通知(advised)对象
  • 代理
    代理是通知目标对象后创建的对象(底层其实是动态代理),从客户端的角度看,代理对象和目标对象是一样的
  • 有几种不同类型的自动代理
    BeanNameAutoProxyCreator、DefaultAdvisorAutoProxyCreator、Metadata AutoProxying

4.8.2.8 什么是编织(Weaving)?

Weaving编织也叫做织入,为了创建一个advice通知对象而链接一个aspect和其它应用类型或对象的过程。织入可以在编译时,加载时,或运行时完成,比如说,在SpringAOP中,编织在运行时执行

简单来说,织入是将切面和其他应用类型或对象连接或创建一个被通知对象的过程

4.8.2.9 AOP有哪些实现方式?

  • 基于XMLSchema方式的切面实现
    在这种情况下,切面由常规类以及基于XML的配置实现
  • 基于注解的切面实现
    在这种情况下(基于@AspectJ的实现)

实现AOP的技术,主要分为两大类:

  • 静态代理
    指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也称为编译时增强。编译时编织类(特殊编译器实现)加载时进行编织(特殊的类加载器实现)。

  • 动态代理
    在运行时在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强。主要有JDK动态代理CGLIB动态代理两种方式

4.8.2.10 Spring AOP和AspectJ AOP有什么区别?

SpringAOP基于动态代理方式实现,AspectJ基于静态代理方式实现。另外,SpringAOP仅支持方法级别的PointCut,提供了完全的AOP支持,它还支持属性级别的PointCut,功能更加强大

4.8.2.11 如何理解Spring中的代理?

Advice通知应用于目标对象后创建的对象称为代理,代理是通知目标对象后创建的对象,在客户端对象的情况下,目标对象和代理对象是相同的。

AOP底层是使用cglib动态代理来实现,而cglib是创建动态子类继承了业务方法类,在业务方法操作之前,来完成功能的前置或者后置增强

Advice+TargetObject=Proxy

4.9 说一下BeanFactory和FactoryBean的区别?

  • BeanFacotry是spring中比较原始的Factory,BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范。
  • FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,我们可以在getObject()方法中灵活配置。

相同点:
都是接口

主要区别: BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

详细的内容可以参考资料: https://www.cnblogs.com/aspirant/p/9082858.html

4.10 @Component, @Controller, @Repository, @Service 有何区别?

从源码级别来看, @Repository、@Service、@Controller是@Component的别名 ,@Service用于标注业务层组件,@Controller用于标注控制层组件(如struts中的action),@Repository用于标注数据访问组件,即Dao组件,@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注

参看源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

注: @Repository这个注解是具有类似用途和功能的@Component注解的特化 。@Repository为DAO提供了额外的好处,它将DAO导入IoC容器,并使未经检查的异常有资格转换为Spring DataAccessException

4.11 你怎样定义类的作用域 Spring支持哪些bean的作用域

Spring中,我们可以给bean实例声明一个作用域,我们可以通过bean定义中的scope属性来定义
比如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。如果一个bean每次使用的时候必须返回同一个实例,这个bean的scope属性必须设为singleton

Spring默认的Springbean的scop作用域是Singleton

Spring框架支持以下五种bean的作用域:

  • singleton
    bean在每个Springioc容器中只有一个实例
  • prototype
    一个bean的定义可以有多个实例
  • request
    每次http请求都会创建一个bean,该作用域仅在基于web的SpringApplicationContext情形下有效
  • session
    在一个HTTPSession中,一个bean定义对应一个实例。该作用域仅在基于web的SpringApplicationContext情形下有效
  • global-session
    在一个全局的HTTPSession中,一个bean定义对应一个实例。该作用域仅在基于web的SpringApplicationContext情形下有效

4.12 Spring框架中的单例bean是线程安全的吗

答: Spring 框架中的单例bean不是线程安全的

Spring框架并没有对单例bean进行任何多线程的封装处理,关于单例bean的线程安全和并发问题需要开发者自行去搞定。最简单的有效解决办法就是:将多态bean的作用域由“singleton”变更为“prototype”

  • 在@Controller或者@Service等容器中,默认情况下,scope值是singleton单例的,也是线程不安全的
  • 尽量不要在@Controller或者@Service等容器中定义静态变量,不论是单例(singleton)还是多实例(prototype)他都是线程不安全的。
  • 默认注入的Bean对象,在不设置scope的时候他也是线程不安全的。
  • 一定要定义变量的话,用ThreadLocal来封装,这个是线程安全的
    eg: ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); // 用ThreadLocal来封装变量

参考资料: Spring框架中的单例bean是线程安全的吗

4.13 解释Spring框架中bean的生命周期

  • Spring容器从XML文件中读取bean的定义,并实例化bean
  • Spring根据bean的定义填充所有的属性
  • 如果bean实现了BeanNameAware接口,Spring传递bean的ID到setBeanName方法。如果Bean实现了BeanFactoryAware接口,Spring传递beanfactory给setBeanFactory方法
  • 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们
  • 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法
  • 如果有BeanPostProcessors和bean关联,这些bean的postProcessAfterInitialization()方法将被调用
  • 如果bean实现了DisposableBean,它将调用destroy()方法。

说一下动态代理和静态代理?动态代理有JDK动态代理和Cglib动态代理,这两个代理如何实现的,有何区别?

5 设计模式

5.1 策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

意图: 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
主要解决: 在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护
何时使用: 一个系统有许多许多类,而区分它们的只是他们直接的行为
如何解决: 将这些算法封装成一个一个的类,任意地替换
关键代码: 实现同一个接口

应用实例:
1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略
2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略
3、JAVA AWT 中的 LayoutManager。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为
2、一个系统需要动态地在几种算法中选择一种
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现

注意事项: 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题

5.2 项目中用到了那些设计模式?哪些好的框架里面有使用过这些设计模式?

这个根据实际情况回答即可

5.2.1 项目中用到了那些设计模式

  • 示例1: 我在项目中,做日志管理模块时,使用了一个代理模式。在访问业务方法时,基于AOP切面,对业务进行了拦截。
    具体操作大致如下:
    首先我自定义了OperateLog注解,然后设计了日志切面管理类,使用@Pointcut定义切入点,在加注解的位置切入代码,使用了@Around环绕通知,在业务操作前进行Api基本信息的存储,当业务方法执行完成后,存储了相应结果JSON报文,以及Api请求到响应的耗时时间。

  • 示例2: 我在做车辆发行时,多线程执行job任务时,使用了单例模式,封装了一个线程操作工具类,代码示例如下:

    package com.smilehappiness.service.common.utils;
    
    import com.google.common.util.concurrent.ThreadFactoryBuilder;
    
    import java.util.concurrent.*;
    
    /**
     * <p>
     * 线程工具类
     * <p/>
     *
     * @author smilehappiness
     * @Date 2018/10/27 10:05
     */
    public class ThreadUtil {
    
        private static volatile ThreadPoolExecutor THREAD_POOL = null;
    
        private ThreadUtil() {
        }
    
        static {
            //如果实例为空,创建实例
            if (THREAD_POOL == null) {
                synchronized (ThreadUtil.class) {
                    if (THREAD_POOL == null) {
                        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
                        ThreadFactory nameThreadFactory = new ThreadFactoryBuilder().setNameFormat("vehicle-pool-%d").build();
                        THREAD_POOL = new ThreadPoolExecutor(corePoolSize , corePoolSize  + 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100000), nameThreadFactory);
                    }
                }
            }
        }
    
        public static void execute(Runnable runnable) {
            THREAD_POOL.execute(runnable);
        }
    
        /**
         * <p>
         * 返回结果
         * <p/>
         *
         * @param runnable
         * @return java.util.concurrent.Future<?>
         * @Date 2018/10/27 11:36
         */
        public static <T> Future<T> submit(Runnable runnable, T result) {
            return THREAD_POOL.submit(runnable, result);
        }
    
        public static <T> Future<T> submit(Callable<T> task) {
            return THREAD_POOL.submit(task);
        }
    
        /**
         * <p>
         * 通知关闭线程池
         * <p/>
         *
         * @param
         * @return void
         * @Date 2018/10/27 11:07
         */
        public static void shutdown() {
            THREAD_POOL.shutdown();
        }
    
        public static ThreadPoolExecutor getExecutorObject() {
            return THREAD_POOL;
        }
    }
    
  • 示例3:
    再做医疗供应链保理时,搜集票据单据时,我使用了策略模式,不同的excel数据,我使用不同的模板策略去解析处理

5.2.2 哪些好的框架里面有使用过这些设计模式?

优秀的Spring Boot框架大量使用了这些设计模式

6 MySQL总结

6.1 Java JDBC编程

Java JDBC编程

6.2 MySQL使用总结以及MySQL性能优化

MySQL使用总结以及MySQL性能优化

6.3 MySQL四大特性是什么(MySQL事务的基本要素(ACID)有哪些)?

  • 原子性(Atomicity)
    事务开始后所有操作,要么全部做完(要么都成功),要么全部不做(要么都失败),不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

  • 一致性(Consistency)
    事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

  • 隔离性(Isolation)
    同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程中,B不能向这张卡转账。

  • 持久性(Durability)
    事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

6.4 MySQL隔离级别有几种,有什么区别?

6.4.1 MySQL四种隔离级别

MySQL默认的隔离级别是可重复读(repeatable-read),MySQL事务隔离级别有以下四种:

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted)

读已提交(read-committed)

可重复读(repeatable-read)

串行化(serializable)

6.4.2 MySQL事务的并发问题

  • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

  • 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  • 小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

    具体示例,可以参考我另一篇博客: https://blog.csdn.net/smilehappiness/article/details/119656138

6.5 影响数据库性能的因素

  • 服务器硬件
    cpu、内存、磁盘io、网卡流量

  • 服务器的操作系统
    Linux的配置参数不同性能不同

  • 数据库存储引擎的选择
    根据需要选择不同的存储引擎,MyISAM 不支持事务,表级锁,InnoDB 事务级存储引擎,完美支持行级锁和事务ACID特性

  • 数据库自身配置参数
    MySQL有上百项的数据库配置参数;(my.cnf)

  • 数据库表结构的设计和SQL语句的执行效率(影响最大的)
    慢查询是性能问题的罪魁祸首,不合理的数据库表结构设计和不合理的索引是影响数据库查询性能的重要因素

  • 数据库架构
    高并发下读写分离、分库分表、多级缓存、搜索引擎

6.6 MySQL索引,主键索引与普通索引区别?

主键索引,也叫集聚索引,也叫聚簇索引(针对innodb而言)。辅助索引,也叫二级索引,也叫非集聚索引,也叫非聚簇索引。

主键索引在叶子节点上可以直接查找到数据,而InnoDB的辅助索引(Secondary Key)(普通索引)data域存储相应记录主键的值而不是当前索引键的值,也不是数据地址,换句话说,InnoDB的所有辅助索引都引用主键作为data域,所以对于辅助索引,还需要主键值来回表查询到一条完整记录,因此按辅助索引检索实际上进行了二次查询,效率肯定是没有按照主键检索高的。

6.7 B+Tree索引结构底层实现原理?

可以参考我的另一篇博客https://blog.csdn.net/smilehappiness/article/details/119656138,11.2那一节,为什么是B+Tree索引,你就可以理解它的实现原理。

6.8 MySQL如何进行性能优化?

使用EXPLAIN,在查询sql前添加该关键字,进行执行计划的分析。

执行计划的type属性,是sql查询优化中一个很重要的指标,结果值从好到坏依次是:
NULL > system > const > eq_ref > ref > range > index > ALL

6.9 MySQL什么情况下索引会失效?

  • 在使用like模式搜索时,可能会导致索引失效
    如: like '9999%'可以用到索引,而like ‘%9999%’like '%9999’用不到索引,比如下面这条sql,就无法命中索引:

    EXPLAIN select * from users where name like '%张三'; 
    
  • 如果查询条件中有函数或表达式,则MySQL不会为该列使用索引

    explain select * from users where id = id - 2;
    explain select * from users where left(name, 5) = 'zhangsan';
    
  • sql使用or进行查询时,会导致索引失效

    下面这两个sql,or导致索引失效
    EXPLAIN select * from users where name = 'zhangsan' or phone = '18266668888';
    EXPLAIN select * from users where phone in ('18266668888') or name in ('zhangsan');
    

6.10 说说MySQL的最左前缀原则?

这种情况指的是,只使用联合索引的第一列进行匹配,如果使用索引的第二列或者其他列,而不是用第一列查询,无法命中索引例如:

EXPLAIN select * from users where name = 'zhangsan';
EXPLAIN select * from users where name = 'zhangsan' and date(create_time) = '2021-06-28';
-- 上面两条sql使用了最左列字段,可以命中索引,而下面这一条,只使用了第二列,无法命中索引
EXPLAIN select * from users where phone = '18266668888';

6.11 什么是MySQL索引下推?

索引下推是针对回表查询进行的优化,那么,回表有什么坏处呢?

普通索引查询数据时,会进行回表操作,但是回表会增加查询的步骤、降低查询效率,试想你创建了一个(name,age)的二级索引。当你通过name和age查询时,会先在二级索引上根据name得到主键值并回表,回表后还要在主键索引上查询age。

可以发现回表直接导致了二级索引上的age没有被使用到,当使用索引下推后就不存在这种情况了。MySQL是默认开启索引下推的。

手动开关代码如下:

set optimizer_switch='index_condition_pushdown=off'
set optimizer_switch='index_condition_pushdown=on'

使用索引下推后会优先在索引上进行查询过滤,使用explain查询会发现:

Extra的值为Using index condition,说明你查询的字段全部在索引上完成了过滤操作,回表时会根据主键直接得到对应的行数据。

当Extra的值为Using where时,可能你的查询字段没有设置索引,这时会直接在主键索引上查询,效率低下。

当Extra的值为Using index condition; Using where时,说明你查询的一部分部分字段有索引,另一部分没有索引,当有索引的字段完成查询时回表,并不会立马返回数据,而是还要再全表查询那些没有索引的字段。

6.12 请说一说MySQL数据库三范式

  • 第一范式:保证每列的原子性
  • 第二范式:保证一张表只描述一件事情
  • 第三范式----保证每列都和主键直接相关

详细的介绍,请参考资料: MySQL数据库三范式

7 JDK和JVM总结

7.1 Java8新特性有哪些?

在这里插入图片描述

Stream Api,函数作为参数传递

Java8有哪些优点:

  • 速度更快
  • 代码更少(增加了新的语法Lambda表达式)
  • 强大的Stream API
  • 最大化减少空指针异常Optional

7.2 Java类加载器

26、Jvm垃圾回收、老年代、新生代、双亲委派模型等,以及JVM参数优化

27、如何阻止gc垃圾回收?

8 多线程总结

8.1 常用的线程池有哪些?

  • newSingleThreadExecutor
    创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  • newFixedThreadPool
    创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  • newCachedThreadPool
    创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,
    那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

  • newScheduledThreadPool
    创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

8.2 为什么不建议使用Executors静态工厂构建线程池

Executors返回的线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadExecutor:
    允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE可能会堆积大量的请求,从而导致OOM
  • CachedThreadPool 和 ScheduledThreadPool
    允许的创建线程数量为Integer.MAX_VALUE可能会创建大量的线程,从而导致OOM

创建线程池的正确方式:
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池,在创建的同时,给BlockQueue指定容量就可以了,参考示例如下:

  //list数据
  List<Object> dataList= new ArrayList<>();
  ExecutorService executorService = new ThreadPoolExecutor(4, 9, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(dataList.size() + 512));
  dataList.forEach(item -> {
      executorService.execute(() -> {
          try {
              //执行业务方法...
          } catch (Exception e) {
              logger.info("业务方法处理异常,异常信息:{}", e.getMessage());
          }
      });
  });

  try {
      executorService.shutdown();
      long time = 120;
      while (!executorService.awaitTermination(time, TimeUnit.SECONDS)) {
      }

      logger.info("业务方法处理完成!");
  } catch (Exception e) {
      logger.info("业务方法处理异常,异常信息:{}", e.getMessage());
  }

当然,也可以使用下面方式创建线程:

//list数据
List<Object> dataList= new ArrayList<>();
ExecutorService executorService = new ThreadPoolExecutor(4, 9, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(dataList.size() + 512));
CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
 dataList.forEach(item -> {
     executorService.execute(() -> {
         try {
            //执行业务方法...
          } catch (Exception e) {
              logger.info("业务方法处理异常:{}", e.getMessage());
          } finally {
              countDownLatch.countDown();
          }
      });
 }
 
 try {
     countDownLatch.await();
     logger.info("业务方法执行完成");
     executorService.shutdown();
 } catch (InterruptedException e) {
     logger.error(e.getMessage());
 }

8.3 线程池常用参数有哪些?

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) { }

参数说明如下:

参数

说明

corePoolSize

核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true

maximumPoolSize

线程池允许的最大线程池数量

keepAliveTime

线程数量超过corePoolSize,空闲线程的最大超时时间

unit

超时时间的单位

workQueue

工作队列,保存未执行的Runnable 任务

threadFactory

创建线程的工厂类

handler

当线程已满,工作队列也满了的时候,会被调用,被用来实现各种拒绝策略

8.4 ThreadLocal底层是如何实现的

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本这些对象之间的引用关系如下图所示:
在这里插入图片描述

8.5 ThreadLocal的内存泄露?什么原因?如何避免?

ThreadLocal的内存泄露分析以及如何避免

9 分布式技术总结

9.1 Redis缓存穿透 && 缓存击穿 && 缓存雪崩

9.1.1 缓存处理流程

前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。

Created with Raphal 2.3.0 开始 Redis缓存中是否有数据 返回正常数据 结束 数据库中是否有数据 更新缓存数据 返回空数据 yes no yes no

9.1.2 Redis缓存穿透

缓存穿透是指大量不存在的key请求,由于缓存没有,便开始查询数据库,但数据库也没有查到数据,比如一些恶意攻击、爬虫等造成大量空命中。即:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,会导致数据库压力过大,甚至宕机

解决方案:

  • 方案一: 缓存空结果,对数据库查询不存在的数据仍然记录在缓存中缓存一条数据,比如缓存一条空值 unknow,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用不存在的key暴力攻击,这样能有效的减少查询数据库的次数。(无论如何,要保证数据库的可用性

  • 方案二: 使用布隆过滤器

9.1.3 Redis缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力导致数据库不可用的现象。

高并发条件下,对于热点数据(一般地,80%的情况下都是访问某些热点数据,也就是访问某些热点key,其他key访问会比较少),当数据缓存失效的一瞬间,或者刚开始时缓存中还没有对热点数据进行缓存,所有请求都被发送到数据库去查询,导致数据库被压垮。

解决方案:

  • 方案一: 使用全局互斥锁,就是在访问数据库之前都先请求全局锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。由于现在的业务都是分布式的,本地锁没法控制其他服务器也等待,所以要用到全局锁,比如分布式锁。

  • 方案二: 对即将过期的数据主动刷新,比如起一个后台定时任务轮询,主动更新缓存数据,保证缓存不会全部失效。

  • 方案三: 设置热点数据永远不过期

9.1.4 Redis缓存雪崩

缓存雪崩是指:比如我们给所有的数据设置了同样的过期时间,然后在某一个历史性时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都落到数据库,数据库被压垮,或者是缓存发生故障,导致所有的请求都落入到数据库,数据库被压垮

简单来说就是,缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库,缓存雪崩更加的严重。

缓存雪崩的核心就是你的缓存不能用了,不能用了包含两种情况:
突然有一个高并发请求:

  • 我要查的数据都没有缓存,那么都查询数据库,数据库可能被查询宕机
  • 缓存本身就不能用了,比如缓存宕机了,那么也导致所有请求都查询数据库,数据库宕机

解决方案:
事前:

  • redis要高可用(搭建集群或者主从哨兵),避免redis不可用
  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
  • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中

事中:

  • 本地ehcache缓存(mybatis二级缓存)+限流&降级(比如返回:系统繁忙,请稍后再试或者网络开小差了等等),避免数据库被压垮
  • 也可以使用极端的处理,把热点数据设置为-1,即永远不过期

事后: redis持久化,快速恢复缓存数据
在这里插入图片描述

9.2 Redis系列-Java面试题干货系列(四)

Redis系列问题总结

9.3 分布式系统如何保证系统数据一致性?有哪几种分布式锁实现方式?

Java中如何实现分布式锁,详细教程来啦

9.4 redis有哪些数据类型?项目中使用了那些场景?

Redis基础命令使用Api详解

主要使用String类型,对热点数据进行缓存

9.5 分布式事务怎么实现的?

更新中…

9.6 Elasticsearch底层实现原理?

Elasticsearch底层使用了倒排索引,通常情况下,都是基于key去寻找value,这个是通过value反向寻找key

9.7 ELK如何搜集日志信息到Elasticsearch中的?

更新中…

9.8 使用了消费组件有哪些?有哪些组件类?延迟队列有使用过吗?消息丢失怎么处理?

项目中使用了RabbitMQ消息组件,

9.9 Nginx如何实现不同资源的转发访问?

高性能web服务器之Nginx使用详解-第5节内容

  • 基于域名的虚拟主机
  • 基于端口的虚拟主机

无论基于域名还是基于端口,都是结合location path,在访问时的进行路径匹配,最终映射到root配置指定的路径,从而访问相关资源

9.10 Nginx如何实现负载均衡,有哪几种方式?

高性能web服务器之Nginx使用详解-第7节内容

10 微服务总结

10.1 Spring Cloud使用了那些组件?负载均衡如何实现的?如何鉴权?

nacos注册与发现以及配置中心管理、spring cloud gateway网关、ribbon负载均衡、feign服务http调用、hystrix熔断降级

注: 基于ribbon实现动态负载均衡,基于nginx实现静态负载均衡

10.2 nacos服务注册如何实现的?

10.3 nacos服务配置动态刷新如何实现?底层如何实现的?

@RefreshScope,注解底层是使用cglib动态代理来实现,而cglib是创建动态子类继承来完成功能的增强

10.4 如何使用Hystrix进行熔断降级?

如果A服务调用B服务、然后调用C服务,假设C服务故障无法响应,这时候使用故障备用服务器响应一个结果,或者直接给一个系统升级中的结果也行,不能因为C服务挂了,导致整个系统崩盘。

10.5 如何抗住高并发?

单应用服务A,可以进行业务细粒度的拆分,比如说crm系统,可以拆分为客户服务、订单服务、附件服务等,每个细粒度的应用服务,又可以进行多实例集群,这样就可以提高系统的吞吐量

10.6 高并发海量数据,如何处理?

分库分表(横向拆分:按省份,每个省份一个库,每个库再分多个订单表,比如0_order、1-order等,当然了,也可以对6556进行取余,余数作为库名,比如0_order、1_order,库划分好了,再分多个表,进行读写分离)

11 项目问题总结

11.1 Git使用总结

Git使用详解

11.2 项目中使用的Spring Boot版本是什么?MySQL版本是哪个?Spring Cloud版本是哪个?

  • Spring Version (5.2.15.RELEASE)
  • Spring Boot Version (2.3.12.RELEASE)
  • Spring Cloud Version (Hoxton.SR12)
  • Spring Cloud Alibaba Version (2.2.1.RELEASE)
  • Spring Data Redis Version (2.4.13)
  • Jedis Version (3.3.0)
  • MyBatis-Plus Version (3.3.2)
  • HikariCP Version (3.4.5)
  • MySQL Connector Version (8.0.22)

11.3 项目中使用的数据库连接池是哪个?常用的参数有哪些?

项目中使用的是HikariCP连接池。

常用的参数有:

  • maximum-pool-size 池中最大连接数(包括空闲和正在使用的连接)
  • minimum-idle 池中最小空闲连接数量。默认值10
  • pool-name 连接池的名字
  • auto-commit 是否自动提交池中返回的连接。默认值为true。
  • idle-timeout 空闲时间。仅在minimum-idle小于maximum-poop-size的时候才会起作用。默认值10分钟。
  • max-lifetime 连接池中连接的最大生命周期。当连接一致处于闲置状态时,数据库可能会主动断开连接。
  • connection-timeout 连接超时时间。默认值为30s,可以接收的最小超时时间为250ms。但是连接池请求也可以自定义超时时间

详细介绍可参考资料: HikariCP数据连接池基础介绍

11.4 项目中遇到问题,如何排查?

在项目中,我设计了一个日志管理模块,使用ELK进行了日志的搜集和可视化展示。另外,使用SkyWalking做了分布式链路跟踪,当线上出现问题时,我会第一时间查看是哪个服务挂掉了,然后迅速使用ELK查看那个服务的error级别的日志,可以快速查出问题

11.5 解决线上bug问题,举一个印象最深刻的例子?

  • 多次提交,重复数据问题,基于token做幂等校验
    具体实现: 我是基于Redis实现的,请求接口前,先获取token接口,然后第一次执行业务方法时,如果此时用户再次点击提交表单,这时候发现token已经存在,不允许再次提交,当业务方法执行完成后,删除相关的token。

  • 线上问题不能及时反馈,添加钉钉预警及时触达

12 数据结构和算法

这个根据实际情况发挥吧,笔者这块掌握的一般,也需要好好学习

13 Linux常用命令有哪些

Linux使用详解,10分钟带你玩转Linux基础操作
Linux使用详解(进阶篇)
Linux crontab 命令
Linux怎样使用top命令查看系统状态

14 前端问题

14.1 Vue生命周期函数有哪些?

Vue生命周期函数 也叫Vue生命周期钩子,就是Vue实例在某一时间点自动执行的函数。一共有11个生命周期函数,分别是:

  • beforeCreate : 创建Vue实例前的时候执行,
  • created : 创建Vue实例完成后执行,
  • beforeMount : Vue实例开始渲染前执行,
  • mounted : Vue实例渲染完成后执行,
  • beforeUpdate : Vue实例修改前执行,
  • updated : Vue实例修改完成后执行,
  • beforeDestroy : Vue开始消亡前执行,
  • destroyed : Vue实例消亡后执行,
  • activated :组件激活时调用。该钩子在服务器端渲染期间不被调用。
  • deactivated : 组件停用时调用。该钩子在服务器端渲染期间不被调用。
  • errorCaptured : 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

生命周期图示如下:
在这里插入图片描述

14.2 React生命周期函数有哪些?

这块可以参考资料链接: https://www.jianshu.com/p/514fe21b9914

15 有什么想要问的吗?

  • 如果有幸加入的话,主要负责的项目是什么?
  • 咱们项目用到了什么技术、架构?

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_67390963/article/details/125383398

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--