每天十道面试题-20200328

题目

  • 1、Spring解决相互依赖
  • 2、zookeeper和Spring-Eureka的区别
  • 3、BeanFactory-ApplicationContext的区别
  • 4、栈溢出原因剖析
  • 5、Apollo-SpringCloudConfig-Nacos区别
  • 6、Tomcat架构
  • 7、数据库分库分表
  • 8、MySQL 分表分库全局 ID
  • 9、并发编程三要素?
  • 10、介绍一下 Spring 的事务实现方式及了解?

解答

题目一
  • 题干:Spring解决相互依赖
  • 分析:
  • 考察Spring循环依赖,首先我们要明确循环依赖和循环调用的区别,先说一下循环调用,比如递归调用,自己调用自己然后给出一个终结条件,再如:方法A调用方法B然后方法B内部调用方法A,也是需要给出终结条件才会停止,如果循环调用没有给出正确的终结条件,最终将会导致栈溢出;然后我们主要说一下循环依赖,也就是循环引用,就是BeanA内部需要注入BeanB的引用,BeanB内部需要注入BeanC 的引用,BeanC内部需要注入BeanA的引用,在谈这个问题之前我们先补充一句就是我们需要知道Spring注入的几种方式,手动注入一般是构造方法注入 setter注入 以及工厂注入,对于循环依赖来讲存在于构造方法注入和setter注入中;
    构造器循环依赖:表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖;Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
    setter循环依赖(singleton):表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。会对外暴露一个ObjectFactory类型的对象,这样在去正在创建的Bean的缓存池里获取的时候由于已经暴露了ObjectFactory所以不会像构造器注入那样拿不到Bean,这样就解决了循环依赖.setter循环依赖我们可以通过设置参数来禁止和启用循环依赖,setAllowCircularReferences(false);来禁用循环引用
    setter循环依赖(非singleton):对于prototype作用域Bean,Spring容器无法完成依赖注入,因为prototype作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
    思想:中间对象去解决循环依赖

  • 回答:
  • 1、首先我们一定要避免循环依赖,这是设计上的问题,杜绝循环调用因为如果没有跳出条件将陷入死循环。
    2、只有setter【singleton】循环依赖Spring可以解决,我们可以,因为可以解决所以我们可以通过设置参数setAllowCircularReferences(false);来禁用循环引用。
    3、对于setter【非singleton】,由于我们不进行缓存,所以没有办法暴露出来已经创建的Bean,所以没有办法解决。
    4、构造器产生的循环依赖此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
    5、Setter【singleton】可以解决主要是因为提前暴露一个单例工厂方法暴露出来一个ObjectFactory。当找到已经创建的bean 的时候直接使用暴露出来的ObjectFactory,就不会像构造函数一样去池子里找导致异常抛出了。
    总结能够循环依赖的条件:
    1、setter 注入(构造器注入不能循环依赖)
    2、singleton bean(非单例 bean 不支持)
    3、AbstractAutowireCapableBeanFactory#allowCircularReferences 这个 boolean 属性控制是否可以循环,默认为 true

题目二
  • 题干:zookeeper和Spring-Eureka的区别
  • 分析:
  • 在以Dubbo进行构建的微服务中,可以使用zk作为注册中心,而在SpringCloud进行构建的微服务中,SpringEureka作为注册中心的角色,他们的区别最基本的来讲,在基于CAP理论来讲,zk以CP进行构建的,而Eureka是以CP进行构建的,而我们要知道在微服务中一般我们都需要高可用,而对于数据一致性只要保证最终一致性就可以了,而zk是因为在进行选举的时候有一段不可用的时间,所以一般建议使用Eureka作为注册中心。

  • 回答:
  • 参考上面分析部分 .

题目三
  • 题干:BeanFactory-ApplicationContext的区别
  • 分析:
  • BeanFactory是spring中比较原始的Factory,本身是无法支持如:AOP功能 Web引用等,而ApplicationContext是由BeanFactory派生出来的,ApplicationContext通过扩展了MessageResource ApplicationEventPublisher ResourcePatternResolver 等接口提供了:1MessageSource, 提供国际化的消息访问 2 资源访问,如URL和文件 3 事件传播 4 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层,此外Bean Factory是使用懒加载形式来注入Bean,而这样的行为虽然节省了空间但是很难发现错误的Bean,如果存在错误的Bean只有在getBean使用的时候才会发现Bean配置错误的情况,而Application Context则是在容器启动的时候一次性创建了所有的Bean,这样在容器启动阶段我们就可以发现Bean中存在的配置错误,最后二者都是可以支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

  • 回答:
  • 1 ApplicationContext作为BeanFatory的派生类提供了一些其他的功能aMessageSource, 提供国际化的消息访问 b 资源访问,如URL和文件 c 事件传播 d 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
    2 Bean的注入形式不同Bean Factory使用懒加载的形式,而ApplicationContext容器启动就加载所有Bean
    3 对于BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

题目四
  • 题干:栈溢出原因剖析?
  • 分析:
  • JVM中StackOverFlowError的分析;首先Java中的栈是一块线程私有的内存空间,线程执行的基本行为是函数调用。每次函数调用的数据都是通过Java栈传递的,Java栈和数据结构的栈一样都是后进先出的数据结构,只支持入栈和出栈的操作,Java栈中保存的内容主要是栈帧,每一次函数调用都会有一个对应的栈帧压入栈,每一个函数调用结束,都会有一个栈帧被弹出栈。函数返回栈帧会被从栈中弹出,Java 有两种函数返回的方式,一种是正常的函数返回,一种是异常抛出的返回,但是无论哪种最终都会导致栈帧被弹出。由于函数每次调用都会生成对应的栈帧,从而占用一定的栈空间,因此如果栈空间不足,那么函数调用自然无法进行下去,当请求的栈深度大于最大可用栈深度,系统会抛出StackOverFlowError这个栈溢出的错误。
    扩展:
    1-Xss 是Java中指定线程的最大内存空间的参数。这个参数也决定了函数调用的最大深度。
    2栈帧: 局部变量表、操作数栈、帧数据。
    局部变量表 - 保存函数的参数以及局部变量
    操作数栈 - 主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间
    帧数据 - 除了局部变量表、和操作数栈外,Java栈帧还需要一些数据来支持常量池解析、正常方法返回和异常处理等。大部分Java字节码指令都需要进行常量池访问,在帧数据区中保存着访问常量池的指针,方便程序访问常量池。
    此外当函数返回或者调用异常的时候,虚拟机必须恢复调用者函数的栈帧,并让调用者函数继续执行下去,对于异常处理,虚拟机必须有一个异常处理表,方便在发生异常的时候找到处理异常的代码,因此异常处理表也是帧数据的重要一部分。

  • 回答:
  • 在死循环和递归无法正确跳出的情况下会导致栈溢出错误,归根结底是由于方法的不停调用导致栈上分配的栈帧过多超过了我们配置的Xss参数的大小。

题目五
  • 题干:Apollo-SpringCloudConfig-Nacos区别?
  • 分析:
  • 三者都是配置管理中心,SpringCloudConfig是在SpringCloud全家桶里的一份子,它主要是和git通过webhook来做到感知配置更新的,并通知服务端获取,Apollo是携程开源的配置管理框架,需要结合mysql数据库,性能也比较优异,Nacos是阿里开源的一款配置管理软件,它除了能够作为配置管理中心还可以作为服务发现的注册部分,本身相较于Eureka要由又是因为zk作为注册中心只保证了cp而SpringCloudEureka保证了Ap这个是符合分布式注册中心的初衷的,而nacos 是基于CP+AP它根据服务注册选择临时和永久来决定走AP模式还是CP模式.

  • 回答:
  • Apollo参考:文档
    Nacos参考:文档
    SpringCloudConfig参考:参考Spring官网

题目六
  • 题干:Tomcat架构
  • 分析:
  • Tomcat中最顶层的容器是Server,代表着整个服务器,一个Server可以包含至少一个Service,用于具体提供服务。
    Service主要包含两个部分:Connector和Container。他们的作用如下:
    1、Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;
    2、Container用于封装和管理Servlet,以及具体处理Request请求;
    一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,那就是 Server了!所以整个 Tomcat 的生命周期由 Server 控制。上述的包含关系或者说是父子关系,都可以在tomcat的conf目录下的server.xml配置文件中看出来.
    顶层结构:
    (1)Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container;
    (2) Server掌管着整个Tomcat的生死大权;
    (4)Service 是对外提供服务的;
    (5)Connector用于接受请求并将请求封装成Request和Response来具体处理;
    (6)Container用于封装和管理Servlet,以及具体处理request请求;
    Connector和Container:
    一个请求发送到Tomcat之后,首先经过Service然后会交给我们的Connector,Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了!
    Connector最底层使用的是Socket来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议!
    Connector架构分析:
    Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。
    Container架构分析:
    4个子容器的作用分别是:
    (1)Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine; (2)Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点; (3)Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件; (4)Wrapper:每一Wrapper封装着一个Servlet;

  • 回答:
  • Tomcat中最顶层的容器是Server,代表着整个服务器,一个Server可以包含至少一个Service,用于具体提供服务。
    Service主要包含两个部分:Connector和Container。他们的作用如下:
    1、Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;
    2、Container用于封装和管理Servlet,以及具体处理Request请求;
    一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,那就是 Server了!所以整个 Tomcat 的生命周期由 Server 控制。上述的包含关系或者说是父子关系,都可以在tomcat的conf目录下的server.xml配置文件中看出来.

题目七
  • 题干:数据库分库分表
  • 分析:
  • 首先对于这个问题我们要知道为什么要分库分表,原因如下:当系统并发量很高的时候会访问个表访问的表会很多,而对于MySQL数据库由于数据是存储在操作系统的文件中被称之为表空间的区域,所以数据访问的时候需要进行IO将磁盘中的数据放到Buffer Pool中进行缓存,而Buffer Pool是有大小限制的,而使用Buffer Pool的目的就是来减少IO访问次数的,当访问的表过多就会多次读取磁盘IO,所以就会产生IO瓶颈,而由于业务的增长也会导致单表数据过大,很可能由于某个SQL比较复杂会导致CPU需要计算很多复杂的数据,这样CPU占用时间很长随着并发的升高,和单表的数据量大,会导致CPU的瓶颈.故此我们需要进行分库分表.
    分库分表:主要分为水平分库/分表 垂直分库/分表.
    水平分库:
    应用场景:当并发量很高的时候,而分表已经提升不了性能,无法解决问题,而且也没有明显的业务划分,无法垂直分库,所以此时需要进行水平分库.
    操作:以字段为依据,按照一定策略(hash等),将一个库中的数据拆分到多个库中
    分库后:
    每个库的结构都一样;
    每个库的数据都不一样,没有交集;
    所有库的并集是全量数据;
    分析:通过增加数据库(就像加服务器一个道理),io和cpu的压力自然可以成倍缓解。
    水平分表:
    应用场景:水平分表的诱因不是高并发量,而是由于单表数据量过大,这样,这样SQL的效率就不高,从而加重了CPU的负担主要是来解决CPU瓶颈的…
    操作:以字段为依据,按照一定策略(hash等),将一个表中的数据拆分到多个表中。
    分表后:
    每个表的结构都一样;
    每个表的数据都不一样,没有交集;
    所有表的并集是全量数据;
    分析:表多了,单表数据量小了,单次SQL效率就高了,CPU负担就降下来了。
    垂直分库:
    应用场景:系统并发量上来了,业务划分清晰,我们可以根据业务划分,比如订单库,用户库等…
    操作:以字段为依据,按照一定策略(hash等),将一个表中的数据拆分到多个表中。
    分库后:
    每个库的结构都不一样;
    每个库的数据也不一样,没有交集;
    所有库的并集是全量数据;
    分析:进行微服务,根据服务拆分,一个服务一个库。
    垂直分表:
    应用场景:系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
    操作:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
    分库后:
    每个表的结构都不一样;
    每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;
    所有表的并集是全量数据;
    分析:可以用列表页和详情页来帮助理解。垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。[别join可以先查出来在处理]
    总结一下:无论是水分还是垂直分表基本上不是由于高并发的问题,而无论是水平还是垂直分库都是当并发量很高的情况下.
    分库分表步骤:
    我们可以计算一下:比如拿学习单词的场景来讲,总共英文单词大概是两万个,当用户学习的时候需要记住用户的错词,假设用户全部学完20000万个单词,那么有1000个用户就是2千万数据,拿对于单表来讲数据量其实蛮大的,如果按照单表不超过500万的标准我们可以进行水平分表,用userid hash后/4然后分为4个表单表500万,如果用户量继续增大我们也可以考虑分个100表.
    对于非partition key的查询问题我们使用映射表来解决.

  • 回答:
  • 回答参考分析部分.

题目八
  • 题干:MySQL 分表分库全局 ID
  • 分析:
  • 全局ID,我们考虑使用Snowflake,这个我在项目中有用到,底层就是使用zk做的分布式锁防止同一数据中心内多个实例共用同一个 worker id,每个服务指定一个datacenterId数据中心ID(0~31).

  • 回答:
  • 来自Flicker的数据库自增ID解决方案由于(auto_increment + replace into + MyISAM),需要使用MyIsAm引擎而我们大多数的服务都是使用的InnoDB引擎,所以可以使用Snowflake.

题目九
  • 题干:并发编程三要素?
  • 分析:
  • 原子性 可见性 有序性
    原子性,就是不可分割的操作,我们可以使用synchronized 和cas保证原子性.
    可见性:就是之前的修改要对之后的修改可见,可是使用synchronized和volatile关键字保证可见性.
    有序性:程序执行的顺序按照代码的先后顺序执行有时候会进行重排序.

  • 回答:
  • 分析

题目十
  • 题干:介绍一下 Spring 的事务实现方式及了解?

  • 分析:

  • 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
    声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
    显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
    声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用

  • 回答:

  • 根据分析大致说一下,再就是谈谈ACID,再就是隔离级别了可以根据隔离级别问问MySQL的RR RC。

发布了122 篇原创文章 · 获赞 32 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/YangzaiLeHeHe/article/details/105137255