Spring面试题

Spring面试题

连接视频

1、spring是什么?

轻量级的开源 J2EE 框架,它是一个容器框架,用来装 javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把 strutshibernate 粘和在一起运用,可以让我们的企业开发更快、更简洁

Spring一个轻量级的控制反转(ioc)和 面向切面(AOP)的容器框架

  • 从大小开销两方面而言spring都是轻量级的
  • 通过控制反转(ioc)的技术达到松耦合的目的
  • 通过了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统逻辑服务进行内聚性开发
  • 包含并管理应用对象(bean)的配置和生命周期,这个意义上是一个容器
  • 将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架

2、谈谈你对AOP的理解

系统是有许多不同的组件所组成的,每一个组件个负责一块特定的功能。除了实现自身核心功能外,这些组件还经常承担者额外的职责。例如日记、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。

当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日记功能等。

日记代码往往水平地散布在所有对象层次中,而与它所散布到对象的核心功能毫无关系。

在OOP设计中,它导致大量代码的重复,而不利于各个模块的重用。

AOP:将程序中的交叉业务逻辑(比例安全、日记、事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情


3、谈谈你对IOC的理解

容器概念、控制反转、依赖注入

ioc容器:实际上是个map(key、value),里面存的是各种对象(在xml里面配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里,扫描到上述注解的类还是通过反射创建对象放到map里。

这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired、resource等注解、xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或者id注入;id就是对象名)。

控制反转:

没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

通过前后的对比,不难看出来:对象A获得依赖对象B的过程,有主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成为了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有把IOC容器比喻成“粘合剂”的由来。

依赖注入:

“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为由IOC容器主动注入。
依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。


4、BeanFactory和ApplicationContext有什么区别

ApplicationContext是BeanFactory的子接口

ApplicationContext提供了更完整的功能:
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供监听器中注册bean事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文,使得每一个上下文都专注与一个特定的层次,比如应用的web层。

  • BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的spring的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才会抛出异常。

  • ApplicationContext,它是在容器启动时,一次创建了所有Bean。这样,在容器启动时,我们就可以发现spring中存在的配置问题错误,这样有利于检查所有依赖属性是否注入。ApplicationContext启动后预载所有的单实例Bean,通过预载入单实例Bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

  • 相对于基本的BeanFactory,ApplicationContext唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。


5、描述一下Spring Bean的生命周期

1、解析类得到BeanDefinition

2、如果有多个构造方法,则要推断构造方法

3、确定好构造方法后,进行实例化得到一个对象

4、对对象中的加了@Autowired注解的属性进行填充

5、回调Aware方法,比如BeanNameWare,BeanFactoryAwre

6、调用BeanPostProcessor的初始化前的方法

7、调用初始化方法

8、调用BeanPostProcessor的初始化后的方法,在这里会进行AOP

9、如果当前创建的bean是单实例的则会把bean放入单例池

10、使用bean

11、Spring容器关闭时调用DisposableBean中destory()方法


6、解释下Spring支持的几种bean作用域

  • singleton:默认,每个容器只有一个bean的实例,单例的模式由BeanFactory自身来维护。该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。

  • prototype:为每一个bean请求提供一个实例。在每次注入是会创建一个新的对象

  • request:bean被定义为每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。

  • session:与request范围类似,确保每个session中有一个bean实例,在session过期后,bean会随之失效

  • application:bean被定义为在ServletContext的生命周期中复用一个单例对象。

  • websocket:bean被定义为在websocket的生命周期中复用一个单例对象。

    global-seeion:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时。它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。


7、Spring框架中的单例Bean是线程安全的吗

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程封装处理。

如果Bean是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域 把“singleton”改为“prototype”这样每次请求Bean就相当于 new Bean() 这样就可以保证线程安全了。

  • 有状态就是有数据存储功能
  • 无状态就是不会保存数据 controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法而且多线程调用一个实例的方法,会再内存中复制变量,这是自己的线程的工作内存,是安全的。

Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使用Threadlocal为不同线程维护一套独立的connection副本,保证线程之间不会相互影响(Spring是如何保证事务获取同一个Connection的)

不要在bean中声明任何有状态的实例变量或者类变量,如果必须如此,那么就使用Threadlocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么只能使用synchronized、lock、CAS等这些实现线程同步的方法了。


8、Spring框架中都用到了哪些设计模式

简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品。

Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一标识获取Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

工厂方法:

实现了FactoryBean接口的bean是一类叫做factory 的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getObject()方法的返回值。

单例模式:保证一个类仅有一个实例,并提供一个访问的全局访问点

spring对单例的实现:spring中的单例模式完成了后半话,即提供了全局访问点BeanFactory。但没有从构造器级别控制单例,这是因为spring管理的是任意的java对象。

适配模式:

spring定义一个适配接口,使得每一种Controller有一种对应适配器实现类,让适配器代替controller执行相应的方法,这样在扩展Controller时,只需要增加一个适配器类就完成 SpringMVC的扩展了。

装饰器模式:动态地给一个对象添加一些额外职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

Sprin中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator

动态代理:

切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。
SpringAOP就是以这种方式织入切面的。
织入:把切面应用到目标对象并创建新的代理对象过程。

观察者模式:

spring的事件驱动模型使用的是 观察者模式,spring中observer模式常用的地方是listener的实现。

策略模式:

Spring框架的资源访问Resource接口,该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。


9、Spring事务的实现方式和原理以及隔离级别

在使用spring框架时,可以有两种使用事务的方式,一种是编程式的,一个种是申明式的,@Transactional注解就是申明式的。

首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些让程序员更加方便操作事务的方式。

比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或者失败。

在一个方法上加@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进回滚。

但然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。

spring事务隔离级别就是数据库的隔离级别:外加一个默认级别

  • read uncommitted(未提交读)
  • read committed(提交读,不可重复读)
  • repeatable read(可重复读)
  • serializable(可串行化)

数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,请问这时隔离级别是以哪个一个为准?
以Spring配置为准,如果spring配置的隔离级别数据库不支持,效果取决于数据库


10、spring事务传播机制

多个事务方法相互调用时,事务如何在这些方面传播

方法A是一个事务方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法定义的事务传播类型所决定。

REQUIRED(spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

MANDATORY:当前存在事务,则加入当前事务,如果当前没有事务,则抛出异常。

REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。

NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起该事务。

NEVER:不使用事务,如果当前存在事务,则抛出异常。

NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

和REQUIRED_NEW的区别
REQUIRED_NEW是新建一个事务并且新开启的这个事务与原事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称为一个子事务),在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原事务回滚,不会影响新开启的事务。

和REQUIRED的区别
REQUIRED情况下,调用存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用是否catch其异常,事务都会回滚,而NESTED情况下,被调用方发送异常时,调用方可以catch其异常,这样只要子事务回滚,父事务不受影响。


11、spring事务什么时候会失效

spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况下有如下几种

1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理,而是UserService对象本身。
解决方法很简单,让那个this变成UserService的代理类即可

2、方法不是public的

@Transactional 只能用于 public 的方法上,否则事务不会生效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

3、数据库不支持事务

4、没有被spring管理

5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)


12、什么是bean的自动装配,有哪些方式

开启自动装配,只需要在xml配置文件 <bean> 中定义 “autowire”属性。

<bean id=“customer” class=“com.xx.xx.Customer” autowire=" " />

autowire属性有五种装配的方式:

  • no:缺省情况下,自动装配是通过“ref”属性手动设定。

    手动装配:以value或ref的方式明确指定属性都是手动装配。
    需要通过“ref”属性来连接bean。

  • byName:根据bean的属性名称进行自动装配。

    Customer的属性名称是 person,Spring会将bean id为person的bean通过setter方法进行自动装配。
    <bean id =“customer” class=“com.xx.xx.Customer” autowire=“byName”/>
    <bean id=“person”,class=“com.xx.xx.Person”/>

  • byType:根据bean的类型进行自动装配。

    Customer的属 person的类型为Person,Spring会将Person类型通过setter方法进行自动装配。
    <bean id =“customer” class=“com.xx.xx.Customer” autowire=“byType”/>
    <bean id=“person”,class=“com.xx.xx.Person”/>

  • constructor:类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型相同,则进行自动装配,否则导致异常。

    Customer构造函数的参数person的类型为Person,Spring会将Person类型构造方法进行自动装配
    <bean id =“customer” class=“com.xx.xx.Customer” autowire=“constructor”/>
    <bean id=“person”,class=“com.xx.xx.Person”/>

  • autodetect:如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。

    如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。

    @Autowired自动装配bean,可以在字段、setter方法、构造函数上使用。


13、Spring Boot、Spring MVC 和 Spring 有什么区别

spring是一个ICO容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题,更方便将不同类不同方法中的共同处理抽取切面,自动注入给方法执行,比如日记、异常等。

springMVC是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端

springBoot是spring提供一个快速开发工具包,让程序能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合看一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用


14、Spring MVC 工作流程

1、用户发送请求至前端控制器 DispatcherServlet。

2、DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器

3、处理器映射器找到具体的处理器(可以根据 xml配置、注解进行查找),生成处理器及处理拦截器(如果有则生成)一并返回给 DispatcherServlet。

4、DispatcherServlet 调用 HandlerAdapter 处理器适配器

5、HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)

6、Controller 执行完成后返回 ModelAndView。

7、HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet 。

8、DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。

9、ViewReslover 解析后返回具体 View 。

10、DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet 响应用户。


15、Spring MVC 的主要组件

Handler:也就是处理器。它直接应对者 MVC 中的 C 也就是 Controller 层,它的具体表现形式有很多,可以是类,也可以是方法。在 Controller 层中 @RequestMapping 标注的所有方法都可以看成是一个 Handler ,只要可以实际处理氢气就可以是 Handler

1、HandlerMapping
initHandlerMappings(context),处理器映射器,根据用户请求的资源 uri 来查找 Handler 的。在 SpringMVC 中会有很多请求,每个请求需要一个 Handler 处理,具体接收到一个请求之后使用哪个 Handler 进行,这就是 HandlerMapping 需要做的事情。

2、HandlerAdapter
initHandlerApapter(contex),适配器。因为 SringMVC 中的 Handler 可以是任意的形式,只要能处理请求就ok,但是 Servlet 需要的处理方法的结构却是固定的,都是以 request 和 response 为参数的方法。如何让固定的 Servlet 处理方法调用灵活的 Handler 来进行处理呢? 这就是 HandlerAdapter 要做的事情。
Handler 是用来干活的工具;HandlerMapping 用来根据需要干的活找到相应的工具;HandlerAdapter 是使用工具干活的人。

3、HandlerExceptionResolver
initHandlerExceptionResoler(context),其它组件都是用来干活的。在干活的过程中难免会出现问题,出现问题后怎么办?这需要有一个专门的角色对异常情况进行处理,在 SpringMVC中就是 HadlerExceptionResolver。具体来说次组件的作用是根据异常设置 ModelAndView,之后交给 render 方法进行渲染。

4、ViewResolver

initViewResolver(context),ViewResolver用来将 String类型的视图名和 Local 解析为 View 类型的视图。View 是用来渲染页面的,也就是将程序返回的参数填入模板里,生成 html (也可能是其他类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是 ViewResolver 主要要做的工作,ViewResolver 需要找到渲染用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。

5、RequestToViewNameTranslator
initRequestToViewNameTranslator(context),ViewResolver 是根据ViewName 查找 View,但有的 Handler 处理完成后并没有设置 View 也没有设置 ViewName,这时就需要从 request 获取 ViewName了,如何从 request 中获取ViewName就是 RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator 在 Spring MVC 容器里只可以配置一个,所以所有 request 到 ViewName 的转换规则都要在一个 Translator 里面全部实现。

6、LocaleResolver
initLocaleResolver(context),解析视图需要两个参数:一个视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从 request 解析出 Locale,Locale就是 zh-cn 之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC 主要有两个地方用到了Locale:一是ViewResolver 视图解析的时候;二是到国际化资源或者主题的时候。

7、ThemeResolver
initThemeResolver(context),用于解析主题。SpringMVC 中一个主题对应的一个 properties 文件,里面存放着跟当前主题相关的所有资源,如图片。css样式等。SpringMVC 的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC 中跟主题相关的类有 ThemeResolver、ThemeSource 和 Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是 ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource 的工作。最后从主题中获取资源就可以了。

8、MultipartResolver
iniMultipartResolver(context),用于处理上传请求。处理方式是将普通的 request 包装成 MultipartHttpServletRequest,后者可以直接调用 getFile方法获取 File,如果上传多个文件,还可以调用 getFileMap 得到 FileName -> File 结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将 request包装成 MultipartHttpServletRequest,处理完成后清理上传过程中产生的临时资源。

9、FlashMapManager
initFlashMapManager(context),用来管理 FlashMap的,FlashMap主要用在 redirect中传递参数。


16、Spring Boot 自动配置原理

@import + @Configuration + Spring spi

自动配置类由各个 starter 提供,使用 @Configuration + @Bean 定义配置类,放到 META-INF/spring.factories 下

使用 Spring spi 扫描 META-INF/spring.factories 下的配置类

使用@Import 导入自动配置类

在这里插入图片描述


17、如何理解 Spring Boot 中的 Starter

使用spring + springMVC使用,如果需要引入 mybatis框架,需要到 xml中定义 mybatis需要的的bean

starter 就是定义一个 starter的jar包,写一个 @Configuration配置类、将这些bean定义在里面,然后在starter包的 META-INF/spring.factories中写入该配置类,springboot会按照约定加载该配置类

开发人员只需要将相应的 starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应功能了,比如 mybatis-spring-boot-starter,spring-boot-starter-redis

18、什么是嵌入式服务器?为什要使用嵌入式服务器

节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行

只需要安装了 java 的虚拟机,就可以直接在上面部署应用程序了
springboot已经内置了tomcat.jar,运行main方法时回去启动tomcat,并利用tomcat的spi机制加载springmvc


19、mybatis的优缺点

优点:

1、基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响, SQL 写在 xml 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。

2、与 JDBC 相比,减少 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;

3、很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBaits 都支持)。

4、能够与 Spring 很好的集成

5、提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

缺点:
1、SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求。

2、SQL 语句依赖与数据库,导致数据库移植性差,不能随意更换数据库。


20、mybatis 与 Hibernate 对比

SQL 和 ORM 的争论,永远都不会终止

开发速度的对比:
Hibernate的真正掌握要比 Mybatis难些。Mybatis框架相对简单很容易上手,但也相对简陋些。
比起两者的开发速度,不仅要考虑到两者的特性及功能,更要根据项目需求去考虑究竟哪一个更合适项目开发,比如:一个项目中用到的复杂查询基本没有,就是简单的增删改查,这样选择hibernate效率就很快了,因为基本的sql语句已经被封装好了,根本不需要你去写sql语句,这就节省大量的时间,但是对于一个大型项目,复杂语句较多,这样再去选择hibernate就不是一个太好的选择,选择mybatis就会快许多,而且语句的管理也比较方便。

开发工作量的对比:
Hibernate和MyBatis都有相应的代码生成工具。可以选择简单基本的DAO层方法。针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap。而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注与业务流程。

sql优化方面:
Hibernate的查询会将表中的所有字段查询出来,这一点会有性能消耗。Hibernate也可以自己写sql来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性。而Mybatis的SQL是手动编写的,所以可以按需求指定查询的字段。

对象管理的对比:
Hibernate 是完整的对象/关系映射解决方案,它提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节。也就是说,相对于常见的 JDBC/SQL 持久层方案中需要管理 SQL 语句,Hibernate采用了更自然的面向对象的视角来持久化 java 应用中的数据。
换句话说,使用 Hibernate 掌握妥当。只有开发者在进行系统性能调优的时候才需要了解。而 MyBatis在这一块没有文档说明,用户需要自己对对象自己进行详细的管理。

缓存机制对比:

形同点:都可以实现自己的缓存或使用其他第三方缓存方案,创建适配器来完成覆盖缓存行为。

不同点:Hibernate的二级缓存配置在 SessionFactory生成的配置文件中进行详细配置,然后再在具体表现的 表-对象 映射中配置是哪种缓存。

Mybatis的二级缓存配置都是在每个具体的 表-对象 映射中进行详细配置,这样针对不同的表可以自己定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref 来实现。

两者比较:因为Hibernate对查询对象有良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

而Mybatis在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。

Hibernate功能强大,数据库无关性好,O/R映射能力强,如果你对Hibernate相当精通,而且对Hibernate进行了适当的封装,那么你的项目整个持久层代码会相当简单,需要写的代码少,开发速度很快,非常爽。

Hibernate的缺点就是学习门槛不低,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好Hibernate方面需要你的经验和能力都很强才行。

iBATIS入门简单,即学即用,通过了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。

iBATIS的缺点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。


21、#{} 和 ${} 区别是什么

#{} 是预编译处理、占位符, ${} 是字符串替换、是拼接符。

Mybatis 在处理 #{} 时,会将 sql 中的 #{} 替换为 ? 号,调用 PreparedStatement 来赋值;

Mybatis 在处理 ${} 时,就是把 ${} 替换为变量的值,调用 Statement 来赋值;

#{} 的变量替换是在 DBMS 中、变量替换后,#{} 对应的变量自动加上单引号

${} 的变量替换是在 DBMS 外,变量替换后, ${} 对应的变量不会加上单引号

使用 #{} 可以有效的防止 SQL 注入,提高系统安全性。


22、简述 Mybatis 的插件运行原理,如何编写一个插件

答:Mybatis 只支持针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,拦截那些你指定需要要拦截的方法。

编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept() 方法,然后在插件编写注解,指定要拦截哪一个接口的哪些方法即可,在配置文件中配置编写的插件。

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.stereotype.Component;

import java.sql.Statement;
import java.util.Properties;


@Intercepts({@Signature(type = StatementHandler.class,method = "qurty",
        args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class,method = "udate",
                args = {Statement.class}),
        @Signature(type = StatementHandler.class,method = "batch",
                args = {Statement.class})})
@Component
public class MybatisConfig implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        invocation.proceed();//执行具体的业务逻辑
        return null;
    }

    @Override
    public Object plugin(Object o) {
        return null;
    }

    @Override
    public void setProperties(Properties properties) {

    }
    
}


23、索引的基本原理

索引用来快速寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。

索引的原理:就是把无序的数据变成有序的查询

1、把创建了索引的列的内容进行排序

2、对排序结果生成倒排表

3、在倒排表内容上拼上数据地址链

4、在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体的数据


24、mysql聚簇和非聚簇索引的区别

都是B+树的数据结构

  • 聚簇索引:将数据存储与索引放到一块,并且是按照一定的顺序组织的,找到索引也就是找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放磁盘上的

  • 非聚簇索引:叶子节点不存储数据,存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。

优势:
1、查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率更高
2、聚簇索引对于范围查询的效率很高。因为其数据是按照大小排列的
3、聚簇索引适合用在排序的场合,非聚簇索引不适合


劣势:
1、维护索引很昂贵,特别是插入新行或者主键被更新导至要分页(page split)的时候。建议在大量插入新行后,选在负载较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片
2、如果表因为使用UUID(随机id)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能比全表扫描更慢,所以建议使用int的auto_increment作为主键
3、如果主键比较大的话,那辅助索引将会变的更大,因为辅助索引的叶子存储的是主键值;过长的主键值,会导致非叶子节点占用更多的物理空间

innoDB中一定有主键,主键一定是聚簇索引,不手动设置,则会使用unique索引,没有unique索引,则会使用数据库内部的一行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。

myISAM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两颗B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。

如果涉及到大数据的排序、全表扫描、count之类的操作的话,还是myISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。


25、mysql索引的数据结构,各自优劣

索引的数据结构和具体存储引擎的实现有关,在MySQL 中使用较多的索引有Hash索引,B+树索引等,InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能快;其余大部分场景,建议使用BTree索引。

B+树:

B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度值不超过1,而且同层级的节点间有指针相互链接。
在B+树上常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因此,B+树索引被广泛应用与数据库、文件系统等场景。

在这里插入图片描述

哈希索引:

哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需要一次哈希算法即可立刻定位相应的位置,速度非常快

在这里插入图片描述

如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;前提是键值都是唯一的。如果键值不是唯一的,就需要找到该键所在的位置,然后再根据链表往后扫描,直到找到相应的数据;

如果是范围查询检索,这时候哈希索引毫无用武之地,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;

哈希索引也没有办法利用索引完成排序,以及 like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);

哈希索引也不支持多列联合索引的最左匹配规则;

B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在哈希碰撞问题。


26、索引设计的原则

查询更快、占用空间小

1、适合索引的列是出现在where子句的列,或者连接子句中指定的列

2、基数较小的表,索引效果较差,没有必要在此列建立索引

3、使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。

4、不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。

5、定义有外键的数据列一定要建立索引

6、更新频繁字段不适合创建索引

7、若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就是三种,区分度实在太低)

8、尽量的扩展索引,不要新建索引。比如表中已经有a索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

9、对于那些查询很少涉及的列,重复值比较多的列不要建立索引。

10、对于定义为text、image和bit的数据类型的列也不要建立索引。


27、mysql锁的类型有哪些

基于锁的分类:共享锁、排他锁。

基于锁的力度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎)、记录锁、间隙锁、临键锁。

基于锁的状态分类:意向共享锁、意向排它锁。

  • 共享锁(Share Lock)

    共享锁又称读锁,简称S锁:当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到私有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。

  • 排他锁(exclusive Lock)

    排他锁又称写锁,简称X锁:当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,查到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题。

  • 表锁

    表锁是指上锁的时候锁住的是整个表,当下一个事务访问表的时候,必须等待前一个事务释放了锁才能进行对表进行访问;
    特点:粒度大,加锁简单,容易冲突;

  • 行锁

    行锁是指上锁的时候锁住的是某一行或者多行记录,其他事务访问一张表时,只又被锁住的记录不能访问,其他的记录可正常访问;
    特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发高;

  • 记录锁(RecordLock)

    记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。
    精准条件命中,并且命中的条件字段是唯一索引
    加了记录锁之后数据可以避免数据在查询的时候被修改的重复问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。

  • 页锁

    页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快、但冲突多,行级锁冲突少、但速度慢。
    所以取了折中的页级锁,一次锁定相邻的一组记录。
    特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁粒度界于表锁和行锁之间,并发都一般

  • 间隙锁(Gap Lock)

    属于行锁的一种,间隙锁时在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。
    范围查询并且为命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_RED(重复读)的事务级别中。
    触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样。
    比如表里面的数据ID1,4,5,7,10,那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10区间,10-n区间(-n代表负无穷大,n代表正无穷大)

  • 临键锁(Next-key Lock)

    也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住
    触发条件:范围查询命中,查询命中了索引。
    结合记录锁和间隙锁的特性,临键锁避免了再范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区间内数据不允许被修改和插入。

如果当事务A加锁成之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁,你们不能对整个表加共享锁或者排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态就知道自己是不是可以对表加锁,避免对整个索引树的每个节点扫描是否加锁,而这个状态就是意向锁。

  • 意向锁

    当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。

  • 意向排他锁

    当一个事务试图对整个表进行加排他锁之前,首先需要获得这个表的意向排他锁。


28、事务的基本特性和隔离级别

事务基本特性 ACID 分别是:

原子性: 指的是一个事务的操作要么全部成功,要么全部失败。

一致性: 指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B 100块钱,假设A只有90块,支付之前我们从数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因为此事务不能成功,这里我们说事务通过了一致性的保证

隔离性: 指的是一个事务的修改在最终提交之前,对其他事务是不可见的。

持久性: 指的是一旦事务提交,所做的修改就会永久保存到数据库中。

隔离性有 4 个隔离级别,分别是:

  • read uncommit 读未提交,可能会读取其他事务未提交的数据,也叫做脏读。
    用户本来应该读取到 id=1 的用户age应该是 10 ,结果读取到了其他事务还没有提交的事务,结果读取结果 age=20 ,这就是脏读。

  • read commit 读已提交,两次读取结果不一致,叫做不可重复读。
    不可重复读解决了脏读的问题,他只会读取已经提交后的事务。
    用户开启事务读取 id=1 用户,查询到 age=10,再次读取发现结果 age=20,在同一个事务同一个查询读取到不同的结果叫做不可重复读。

  • repeatable read 可重复读,这是mysql的默认级别,就是每次读取结果都是一样,但是有可能产生幻读。

  • serializable 串行,一般是不会使用的,他会给每一行读取数据的数据加锁,会导致大量超时和锁竞争的问题。


29、关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过

在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要有运维在做,会定期将业务中的慢查询反馈给我们。

慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方面来的,

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写

  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。

  • 如果对语句的优化已经无法进行了,可以考虑表中的数据是否太大,如果是的话可以进行横向合在纵向的分表。


30、ACID 靠什么保证的

A 原子性有 undo log 日记保证,它记录了需要回滚的日记信息,事务回滚时撤销已执行成功sql

C 一致性由其他三大特性保证、程序代码要保证业务上的一致性

I 隔离性由 MVCC 来保证

D 持久性由内存 + redo log 来保证,mysql修改数据同时在内存和 redo log记录这次操作,宕机的时候可以从redo log回复

InnoDB redo log 写盘,InnoDB 事务进行入 prepare 状态。
如果前面 prepare 成功,binlog 写盘,再继续事务日记持久化到 binlog,如果持久化成功,那么 InnoDB 事务则进入 commit 状态(在 redo log 里面写一个 commit 记录)

redolog的刷盘会在系统空闲时进行


31、什么是 MVCC

多版本并发控制:读取数据通过一种类似快照的方式将数据保存下来,这样读锁和写锁不冲突 ,不同的事务session会看自己特定版本的数据,版本链

MVCC 只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和 MVCC 不兼容,因为 READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。 而 SERIALIZABLE 则会对所有读取的行都加锁。


聚簇索引记录有两个必要的隐藏列:

trx_id: 用来存储每次对某条聚簇索引记录进行修改的时候的事务id

roll_pointer: 每次对哪条聚簇索引记录有修改的时候,都会啊老版本写入 undo 日记中。这个 roll_pointer 就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本记录信息。(注意插入操作的 undo 日记没有这个属性,因为它没有老版本)

已提交读 和 可重复读 的区别在于它们生成 ReadView 的策略不同。

在这里插入图片描述

开始事务是创建 readView,readView维护当前活动的事务id,即未提交的事务id,排序生成一个数组

访问数据,获取数据中的事务id(获取的是事务id最大记录),对比 readview:

如果在 readview 的左边(比readview都小),可以访问(在左边意味着该事务已经提交)

如果在readview 的右边(比readview都大)或者就在readview中,不可以访问,获得roll_pointer,取上一个版本重新对比(在右边意味着,该事务在readview生成之后出现,在readview中意味着该事务还未提交)


已提交读隔离级别下的事务在每次查询开始都会生成一个独立的 ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都是复用之前的ReadView。

这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读,通过ReadView生成策略的不同的隔离级别。


32、mysql主从同步原理

mysql主从同步过程:

Msyql的主从复制中主要三个线程 :master(binlog dump thread)、slave(I/O thread、SQL thread)Mater 一条线程和 Slave 中两条线程。

  • 主节点 binlog,主从复制的基础是主库记录数控的所有变更记录到 binlog。binlog 是数据库服务启动的那一刻,保存所有修改数据库结构或内容的一个文件。
  • 主节点 log dump 线程,当binlog 有变动时,log binlog 线程读取其内容并发送给从节点。
  • 从节点 I/O 线程接收 binog 内容,并将其写入到 relay log 文件中。
  • 从节点的 SQL 线程读取 relay log 文件内容对数据更新进行重放,最终保证主从数据库是一致性。

注:主从节点使用 binlog 文件 + position 偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量,如果从节点发送宕机重启,则会自动从 position 的位置发起同步。


由于mysql 默认的复制方式是异步的,主库把日记发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日记就丢失了。因此产生两个概念。

全同步复制

主库写入binlog 后强制同步日记到主库,所有的从库执行完成后才返回客户端,但是很显然这个方式的话性能会受到严重影响。

半同步复制

和全同步不同的是,半同步复制的逻辑是这样,从库写入日记成功返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。


33、简述MyISAM 和 InnoDB 的区别

MyISAM

不支持事务,但是每次查询都是原子的;

支持表级锁,即每次查询是整个表加锁;

存储表的总行数;

一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;

采用非聚簇索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。


InnoDB

支持 ACID 的事务,支持事务的四种隔离级别;

支持行级锁及外键约束:因此可以支持写并发;

不存储总行数;

一个 InnoDB 引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也可能为多个(设置为独立表空间,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;

主键索引采用聚簇索引(索引的数据存储数据文件本身),辅索引的数据存储主键值:因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自身自增主键,防止插入数据时,为维护B+树结构,文件的大调整。


34、简述mysql中索引类型及对数据库的性能的影响

普通索引:允许被索引的数据类包含重复的值。

唯一索引:可以保证数据记录的唯一性。

主键:是一种特殊的唯一索引,在一张表中只能定义主键索引,主键用于标识一条记录,使用关键字 PRIMARY KEY 来创建。

联合索引:索引可以覆盖多个数据列,如像 INDEX(columnA、columnB)索引。

全文索引: 通过建立 倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过 ALTER TABLE table_name ADD FULLTEXT(column);创建全文索引


索引可以极大的提高数据的查询速度。

通过使用索引,可以再查询的过程中,使用优化隐藏器,提高系统的性能。

但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,这要操作索引文件

索引需要占物理空间,除了数据表占数据空间之外,每一个索引还有占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚簇索引很多,一旦聚簇改变,那么所有非聚簇索引都会跟着变。


35、RDB 和 AOF 机制

RDB: Redis DataBase

在指定的时间间隔将内存中的数据集 快照 写入磁盘,实际操作过程是 fork 一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

优点:

1、整个Redis数据库将只包含一个文件 dump.rdb,方便持久化。

2、容灾性好,方便备份。

3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程进行持久化,主进程不会进行任何 IO 操作,保证了redis 的高性能。

4、相对于数据集大,比 AOF 的启动效率更高。

缺点:

1、数据安全性低。 RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发送故障,会发送数据丢失。所以这种方式更适合数据要求不严谨的时候。

2、由于 RDB 是通过 fork 子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务停止几百毫秒,甚至是1秒钟。


AOF:Append Only File

以日记的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详情的操作记录

优点:

1、数据安全,Redis中提供 3 种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化会被立即记录到磁盘中。

2、通过 append 模式写文件,即使中途服务器宕机也不会被破坏已经存在的内容,可以通过 reids-check-aof 工具解决数据一致性问题。

3、AOF 机制的 rewrite 模式。定期对 AOF 文件进行重写,以达到压缩的目的。

缺点:

1、AOF 文件比 RDB 文件大,具恢复速度慢

2、数据集大的时候,比 rdb 启动效率低。

3、运行效率没有 RDB 高


AOP 文件比 RDB 更新频繁高,优先使用 AOF 还原数据。

AOF 比 RDB 更安全也更大

RDB 性能比 AOF 好

如果两个都配置了优先加载 AOF


36、Redis的过期键的删除策略

Redis 是 key-value 数据库,我们可以设置 Redis 中缓存的 key 的过期时间。Redis 的过期策略就是指当 Redis 中缓存的 key 过期了,Redis如何处理。

  • 惰性过期:只有当访问一个 key 时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次访问,从而不会被清除,占用大量内存。

  • 定期清除:每隔一定时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key ,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和 内存资源 达到最大的平衡效果。

(expires 字典保存私有设置了过期时间的key的过期时间数据,其中,key是指向键空间的某个键的指针,value是该键的毫秒精度的 UNIX 时间戳表示的过期时间。键空间是指该 Redis 集群中保存的所有键。)

Redis 中同时使用了 惰性过期 和 定期过期 两种策略。


37、Redis线程模型,但线程为什么那么快

Redis 基于Reactor 模式开发了网络事件处理器,这个处理器加做 文件事件处理器 (file event handler)。这个文件事件处理器,它是单线程的,所以 Redis 才叫做单线程的模型,它采用 IO 多路复用机制来同时监听多个 Socket ,根据 Socket 上的事件类型来选择对应事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模型进行对接,保证了 Redis 内部的线程模型的简单性。

文件事件处理器的结构包含 4 各部分:多个 Socket、IO 多路复用程序、文件事件分派器 以及 事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等 )。

多个Socket 可能并发的产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序监听多个 Socket ,会将 Socket 放入一个队列中排队,每次从队列中取出一个 Socket 给事件分派器,事件分派器把 Socket 给对应的事件处理器。

然后一个 Socket 的事件处理完成之后,IO 多路复用程序才会将队列中的下一个 Socket 给事件分派器。文件事件分派器会根据每个 Socket 当前产生的事件,来选择对应的事件处理器来处理。


单线程快的原因:

  1. 纯内存操作
  2. 核心是基于非阻塞的 IO 多路复用机制
  3. 单线程反而避免了多线程的频繁上下文切换带来的性能消耗

38、缓存雪崩、缓存穿透、缓存击穿

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间承受大量请求而崩掉。

解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发送
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新缓存数据
  • 缓存预热
  • 互斥锁

缓存穿透是指缓存和数据库中都没有的数据,导致私有的请求都落在数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

  • 接口层面增加效验,如用户鉴权效验,id做基础效验,id <= 0 的直接拦截
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写成为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没有办法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。

缓存击穿是指缓存中没有但数据库中有的数据(一般缓存时间到期),这是由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库去获取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据库查不到从而查询数据库。

解决方案:

  • 设置热点数据永久不过期
  • 加互斥锁
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值