Java面经(一)

不管何时何地,要总结,要笔记,走过的路,趟过的坑,都是经验和教训......

 

1、描述一下你最认可的一个软件项目的架构

这个问题如果忽然遇到很可能会懵,需要平常总结,大体的回答可以参考下面的这个套路:

可先做点铺垫,如我之所拿这个项目来讲,主要是因为虽然这个项目可能不是我做过最好,但是我设计过相对最复杂的,...。

从软件架构上来说,这是一个B/S模式系统,基于Spring Cloud相关技术标准实现的一个xxx系统,整个架构分为4层,即负责入口路由的接入层、负责具体业务需求实现的业务层、为其它服务提供共享数据访问的基础层以及数据存储层。基础层的注册中心和配置中心采用成熟稳定可独立部署的阿里Nacos,其它子服务系统均采用目前流行的开发框架SpringBoot实现,分为显示层(view)、控制层(Controller)、业务层(Service)和数据访问层(DAL)共4层。由于服务请求均以接口的形式通过网关访问,所以显示层设计为与服务端分离的独立的层,使用接口通过网关访问对应的服务系统,显示层包括基于浏览器的Html和基于Android和IOS的手机应用;常规的控制层和业务层没什么好说的,数据访问层主要是对关系数据库(MySQL)、分布式缓存(Redis)及文件的访问,使用MyBatis持久框架访问主从结构的MySQL数据库,以Jedis客户端集群方式访问Redis缓存。这些服务之间常规通信使用Feign,异步通讯使用RocketMQ,为保证服务的可用性,使用Sentinel做熔断和服务降级。

从硬件架构上来说,是采用物理防火墙+前置机+静态Web服务器+Docker服务器+数据库服务器的结构,前置机、静态Web服务器、Docker服务器和数据服务器均为运行在物理主机上的虚拟服务器。前置机连接外网和内网,静态Web服务器部署前端Html静态页面,Docker服务器部署各业务子系统和Redis缓存服务,数据库服务器部署MySQL数据库和静态文件服务。

用户请求通过域名解析到物理防火墙,接着进入到前置机(外网卡),在前置机上通过Nginx代理进入内网(内网卡)的网关或静态Web服务器,再由网关将请求转发到相应的服务进行业务处理,这些服务再去调用其它服务或者访问数据库和缓存等。

 

2、Nacos实现服发现的原理

(1)、场景:Nacos、多个服务A和多个服务B,服务A需要调用服务B;

(2)、启动Nacos、服务A和服务B,服务A和服务B分别将自己向Nacos注册,并各自向Nacos发起长轮询连接,检查服务是否有变动,如果有变动则摘取服务信息;

(3)、服务A根据从Nacos拉取到的服务B清单,从清单中选择某一个主机发起调用请求;

(4)、Nacos监控所有服务的长轮询连接,如果在固定超时时间内未收到某个B服务的轮询请求连接,同时Nacos也会发送健康检查请示,如果检查失败,则认为这一个服务B下线,会将这个服务B从清单中踢除,同时通知所有服务A的长轮询连接服务B有变动,所有服务A重新拉取服务B清单。

 

3、MySQL数据库主从同步不一致的原因

(1)、从库发生写入(从库可写);

(2)、设置了ignore/do/rewrite等同步规则;

(3)、主从sql_mode不一致;

(4)、Binlog设置statement格式;

(5)、从库Memory引擎表重启后数据丢失;

(6)、错误使用set sql_log_bin = 0;

(7)、主从表存储引擎不一致;

(8)、主库宕机丢失事务导致从库丢失这部分数据;

(9)、主从异步复制,主库异常宕机且无法恢复丢失的一部分数据。

 

4、Spring与SpringBoot的区别

Spring是一系列轻量级框架的集合(JDBC、MVC、AOP、ORM等等),Spring的核心是IOC容器,Spring的其它模块或扩展都需要依赖于IOC容器,原因有两个,一是IOC容器本身是符合依赖控制反转原则的,即抽象不依赖于细节,细节依赖于抽象,二是IOC容器本身也是一个对象缓存池,可以节约对象重复创建时的开销,对系统的性能是利的。

SpringBoot则是Spring的扩展,消除了平常Spring应用程序所需要设置的一系列配置文件,就是通常说的约定优于配置,这样就可以省去复杂配置的烦恼,可以更快更高效地进行业务开发。SpringBoot主要扩展出来的内容是可以独立创建Spring应用,并尽量自动配置Spring应用,如果是开发Web应用还提供了内置的Web容器(Tomcat、Jetty、Undotow),还做了很多的Starters来简化Maven的配置。

 

5、SpringBoot的启动过程

(1)、创建SpringApplication实例,在其构造方法中完成3件事:

a.判断服务类型。尝试去加载相应的启动类,如能加载到Web相关的DispatcherServlet类就判断为Web类型;

b.加载初始化器。通过核心配置文件META/Spring.factories文件加载;

c.加载监听器。加载方式同上。

(2)、执行run方法

a.获取并启动构造方法中加载的监听器;

b.加载用户配置,配置运行环境;

c.打印Banner;

d.创建Spring容器(从SpringBoot正式进到Spring);

e.创建异常报告,用户于收集Spring容器过程中的异常信息;

f.刷新容器(创建Tomcat等子容器,实例化非懒加载对象);

g.刷新收尾(这是一个模板模式,事实上在这里SpringBoot并没干什么);

h.发布监听事件等,启动完成。

 

6、Java如何处理高并发

处理高并发通常就是处理高并发读高并发写两种情况。高并发处理的具体做法总结起来就是三点:请求路径尽可能短、传输数据尽可能少和服务端处理尽可能快。Java对高并发主要是作用在服务处理尽可能快这一点上,可以从以下几个方面来处理:

(1)、代码方面:有减少不必要的系统开销和提升代码执行效率两个方向,尽量减少new对象、使用合适的设计模式、使用池化技术(线程池、连接池、对象池等)等可以有效减少系统开销,使用合适的数据结构和算法可以用来提升代码的执行效率;

(2)、多线程方面:多线程只是在同/异步上解决高并发问题的方法之一,实质是在同一时刻复用计算机闲置资源的方式。多线程在应用高并发问题所起作用就使用计算机资源在每一时刻都能达到最大复用率,不浪费计算机资源使用其闲置。具体的做法是将对不影响请求主业务的次要业务逻辑使用其它线程进行异步处理;

(3)、JVM方面:两个目标,一是尽量减少Full GC的频率,二是减少发生Full GC时的JVM停顿时间。需要选择合适的运行模式(Server/Client)和GC策略以及进行合理的堆栈配置;

(4)、通信方面:通信是所有请求的基础,涉及的是通信方式的选择(组播/广播/单播)、通讯协议选择(TCP/UDP/HTTP/RPC/Socket)、连接方式选择(长连接/短连接)及通讯框架选择(Netty/Fegin/Dubbo)等。Java应用中一般在内部各子系统间通信时使用基于RPC的Dubbo通信框架,而对外则是提供基于HTTP的接口访问,使用优秀的通信框架(Netty、Dubbo等)可能有效提升系统的并发能力,事实上这里又回到多线程的范畴了。

 

7、如何处理高并发

处理高并发通常就是处理高并发读高并发写两种情况。高并发处理的具体做法总结起来就是三点:请求路径尽可能短、传输数据尽可能少和服务端处理尽可能快(与第6题有重复的地方)。

首先,请求路越径短,单次请求的效率就越高。缩短请求路径最重要的一点就是做动静分离,具体做法是将动态页面进行改造成适合缓存的静态页面,分离出动静态数据后,将静态数据进行合理缓存(静态化改造的一个特点就是直接缓存整个HTTP连接而不仅仅是缓存静态数据,这样一来,响应过程无需重组HTTP协议,也无需解析HTTP请求头等)。静态数据缓存的地方有浏览器、CDN和服务端,浏览器是第一选择,但不可控的,而服务端主要职责是进行动态逻辑计算和加载,静态数据如果下沉至此就会拉长请求路径,因此通常CND是比较理想的,但需要缓存失效问题和命中率问题,CDN节点选择需要满足几个条件:临近访问量集中地区、距离主站点较远、节点与主站点间网络质量良好。

其次,传输数据越少,传输速度就越快。包含两层意思,一是请求携带的参数和响应返回的数据量要小,没有非必须数据;二是减少没必要的请求,从架构上过滤不必要的请求(实事上是缩短请求路径,还有批处理),对固有的请求进行分流。

最后,服务端处理速度越快,单位时间处理的任务就越多。可以从很多方面入手,有硬件(多服务器、多CPU、多内存、高速磁盘、高速网卡等)、网络(增加带宽、高速光纤)、系统架构、开发语言、数据结构、算法、数据库(读写分离、集群等)等等。但总体的思路是分层校验,即分不同层次尽可过滤掉无效请求,只在最末端集中资源进行有效处理,缩短系统瓶颈的影响路径。在分层校验设定下,系统可能使用分布式缓存甚至本地缓存来抵抗高并发读。

 

8、说一说热点数据

分为静态热点和动态热点。

静态热点能够提前预测,一是根据业务特点分析出热点,如可以根据商品大促的行业特点、活动商家等分析出热点商品;二是通过技术手段预测,如对买家每天访问的商品进行大数据统计,得出TOP N的商品,即为热点商品。

动态热点无法提前预测,这种情况时瞬时流量涌入,往往导致缓存击穿,实现热点数据动态发现能力,常见思路是,异步采集请求链路各环节的热点信息,按一定规则提前识别热点,将识别的热点信息分发到各系统,各系统根据自身的需求来处理热点,或限流或缓存,来实现热点保护,需要注意的是,热点数据采集要使用异步方式,不能影响业务核心链路,保证采集方式通用,同时热点发现最好要做到秒级实现,否则意义不大。

热点识别出来后,第一原则是要将热点数据隔离出来,目的是把已经识别的热点请求和普通请求区分开来,不要让1%影响到另外99%。隔离方法有很多,业务隔离(做缓存预热),系统隔离(独立部署),数据隔离(单独缓存集群或DB组)等等。第二原则就是优化,方式无外乎缓存和限流两种,缓存是最有效的办法,限制更多是一种保护机制,各服务要时刻关注是否触发限流并及时检查。

 

9、怎么做到高可用

高可用指的是系统持续提供服务的能力较高。在系统建设的整个生命周期中,每个环节都有可能犯错,甚至有些错是后面无法弥补或弥补成本极高的,所以高可用是一个系统工程,必须放到整个生命周期中进行全面考虑。同时,还要考虑到服务的增长性,高可用更需要长期规划并进行体系化建设。

具体来说,系统的高可用建设涉及架构阶段、编码阶段、测试阶段、发布阶段、运行阶段以及故障发生时等重要阶段。在架构阶段需要考虑可扩展性和容错性,避免单点故障问题,如异地多活,即使某个机房出现故障,仍然不会影响系统运行;在编码阶段要保证代码的健壮性,要有超时处理,要有错误捕获,要有降级兜底;测试阶段要保证测试覆盖度,对基础质量进行二次校验;发布阶段系统部署最容易暴露错误,要有版本控制,要有回滚机制;运行阶段最重要的是实时监控,及时发现问题、发出准确警报信息,以便排查问题;故障发生时首要是及时止损,防止影响扩大,然后定位原因、解决问题,最后恢复服务。

对于日常运维,高可用更多是针对运行阶段而言的,所以这个阶段需要额外进行加强建设,使用的手段以预防为主,以管控、监控、恢复为辅。建立常态压测体系,定期对服务进行单点压测及全链路压测,摸排水位,做好预防工作;管控的主要内容是线上运行降级、限流和熔断保护(对业务有损,操作前一定要和上下游业务确认好再进行);建立性能基线,记录服务性能变化趋势,建立警报体系,及时发现问题及时预警;遇到故障能够止损,并提供快速订正工具,不一定要好,但一定要有。

最后,当一个系统面临持续高峰流量时,其实很难单靠自身调整来恢复状态的,日常运维也不可能预料到所有意外情况,为了保证系统的高可用,必须设计一个Plan B方案来进行兜底。

 

10、流量削峰

削峰的目的是设计一些规则,人为地延缓请求,甚至过滤掉一些无效请求。常见的手段有以下几种:

在请求入口增加额外业务逻辑,如发出请求前增加答题或其它广告等业务,本质是通过在入口层削减流量,从而让系统更好地支撑瞬时峰值。

请求排队,常见的是使用消息队列,通过把同步的直接请求转换成异步的瞬时流量,也还有如线程池等其它实现方式,但问题就是会造成请求积压,影响用户体验。排队的本质是在业务层将一步操作转变成两步操作,从而起到缓冲的作用。鉴于此种方式的弊端,使用时需要基于业务数量级和业务场景做出妥协与平衡。

请求过滤,目的是通过减少无效请求的数据IO,保障有效请求的IO性能,其核心结构在于分层,通过在不同层次过滤掉无效请求,达到读写数据的精确触发。比如对请求做限流保护,将超出系统承载能力的请求滤掉;对读请求做数据缓存,将重复请求滤掉;对写请求做一致性校验,只保留最终有效的数据。

你可能感兴趣的:(Java,分布式,java,面试)