问:Spring中的设计模式有哪些?

我们目前主要了解的就是工厂模式,代理模式,单例模式,策略模式,责任链模式。

工厂模式:常见的工厂模式主要就是BeanFactory(延迟注入)和ApplicationContext(完全注入),我们无需知道类如何创建,直接从工厂中获取即可。

单例模式:ioc默认情况下就是一个单例模式,每次获取相同恶的类的时候,获取的对象是同一个。不会就行多次的创建。单例模式还分为饿汉式(主动创建单例)及懒汉式(需要时再创建单例)。

代理模式 :aop就是采用代理模式来实现的。当要代理的对象实现接口的时候,我们就会使用JDK Proxy来生成代理对象。如果代理的对象没有实现接口的话,我们就回使用CGLIB生成一个被代理对象的子类作为代理对象。

策略模式:在我们编写项目的时候就有使用到策略模式,因为我要控制分布式锁的失败策略,我们会在枚举类编写一个抽象方法,编写多个内部方法去重新该方法,不同的内部方法就是不同的策略。
责任链模式:stram流就是使用该模式的,按照对应的顺序去执行方法,并向下传递对象。起到解耦合的作用。

问:spring框架的单例bean是线程安全的吗?

在spring框架中有个注解叫@Scope可以设置bean的状态,默认就是singleton也就是单例。

bean进行注入的时都是无状态的,其不会被修改的。所以没有线程安全的问题。但是如果bean中有成员变量时就可能会有线程安全的问题,因为该成员变量可能会被多个线程修改,为了解决这个问题我们可以加锁或将bean设置为多例。(@Scope设置为prototype)

问:什么是AOP?

面向切面编程,将那些于业务无关的复用性比较高的代码快抽取出来,较低代码的耦合度。

问:在你的项目中有使用过AOP吗?

在我的云盘项目中就使用到AOP,在记录日志的时候,我创建有个自定义注解,aop的切面就是这个注解,使用环绕通知在方法中,我们通过传入的参数(joinPoint)获取对应的类和方法,从而获取前端传来的参数和其他主要信息,实现记录日志的效果。

问:Spring中的事务是怎么实现的?

本质就是通过AOP实现的,通过环绕通知对应方法进行前后拦截,在方法执行前开启事务,在执行后提交事务,会对此过程进行try/catch,如果报错直接回滚。(就是@transactional)

问:spring中事务失效场景有哪些?

1.在出现异常后,方法中try/catch了该异常并且没有主动抛出异常,这时候就会导致事务失效。解决方法:在方法try/catch异常后手动的抛出异常。(就会导致事务不知道出现异常了)

2.抛出检查异常时会导致事务失效,spring中的事务只会对runtime异常进行回滚。就比如:Not found Exception就是检查异常。解决方法:在@transactional中设置属性 rollbackFor = Exception.class。使得事务会对所有的异常进行回滚。

3.非public方法会导致事务失效。解决方法:将方法的作用域该为public。

4.在非事务方法中调用了事务的方法,此时就会导致事务失效。(就比如某给没有加事务注解的方法调用了加了事务注解的方法)

5.回滚异常类型不匹配。(我们可能会设置需要进行回滚的异常,就是rollback的值,如果抛出的异常类型不匹配就会导致事务失效)

6.事务的传播行为错误。(就比如在事务方法中调用了其他的事务方法,初始化如果其他的时候方法设置开启新事务的话,在其事务成功后,就不会参与外部事务的回滚操作)

61b16202cfc74859952c5a156f16e7ae.png

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记及答案【点击此处即可】免费获取

问:事务的传播性有哪些?

主要就是三个:Propagation_Required,Propagation_Required_new,Propagation_nested。

  1.   Propagation_Required:如果B为A的子方法,并且都为该传播类型,那么它们都会在同一个事务中。如果主方法中没有开启事务的话就会开启新事务。
  2.   Propagation_Required_new:如果B为A子方法,B会创建一个独立的事务,B不会受A事务的影响。
  3.   Propagation_nested:如果B为A子方法,B会创建一个事务内嵌到A的事务中,如果A中没有事务的话,就单独开启一个事务。
问:@Transactional的原理有了解过嘛?

事务注解是基于AOP来实现的,也就是在执行方法前开启事务,如果try/cath捕获到异常的话就会进行回滚,在方法执行完成后就会进行提交。那AOP实现涉及到动态代理的流程。如果被代理目标对象实现了接口的话,就会使用JDK Proxy生成代理对象。如果代理对象没有实现接口的话,就会使用CGLIB Proxy生成被代理对象的子类作为代理对象。

问:说说CGLIB的执行流程?

问:spring中bean的生命周期有了解过吗?

结构图:

c2b1dcf71aaf44e5a14ed868a1a95c82.png

生命周期的流程为下:

1.通过BeanDefinition获取bean的定义信息。

 2.通过构造函数创建bean,可以将当前的bean理解为一个空壳。

3.进行依赖注入,对bean中的属性进行赋值。

4.处理Aware接口,也就是一些以Aware结尾的接口,就比如:beanNameAware,beanFactoryAware,applicationContextAware。如果实现了个接口的话,我们需要重写一些方法。

5加载BeanPostProcessor对象,并执行处理器的前置初始化方法(PostProcessorAfterInitiazilation方法)。

6.执行初始化方法,包括 IntializingBean和自定义的初始化方法。

7.加载BeanPostProcessor对象,并执行处理器的后置初始化方法(PostProcessorBeforeInitiazilation方法)。在此后置处理器中我们可以通过aop对原始的bean做增强也就是进行代理,代理就包括 JVM代理和CGLIB代理。

8.将bean进行销毁。

问:说说CGLIB动态代理的执行流程吧?

aop就是采用代理模式来实现的。当要代理的对象实现接口的时候,我们就会使用JDK Proxy来生成代理对象。如果代理的对象没有实现接口的话,我们就回使用CGLIB生成一个被代理对象的子类作为代理对象。

问:有了解过bean的循环依赖吗?

循环依赖也就是循环引用,两个或以上的bean同时相互依赖对方,最终形成闭环。就比如:A依赖B,B依赖A。

spring提供了解决方案:三级缓存。

1.一级缓存:单例池,存储已经初始化完成的单例bean。

2.二级缓存:缓存早期的bean单例对象,就bean只完成到执行构造方法。

3.三级缓存:缓存创建bean的factory,这些factory用于创建代理对象和普通对象。

11fa6615f5f9475e8aff8cf805f13bc0.png

三级缓存的解决流程(当前问题为A,B相互依赖):

 实例化A,并将生成的A的objectFacttory将其存入三级缓存,因为A依赖B,B也会去实例化,创建对应的objectFactory存入三级缓存。

此时B依赖A,就会通过三级缓存中A的objectFactory生成早期A实例,并将该A实例存储到二级缓存中,将这个早期的A注入B中,此时B就创建完成了,将B实例存储到一级缓存中。

将完整的B注入A中,将A实例存储到一级缓存中,并将二级缓存中A的实例删除。

问:构造方法中出现循环依赖怎么办?

在构造函数中如果存在循环依赖我们在函数的参数上添加@Lazy就可以解决循环依赖的问题,保证bean在需要的时候才去加载。

问:可以只使用二级缓存来解决循环依赖吗?

在没有Aop的时候就可以使用一级和三级缓存来解决依赖。但是呢,如果存在aop的问题,那就必须使用三级缓存,因为在二级缓存中可以保证即使存在多个半成品bean的引用时,始终会返回相同的代理对象。避免Bean有多个代理对象。

问:除了使用三级缓存解决循环依赖,还有什么方法吗?

使用@Lazy就行懒加载,就比如A,B相互依赖。但是呢A添加了@Lazy注解,此时会创建一个B的代理对象,而非B对象,会将这个代理对象注入到A中,完成A的创建。之后会做B的初始化和实例化,会将创建好的A注入到B,最终完成B的创建。

问:SpringMVC的执行流程有了解过吗?

4e3c2253202c4e2aaf71386ab85f530e.png

jsp版本:

1.用户发送请求到DispatchServlet。

2.dispatchServlet调用处理器映射器HandlerMappering,处理器就会去Controller中找到对应的方法通过映射的路径,然后将处理器执行链返回给dispatchServlet。

3.dispatchServlet调用处理器适配器HandlerAdaptor找到对应的处理器,该处理器就会处理对应的参数和处理返回值,最终会返回ModelAndView给DispatchServlet。

4.Dispatch调用ViewResolver视图解析器,并将ModelAndView传入,最终返回View给dispatchServlet,通过View渲染视图。(就比如:JSP)

前后端分离版本:

1.用户发送请求搭配DispatchServlet。

2.DispatchServlet调用处理器映射器HandlerMappering,会去Controller中找到对应的方法,最终处理器返回执行链给DispatchServlet。

3.DispatchServlet会调用处理器适配器HandlerAdaptor找到对应的处理器,该处理器就会处理对应的方法的参数和处理返回值,在方法上添加了@ReponseBody。

4.通过HttpMessageConverter将数据转为Json返回

问:有了解过springboot的自动装配原理吗?

在启动类上有个注解的叫@SpringbootApplication,在该注解中包含springbootConfiguration表示该类为配置类,还有个注解叫@EnableAutoConfiguration,这个就是实现自动装配的核心注解。

在@EnableAutoConfiguration中使用注解@Import引入了一个自动自动装配的选择器。

该选择器会到jar中的 /META-INF/spring.factories中按照条件加载对应的类,并将该类配置到ioc中。这里面添加的注解就包括:@ConditionOnClass表示当某个类存在时才进行类加载。@ConditionOnMissingBean表示当某个bean不存在的时候才进行类加载。

问:spring中有那些常见的注解?

在Spring中主要注解有:@Componment,@Controller,@Service,@Repository将类配置到ioc中,@AutoWired(根据class),@Qualifier(根据名字)实现依赖注入。@scope设置bean的作用范围。@Configration设置配置类。@ComponetScan主键扫描。@Bean将某个方法的返回值配置到ioc中。@Import将某类导入ioc中。@Before,@After,@Aspecr,@Around,@Pointcut切面编程的注解。

0d7b1337a2e049859be8aae3469ef44e.png

 在SpringMVC中就是一些关于请求的注解,@RequestMappering,@ReponseBody,@RestController,已经参数的注解 @RequestParam,@PathViriable,@RequestHeader。

fd9109dffa3a484889062a3d2f240448.png

在springboot的注解中就包括:@SpringBootConfiguration,@EnableAutoConfiguration自动装配注解,@ComponentScan之间扫描。

6a87f5d8eeb0460186ad22060402a65f.png

问:能说说你对Mybatis的执行流程的理解吗?

 1.读取mybatis-config.xml文件,里面就是数据库的配置和mapper的地址。

2.创建SqlSessionFactory。

3.通过SqlSessionFactory创建对应的SqlSession,就是项目和数据库的会话,SqlSession包含执行sql语句的所有方法。

4.执行操作数据库的接口,Executor执行器,同时负责缓存的维护。

5.在Executor执行器中的MapperStatement对象,当操作数据库的时候会将Java的类型转为数据库的类型,当输出结果的时候会将数据库的类型转为Java的类型。

cbcaf3572b934c9e8ef56b60dec4da5a.png

 问:Mybatis支持延迟加载吗?

1.延迟加载就是当需要使用数据的时候才去加载数据,不用数据得时候不会主动加载。

2.Mybatis支持一对一关联对象和一对多关联集合的延迟加载。

3.在mybatis的配置文配置文件中的LazyloadEnabled设置为true就开启了全局延迟加载。

问:Mybatis延迟加载的底层有了解过吗?

1.通过GCLIB的代理实现的。

2.当调用目标函数的时候,会调用拦截器的invoke方法,如果发现目标方法中的值为null,则进行sql查询,在获取数据后通过set设置属性值,在后续调用目标方法时就有值了。

问:有了解过Mybatis的一级缓存和二级缓存吗?

一级缓存:基于PrepetualCache的HashMap的本地缓存,是默认开启的,其作用域就是Session,当Session进行了Close,Flush后该Session中的缓存会被全部清空。

二级缓存:其作用域是namespace和mapper,其默认是不开启的,是基于prepetualCache,hashmap存储的。通过在mybatis的配置文件中设置CacheEnabled设置为true,在对应的mapper中添加标签Cache。

问:Mybtis的二级缓存什么时候会清理?

当某个作用域(一级缓存(session)/二级缓存(namespace))中进行了增删改操作时就会清除掉select中的缓存。

问:Mybtis中#{}和${}的区别?
  1.    #{}主要就是做占位符的替换的,在mybatis中会使用?占位,而${}主要就是做文本替换的。替换${}中的文本。
  2.   #{}数据的替换发生在DBAS之中,而${}数据替换发生在DBAS之外。
  3.   #{}在做数据替换的时候会自动添加'',而${}数据替换的时候不会自动添加''。
  4.   #{}可以防止SQL的注入,而${}不可以。
微服务

问:springCloud的五大主件有了解过吗?

springCloud: 

注册中心:Eureka。

远程调用:Feign。

负载均衡:Ribbon。

服务保护:Hystrix。

网关:Gateway。

springCloud alibaba:

注册中心:Nacos。

远程调用:Feign。

负载均衡:Ribbon。

服务保护:Sentinel。

网关:Gateway。

f1586c6e868047929759e474a4bfce97.png

问:服务的注册与发现是什么意思?SpringCloud是如何实现服务的注册和发现的?

在我的项目中使用nacos实现服务的注册与发现。那我就以eureka为例吧。

服务的注册:每一个服务的提供者会将自己的信息注册到eureka中, eureka会储存提供者的信息,包括:服务名,ip,端口等等。

服务的发现:消费者会通过eureka拉取对应的服务列表,服务可能会搭建集群,所以会存在多个服务,这时候消费者会根据负载均衡的算法选择一个服务并进行调用。

服务的监控:服务者会每隔30秒发送心跳,保证自己的健康状态,当注册中心发现某个服务90秒都没有发送心跳,则会将该微服务移除。

67475811158941dbbeaf900187ce5ca8.png

问:我看你项目中使用nacos,你能说说nacos和eureka的区别吗?

相同点:

1.都支持服务的注册和发现,整个流程也是非常相似的。

2.都支持服务者向注册中心发送心跳,监控监控状态。

不同点:

1.nacos的注册中心支持自动查询服务者的状态,如果服务者是临时实例时则使用心跳模式,如果服务者是非临时实例时就采用自动检测模式。

2.临时实例如果发现心跳不正常时就会移除对应的服务,而非临时实例即使有异常也不会被移除。

3.nacos的注册中心支持向消费者发送更新后的服务列表,更新更及时。

4.nacos的临时实例使用的AP模式(高可用),eureka页使用AP模式,非临时实例使用CP模式(强一致)。

5.nacos还支持作为配置中心,而eureka则不支持。

 9a8ed08f133b4e17bafeef561746a3c5.png

问:你们项目中的负载均衡是怎么实现的?

通过Ribbon实现的,在我们feign调用服务时的负载均衡就通过Ribbon实现的。

问:Ribbon的负载均衡策略有哪些?

 1.轮询策略。

2.权重策略:按照权重的大小选择服务器,响应的时间越长其权重就越小。

3.随机策略。

4.区域敏感策略:根据消费者区域按照就近原则选择服务器,如果没有区域这个概念,其效果就和轮询策略效果一样。

问:如何自定义负载均衡的策略?

1.通过实现接口IRule设置对应策略并设置到ioc中 ,其的作用域是全局的。

2.通过yml配置文件进行配置,需要指定对应的服务者的名字,其作用域是局部的。

问:什么是服务的雪崩呢?怎么解决这个问题呢?

服务的雪崩:当某个服务调用失败后,导致后续调用的服务全部失败,造成服务雪崩发生。

解决方案

服务的降级:如果某个服务调用失败后直接返回自定义的降级信息。通过实现Feign接口,并在Feign接口的@FeignClient中设置fallBack为实现类。

服务熔断:可以通过Hytrix或Sentinel实现服务熔断,那我就以Hytrix为例吧,其默认是没有开启的,需要我们在启动类上添加注解@EnableCircuitBreaker进行开启。当调用某个服务10s时,发现有50%以上的调用失败就会触发熔断机制(开启),每隔5秒会放行一个请求去请求服务(半开),当发现服务调用失败了就会继续熔断,如果请求成功则会解除熔断(关闭)。

 问:你们微服务是怎么做监控的?

在我们的项目中主要是用Skywalking完成监控的。

Skywalking主要监控监控,服务的状态。特别是在做压力测试的时候就能够找到对应的慢接口,并对接口进行修改。在Skywlaking中还有服务间调用的拓扑图,方便我去理清调用关系。

我们可以在Skywalking中设置警告规则,当我们的项目上线后,发生错误就会向我们设置的管理者发送短信和邮箱信息,保证第一时间进行修改。

问:你们的项目中有没有使用过限流?是怎么实现的?

之前要学习过抢票的业务就使用到限流,因为当时的QPS到达2000左右,为了保证正常的运行我需要做限流,通过限流将QPS控制在1000左右。

实现方案:

Nginx

1.控制速率:使用漏桶算法实现。可以存储一定数量的请求,按照一定的速率处理请求,如果进入漏桶的个数大于最大值则会直接丢弃。

439efb33e3e44b06ae96b6cacae24830.png

2.控制并发数:控制单个ip的链接数和并发链接的总数。

网关

通过GateWay中的提供过滤器RequestRateLimiter实现,其思想就是令牌桶思想,每个请求需要携带令牌才能进行访问。我们可以设置令牌桶的容量和令牌每秒生成的个数,在GateWay的配置文件中进行配置。

f16263a8417e4fe3a9d05a0d5ec1da42.png

 问:限流的算法有哪些?有什么区别呢?

主要就是漏桶算法和令牌桶算法。不同于漏桶算法按一定速率处理请求,当令牌桶中还有令牌,且被处理的请求数大于生成令牌的速率时,执行的请求个数就会大于每秒生成的令牌数。

问:能解释一下CAP和BASE吗?

CAP:一致性(c),可用性(a),分区容错性(p)。

1.分布式系统的节点都是通过网络连接的。所以一定会出现分区的问题(因为网络的问题)。

2.因为分区的出现我们就不能同时满足可用性和一致性。

3.cp为强一致性(nacos的非临时实例就是采用此模式),ap为可用性(eureka和nacos的临时实例就是常用此模式)

BASE

基本可用:分布式出现故障的时候,允许损失部分的可用性,保证核心可用。

软状态:允许出现中间状态。比如:临时不一致的情况。

最终一致性:在软状态结束后,保证最终的一致。

解决分布式事务的思想和模型

最终一致性:各个分支的事务都进行提交,彼此结果不一样就执行相反的操作(插入->删除)保证最终的一致性。
强一致性:各个分支的事务在执行完业务之后不会提交,都在等待彼此的结果,最终一起提交或回滚。

问:你们是采用哪种分布式事务来解决问题的?

在我们的物流项目种主要使用RabbitMQ来实现分布式事务的。

MQ 

在我们调用服务A的时候,做完一系列的判断操作后就去执行数据库的操作,此时也会调用消息队列发送消息到其他的服务去,此时消息队列操作和数据库操作是在同个事务中的。因为其是异步的,所以性能比较好。(可以拿做缓存举例子:双写一致性)

Seata

XA模式:强一致性(cp),各个分支事务不会提交事务,都在等待彼此的结果,最终一起提交或回滚。

TA模式:最终一致性(ap),各个分支事务都会进行提交,当彼此结果不一样的时候就会通过undo(回滚日志)进行逆操作,保证数据最终的一致性。 

TCC模式:最终一致性,类是TA模式,性能比较好。但是需要人工编码,耦合度比较高。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记及答案【点击此处即可】免费获取

问:你们分布式服务中接口的幂等性性时怎么设计的?

在我们物流项目的订单支付模块就需要考虑幂等性的问题,防止用户重复支付。保证多次调用订单支付的接口和调用一次的结果时一样的。我们主要通过分布式锁和交易单状态的判断来保证接口的幂等性。(将模式官引导到物流项目中)

在我们项目中判断交易单的状态有:已结算,免单,支付中,取消订单,挂账(累计结算)。

接口会传入支付状态。

1.如果是已结算和免单直接返回错误信息。

2.如果是支付中,则需要判断支付方式是否改变,如果改变则生成新的交易单作为新的数据,如果没有改变则直接返回错误信息。(传入订单Id,去匹配对应的交易单信息,如果没有就创建)

3.如果是取消订单和挂账则生成新的交易单作为新的数据。

解决方案(以支付为例)

1.redis + token,token通过UUID生成,在生成订单的时候生成token,将token存到redis中,请求会携带这个token进行,先判断token是否存在于redis中,如果存在就进行支付然后删除redis中的token,最终保证幂等性。(但是没有考虑更换支付方式的问题)

2.分布式锁,保证每次只有一个支付请求,性能比较低。

问:xxl-job的路由策略有哪些?

1.轮询:轮流被选择。

2.故障转移:通过心跳机制,找到第一个健康实例。

3.分片广播:通过广播触发触发定时任务。适用于任务数量多的时候。在我们的物流项目中定时任务就是使用分片广播。(引导面试官到你的项目中)

问:xxl-job任务执行失败怎么解决?

故障转移策略 + 设置重试次数 + 在设置邮箱告警。

问:如果大量的任务需要被执行,怎么解决?

使用分片广播,在代码编写上我们可以获取当前xxl-job节点的索引和节点的总数。通过取总数模来确定执行任务的节点。

在我们的计算运力模块中,司机执行运力计算时就需要执行定时任务。通过去模的方式选择对应的节点来执行计算运力。那再这个过程中我们为了保证用户的体验,我们需要给在计算运力的时候添加分布式锁,保证同一个用户的商品尽可能的在同一辆车上。

消息中间件

问:RabbitMq如何保证数据的不丢失?

在我们物流项目中的支付模块中的支付方式的模板做了缓存需要达到mysql,redis双写一致性,我们使用RabbitMq来实现的,此时我们就需要把证Rabbit中数据的不丢失。

我们需要从三个方面进行考虑:

1.开启生产者的确认机制,成功返回ack,失败则返回nack,包括publisher-confirm机制和publisher-return机制,前者作用在生产者发送消息到交换机和消费者消费消息,后者作用在交换机发送消息到消息队列中。

2.开启持久化功能,设置交换机(在创建交换机的函数中可以设置为持久化),消息队列(通过queueBuilder中设置持久化),消息的持久化(可以在创建消息时设置其模式)。

3.开启消费者的确认机制,设置消费者的确认机制的模式为auto,在spring处理消息成功后返回ack(此时就会将消息从消息队列中删除),反之返回nack,我们可以通过spring的retry机制设置重试次数,在我们的项目中设置为三次,如果三次都失败了,则直接将消息发送到异常队列中,人工去处理异常的消息。

问:RabbitMq中的消息重复消费问题怎么解决?

之前在写项目的时候就遇见过这种问题,因为网络的问题,消费者没有及时的发送ack,然后此时消费者服务又宕机了,在服务重启后就会重复消费消息。

解决方案:

1.为每条消息设置一个唯一的Id标识,当消费者在消费消息的时候会先判断id在数据库中是否存在,如果不存在就消费信息,如果将id存储数据库中,如果存在则就不消费消息。

2.使用分布式锁,其性能比较低。

 问:RabbitMq的死信队列又了解过吗?(RabbitMq的延迟队列有了解过吗?)

在我们的物流项目中,快递员的上门取件就使用延迟队列,当前时间大于取件时间的前两小时时就会做发送延迟消息。

解决方案:

延迟队列的实现方式就是:死信队列 + TTL

1.在我们的消息队列中通过属性 dead-letter-exchange设置死信交换机。自定义死信交换机和绑定对应的自定义的死信消息队列。给需要延迟发送的消息设置ttl。

2.当消息队列中的消息过期时,消息被拒接消费时,队列满之后,消息就会进入私死信队列中。

3.我们就可以设置一个消息队列用于存储需要延迟的消息,当消息过期后直接进入死信队列中,消费者去消费死信队列中的消息达到延迟处理的效果。我们项目中就是这么实现的。

下载延迟队列插件(问还有什么解决方案时答)

这个我只了解过,其使用就是在配置交换机的时候设置属性delayed为true,然后在创建消息的时候添加头信息 x-delay头,值为过期时间。

问:RabbitMq的消息堆积该怎么解决呢?(如果有100万的堆积在RabbitMq的消息队列中该怎么办)

在项目中目前没有遇到过这种情况,但还是有有了解过解决方案的。

1.消息堆积的原因是因为消费信息的速度小于生成消息的速度,所以我们可以多增加几个消费者。

2.在消费者中开启线程池加快消费速度。

3.找到消息队列的容量。通过间消息队列设置为惰性队列,造创建队列的时候使用lazy方法实现。

惰性队列的优点:存储空间大。缺点:因为消息都是存储在磁盘上的,所以需要做IO操作,效率比较低。

问:RabbitMQ高可用机制有了解过吗?

在我们的生产环境下,当时采用时镜像模式搭建的集群,总共三个主节点,所有的操作都是在主节点上完成的,并会将数据同步到镜像节点上。在主节点宕机后会有镜像节点作为新的主节点。

此时存在一个情况,当主节点操作完成时,就给宕机了,数据没有即使同步,导致数据丢失。

问:那出现这种数据丢失该这么解决呢?

我们可以使用仲裁队列,它和镜像模式一致,也是一种主从的模式,其遵循Raft协议,具有强一致性,其配置起来是很简单的,在创建队列的时候调用可以设置仲裁队列方法(quorum方法),一个q开头的方法,名字现在忘了。

问:kafka是如何保证消息不丢失的?

b55f441c07ef40d2a4f9c96c00591d83.png

1.服务者发送消息到broker中时可能会出现数据丢失的情况。

解决方案:在我们异步发送消息的时候会重写一个回调方法,这个方法有个次数就异常类,我们可以判断该异常是否存在进行重写发送消息。可以给kafka设置重试次数的参数配置。

2.消息在broker中可能会出现消息丢失的情况。

解决方案:确认发送就是ack机制,我们可以设置ack的值来防止消息的丢失,ack=all就是保证消息存储到主从节点后才进行确认。但在我们的项目中 一般设置ack=1,也就是leader节点存储数据后就进行确认。

3.消费者从broker中获取的消息丢失。当存在多个消费者时,每个消费者分区进行消费,每隔5秒都会自动提交已消费的各个的偏移量(offset,此时就可能出现提交偏移量后消费和消费后提交偏移量)。情况1:当某个消费者在消费完消息后未提交偏移就宕机了,此时其他的消费者就会使用就得偏移量消费信息,就出现重复消费的情况。情况2:当提交完偏移量后还没消费完服务就宕机了,此时其他消费者就用新的偏移量来消费消息,就会出现消息丢失的情况。

解决方案:关闭主动添加,使用手动提交。提交发送使用 同步 + 异步提交。(先在消费消息后异步提交,在最后再进行同步提交)

问:kafka是如何保证消费的顺序性的?

在kafka存储消息时会将消息存储到不同的分区中不能保证消息的顺序性。

解决方案:

1.为需要顺序性的消息设置相同的分区,通过kafkaTemplate在发送消息时进行设置。

2.为需要顺序性的消息设置相同的key,通过hash函数计算到相同的位置,将信息散列到同一个分区中,通过kafkaTemplate在发送消息时进行设置。

问:kafka的高可用机制有了解过吗?

kafka的高可用机制就是集群和副本复制机制。

集群:在kafka集群中会有多个broker,即使有某个broker宕机也不会服务的使用。

副本复制机制:一个topic会有多个分区,一个分区会有多个副本,副本分为leader和follower,副本会存储在不同的broker上,当副本中的leader挂掉后就会选择一个follower作为新的leader。

问:能解释一些复制机制中的ISR吗?

ISR(in-sync-replica):同步复制。也就是此时的副本需要leader的同步复制,而不同的副本则是异步复制,因为异步复制可能会出现数据丢失的情况,所以ISR副本的数据更加接近leader,在leader挂掉后会优先使用ISR副本作为新的leader。(我们可以在broker的配置文件中设置ISR的个数)

问:kafka的消息清理机制有了解过吗?

59bea442177f47f08c7d0d7b092317c6.png

先说说数据的存储机制吧。

每个topic会有多个分区,每个分区又有多个分段segment,分段主要以索引文件和日志文件存储在磁盘中的,其优点就是减少单个文件的内容大小,加快查询速度和消息清除的速度(索引和日志文件的清除)。 

数据清除策略

1.根据消息的保留时间进行清除,当消息的保留时间超过指定的时间时就将其清除。默认的指定时间是7天。

2.根据topic存储的大小进行清除,当topic存储的大小到达阈值时就会触发清除,删除掉存储最久的消息,默认是不开启的。(在broker配置文件中开启)

问:kafka实现高性能的设计有了解过吗?

1.消息分区:消息不再局限于存储到单个服务器上。可以处理更多的数据。

2.顺序读写:磁盘采用顺序读写,速度更快。

3.页缓存:把磁盘数据先缓存到内存中,在读取数据时将磁盘读取变为内存读取,速度更快。

4.零拷贝:减少数据的拷贝,加快速度。原本的:系统资源->页缓存->kafka->socket缓存区->网卡最终发送给消费者此时为四次拷贝,新的:系统资源->页缓存->网卡最终发送给消费者,新的拷贝比原来少两次。速度更快。

4b838200d78843b3be9df85efc37794f.png

4e20c1923fef4b369df0a104a38f53da.png

5.消息压缩:减少磁盘io和网络io。

6.分批发送:将消息打包批量发送,减少网络开销。

集合

问:数组的索引为什么从0开始而不是从1开始呢?

数组的寻址公式就是 数组的头地址 + 索引 * 节点的大小。

如果此时我们使用1作为索引头,则公式就变成了 数组的头地址 + (索引 - 1) * 节点的大小。

在公式上多一个减法操作,效率没有从0开始高。

问:ArrayList底层是怎么实现的呢?

1.ArrayList是基于动态数组实现的。

2.在开始的时候ArrayList的长度为0,第一次提交数据的时时候就会将扩容到10。

3.在后续我们做新增的时候会先判断size + 1是否大于当前的数组容量,如果大于的话就将容量扩容到原来的1.5倍,并对数组进行拷贝。

4.新数据就存储到索引为size的位置。最终返回boolean值。

问:new ArrayLIst(10)扩容了几次?

当构造方法传入的是长度的时候,其底层实现就是创建一个object数组,并没有进行扩容。

问:如果实现数组和List之间的转化?

数组转List使用Arrays.asList方法,List转数组使用ArrayList.toArray方法。

问:用Arrays.asList方法后,修改了数组中的内容,List会受影响吗?

 在Arrays类中的定义了getArrayList就是将传入的数组进行包装了一下,还是对该数组的引用,所以在数据修改时会受影响。

5c9c7fa8598d4090a2e247e9a5fd7106.png

问:用ArrayList.toArray方法后,修改List中的值,Array会受到影响吗?

返回的数组就是ArrayList中动态数组的拷贝, 因此不会受影响。

76a0f448678a47dbb02375bcbf0100b4.png

问:ArrayList和LinkedList的区别是什么?

1.ArrayList的底层是一个动态的数组,而LinkedList的底层是双向链表。(底层区别)

2.ArrayList的存储空间是连续的存储空间,而LinkedList的每个节点都需要存储值和两个指针,在存储空间上比ArrayList大。(存储空间)

3.查询时:索引条件已知的时候,ArrayList的时间复杂度为O(1),而LinkedList需要遍历查找,其时间复杂度为O(N)。在索引未知的情况下,都需要遍历查找,它们的时间复杂度都是O(N)。

增加和删除时(索引已知):ArrayList的头尾节点的增加和删除时间复杂度为O(1),而其他节点为O(N)。LinkedList的头尾节点增加和删除时间复杂度为O(1),其他节点的时间复杂度为O(N)。(时间复杂度)

4.ArrayList和LinkedList都是线程不安全的,所以我们在使用的时候,将其设置为局部变量。

或者通过使用Collections.synchronizedList方法进行包装,虽然线程安全但是效率比较低。

问:说一下hashMap的原理?

HashMap的底层是通过散列表 + 链表  + 红黑树实现的。

通过调用hash函数计算出值得索引从而进行数据得存储。

当位置有只值得时候:如果key的值相同则进行覆盖,如果不同就使用链表或红黑树进行存储。当然只有当链表的长度大于等于8且HashMap的长度大于等于64的时候才会使用红黑树进行存储。

问:HashMap在JDK1.7和JDK1.8有什么区别?

在JDK1.7时,HashMap在解决hash函数冲突的时候使用拉链法。当链表上存储的大量值得时候其时间复杂度就从O(1)变成了O(N)。

在JDK1.7时,HashMap在解决hash函数冲突得时候使用链表和红黑树解决,当链表的长度大于等于8且HashMap的长度大于等于64时会将链表转化为红黑树,此时它的时间复杂度为O(logN)。

问:说一下hashMap的扩容机制?

1.在进行扩容的时候使用函数resize方法进行扩容,并且在第一次初始化的时候容量为16,容量阀值12,当容量大于12时就会触发扩容。

2.每次扩容都是用来的两倍。

3.扩容之后,会创建新的数组,将旧数组中的数据按照规则(计算索引位置)复制到新的数组中。

没有哈希冲突的时候直接复制到e.hash&(newCap -  1) (旧hash值取新容量的的值,写成&运算效率更高)索引的位置上。 

如果是红黑树,直接做红黑树的添加。

如果是链表,则遍历该链表,此时链表中的数据可能会存储在不同的新节点上。通过e.hash&oldCap表达式判断存储的位置,要么存储到原始位置上,要么存储到原始位置 + 旧容量的位置上。

问:说说hashMap的寻址算法的理解?

通过计算数据的hashCode的值,并将值右移16位做异或操作,计算出新的hash值,做二次hash的目的主要让数据分布的更均匀。在存储数据的时候将该hash值做取模的操作,但是在源码中使用(capacity - 1)& hash其是等价取模的,因为与运算效率更高。

问:为什么hashMap的大小要设置为2的N次幂?

在计算索引的时候效率更高,只有当大小为2的N次幂,与运算才会等价于取模操作。

在扩容是重新计算索引的效率更高,当hash & oldCap == 0时,就存储到原来的位置,反之旧存储到原来的位置 + 旧容量的位置上。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐