
(二)框架相关面试题
后端框架
1 Spring
1.1 说一下对Spring框架的理解
Spring 是一个轻量级的 IoC 和 AOP 容器框架。是为 Java 应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于 XML 的配置、基于注解的配置、基于 Java 的配置。
主要由以下几个模块组成:
Spring Core:核心类库,提供 IOC 服务;
Spring Context:提供框架式的 Bean 访问方式,以及企业级功能(JNDI、定时任务等);
Spring AOP:AOP 服务;
Spring DAO:对 JDBC 的抽象,简化了数据访问异常的处理;
Spring ORM:对现有的 ORM 框架的支持;
Spring Web:提供了基本的面向 Web 的综合特性,例如多方文件上传;
Spring MVC:提供面向 Web 应用的 Model-View-Controller 实现。
1.2 Spring 的优点?
(1)spring 属于低侵入式设计,代码的污染极低;
(2)spring 的 DI 机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
(3)Spring 提供了 AOP 技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
(4)spring 对于主流的应用框架提供了集成支持。
1.3 SpringIOC原理是什么?
IOC 就是控制反转,原来我们需要创建对象的,必须自己 new,但是现在有了 spring 容器,我们不需要再自己 new了,有两个好处,解耦,因为对象自己 new 完之后,无法更改,如果依赖对象发生异常,则会对我们自己的类造成影响。
springIOC,用户只需要进行配置,容器会在容器中自动实例化依赖对象,并且是单例模式,直接通过@autowired直接注入即可。
IOC 主要是通过反射实现,底层原理大概是这样的:
1、项目启动,加载 web.xml;
2、在 web.xml 根据 context-param 通过 contextListener 加载 spring 上下文,加载 spring 配置文件;
3、我猜,spring 可能通过 dom4j 解析 xml 配置文件;
4、获取 bean 标签,一旦发现 bean 标签,则读取 class 属性;
5、通过反射,Class.forName,获取该类的 class 字节码对象;
6、调用通过 Constructor 的 newInstance()实例化对象;
7、说 IOC 就离不开 DI,所谓 DI 是依赖注入,spring 的依赖注入有两种方式:构造注入和 setter 注入;
8、所谓构造注入,就是读取<bean>标签的<constract-arg index="" value="">,通过 class 字节码文件对象,获取构造参数;
9、通过 Constructor,直接构造对象,并对依赖对象进行初始化;
10、setter 注入,就是通过反射的<property name="" value="">;
11、通过 Field,设置依赖对象的属性值,dom4j 读取 property 标签的 name 和 value 属性值,根据 name 获取 Field 成员,一般来说会通过 setAccessible 暴力破解,然后通过 set 方法,指定 field 的对象和属性值,完成 setter 注入。
总结:以上就是对于 spring 的 IOC 和 DI 的理解,大概就是通过反射和 dom4j 完成控制反转和依赖注入。
Spring 的 IOC 有三种注入方式 :构造器注入、setter 方法注入、根据注解注入。
1.4 SpringAOP原理是什么?
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。AOP 实现的关键在于 代理模式,AOP 代理主要分为静态代理和动态代理。静态代理的代表为 AspectJ;动态代理则以 Spring AOP 为代表。
(1)AspectJ 是静态代理的增强,所谓静态代理,就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强,他会在编译阶段将 AspectJ(切面)织入到 Java 字节码中,运行的时候就是增强之后的 AOP 对象。
(2)Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:
(1)JDK 动态代理只提供接口的代理,不支持类的代理。核心 InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy 利用 InvocationHandler 动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
(2)如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。
(3)静态代理与动态代理区别在于生成 AOP 代理对象的时机不同,相对来说 AspectJ 的静态代理方式具有更好的性能,但是 AspectJ 需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理。InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy 是最终生成的代理实例; method是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
1.5 SpringAOP使用的场景有哪些?
AOP 用来封装横切关注点,具体可以在下面的场景中使用:
权限,日志跟踪优化,事务处理,缓存等
1.6 SpringBean的作用域有哪些?
scope 配置项有 5 个属性,用于描述不同的作用域。
(1)singleton(单例)使用该属性定义 Bean 时,IOC 容器仅创建一个 Bean 实例,IOC 容器每次返回的是同一个 Bean 实例。
(2)prototype(多例)使用该属性定义 Bean 时,IOC 容器可以创建多个 Bean 实例,每次返回的都是一个新的实例。
(3)request该属性仅对 HTTP 请求产生作用,使用该属性定义 Bean 时,每次 HTTP 请求都会创建一个新的 Bean,适用于WebApplicationContext 环境。
(4)session该属性仅用于 HTTP Session,同一个 Session 共享一个 Bean 实例。不同 Session 使用不同的实例。
(5)global-session该属性仅用于 HTTP Session,同 session 作用域不同的是,所有的 Session 共享一个 Bean 实例。
1.7 Spring 框架中的单例 Bean 是线程安全的吗?
Spring 框架并没有对单例 bean 进行任何多线程的封装处理。关于单例 bean 的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的 Spring bean 并没有可变的状态(比如 Serview 类和 DAO 类),所以在某种程度上说 Spring 的单例 bean 是线程安全的。如果你的 bean 有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态 bean 的作用域由“singleton”变更为“prototype”
1.8 Spring 如何处理线程并发问题?
在 Spring 中,绝大部分 Bean 都可以声明为singleton 作用域,因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理,解决线程安全问题。ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而 ThreadLocal 采用了“空间换时间”的方式。
ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
1.9 @Autowired 和@Resource 之间的区别是什么?
(1) @Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它 required 属性为 false)。
(2) @Resource 默认是按照名称来装配注入的,只有当找不到与名称匹配的 bean 才会按照类型来装配注入。
1.10 Spring 框架中都用到了哪些设计模式
(1)工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean 默认为单例模式。
(3)代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
1.11 Spring中bean的注入方式有哪些?
setter 方法
构造器方式
注解 方式
1.12 Spring中bean的生命周期是什么
1. 实例化 (调构造器)
2. 属性赋值 (set方法)
3. 初始化 (init-method)
4. 销毁(destroy-method)
1.13 Spring的事务了解吗?
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界如自己需要手动执行commit或者rollback,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。
1.14 Spring和SpringMvc及SpringBoot区别是什么?
Spring 就像一个大家族,有众多衍生产品例如 Boot,Security,JPA等等。但他们的基础都是Spring 的 IOC 和 AOP,IOC提供了依赖注入的容器,而AOP解决了面向切面的编程,然后在此两者的基础上实现了其他衍生产品的高级功能;Spring MVC是基于 Servlet 的一个 MVC 框架,主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种xml,properties处理起来比较繁琐。于是为了简化开发者的使用,Spring社区创造性地推出了Spring Boot,它遵循约定优于配置,极大降低了Spring使用门槛,但又不失Spring原本灵活强大的功能
2 SpringMvc
2.1 SpringMvc运行原理
1.用户发送请求到前端控制器(DispatcherServlet)
2.前端控制器(DispatcherServlet)接收请求并转发请求到处理器映射器HandlerMapping
3.处理器映射器HandlerMapping返回执行器链到前端控制器(DispatcherServlet)t
4.DispatcherServlet将请求转发到处理器适配器HandlerAdapter
5.处理器适配器HandlerAdapter执行具体的处理器,也就是Controller并返回ModelAndView到前端控制器(DispatcherServlet)
6.前端控制器(DispatcherServlet)将返回的结果转发到视图解析器ViewResolver中解析视图并返回view和model到前端控制器
7.前端控制器(DispatcherServlet)将返回的view及model进行渲染视图,最终响应用户
2.2 SpringMvc常用的注解有哪些
@requestMapping: 请求路径映射处理方法
@responseBody:响应数据为 json 数据
@requestBody:接收 json 数据,反序列化为 java 对象
@pathVariable:接收 restful 风格的参数,参数在 url 路径中,需要通过该注解获取参数
@ModelAttribute:将数据保存到 request 域中
@RequestParam:接收参数
2.3 @RestController 和 @Controller区别
Controller标明一个类为一个控制器,返回一个页面:
单独使用 Controller 不加 @ResponseBody 使用的话就要经过视图解析流程 (ViewResolve),最后返回给到前端一页面,这种情况属于比较传统的 Spring MVC 应用,对应前后端不分离的情况;
@RestController 返回 json 或者 xml 形式的数据:是@Controller和@ResponseBody的组合
@RestController 只返回对象,对象以 json 或者 xml 格式写入到 http 响应中,属于 RESTful 风格,也是目前流行的前后端分离方式;
2.4 Spring MVC的主要组件
前端控制器(DispatcherServlet):接收用户请求,给用户返回结果。
处理器映射器(HandlerMapping):根据请求的url路径,通过注解或者xml配置,寻找匹配的Handler。
处理器适配器(HandlerAdapter):Handler 的适配器,调用 handler 的方法处理请求。
处理器(Handler):执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装到ModelAndView对象中。
视图解析器(ViewResolver):将逻辑视图名解析成真正的视图View。
视图(View):接口类,实现类可支持不同的View类型(JSP、FreeMarker、Excel等)。
2.5 @RequestParam 和 @PathVariable 两个注解的区别
两个注解都用于方法参数,获取参数值的方式不同,
@RequestParam 注解的参数从请求携带的参数中获取
@PathVariable 注解从请求的 URI 中获取
2.6 @RequestBody和@RequestParam的区别
@RequestBody一般处理的是在ajax请求中声明contentType:"application/json;charset=utf-8"时候。也就是json数据或者xml数据。
@RequestParam一般就是在ajax里面没有声明contentType的时候,为默认的x-www-form-urlencoded格式。
2.7 Spring MVC的异常处理
可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。
使用系统定义好的异常处理器 SimpleMappingExceptionResolver
使用自定义异常处理器
使用异常处理注解
2.8 SpringMvc的Controller是不是单例模式?
单例模式。在多线程访问的时候有线程安全问题,解决方案是在控制器里面不要写可变状态量,如果需要使用这些可变状态,可以使用ThreadLocal,为每个线程单独生成一份变量副本,独立操作,互不影响。
2.9 介绍下 Spring MVC 拦截器
Spring MVC 拦截器对应HandlerInterceor接口,定义了三个方法,若要实现该接口,就要实现其三个方法:
前置处理(preHandle()方法):该方法在执行控制器方法之前执行。返回值为Boolean类型,如果返回false,表示拦截请求,不再向下执行,如果返回true,表示放行,程序继续向下执行。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理。
后置处理(postHandle()方法):该方法在执行控制器方法调用之后,且在返回ModelAndView之前执行。由于该方法会DispatcherServlet进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图,可通过此方法对请求域中的模型和视图做进一步的修改。
已完成处理(afterCompletion()方法):该方法在执行完控制器之后执行,由于是在Controller方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作。
可以通过拦截器进行权限检验,参数校验,记录日志等操作
2.10 Spring MVC 的拦截器和 Filter 过滤器有什么差别?
功能相同:拦截器和 Filter 都能实现相应的功能
容器不同:拦截器构建在 Spring MVC 体系中;Filter 构建在 Servlet 容器之上
使用便利性不同:拦截器提供了三个方法,分别在不同的时机执行;过滤器仅提供一个方法
3 Mybatis
3.1 什么是Mybatis?
(1)Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
(2)MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
(3)通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返回 result 的过程)。
3.2 Mybatis的特点有哪些?
(1)基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
(2)与 JDBC 相比,减少了代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;
(3)很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis
都支持)。
(4)能够与 Spring 很好的集成;
(5)提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
3、MyBatis 框架的缺点:
(1)SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求。
(2)SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
4、MyBatis 框架适用场合:
(1)MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。
(2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis 将是不错的选择。
3.3 Mybatis的原理/执行流程?
1、加载 sqlMapConfig 文件,创建 Configation 对象;
2、根据 Configation 创建 SessionFactory 对象;
3、通过 SessionFactory 生成 session,session 是恒程序员操作数据库的接口;
4、session 会话通过底层真正操作数据库的是 Excutor 接口操作数据库;
5、底层实际上是通过 MappedStatement 进行数据的操作,封装了 SQL 语句、输入参数、输出参数;
3.4 Mybatis有哪些动态sql标签?
if标签:判断是否符合条件
foreach标签 :对一个集合进行遍历
collection:合 遍历集合
item :遍历集合元素
index :元素索引
open始 :遍历开始
close:遍历结束
separator :遍历元素的分隔符 。
where 标签:补充相应的where的sql语句 ,解决了if标签在上面所容易造成错误的问题 。更好地简化了 where的条件判断
sql 标签:这个元素可以被用来定义可重用的SQL代码段,可以包含在其他语句中。
3.5 Mybatis的一对一和一对多如何处理?
一对一和一对多映射可以嵌套使用,查询结果用 ResultType 和 ResultMap 接收;
resultType:要求 pojo 和数据库字段、类型一模一样。
resultMap:可以根据映射与实体 bean 数据绑定,SQL 查询列名别名符合即可。
一对一的标签:association
一对多的标签:collection
3.6 #{}和${}有什么区别?
#{}是预编译处理,${}是字符串替换
mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
mybatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
使用 #{} 可以有效的防止SQL注入,提高系统安全性。
3.7 Mybatis如何分页?
Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。可以在 sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
3.8 Mybatis如何获取自增返回的主键?
获取返回主键有3种方式
第一:在insert标签中声明useGeneratedKeys="true",使用后 MyBatis会使用JDBC的getGeneratedKeys方法取出由数据库内部生成的主键,获得主键值之后将其赋给keyProperty配置的 id 属性
第二:采用 <selectKey>标签获取主键的值 , 这种方式对提供和不提供主键自增功能的数据库同样适用
第三:可以在insert方法上添加@Option 注解 , 在这个注解上配置了useGeneratedKeys 和 keyProperty属性
3.9 在Mapper中如何传递多个参数?
有四种方式可以传递
第一:若Dao层函数有多个参数,那么其对应的xml中,#{0}代表接收的是Dao层中的第一个参数,#{1}代表Dao中的第二个参数,以此类推。
第二:使用@Param注解来显式指定每个参数的名称
第三:将多个参数封装成Map并传递到Mapper中
第四:如果Dao层函数传递的是一个对象,该对象包含多个参数,MyBatis会将该对象看做是一个参数,并且会自动地将对象中的属性值映射到Mapper中的SQL语句中。
3.10 在使用Mapper接口开发时有哪些要求?
① Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同
② Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
③ Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
④ Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径。
3.11 说一下Mybatis中的一级缓存和二级缓存
一级缓存:是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的是 SqlSession 之间的缓存数据区(HashMap)是互相不影响。自动开启
二级缓存:是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。需要手动开启,在核心配置文件的settings标签中开启二级缓存,在指定的mapper文件中加入cache就可以了
3.12 当实体类中的属性名和表中的字段名不一样 ,怎么办?
第一种方法:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致;
第二种方法:通过来映射字段名和实体类属性名的一一对应的关系;
第三种方法:在实体类通过@Column注解也可以实现;
3.13 MyBatis 和MyBatisPlus的区别?
1、mybatis 是一个优秀的持久性框架,它简化了 jdbc 的代码,可以使用简单的 xml 或注解来配置来映射;
2、plus加强版的意思,对 mybatis 继续简化。
3、mybatis-plus 是 mybatis 的增强工具,它在 mybatis 的基础上又添加了许多的功能,在 mybatis-plus 上既可以使用自身特有的功能,还可以使用 mybatis 的原生功能;所以说mybatis-plus 是为简化开发,提高效率而生。
4 SpringBoot
4.1 什么是SpringBoot?
spring boot来简化spring应用开发,约定大于配置,去繁从简,just run就能创建一个独立的,产品级别的应用,内置tomcat,简化maven
4.2 SpringBoot有哪些优点?
(1)简化配置, 它实现了自动化配置 ;
(2)提供maven极简配置,各种便捷的starter启动器;
(3)基于Spring构建,使开发者快速入门,门槛很低;
(4)内置tomcat服务器,SpringBoot可以创建独立运行的应用而不需要依赖于容器;
(5)为微服务SpringCloud奠定了基础,使得微服务的构建变得简单;
4.3 springboot自动配置的原理是什么样的?
这要从springboot项目的核心注解@SpringbootApplication说起了,这个注解包含了三个注解,其中一个是@EnableAutoConfiguration注解,这个注解主要是开启自动配置的,这个注解会"猜"你将如何配置 spring,spring会根据反射的原理,创建这些对象,放到IOC容器中,加载时需要的参数,通过JavaConfig的方式加载配置文件中的参数然后创建了对应的对象,这就是自动配置的原理。
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能
@ComponentScan:Spring组件扫描,从当前类所在的包以及子包扫描,之外的包扫描不到,所以我们在开发的时候,所有的类都在主类的子包下
4.4 说一下SpringBoot启动的过程
springboot启动流程分为两部分,第一是准备阶段,第二是运行阶段
准备阶段:
1.准备bean源,就是bean的来源
2.推断,推断加载的有哪些starter
3.加载上下文初始化器,调整ApplicationContext
运行阶段:
1.加载springApplication运行时监听器,需要传入准备阶段加载的监听器
2.创建ApplicationContext初始化应用
3.创建应用上下文
4.5 如何重新加载SpringBoot而不需要重启服务器?
可以使用DEV工具实现,通过依赖关系,节省任何更改,嵌入式服务器tomcat将重新启动
4.6 Springboot的核心配置文件有几个,有什么区别?
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
使用SpringCloudConfig配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
一些固定的不能被覆盖的属性;
一些加密/解密的场景;
5 SpringCloud及SpringCloudAlibaba
5.1 什么是SpringCloud?
Spring Cloud 是一个服务治理平台,是若干个框架的集合,提供了全套的分布式系统解决方案。包含了:服务注册与发现、配置中心、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列等等
5.2 SpringCloud和SpringCloudAlibaba技术对比(组件)
5.3 什么是SpringCloudAlibaba?
SpringCloudAlibaba其实是对SpringCloud的一个升级版,里面包含了
服务限流降级:默认支持 OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
5.4 SpringCloudAlibaba中有哪些常用的组件?
Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
5.5 服务注册中心的比较(Eureka,Zookeeper,Nacos)
根据CAP理论对注册中心进行分类:
- 保证CP(注重一致性):Zookeeper、Consul
- 保证AP(注重可用性):Eureka
- 既支持CP又支持AP:Nacos
Zookeeper通过Zab协议保证强一致性:所有的写请求必须经过leader节点传递给其他follower节点
Eureka保证高可用性:Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务,而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用,只不过查到的信息可能不是最新的(不保证强一致性)。
Nacos既支持AP模式又支持CP模式:AP模式:不需要存储服务级别信息且服务实例是通过nacos-clinet注册,且能够保持心跳上报,可采用AP模式。如SpringCloud服务。AP模式下只支持注册临时实例
5.6 服务调用框架的比较(Ribbon,OpenFeign,Dubbo)
Ribbon:
- Ribbon是一套客户端负载均衡的工具,使用时需与RestTemplate配合使用
- 使用是需要模拟http请求然后使用RestTemplate发送给其他服务,步骤比较繁琐。
- 负载均衡:支持轮询、随机、空闲策略、响应时间策略
OpenFeign
- 同样使用HTTP协议进行通讯
- 使用时只需创建一个接口并使用注解(@FeignClient)的方式配置, 即可完成对服务提供方的接口绑定,是对Ribbon+RestTemplate的进一步封装
- OpenFeign内部集成了 Ribbon,本质上是通过Ribbon完成负载均衡功能
Dubbo
- 支持多种传输协议:Dubbo、Rmi、http、redis。适合数据量小、高并发和服务提供者远远少于消费者的场景
- 负载均衡:支持随机、轮询、活跃度、Hash一致性,负载均衡的算法可以精准到某个服务接口的某个方法。
5.7 服务降级框架的比较(Hystrix和Sentinel)
1.关于服务降级的概念
服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的”雪崩效应”.
服务熔断:熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。如Hystrix默认发现5秒内20次调用失败就会启动熔断机制。
服务降级:某个服务故障后,向调用方返回一个符合预期的、可处理的备选响应(Fallback)。发生降级的场景:程序运行异常、服务调用超时、服务熔断、线程池/信号量被打满
服务限流:限制服务的请求速率
2.Hystrix和Sentinel的比较
Hystrix本身就是一个非常出色的熔断降级框架,Sentinel则是在Hystrix的基础上对其进行进一步的升级
Sentinel使用更方便:Sentinel提供了一个非常简洁的控制台界面,在控制台界面中即可非常方便地配置限流降级规则
Sentinel功能更丰富:Sentinel除了降级和熔断功能外,还可以配置限流规则、热点规则、系统规则,且规则的配置项更多更精确,使用更加灵活5.3.5 OpenFeign和Feign有什么区别?
相同点:二都都有服务远程调用的功能
不同点1、Feign 本身不支持 Spring MVC 的注解,它有一套自己的注解。openfeign 支持springMVC注解,我们springcloud中用的是openfeign
5.8 OpenFeign的底层原理?
- 首先通过@EnableFeignCleints注解开启FeignCleint
- 根据Feign的规则实现接口,并加@FeignCleint注解
- 程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
- 当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
- RequesTemplate再生成Request
- Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
- 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
5.9 nacos原理(注册中心和配置中心)
服务注册原理:server向nacos发起注册任务请求,并维持一个心跳检测的定时任务,naocs会通过阻塞队列异步处理这些请求,并实时的通过UDP推送到client,为防止UDP数据丢失,client也会通过定时任务每隔10s向nacos发送拉取请求,但这个心跳机制是长心跳,就是当nacos发现服务列表没有发生变化的时候,并没有对当前的请求做响应,等服务列表改变,nacos再返回(当然如果每隔10秒拉去请求的时候发生改变了,nacos会立马做出相应)。
项目启动后服务会读取配置信息,而很多配置的话是动态的,比如环境切换,正常来讲当我们修改配置后需要重启服务,对于正式环境来讲,重新打包部署的代价是相当高的,所以我们将动态配置,配置在配置中心,如果需要修改的话,只需要改配置中心的配置信息就行,服务会基于长心跳动态拉去更新的配置。(通过是通refreshscope注解实现的,底层使用代理模式使用反射动态更新)支持配置信息的持久化,支持集群。
- Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
- Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
5.10 OpenFeign和Feign有什么区别?
相同点:二都都有服务远程调用的功能,他们底层都是内置了Ribbon,去调用注册中心的服务
不同点:Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。
OpenFeign是SpringCloud自己研发的,在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。是SpringCloud中的第二代负载均衡客户端。
5.11 Ribbon(负载均衡)
之前项目上负载均衡这块儿用过ribbon,ribbon负载均衡的策略总共有七种,之前我们主要通过三种策略来实现,轮询、随机、权重,默认使用的是轮询。
轮询底层每次记录当前服务的位置,当有新的任务分配的时候,以当前位置加一作为即将接受任务的角标,然后根据该角标从集群服务的列表中拿到对应的服务,然后将这个任务分配给该服务。
随机其实就是拿到一个不超过集群数量的随机值,然后使用该随机值作为下标去集群服务的列表中获取对应的服务,然后将当前任务交给他执行(这种算法策略在少量请求时,不能做到均衡分配,但如果请求量大的时候,每台集群的服务器接受的任务趋于均衡)
权重策略在初始化的时候会启动一个定时任务,每隔30S重新计算一次,具体的计算方式就是通过集群服务器的平均响应时间减去当前服务器的响应时间,作为分配给当前服务器的权重比例(当前服务器压力越大,响应越慢,响应时间越长,算出来的结果越小,权重越低)。
源码追踪
为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。
显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。
我们进行源码跟踪:
1.LoadBalancerIntercepor
可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:
- request.getURI():获取请求uri,本例中就是 http://user-service/user/8
- originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-service
- this.loadBalancer.execute():处理服务id,和用户请求。
这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。
2.LoadBalancerClient
继续跟入execute方法:
代码是这样的:
- getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
- getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务
3.负载均衡策略IRule
在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:
我们继续跟入:
继续跟踪源码chooseServer方法,发现这么一段代码:
我们看看这个rule是谁:
这里的rule默认值是一个RoundRobinRule,看类的介绍:
这不就是轮询的意思嘛。
到这里,整个负载均衡的流程我们就清楚了。
SpringCloud Ribbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改
基本流程如下:
- 拦截我们的RestTemplate请求http://userservice/user/1
- RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
- DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
- eureka返回列表,localhost:8081、localhost:8082
- IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
- RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
5.12 sentinel(限流)
我们公司限流用的是sentinel,sentinel限流主要使用两种方式,一种是QPS,一种是线程限流。QPS就是我们可以在客户端设置某个接口在一秒内最多访问多少次,线程限流就是限制某个接口同一时间最多几个线程调用。流控效果我们大部分接口采用的是排队等待,也就是说如果某个请求被限流了,也不会直接让他失败,而是等并发量下去的时候也给请求者一个具体的响应结果。
另外部分接口进行了熔断降级的设置,就是如果一个接口访问时间太长或者直接出现异常情况,我们先采用降级规则返回托底数据,如果超过降级的阀值,直接对服务进行熔断,给用户返回熔断的托底数据(熔断和降级都是返回提前设置好的托底数据,但降级还会尝试访问服务,而熔断直接返回数据,不会进行访问)。
5.13 gateway(网关)
因为我们的项目是采用微服务架构,微服务之间调用的话如果没有网关的话那么客户端多次请求不同的微服务会增加客户端代码的复杂性,因为配置要增多,其次认证比较复杂,每个服务都需要进行独立认证,另外存在跨域问题,那么这里我们加入了springcloud gateway 它封装了应用系统的内部结构,给客户端提供统一的服务,比如认证,鉴权,监控,路由转发等。当用户请求到来时,先到达网关进行一系列的认证鉴权等。不仅提供了统一的路由方式,且基于filter过滤器链的方式提供了网关最基本的功能。
其中路由是gateway中最基本的组件之一,表示一个具体的路由信息载体,包含的信息有:
id:路由标识符,区别于其他route
uri:路由指向的uri,客户端请求最终被转发到的微服务
order:用于多个route之间的排序,值越小,优先级越高
predicate:断言的作用进行条件判断,返回真才会真正的执行路由
filter:过滤器用于修改请求和相应
原理:
1.当发送请求到gateway时,gateway根据配置的路由规则,找到对应的服务名称
2.如果某个服务存在多个实例,gateway会根据负载均衡算法(比如轮询)丛中挑选出一个实例,然后将请求转发过去
3.服务实例返回的相应结果会再经过gateway转发给请求方
5.14 Ribbon和Feign的区别?
1.Ribbon都是调用其他服务的,但方式不同。
2.启动类注解不同,Ribbon是@RibbonClient feign的是@EnableFeignClients
3.服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
4.调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。
5.15 服务注册和发现是什么意思?spring cloud如何实现?
当我们开始一个项目时,我们通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署,添加和修改这些属性变得更加复杂。有些服务可能会下降,而某些位置可能会发生变化。手动更改属性可能会产生问题。 nacos服务注册和发现可以在这种情况下提供帮助。由于所有服务都在nacos服务器上注册并通过调用nacos服务器完成查找,因此无需处理服务地点的任何更改和处理。
1.服务发布时,指定对应的服务名,将服务注册到 注册中心(eureka zookeeper,nacos)
2.注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现
5.16 什么是服务熔断?什么是服务降级?
在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。
所谓的服务熔断指的是某个服务故障或异常一起类似显示世界中的“保险丝"当某个异常条件被触发就直接熔断整个服务,而不是一直等到此服务超时。服务熔断就是相当于我们电闸的保险丝,一旦发生服务雪崩的,就会熔断整个服务,通过维护一个自己的线程池,当线程达到阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值。