java基础
1、谈谈SpringIOC的理解,原理与实现?
控制反转:理论思想,原来的对象是由使用者来进行控制的 ,有了spring之后,可以把整个对象交给spring来管理
DI:依赖注入,把对应的属性的值注入到具体的对象中,@Autowired,populateBean完成属性值的注入
容器:存储对象,使用map结构来存储,在spring的一般存在三级缓存,singletonObjects存放完整的bean对象,整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理(bean的生命周期)
1、一般聊IOC的容器的时候涉及到容器的创建过程(benaFactory,DefaultLisableBeanFactory),向bean工厂中设置一些参数(BeanPostProcessor,Aware接口的子类)等属性
2、加载解析bean对象,准备要创建的bean对象的定义对象BeanDefinition(xml或者注解的解析过程)
3、beanFactoryPostProcessor的处理,此处扩展点,PlaceHolderConfigurSupport,ConfigurationClassPostProcessor
4、beanPostProcessor的注册功能,方便后续对bean对象完成具体的扩展功能
5、通过反射的方式讲BeanDefinition(xml或者注解的解析过程)
6、bean对象的初始化过程(填充属性,调用aware子类的方法,调用beanPostProcessor前置处理方法,调用init-method方法,调用beanPostProcessor的后置处理方法)
7、生成完整的bean对象,通过getBean方法可以直接获取
8、销毁过程
面试官,这是我对ioc的整体理解,包含了一些详细的处理过程,您看-下有什么问题, 可以指点我一下(允许你把整个流程说完)
老师,我没看过源码怎么办?
具体的细节我记不太清了,但是spring中的bean都是通过反射的方式生成的,同时其中包含了很多的扩展点,比如最常用的对BeanFactory的扩展,对bean的扩展(对占位符的处理),我们在公司对这方 面的使用是比较多的,除此之外,ioc中最核心的也就是填充具体bean的属性,和生命周期(背一下)。
2、谈-下spring IOC的底层实现
底层实现: 工作原理,过程,数据结构,流程,设计模式,设计思想
你对他的理解和你了解过的实现过程
反射,厂,设计模式(会的说,不会的不说),关键的几个方法
createBeanFactory,getBean,doGetBean,createBean,doCreateBean,createBeanInstance(getDeclaredConstructor,newinstance),populateBean,initializingBean
1、先通过createBeanFactory创建 出一一个BeanI厂(DefaultListableBeanFactory)
2、开始循环创建对象,因为容器中的bean默认都是单例的,所以优先通过getBean,doGetBean从容器中查找,找不到的话,
3、通过createBean,doCreateBean方法, 以反射的方式创建对象一般情况下使用的是无参的构造方法(getDeclaredConstructor,newInstance)
4、进行对象的属性填充populateBean
5、进行其他的初始化操作(initializingBean)
3、描述一下bean的生命周期
背图:记住图中的流程
在表述的时候不要只说图中有的关键点,要学会扩展描述
1、实例化bean:反射的方式生成对象
2、填充bean的属性: populateBean),循环依赖的问题 (三级缓存)
3、调用aware接口相关的方法: invokeAwareMethod(完成BeanName,BeanFactory,BeanClassLoader对象的属性设置)
4、调用BeanPostProcessor中的前置处理方法:使用比较多的有(ApplicationContextPostProcessor,设置
ApplicationContext,Environment,ResourceLoader,EmbeddValueResolver等对象)
5、调用initmethod方法: invokelnitmethod(),判断是 否实现了initializingBean接口,如果有,调用afterPropertiesSet方法, 没有就不
调用
6、调用BeanPostProcessor的后置处理方法: spring的aop就是在此处实现的,AbstractAutoProxyCreator 注册Destuction相关的回调接口
7、获取到完整的对象,可以通过getBean的方 式来进行对象的获取
8、销毁流程,1;判断是否实现了DispoableBean接口, 2,调用destroyMethod方法
4.Spring是如何解决循环依赖的问题的?
三级缓存,提前暴露对象,aop
总:什么是循环依赖问题,A依赖B,B依赖A
分:先说明bean的创建过程:实例化,初始化(填充属性)
1、先创建A对象,实例化A对象,此时A对象中的b属性为空,填充属性b
2、从容器中查找B对象,如果找到了,直接赋值不存在循环依赖问题(不通),找不到直接创建B对象
3、实例化B对象,此时B对象中的a属性为空,填充属性a
4、从容器中查找A对象,找不到,直接创建
形成闭环的原因
此时,如果仔细琢磨的话,会发现A对象是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但是未完成初始化,如果在程序调用过程中,拥有了某个对象的引用,能否在后期给他完成赋值操作,可以优先把非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖问题的关键,
当所有的对象都完成实例化和初始化操作之后,还要把完整对象放到容器中,此时在容器中存在对象的几个状态,完成实例化=但未完成初始化,完整状态,因为都在容器中,所以要使用不同的map结构来进行存储,此时就有了一级缓存和二 级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1, 2, 3这样的方式来查找的。一级缓存中放的是完整对象,二级缓存中放的是非完整对象。
为什么需要三级缓存?三级缓存的value类型是ObjectFactory,是一 一个函数式接口,存在的意义是保证在整个容器的运行过程中同名
的bean对象只能有一个。
如果一个对象需要被代理,或者说需要生成代理对象,那么要不要优先生成一个普通对象?要
普通对象和代理对象是不能同时出现在容器中的,因此当一个对象需 要被代理的时候,就要使用代理对象覆盖掉之前的普通对象,在实际的调用过程中,是没有办法确定什么时候对象被使用,所以就要求当某个对象被调用的时候,优先判断此对象是否需要被代理,类似于一种回调机制的实现,因此传入lambda表达式的时候,可以通过lambda表达式来执行对象的覆盖过程, getEarlyBeanReference()
因此,所有的bean对象在创建的时候都要优先放到三级缓存中,在后续的使用过程中,如果需要被代理则返回代理对象,如果不需要被代理,则直接返回普通对象
4.1缓存的放置时间和删除时间
三级缓存:createBeanInstance之后: addsingletonFactory
二级缓存:第一次从三级缓存确定对象是代理对象还是普通对象的时候,同时删除三级缓存getSingleton .
一级缓存: 生成完整对象之后放到一级缓存,删除二三 级缓存:addSingleton
5.Bean Factory与FactoryBean有什么区别?
相同点: 都是用来创建bean对象的
不同点: 使用BeanFactory创建对象的时候,必须要遵循严格的生命周期流程,太复杂了,如果想要简单的自定义某个对象的创建,同时创建完成的对象想交给spring来管理,那么就需要实现FactroyBean接口了
- isSingleton:是否是单例对象
- getObjectType:获取返回对象的类型
- getObject:自定义创建对象的过程(new,反射,动态代理)
6.Spring中用到的设计模式?
- 单例模式: bean默认都是单例的
- 原型模式:指定作用域为prototype
- 工厂模式: BeanFactory
- 模板方法: postProcessBeanFactory,onRefresh,initPropertyValue
- 策略模式: XmlBeanDefinitionReader,PropertiesBeanDefinitionReader
- 观察者模式: listener, event, multicast
- 适配器模式: Adapter
- 装饰者模式: BeanWrapper
- 责任链模式:使用aop的时候会先生成一个拦截器链
- 代理模式:动态代理
- 委托者模式: delegate
7.Spring的AOP的底层实现原理?
动态代理
aop是ioc的一一个扩展功能,先有的ioc, 再有的aop,只是在ioc的整 个流程中新增的一个扩展点而已: BeanPostProcessor
总: aop概念,应用场景,动态代理
分:
bean的创建过程中有- -个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在BeanPostProcessor的后置处理方法
中来进行实现
1、代理对象的创建过程(advice, 切面,切点)
2、通过jdk或者cglib的方式来生成代理对象
3、在执行方法调用的时候,会调用到生成的字节码文件中,直接回找到DynamicAdvisoredInterceptor类中的intercept方法, 从此方法开始执行
4、根据之前定义好的通知来生成拦截器链
5、从拦截器链中依次获取每一个通知开始进行执行, 在执行过程中,为了方便找到下一个通知是哪个,会有一个CglibMethodInvocation的对象,找的时候是从-1的位置-次开始查找并且执行的。
8.Spring的事务是如何回滚的?
spring的事务管理是如何实现的?
总: spring的事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过一个TransactionInterceptor来实现的, 然后调用invoke来实现具体的逻辑。
分:
1、先做准备工作,解析各个方法上事务相关的属性,根据具体的属性来判断是否开始新事务
2、当需要开启的时候,获取数据库连接,关闭自动提交功能,开起事务
3、执行具体的sq|i逻辑操作
4、在操作过程中,如果执行失败了,那么会通过completeTransactionAfterThrowing看来完成事务的回滚操作, 回滚的具体逻
辑是通过doRollBack方法来实现的,实现的时候也是要先获取连接对象,通过连接对象来回滚
5、如果执行过程中,没有任何意外情况的发生,那么通过commitTransactionAfterReturning来完成事务的提交操作, 提交的
具体逻辑是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象来提交
6、当事务执行完毕之后需要清除相关的事务信息cleanupTransactionInfo
9.谈一 下spring事务传播?
传播特性有几种? 7种
Required,Requires_ new,nested,Support,Not. Support,Never,Mandatory
某一个事务嵌套另一个事务的时候怎么办?
A方法调用B方法,AB方法都有事务,并且传播特性不同,那么A如果有异常, B怎么办,B如果有异常,A怎么办?
总: 事务的传播特性指的是不同方法的嵌套调用过程中,事务应该如何进行处理,是用同一个事务还是不同的事务,当出现异常的时候会回滚还是提交,两个方法之间的相关影响,在日常工作中,使用比较多的是required, Requires_ new,nested
分:
1、先说事务的不同分类,可以分为三类:支持当前事务,不支持当前事务,嵌套事务
2、如果外层方法是required,内层方法是,required,requires_ new,nested
3、如果外层方法是requires_ new,内层方法是,required,requires_ new,nested
4、如果外层方法是nested,内层方法是,required,requires. new,nested
10、说说你对Spring的理解?
Spring使创建Java 企业应用程序变得更加容易。它提供了在企业环境中接受Java语言所需的一切,,并支持Groovy和Kot1in作为JVM. 上的替代语言, 并可根据应用程序的需要灵活地创建多种体系结构。从Spring Framework 5.0开始,Spring 需要JDK 8(Java SE 8+),并且已经为JDK 9提供了现成的支持。Spring支持各种应用场景,在大型 企业中,应用程序通常需要运行很长时间,而且必须运行在jdk 和应用服务器上,这种场景开发人员无法控制其升级周期。其他可能作 为一个单独的jar嵌入到服务器去运行,也有可能在云环境中。还有一些可 能是不需要服务器的独立应用程序(如批处理或集成的工作任务)。
Spring是开源的。它拥有一个庞大而且活跃的社区,提供不同范围的,真实用户的持续反馈。这也帮助Spri ng不断地改进,不断发展。
Spring是一个框架,同时是一个容器,还是一个生态。
11、说说你对AOP的理解?
AOP全称叫做Aspect Oriented Programming面向切面编程。它是为解耦而生的,解耦是程序员编码开发过程中一直追求的境界,AOP在业务类的隔离上,绝对是做到了解耦,在这里面有几个核心的概念:
- 切面(Aspect) :指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。
- 连接点(Join point) :在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中, 一个连接点总是代表-一个方法的执行。
- 通知(Advice) :在切面的某个特定的连接点上执行的动作。通知有多种类型,包括"around", “before” and"after”等等。通知的类型将在后面的章节进行讨论。许多AOP框架, 包括Spring在内, 都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
- 切点(Pointcut) :匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心: Spring默认使用AspectJ切点语义。
- 引入(Introduction) :声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及-一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现IsModified接口,以便简化缓存机制(在Aspect社区,引入也被称为内部类型声明(inter) )。
- 目标对象(Target object) : 被一个或者多个切面所通知的对象。也被称作被通知(advised) 对象。既然
Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied) 的对象。 - AOP代理(AOP proxy) :AOP框架创建的对象,用来实现切面契约(aspect contract) (包括通知方法执行等功能)。在Spring中, AOP代理可以是DK动态代理或CGLB代理。
- 织入(Weaving) :把切面连接到其它的应用程序类型或者对象上,并创建- 个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
这些概念都太学术了,如果更简单的解释呢,其实非常简单:
任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
12、spring事务什么时候会失效?
1、bean对象没有被spring容器管理
2、方法的访问修饰符不是public
3、自身调用问题
4、数据源没有配置事务管理器
5、数据库不支持事务
6、异常被捕获
7、异常类型错误或者配置错误
13、说一下使用spring的优势?
1、Spring通过DI、 AOP和消除样板式代码来简化企业级Java开发
2、Spring框架之外还存在一 个构建在核心框架之上的庞大生态圈,它将Spring扩 展到不同的领域,如Web服务、REST、移动开发以及NoSQL
3、低侵入式设计,代码的污染极低
4、独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
5、Spring的IoC容器降低 了业务对象替换的复杂性,提高了组件之间的解耦
6、Spring的AOP支持允许将一 些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
7、Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
8、Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部
14、如何实现一个IOC容器?
I0C(Inversion of Control),意思是控制反转,不是什么技术,而是一种设计思想,I0C意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
在传统的程序设计中,我们直接在对象内部通过new进行对象创建,是程序主动去创建依赖对象,而I0C是有专门的容器来进行对象的创建,即IOC容器来控制对象的创建。
在传统的应用程序中,我们是在对象中主动控制去直接获取依赖对象,这个是正转,反转是由容器来帮忙创建及注入依赖对象,在这个过程过程中,由容器帮我们查找级注入依赖对象,对象只是被动的接受依赖对象。
1、先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象
2、进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中
3、容器将封装好的BeanDefinition对象通过反射的方 式进行实例化,完成对象的实例化工作
4、进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象的创建,变成一个完整的bean对象,存储在容器的某个map结构中
5、通过容器对象来获取对象,进行对象的获取和逻辑处理工作
6、提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁
15、spring、springmvc、springboot的区别
spring和springMvc:
1、spring是一个- 站式的轻量级的java开发框架,核心是控制反转(I0C) 和面向切面(AOP),针对于开发的WEB层(springMvc)、业务层(loc)、 持久层(jdbcTemplate)等 都提供了多种配置解决方案;
2. springMvc是spring基础之上的一个MVC框架,主要处理web开发的路径映射和视图渲染,属于spring框架中WEB层开发的一部分;
springMvc和springBoot:
1、springMvc属于一 个企业WEB开发的MVC框架, 涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等, XML、config等配置 相对比较繁琐复杂;
2、springBoot框架相对于springMvc框架来说, 更专注于开发微服务后台接口,不开发前端视图,同时遵循默认优于配置,简化了插件配置流程,不需要配置xml,相对springmvc, 大大简化了配置流程;
总结:
1、Spring 框架就像一个家族, 有众多衍生产品例如boot、security、 jpa等等。 但他们的基础都是Spring的ioc、aop等. ioc提供了依赖注入的容器,aop解决 了面向横切面编程,然后在此两者的基础上实现了其他延伸产品的高级功能; spring 底层
2、springMvc主要解决WEB开发的问题, 是基于Servlet 的一个MVC框架, 通过XML配置,统一 开发前端视图和后端逻辑; == springMVC脚手架==
3、由于Spring的配置非常复杂,各种XML、JavaConfig. servlet处理起来比较繁琐,为了简化开发者的使用,从而创造性地推出了springBoot框架,默认优于配置,简化了springMvc的配置流程;但区别于springMvc的是,springBoot专注于单体微服务接口开发,和前端解耦,虽然springBoot也可以做成springMvc前后台一起开发, 但是这就有点不符合springBoot框架的初衷了; == springboot 脚手架 ==
16、spring框架中,单例bean是线程安全的吗?
Spring中的Bean对象默认是单例的,框架并没有对bean进行多线程的封装处理
如果Bean是有状态的,那么就需要开发人员自己来保证线程安全的保证,最简单的办法就是改变bean的作用域把singleton改成prototype,这样每次请求bean对象就相当于是创建新的对象来保证线程的安全
有状态就是由数据存储的功能
无状态就是不会存储数据,你想一下,我们的controller, service和dao本身并不是线程安全的,只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全的。
因此在进行使用的时候,不要在bean中声明任何有状态的实例变量或者类变量,如果必须如此,也推荐大家使用ThreadLocal把变量变成线程私有,如果bean的实例变量或者类变量需要在多个线程之间共享,那么就只能使用synchronized, lock, cas等这些实现线程同步的方法了。
17、spring是如何简化开发的?
- 基于POJO的轻量级和最小侵入性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样板式代码
18、spring支持的bean作用域有哪些?
①singleton
使用该属性定义Bean时,I0C容器仅创建一个Bean实例, I0C容器每次返回的是同-一个Bean实例。
②prototype
使用该属性定义Bean时工I0C容器可以创建多个Bean实例,每次返回的都是- -个新的实例。
③request
该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean, 适用于
WebApplicationContext环境。
④session
该属性仅用于HTTP Session,同-个Session共享一个Bean实例。 不同Session使用不同的实例。
⑤global-session
该属性仅用于HTTP Session,同session作用域不同的是, 所有的Session共享一个Bean实例。
19、spring事务的实现方式原理是什么?
在使用Spring框架的时候,可以有两种事务的实现方式,-种是编程式事务,有用户自己通过代码来控制事务的处理逻辑,还有一种是声明式事务, 通过@Transactional注解来实现。
其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作, spring对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,有spring框架来帮助进行控制。
其实事务操作是AOP的一个核心体现,当-一个方法添加@Transactional注解之后, spring会基 于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关系,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。
20、如何理解springboot中的starter?
使用spring+springmvc框架进行开发的时候,如果需要引入mybatis框架,那么需要在xml中定义需要的bean对象,这个过程很明显是很麻烦的,如果需要引入额外的其他组件,那么也需要进行复杂的配置,因此在springboot中引入了starter
starter就是一个jar包, 写一个@Configuration的配置类,将这些bean定义在其中,然后再starter包的
META-INF/spring.factories中写入配置类,那么springboot程 序在启动的时候就会按照约定来加载该配置类
开发人员只需要将相应的starter包依赖进应用中,进行相关的属性配置,就可以进行代码开发,而不需要单独进行bean对象的配置
21、springboot自动配置原理是什么?
一、入口类及其源码解析
@SpringBootApplication
public class SpringbootDemo1Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemo1Application.class, args);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
注解分析:
@SpringApplication 注解
Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用
源码解析:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
从源码可以看出,这个注解是@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan这三个注解的组合
① @SpringBootConfiguration
Spring Boot的配置类;标注在某个类上,表示一个类提供了Spring Boot应用程序
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@Configuration:配置类上来标注这个注解;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
注意:
配置类相当于配置文件;配置类也是容器中的一个组件,它使用了@Component这个注解。
② @EnableAutoConfiguration
告诉SpringBoot开启自动配置功能,这样自动配置才能生效
借助@import,扫描并实例化满足条件的自动配置的bean,然后加载到IOC容器中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
@AutoConfigurationPackage:自动配置包
@Import(EnableAutoConfigurationImportSelector.class):给容器中导入组件
使用@EnableAutoConfiguration
这个注解开启自动扫描,然后使用select选择挑选满足条件的文件,并且使用SpringFactoriesLoader进行实例化。最后加载到IOC容器里面,即ApplicationContext中。
③ @ComponentScan
@ComponentScan就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IOC容器中去 。
二、实例化SpringApplication对象的源码剖析
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 初始化资源加载器
this.resourceLoader = resourceLoader;
// 资源加载类不能为null
Assert.notNull(primarySources, "PrimarySources must not be null");
// 初始化加载资源类集合并去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断应用程序是不是web应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
// 设置初始化器(Initializer)
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断出主应用的入口
this.mainApplicationClass = deduceMainApplicationClass();
}
其中,在推断应用程序是不是web应用的时候调用了deduceFromClasspath() 方法
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
// springboot2.0提出的响应式web应用
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
// 如果两个包路径都没有的话,就是普通应用
if (!ClassUtils.isPresent(className, null)) {
// 普通的应用
return WebApplicationType.NONE;
}
}
// 其实最后返回的就是这个servlet,因为是web应用
return WebApplicationType.SERVLET;
}
1. 设置初始化器(Initializer)
initializers 是 SpringApplication 中的一个实例属性
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
initailizer实现了ApplicationContextInitializer接口
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
// 把初始化的ApplicationContextInitializer实现类加载到SpringApplication中
void initialize(C var1);
}
总结:
- ApplicationContextInitializer接口的作用,在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。
- 调用initialize()方法,把初始化的ApplicationContextInitializer实现加载到SpringApplication中
通过getSpringFactoriesInstances(
ApplicationContextInitializer.class)方法获得实现类
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 使用 Set保存names
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据names进行实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 对实例进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
2. 设置监听器
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
继承了ApplicationListener()接口
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
return (event) -> {
consumer.accept(event.getPayload());
};
}
}
总结:
在这里使用到了观察者模式,有一个被观察者和许多观察者,当被观察者的状态发生改变时,要通知所有的观察者做一些操作。
3. 推断主应用入口类
private Class<?> deduceMainApplicationClass() {
try {
// 构造一个异常类
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
// 通过main的栈帧推断出入口类的名字
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
三、run() 方法源码剖析
public ConfigurableApplicationContext run(String... args) {
//计时器,统计应用启动的时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
// 设置系统属性java.awt.headless的值,默认为true
configureHeadlessProperty();
// 监听器,SpringApplicationRunListeners实际上是一个集合
SpringApplicationRunListeners listeners = getRunListeners(args);
// 回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 初始化默认参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备 Spring 环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 创建环境完成后回调,配置bean
configureIgnoreBeanInfo(environment);
// 打印器,springboot启动的时候会打印springboot的标志以及对应的版本
Banner printedBanner = printBanner(environment);
// 创建Spring应用上下文,来决定创建web的ioc还是普通的ioc
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//准备上下文环境
// Spring上下文前置处理
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
// Spring上下文刷新,表示刷新完成,进行后续的一些操作
refreshContext(context);
// Spring上下文后置处理
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
// 输出日志记录的类名、时间信息
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发布应用上下文启动完成事件
listeners.started(context);
// 执行所有 Runner 运行器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 发布应用上下文就绪事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 返回应用上下文
return context;
}
1. 开启计时器
开启计时器,用来统计应用启动的时间
public void start() throws IllegalStateException {
// 传入一个空字符串作为当前任务的名称
this.start("");
}
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
// 如果当前任务名字不为空,抛出异常
throw new IllegalStateException("Can't start StopWatch: it's already running");
} else {
// 否则,记录当前任务的开始时间
this.currentTaskName = taskName;
this.startTimeNanos = System.nanoTime();
}
}
- 首先,传入一个空字符串作为当前任务的名称
- 其次,判断当前任务名是否空,如果为空,则记录当前应用启动的开始时间
2. 设置系统属性的值
系统属性的值默认是true,系统属性的值来源于System.getProperty()。
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
3. 监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {
// 类加载对应的监听器
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
// 创建SpringApplicationRunListener实例
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
4. 初始化默认参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
5.创建 Spring 环境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 获取环境。如果存在就直接返回,否则先创建一个再返回
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 配置环境
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 将环境绑定到SpringApplication上面
bindToSpringApplication(environment);
// 如果不是web应用环境,将环境转换成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
// 返回环境
return environment;
}
总结:
- 获取环境。如果存在就直接返回,否则先创建一个再返回
- 配置环境
- 准备监听器环境
- 将环境绑定到SpringApplication上面
- 如果不是web应用环境,将环境转换成StandardEnvironment
- 最后返回环境
6. 打印器
springboot启动的时候会打印springboot的标志以及对应的版本
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
7. 创建Spring应用上下文
protected ConfigurableApplicationContext createApplicationContext() {
// 通过反射,得到创建的对象
return this.applicationContextFactory.create(this.webApplicationType);
}
8. Spring上下文前置处理
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 给IOC容器设置一些环境属性
context.setEnvironment(environment);
// 给IOC容器注册一些组件
postProcessApplicationContext(context);
// 调用初始化方法
applyInitializers(context);
// 监听器,触发contextPrepared 事件
listeners.contextPrepared(context);
bootstrapContext.close(context);
// 记录启动过程中的日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 添加特定的单例beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 加载所有资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载启动类,将启动类注入到容器中去
load(context, sources.toArray(new Object[0]));
// 触发contextLoaded 事件
listeners.contextLoaded(context);
}
9.Spring上下文刷新
刷新完成以后,会进行后续的一些操作
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) { //调用了registerShutdownHook()方法*
// 注册一个关闭容器时的钩子函数,在JVM关机的时候关闭这个上下文
shutdownHook.registerApplicationContext(context);
}
// 调用父类的refresh操作
refresh(context);
}
10.Spring上下文后置处理
在Spring容器刷新上下文后进行调用,依次调用注册的Runners。
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
// CommandLineRunner、ApplicationRunner 这两个接口,是在容器启动成功后的最后一步进行回调
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
22、beanfactory和applicationcontext有什么区别?
相同:
- Spring提供了两种不同的IOC容器,-个是BeanFactory, 另外-个是ApplicationContext, 它们都是Java
interface, ApplicationContext继承于BeanFactory(ApplicationContext继承ListableBeanFactory。 - 它们都可以用来配置XML属性,也支持属性的自动注入。
- 而ListableBeanFactory继承BeanFactory), BeanFactory和ApplicationContext都提供了- -种方式,使用getBean(“bean name”)获取bean。
不同:
- 当你调用getBean()方法时, BeanFactory仅实例化bean,而ApplicationContext在启动容器的时候实例化单例bean,不会等待调用getBean()方法时再实例化。
- BeanFactory不支持国际化,即i18n, 但ApplicationContext提供了对它的支持。
- BeanFactory与ApplicationContext之间的另- -个区别是能够将事件发布到注册为监听器的bean。
- BeanFactory的-一个核心实现是XMLBeanFactory而ApplicationContext的一个核心实现是ClassPathXmlApplicationContext, Web容器的环境我们使用WebApplicationContext并且增加了getServletContext方法。
- 如果使用自动注入并使用BeanFactory,则需要使用API注册AutoWiredBeanPostProcessor,如果使用
ApplicationContext,则可以使用XML进行配置。 - 简而言之,BeanFactory提供基本的I0C和DI功能,而ApplicationContext提供高级功能,BeanFactory可用于测试和非生产使用,但ApplicationContext是功能更丰富的容器实现,应该优于BeanFactory
redis
1、说一下redis的应用 场景
1、五大value类型
2、基本上就是缓存,
3、为的是服务无状态,延伸思考,项目中有哪些数据结构或对象,在单机里需要单机锁,在多机需要分布式锁,抽出来放入redis中;
4、无锁化
2、redis是单线程还是多线程?
1,无论是什么版本,工作线程就是一个
2,6. x高版本出现了I0多线程
3,[去学一下系统I0课],你要真正的理解面向IO模型编程的时候,有内核的事,从内核把数据搬运到程序里这是第一步,然后,搬运回来的数据做的计算式第二步,netty
4、单线程,满足redis的串行原子,只不过I0多线程后,把输入/输出放到更多的线程里去并行,
好处如下:
1,执行时间缩短,更快;
2,更好的压榨系统及硬件的资源(网卡能够高效的使用);
3、redis存在线程安全的问题吗?
重复2中的单线程串行
redis可以保障内部串行
外界使用的时候要保障,业务上要自行保障顺序~!
4、缓存穿透、缓存击穿、缓存雪崩
以上问题,核心就是避免DB无效/重复请求,结合图去理解涉及一些架构思想上的提升
5、缓存如何回收?redis如何删除过期key?
1,后台在轮询,分段分批的删除哪些过期的key
2,请求的时候判断时候已经过期了
尽量的把内存无用空间回收回来~!
6、缓存是如何淘汰的?
0,内存空间不足的情况下:
1,淘汰机制里有不允许淘汰
2,1 ru/1 fu/random/TTL
3,全空间
4,设置过过期的key的集合中
7、如何进行缓存预热?
1,提前把数据塞入redis,(你知道那些是热数据吗?肯定不知道,会造成上线很多数据没有缓存.
命中)
2,开发逻辑上也要规避差集(你没缓存的),会造成击穿,穿透,雪崩,实施456中的锁方案
3,一劳永逸,未来也不怕了
*,结合击穿,穿透,雪崩去看,看图理解
8、数据库与缓存不一致如何解决?
1,恶心点的,我们可以使用分布式事务来解决,(意义不大) ,顶多读多,写稀有情况下
结合图去思考
1,redis是缓存, 更倾向于稍微的有时差
2,还是减少DB的操作
3,真的要落地,咋就canal
9、简述一下主从不一致的问题?
1,redi s的确默认是弱一致性, 异步的同步
2,锁不能用主从(单实例/分片集群/red1ock==> redisson
3,在配置中提供了必须有多少个cli ent连接能同步,你可以配置同步因子,趋向于强制一性
4,wait20小心
5,34点就有点违背redis的初衷了
10、redis持久化原理?
Redis为持久化提供了两种方式:
RDB:在指定的时间间隔能对你的数据进行快照存储。
AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
11、redis持久化的方式?
1,RDB, AOF;主从同步也算持久化;
2,高版本:开启AOF,AOF是可以通过执行日志得到全部内存数据的方式,但是追求性能:
2.1,体积变大,重复无效指令 重写,后台用线程把内存的kv生成指令写个新的aof
2.2,4.x新增更有性能模式:把重写方式换成直接RDB放到aof文件的头部,比2.1的方法快
了,再追加日志
12、什么是mvcc?
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是-种并发控制的方法, 一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
2、当前读.
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
3、快照读(提高数据库的并发查询能力)
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本, 而有可能是之前的历史版本
4、当前读、快照读、MVCC关系
MVCC多版本并发控制指的是维持一个数据的多 个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞读功能。MVCC模块在MySQL 中的具体实现匙由三个隐式字段, undo日志、read view三个组件来实现的。
13、MVCC解决的问题是什么?
数据库并发场景有三种,分别为:
1、读读:不存在任何问题,也不需要并发控制
2、读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读
3、写写:有线程安全问题,可能存在更新丢失问题
MvCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决一下问题:
1、在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
2、解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题
14、MVCC实现原理是什么?
mvcc的实现原理主要依赖于记录中的三个隐藏字段,undolog, read view来实现的。
隐藏字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_ TRX_ ID,DB_ ROLL_ PTR,DB_ ROW_ ID等字段
DB_ TRX_ ID
6字节,最近修改事务id,记录创建这条记录或者最后一-次修改该记录的事务id
DB_ ROLL_ PTR
7字节,回滚指针,指向这条记录的上-个版本,用于配合undolog,指向上一个旧版本
DB_ROW_JD
6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_ id
记录如图所示:
在上图中,DB_ ROW_ ID是数据库默认为该行记录生成的唯一隐式主键,DB_ TRX_ ID是当前操作该记录的事务ID, DB_ ROLL_PTR是-一个回滚指针,用于配合undo日志,指向上一个旧版本
undo log
undolog被称之为回滚日志,表示在进行insert, delete, update操作的时候产生的方便回滚的日志
当进行insert操作的时候,产生的undolog只在事务回滚的时候需要, 并且在事务提交之后可以被立刻丢弃
当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需
要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除(当数据发生更新和删除操作的时候都只是设置一下老记录的deleted_ bit, 并不是真正的将过时的记录删除,因为为了节省磁盘空间,innodb有专门 ]的purge线程来清除deleted bit为true的记录,如果某个记录的deleted id为true,并且DB_ TRX_ ID相对于purge线程的read view可见,那么这条记录一定时可以被清除的)
下面我们来看一下undolog生成的记录链
1、假设有一个事务编号为1的事务向表中插入一条记录,那么此时行数据的状态为:
2、假设有第二个事务编号为2对该记录的name做出修改入改为lisi
在事务2修改该行记录数据时,数据库会对该行加排他锁
然后把该行数据拷贝到undolog中,作为旧记录,即在undolog中有当前行的拷贝副本
拷贝完毕后,修改该行name为lisi,并且修改隐藏字段的事务id为当前事务2的id,回滚指针指向拷贝到.
undolog的副本记录中
事务提交后,释放锁
Read View
上面的流程如果看明白了,那么大家需要再深入理解下read view的概念了。
Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一-刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。
其实Read View的最大作用是用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,也有可能读取的是当前行记录的undolog中某个版本的数据
Read View遵循的可见性算法主要是将要被修改的数据的最新记录中的DB_ TRX_ ID (当前事务id) 取出来,与系统当前其他活跃事务的id去对比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过DB_ ROLL_ PTR回滚指针去取出undolog中的DB_ TRX_ ID做比较,即遍历链表中的DB_ TRX_ ID,直到找到满足条件的DB_ TRX_ID,这个DB_TRX_ ID所在的旧记录就是当前事务能看到的最新老版本数据。
15、MySQL的隔离级别有哪些?
MySQL定义了四种隔离级别,包括- -些具体规则,用于限定事务内外哪些改变是可见的,哪些改变是不可见的。低级别的隔离一般支持更高的并发处理,并且拥有更低的系统开销。
READ UNCOMMITTED读取未提交内容
在这个隔离级别,所有事务都可以"看到”未提交事务的执行结果。在这种级别上,可能会产生很多问题,除非用户真的知道自己在做什么,并有很好的理由选择这样做。本隔离级别很少用于实际应用,因为它的性能也不必其他性能好多少,而别的级别还有其他更多的优点。读取未提交数据,也被称为"脏读”
READ COMMITTED读取提交内容
大多数数据库系统的默认隔离级别(但是不是MySQL的默认隔离级别), 满足了隔离的早先简单定义:一个事务开始时,只能”看见"已经提交事务所做的改变, -个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别也支持所谓的"不可重复读”。这意味着用户运行同-一个语句两次,看到的结果是不同的。
REPEATABLE READ可重复读
MySQL数据库默认的隔离级别。该级别解决了READ UNCOMMITTED隔离级别导致的问题。它保证同一事务的多个实例在并发读取事务时,会"看到同样的”数据行。不过,这会导致另外-个棘手问题”幻读”。InnoDB和Falcon存储弓|擎通过多版本并发控制机制解决了幻读问题。
SERIALIZABLE可串行化
该级别是最高级别的隔离级。它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简而言之,SERIALIZABLE是在每个读的数据行上加锁。在这个级别,可能导致大量的超时Timeout和锁竞争Lock Contention .现象,实际应用中很少使用到这个级别,但如果用户的应用为了数据的稳定性,需要强制减少并发的话,也可以选择这种隔离级。
1.脏读
脏读是指一个事务读取了未提交事务执行过程中的数据。
当一个事务的操作正在多次修改数据,而在事务还未提交的时候,另外- -个并发事务来读取了数据,就会导致读取到的数据并非是最终持久化之后的数据,这个数据就是脏读的数据。
2.不可重复读
不可重复读是指对于数据库中的某个数据,一个事务执行过程中多次查询返回不同查询结果,这就是在事务执行过程中,数据被其他事务提交修改了。
不可重复读同脏读的区别在于,脏读是一个事务读取了 另一未完成的事务执行过程中的数据,而不可重复读是一个事务执行过程中,另-事务提交并修改了当前事务正在读取的数据。
3.虚读(幻读)
幻读是事务非独立执行时发生的一种现象,例如事务T1批量对一-个表中某-列列值为1的数据修改为2的变更,但是在这时,事务T2对这张表插入了一条列值为1的数据,并完成提交。此时,如果事务T1查看刚刚完成操作的数据,发现还有一条列值为1的数据没有进行修改,而这条数据其实是T2刚刚提交插入的,这就是幻读
幻读和不可重复读都是读取了另一条已经提交的事务(这点同脏读不同), 所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一 批数据整体(比如数据的个数)。
16、mysqI复制原理是什么?
(1) master服务 器将数据的改变记录二进制binlog日志, 当master. 上的数据发生改变时,则将其改变写入二进制日志中;
(2) slave服务 器会在一定时间间隔内对master二进制日志进行探测其是否发生改变, 如果发生改变,则开始一个I/OThread请求master二进制事件
(3)同时主节点为每个I/O线程启动- -个dump线程, 用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。
也就是说:
- 从库会生成两个线程,一个I/O线程,一个SQL线程;
- I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
- 主库会生成-个log dump线程,用来给从库I/O线程传binlog;
- SQL线程,会读取relay log文件中的日志,并解析成sq|语句逐一执行;
注意:
1–master将操作语句记录到binlog日志中,然后授予slave远程连接的权限(master- 定要开启binlog= 进制日志功能;通常为了数据安全考虑, slave也开启binlog功能) 。
2–slave开启两个线程: 10线程和SQL线程。其中: 10线程负责读取master的binlog内容到中继日志relay log里;
SQL线程负责从relay log日志里读出binlog内容,并更新到slave的数据库里, 这样就能保证slave数据和master数据保持一致了。
3–Mysq|复制至少需要两个Mysql的服务,当然Mysq|服务可以分布在不同的服务器上,也可以在一台服务器 上启动多个服务。
4–Mysq|复制最好确保master和slave服务器上的Mysq|版本相同(如果不能满足版本-致,那么要保证master主节点的版本低于slave从节点的版本)
5–master和slave两节点间时间需同步
具体步骤:
1、从库通过手工执行change master to语句连接主库,提供了连接的用户- -切条件(user 、password.
port、ip) ,并且让从库知道,二进制日志的起点位置(file名 position号) ; start slave
2、从库的I0线程和主库的dump线程建立连接。
3、从库根据change master to语句提供的file名和position号, I0线程向主库发起binlog的请求。
4、主库dump线程根据从库的请求,将本地binlog以events的方式发给从库I0线程。
5、从库I0线程接收binlog events, 并存放到本地relay-log中,传送过来的信息,会记录到master.info中
6、从库SQL线程应用relay-log, 并且把应用过的记录到relay-log.info中, 默认情况下,已经应用过的relay会自动被清理purge
17、mysq|聚族和非聚簇索引的区别是什么?
mysq|的索引类型跟存储引擎是相关的,innoqb存储引擎数据文件跟索引文件全部放在ibd文件中,而
myisam的数据文件放在myd文件中,索引放在myi文件中,其实区分聚簇索引|和非聚簇索引非常简单,只要判断数据跟索引是否存储在一起就可以了。
innodb存储引擎在进行数据插入的时候,数据必须要跟索弓|放在一起,如果有主键就使用主键,没有主键就使用唯一键, 没有唯一键就使用6字节的rowid, 因此跟数据绑定在一起的就是 聚簇索引,而为了避免数据冗余存储,其他的索引的叶子节点中存储的都是聚簇索引的key值,因此innodb中既有聚簇索引也有非聚簇索引,而myisam中只有非聚簇索引。
18、索引基本原理
1、为什么要有索引?
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
2、什么是索引?
索引在MySQL中也叫是一种”键”, 是存储引擎用于快速找到记录的- -种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索弓|对于性能的影响愈发重要。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索弓|相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
3、索引的原理
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等[
本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同- -种查找方式来锁定数据。
数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、 in). 模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一 -段, 101到200分成第二二段, 201 到300分成第三段…这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?按照搜索树的模型,其平均复杂度是IgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,-方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
4、索引的数据结构
MySQL主要用到两种结构: B+ Tree索弓和Hash索引
Inodb存储弓|擎默认是B+Tree索引
Memory存储弓|擎默认Hash索引;
MySQL中,只有Memory(Memory表只存在内存中,断电会消失,适用于临时表)存储弓|擎显示支持Hash索引,是Memory表的默认索引类型,尽管Memory表也可以使用B+Tree索引。Hash索弓|把数据以hash形式组织起来,因此当查找某一条记录的时候,速度非常快。但是因为hash结构,每个键只对应一个值,而且是散列的方式分布。所以它并不支持范围查找和排序等功能。
B+Tree是mysq|使用最频繁的一个索引数据结构,是InnoDB和MyISAM存储引擎模式的索引类型。相对Hash索引,B+Tree在查找单条记录的速度比不上Hash索引,但是因为更适合排序等操作,所以它更受欢迎。毕竟不可能只对数据库进行单条记录的操作。
对比:
hash类型的索引:查询单条快,范围查询慢
btree类型的索引: b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
5、B+树在实现索引上的优势和过程
19、mysq|索引结构有哪些,各自的优劣是什么?
索引的数据结构和具体存储弓|擎的实现有关,mysq|中使用较多的索引有hash索引,B+树索引,innodb的索引实现为B+树,memory存储引擎为hash索引。
B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的二节点间有指针相关连接,在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因为,B+树索引|被广泛应用于数据库、文件系统等场景。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需- -次哈希算法即可立刻定位到相应的位置,速度非常快。
如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值,前提是键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,知道找到对应的数据如果是范围查询检索,这时候哈徐索引|就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索弓|完成范围查询检索
哈希所有也没办法利用索引|完成排序,以及like这样的部分模糊查询
哈希索引也不支持多列联合索弓|的最左匹配规则
B+树索引|的关键字检索效率比较平均,不像B树那样波动大,在有大量重复键值情况下,哈希索弓|的效率也是极低的,因此存在哈希碰撞问题。
20、mysql锁的类型有哪些?
基于锁的属性分类:共享锁、排他锁。
基于锁的粒度分类:行级锁(innodb) 、表级锁( innodb、myisam) 、页级锁( innodb引擎)、记录锁、间
隙锁、临键锁。
基于锁的状态分类:意向共享锁、意向排它锁。
共享锁(share lock) :共享锁又称读锁, 简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。
排他锁(exclusive lock) : 排他锁又称写锁,简称X锁;当一个事务为数据加,上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题。
表锁(table lock) : 表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;特点:粒度大,加锁简单,容易冲突;
行锁:行锁是指上锁的时候锁住的是表的某-行或多行记录,其他事务访问同- -张表时,只有被锁住的记录不能访问,其他的记录可正常访问,特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高记录锁(Record lock) :记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录, 记录锁是说事务在加锁后锁住的只是表的某一条记录,加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题
页锁:页级锁是MysQL中锁定粒度介于行级锁和表级锁中间的一种锁.表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级, 一次锁定相邻的一组记录。特点:开销和加锁时间界于表锁和行锁之间,会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
间隙锁:是属于行锁的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间, 当表的相邻ID之间出现空隙则会形成一一个区间, 遵循左开右闭原则。范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_ READ (重复读)的事务级别中。
临键锁(Next-Key lock):也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住。
21、mysql为什么需要主从同步?
1、在业务复杂的系统中,有这么一个情景, 有一句sq|语句需要锁表, 导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作。
2、做数据的热备
3、架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
22、怎么处理MySQL的慢查询?
1、开启慢查询日志,准确定位到哪个sq|语句出现了问题
2、分析sq|语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写
3、分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引
4、如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
23、事务的基本特性是什么?
事务四大特征:原子性,一致性, 隔离性和持久性。
1.原子性(Atomicity)
一个原子事务要么完整执行,要么干脆不执行。这意味着,工作单元中的每项任务都必须正确执行。如果有任任务执行失败,则整个工作单元或事务就会被终止。即此前对数据所作的任何修改都将被撤销。如果所有任务都被成功执行,事务就会被提交,即对数据所作的修改将会是永久性的。
2. 一致性(Consistency)
一致性代表了底层数据存储的完整性。它必须由事务系统和应用开发人员共同来保证。事务系统通过保证事务的原子性,隔离性和持久性来满足这一要求; 应用开发人员则需要保证数据库有适当的约束(主键,引用完整性等),并且工作单元中所实现的业务逻辑不会导致数据的不一致(即, 数据预期所表达的现实业务情况不相一致)。例如,在一次转账过程中,从某一账户中扣除的金额必须与另一账户中存入的金额相等。支付宝账号100你读到余额要取,有人向你转100但是事物没提交(这时候你读到的余额应该是100,而不是200)这种就是一致性
3.隔离性(Isolation)
隔离性意味着事务必须在不干扰其他进程或事务的前提下独立执行。换言之,在事务或工作单元执行完毕之前,其所访问的数据不能受系统其他部分的影响。
4.持久性(Durbility)
持久性表示在某个事务的执行过程中,对数据所作的所有改动都必须在事务成功结束前保存至某种物理存储设备。这样可以保证,所作的修改在任何系统瘫痪时不至于丢失。
24、索引的设计原则有哪些?
在进行索引设计的时候,应该保证索引字段占用的空间越小越好,这只是一个大的方向,还有- - -些细节点需要注意下:
1、适合索引的列是出现在where字句中的列,或者连接子句中指定的列
2、基数较小的表,索引效果差,没必要创建索引
3、在选择索引|列的时候,越短越好,可以指定某些列的一部分,没必要用全部字段的值
4、不要给表中的每一个字段都创建索引,并不是索引|越多越好
5、定义有外键的数据列一定要创建索引
6、更新频繁的字段不要有索引
7、创建索引的列不要过多,可以创建组合索引,但是组合索引的列的个数不建议太多
8、大文本、大对象不要创建索引
25、什么是mysql的主从复制?
**MySQL主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL默认采用异步复制方式,**这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
26、简述mysql中索引类型有哪些,以及对数据库的性能的影响?
普通索引:允许被索引的数据列包含重复的值
唯一索引:可以保证数据记录的唯一性
主键索引:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一 条记录,使用关键字
primary key来创建
联合索引:索引可以覆盖多个数据列
全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引|擎使用的一种关键技术
索引可以极大地提高数据的查询速度
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能
但是会降低插入、删除、更新表的速度,因为在执行这些写操作的时候,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一-个索引还要占- -定的物理空间,如果要简历聚簇索引,那么需要的空间就会更大,如果非聚簇索引很多, - -旦聚簇索引改变,那么所有非聚簇索引都会跟着变
redis持久化机制:RDB和AOF
Redis持久化
Redis提供了不同级别的持久化方式:
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以redis协议追加保存每次写的操作到文件末尾. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
- 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
- 你也可以同时开启两种持久化方式,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,
因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整. - 最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:
RDB的优点
- RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报
保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同
版本的数据集. - RDB是一个紧凑的单一 文件很方便传送到另一 个远端数据中心或者亚马逊的S3 (可能加密),非常适用于灾难恢复.
- RDB在保存RDB文件时父进程唯一 需要做的就是fork出一个子进程接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
- 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一 些
RDB的缺点
- 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
- RDB需要经常fork子进程来保存数据集到硬盘上, 当数据集比较大的时候, fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况 下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.
AOF优点
- 使用AOF会让你的Redis更加耐久:你可以使用不同的fsync策略:无fsync 每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略, Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
- AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aofI具修复这些问题.
- Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写:重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的, 因为Redis在创建新AOF文件的过程中,会继续将命令追加到现有的AOF文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis 就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
- AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析(parse) 也很轻松。导出(export) AOF 文件也非常简单:举个例子,如果你不小心执行了FLUSHALL命令,但只要AOF文件未被重写,那么只要停止服务器,移除AOF文件末尾的FLUSHALL命令,并重启Redis,就可以将数据集恢复到FLUSHALL执行之前的状态。
AOF 缺点
- 对于相同的数据集来说, AOF文件的体积通常要大于RDB文件的体积。
- 根据所使用的fsync策略, AOF的速度可能会慢于RDB。 在一般情况下,每秒fsync的性能依然非常高,而关闭fsync可以让AOF的速度和RDB -样快,即使在高负荷之 下也是如此。不过在处理巨大的写入载入时,RDB可以提供更有保证的最大延迟时间(latency) 。
4.X版本的整合策略
在AOF重写策略_上做了优化
在重写AOF文件时,4.x版本以前是把内存数据集的操作指令落地,而新版本是把内存的数据集以rdb的形式落地
这样重写后的AOF依然追加的是日志,但是,在恢复的时候是先rdb再增量的日志,性能更优秀
Elasticsearch面试题
1、倒排索引深入骨髓
- 倒排索引的原理以及它是用来解决哪些问题(谈谈你对倒排索引的理解) I
- 倒排索引底层数据结构(倒排索引的数据结构)
- 倒排表的压缩算法(底层算法)
- Trie字典树(Prefix Trees)原理(类似题目: B-Trees/B+Trees/红黑树等)
- FST原理(FST的构建过程以及FST在Lucene中的应用原理)
- 索引文件的内部结构(.tip和.tim文件内部数据结构)
- FST在Lucene的读写过程(Lucene源码实现)
2、elasticsearch写入原理
3、读写性能调优
写入性能调优:
- 尽量使用bulk批量写入
- 增加flush时间间隔,目的是减小数据写入磁盘的频率,减小磁盘|0
- 增加refresh. _interval的参数值,目的是减少segment文件的创建,减少segment的merge次数, merge是发生在jvm中的, 有可能导致full GC,增加refresh会降低搜索的实时性。
- 增加Buffer大小, 本质也是减小refresh的时间间隔, 因为导致segment文件创建的原因不仅有时间阈值,还有buffer空间大小, 写满了也会创建。
默认最小值48MB<默认值堆空间的10% <默认最大无限制 - 大批量的数据写入尽量控制在低检索请求的时间段,大批量的写入请求越集中越好。
- 第一是减小读写之间的资源抢占,读写分离
- 第二,当检索请求数量很少的时候,可以减少甚至完全删除副本分片,关闭segment的自动创建以达到高效利用内存的目的,因为副本的存在会导致主从之间频繁的进行数据同步,大大增加服务器的资源占用。
- Lucene的数据的fsync是发生在OS cache的,要给OS cache预留足够的内从大小,详则VM调优。
- 通用最小化算法,能用更小的字段类型就用更小的,keyword类型比int更快,
- ignore_ above:字段保留的长度,越小越好
- 调整source字段,通过include和exclude过滤
- store: 开辟另- -块存储空间,可以节省带宽
注意:_ sourse: ** 设置为false*, 则不存储元数据, ** 可以节省磁盘*, 并且不影响搜索。但是禁用source必须三思而后行*: *
update, update by_ guery和reindex不可用。
RabbitMq
RabbitMQ的架构设计是什么样的
是AMQP的实现,相关概念语义
- Broker:它提供一种传输服务,它的角色就是维护一 条从生产者到消费者的路线,保证数据能按照指定的方式进行传输
- Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
- Queue:消息的载体,每个消息都会被投到一个或多个队列。
- Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
- Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
- vhost:虚拟主机,一个broker里 可以有多个vhost,用作不同用户的权限分离。
- Producer:消息生产者,就是投递消息的程序.
- Consumer:消息消费者,就是接受消息的程序.
- Channel:消息通道,在客户端的每个连接里,可建立多个channel.
RabbitMQ事务消息原理是什么
事务V.S确认
确认是对一件事的确认
事务是对批量的确认
增删改查中,事务是对于增删改的保证
发送方事务
开启事务,发送多条数据,事务提交或回滚是原子的,要么都提交,要么都回滚
消费方事务
消费方是读取行为,那么事务体现在哪里呢
rabbitmq的消费行为会触发queue中msg的是否删除、是否重新放回队列等行为,类增删改
所以,消费方的ack是要手动提交的,且最终确定以事务的提交和回滚决定
RabbitMQ如何确保消息发送和消息接收
消息发送确认
1 ConfirmCallback方法
ConfirmCallback是-一个回调接口,消息发送到Broker后触发回调,确认消息是否到达Broker服务器,也就是只确认是否正确到达Exchange中。
2 ReturnCallbalck方法
通过实现ReturnCallback接口,启动消息失败返回,此接口是在交换器路由不到队列时触发回调,该方法可以不使用,因为交换器和队列是在代码里绑定的,如果消息成功投递到Broker后几乎不存在绑定队列失败,除非你代码写错了。
消息接收确认
RabbitMQ消息确认机制(ACK) 默认是自动确认的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,假如你用回滚了也只是保证了数据的一致性,但是消息还是丢了,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
消息确认模式有:
AcknowledgeMode.NONE+自动确认。
AcknowledgeMode.AUto:根据情况确认。
AcknowledgeMode.MANUAL:手动确认。
消费者收到消息后,手动调用Basic.Ack 或Basic.Nack或Basic.Reject后,RabbitMQ 收到这些消息后,才认为本次投递完成。
Basic.Ack命令:用于确认当前消息。
Basic.Nack命令:用于否定当前消息(注意:这是AMQP 0-9-1的RabbitMQ扩展)。
Basic.Reject命令:用于拒绝当前消息。
Nack,Reject后都有能力要求是否requeue消息或者进入死信队列
RabbitMQ死信队列、延时队列分别是什么
死信队列
DLX (Dead Letter Exchange),死信交换器。
当队列中的消息被拒绝、或者过期会变成死信,死信可以被重新发布到另一个交换器, 这个交换器就是DLX,与
DLX绑定的队列称为死信队列。
造成死信的原因: .
- 信息被拒绝
- 信息超时
- 超过了队列的最大长度
延迟队列
延迟队列存储的是延迟消息
延迟消息指的是,当消息被发发布出去之后,并不立即投递给消费者,而是在指定时间之后投递。如: 在订单系统中,订单有30秒的付款时间,在订单超时之后在投递给消费者处理超时订单。
rabbitMq没有直接支持延迟队列,可以通过死信队列实现。
在死信队列中,可以为普通交换器绑定多个消息队列,假设绑定过期时间为5分钟,10分钟和30分钟,3个消息队列,然后为每个消息队列设置DLX,为每个DLX关联一 个死信队列。当消息过期之后,被转存到对应的死信队列中,然后投递给指定的消费者消费。