2023年中国平安最新java面试题
平安产险--ai部门
redis各种应⽤场景
-
更多的数据结构;
-
可持久化;
-
计数器;
-
发布-订阅功能;
-
事务功能;
-
过期回调功能;
-
队列功能;
-
排序、聚合查询功能。
redis持久化机制
-
RDB:快照形式是直接把内存中的数据保存到⼀个 dump ⽂件中,定时保存,保存策略。(会丢数据)
-
AOF:把所有的对Redis的服务器进⾏修改的命令都存到⼀个⽂件⾥,命令的集合。(影响性能)
mysql调优
-
explain select语句;
-
当只要⼀条数据时使⽤limit 1;
-
为搜索字段建索引;
-
避免select *;
-
字段尽量使⽤not null;
-
垂直分割;
-
拆分⼤的delete和insert语句:delete和insert会锁表;
-
分表分库分区。
有没了解Docker,Docker和虚拟机有什么区别?
-
虚拟机:我们传统的虚拟机需要模拟整台机器包括硬件,每台虚拟机都需要有⾃⼰的操作系统,虚拟机⼀旦被开启,预分配给他的资源将全部被占⽤。,每⼀个虚拟机包括应⽤,必要的⼆进制和库,以及⼀个完整的⽤户操作系统。
-
Docker:容器技术是和我们的宿主机共享硬件资源及操作系统可以实现资源的动态分配。 容器包含应⽤和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在⽤户空间以分离的进程运⾏。
-
对⽐:
-
docker启动快速属于秒级别。虚拟机通常需要⼏分钟去启动。
-
docker需要的资源更少,docker在操作系统级别进⾏虚拟化,docker容器和内核交互,⼏乎没有性能损耗,性能优于通过Hypervisor层与内核层的虚拟化。
-
docker更轻量,docker的架构可以共⽤⼀个内核与共享应⽤程序库,所占内存极⼩。同样的硬件环境, Docker运⾏的镜像数远多于虚拟机数量。对系统的利⽤率⾮常⾼
-
与虚拟机相⽐,docker隔离性更弱,docker属于进程之间的隔离,虚拟机可实现系统级别隔离;
-
安全性: docker的安全性也更弱。Docker的租户root和宿主机root等同,⼀旦容器内的⽤户从普通⽤户权限提升为root权限,它就直接具备了宿主机的root权限,进⽽可进⾏⽆限制的操作。虚拟机租户root权限和宿主机的root虚拟机权限是分离的,并且虚拟机利⽤如Intel的VT-d和VT-x的ring-1硬件隔离技术,这种隔离技术可以防⽌虚拟机突破和彼此 交互,⽽容器⾄今还没有任何形式的硬件隔离,这使得容器容易受到攻击。
-
可管理性:docker的集中化管理⼯具还不算成熟。各种虚拟化技术都有成熟的管理⼯具,例如VMware vCenter提供完备的虚拟机管理能⼒。
-
⾼可⽤和可恢复性:docker对业务的⾼可⽤⽀持是通过快速重新部署实现的。虚拟化具备负载均衡,⾼可⽤, 容错,迁移和数据保护等经过⽣产实践检验的成熟保障机制,VMware可承诺虚拟机99.999%⾼可⽤,保证业务连续性。
-
快速创建、删除:虚拟化创建是分钟级别的,Docker容器创建是秒级别的,Docker的快速迭代性,决定了⽆论 是开发、测试、部署都可以节约⼤量时间。
-
交付、部署:虚拟机可以通过镜像实现环境交付的⼀致性,但镜像分发⽆法体系化;Docker在Dockerfile中记录了容器构建过程,可在集群中实现快速分发和快速部署;
-
同⼀个宿主机中多个Docker容器之间如何通信?多个宿主机中Docker容器之间如何通信?
-
这⾥同主机不同容器之间通信主要使⽤Docker桥接(Bridge)模式。
-
不同主机的容器之间的通信可以借助于 pipework 这个⼯具。
平安产险-核⼼系统部
简历:
-
介绍简历上主要项⽬,画框架图,说流程。
-
针对简历上的技能进⾏提问
队列:
说说rabbitmq的结构。
-
消息处理过程:
-
四种交换机:
-
直连交换机,Direct exchange:带路由功能的交换机,根据routing_key(消息发送的时候需要指定)直接绑定到队列,⼀个交换机也可以通过过个routing_key绑定多个队列。
-
扇形交换机,Fanout exchange:⼴播消息。
-
主题交换机,Topic exchange:发送到主题交换机上的消息需要携带指定规则的routing_key,主题交换机会 根据这个规则将数据发送到对应的(多个)队列上。
-
⾸部交换机,Headers exchange:⾸部交换机是忽略routing_key的⼀种路由⽅式。路由器和交换机路由的 规则是通过Headers信息来交换的,这个有点像HTTP的Headers。将⼀个交换机声明成⾸部交换机,绑定⼀个 队列的时候,定义⼀个Hash的数据结构,消息发送的时候,会携带⼀组hash数据结构的信息,当Hash的内容匹配上的时候,消息就会被写⼊队列。
rabbitmq队列与消费者的关系?
-
⼀个队列可以绑定多个消费者;
-
消息分发:若该队列⾄少有⼀个消费者订阅,消息将以循环(round-robin)的⽅式发送给消费者。每条消息只会分发给⼀个订阅的消费者(前提是消费者能够正常处理消息并进⾏确认)。
rabbitmq交换器种类。
-
fanout交换器:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中;
-
direct交换器:direct类型的交换器路由规则很简单,它会把消息路由到哪些BindingKey和RoutingKey完全匹配的 队列中;
-
topic交换器:匹配规则⽐direct更灵活。
-
headers交换器:根据发送消息内容的headers属性进⾏匹配(由于性能很差,不实⽤)。
项⽬中哪⾥⽤到了kafka,kafka特性?
-
场景:
-
⼤数据部⻔流数据处理;
-
elk;
-
特性:
-
它被设计为⼀个分布式系统,易于向外扩展;
-
它同时为发布和订阅提供⾼吞吐量;
-
它⽀持多订阅者,当失败时能⾃动平衡消费者;
-
它将消息持久化到磁盘,因此可⽤于批量消费,例如ETL,以及实时应⽤程序。
rabbitmq、RocketMq、kafka对⽐。
-
中⼩型公司⾸选RabbitMQ:管理界⾯简单,⾼并发。
-
⼤型公司可以选择RocketMQ:更⾼并发,可对rocketmq进⾏定制化开发。
-
⽇志采集功能,⾸选kafka,专为⼤数据准备。
介绍springcloud核⼼组件及其作⽤,以及springcloud⼯作流程。
springcloud由以下⼏个核⼼组件构成:
-
Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从 Eureka Server拉取注册表,从⽽知道其他服务在哪⾥
-
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从⼀个服务的多台机器中选择⼀台
-
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
-
Hystrix:发起请求是通过Hystrix的线程池来⾛的,不同的服务⾛不同的线程池,实现了不同服务调⽤的隔离,避免 了服务雪崩的问题
-
Zuul:如果前端、移动端要调⽤后端系统,统⼀从Zuul⽹关进⼊,由Zuul⽹关转发请求给对应的服务
介绍springcloud⼼跳机制,以及消费端如何发现服务端(Ribbon)?
-
当⼀个服务实例启动,会将它的ip地址等信息注册到eureka;
-
当a服务调⽤b服务,a服务会通过Ribbon检查本地是否有b服务实例信息的缓存;
-
Ribbon会定期从eureka刷新本地缓存。
eureka的缺点。
-
某个服务不可⽤时,各个Eureka Client不能及时的知道,需要1~3个⼼跳周期才能感知,但是,由于基于Netflix的 服务调⽤端都会使⽤Hystrix来容错和降级,当服务调⽤不可⽤时Hystrix也能及时感知到,通过熔断机制来降级服务调⽤,因此弥补了基于客户端服务发现的时效性的缺点。
eureka缓存机制?
-
第⼀层缓存:readOnlyCacheMap,本质上是ConcurrentHashMap:这是⼀个JVM的CurrentHashMap只读缓存, 这个主要是为了供客户端获取注册信息时使⽤,其缓存更新,依赖于定时器的更新,通过和readWriteCacheMap 的值做对⽐,如果数据不⼀致,则以readWriteCacheMap 的数据为准。readOnlyCacheMap 缓存更新的定时器时间间 隔,默认为30秒
-
第⼆层缓存:readWriteCacheMap,本质上是Guava缓存:此处存放的是最终的缓存, 当服务下线,过期,注 册,状态变更,都会来清除这个缓存⾥⾯的数据。 然后通过CacheLoader进⾏缓存加载,在进⾏ readWriteCacheMap.get(key)的时候,⾸先看这个缓存⾥⾯有没有该数据,如果没有则通过CacheLoader的load⽅法 去加载,加载成功之后将数据放⼊缓存,同时返回数据。 readWriteCacheMap 缓存过期时间,默认为 180 秒 。
-
缓存机制:设置了⼀个每30秒执⾏⼀次的定时任务,定时去服务端获取注册信息。获取之后,存⼊本地内存。
rpc和http的区别,使⽤场景?
-
区别:
-
传输协议
-
RPC,可以基于TCP协议,也可以基于HTTP协议HTTP,基于HTTP协议
-
传输效率
-
RPC,使⽤⾃定义的TCP协议,可以让请求报⽂体积更⼩,或者使⽤HTTP2协议,也可以很好的减少报⽂的体积,提⾼传输效率
-
HTTP,如果是基于HTTP1.1的协议,请求中会包含很多⽆⽤的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为⼀个RPC来使⽤的,这时标准RPC框架更多的是服务治理
-
性能消耗,主要在于序列化和反序列化的耗时
-
RPC,可以基于thrift实现⾼效的⼆进制传输
-
HTTP,⼤部分是通过json来实现的,字节⼤⼩和序列化耗时都⽐thrift要更消耗性能
-
负载均衡
-
RPC,基本都⾃带了负载均衡策略
-
HTTP,需要配置Nginx,HAProxy来实现
-
服务治理**(下游服务新增,重启,下线时如何不影响上游调⽤者)**
-
RPC,能做到⾃动通知,不影响上游
-
HTTP,需要事先通知,修改Nginx/HAProxy配置
-
-
总结:RPC主要⽤于公司内部的服务调⽤,性能消耗低,传输效率⾼,服务治理⽅便。HTTP主要⽤于对外的异构环境,浏览器接⼝调⽤,APP接⼝调⽤,第三⽅接⼝调⽤等。
分布式事务如何保持⼀致性?
-
⼆阶段提交:
-
概念:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中⽌操作。
-
作⽤:主要保证了分布式事务的原⼦性;第⼀阶段为准备阶段,第⼆阶段为提交阶段;
-
缺点:不仅要锁住参与者的所有资源,⽽且要锁住协调者资源,开销⼤。⼀句话总结就是:2PC效率很低,对⾼并发很不友好。
-
-
三阶段提交:
-
概念:三阶段提交协议在协调者和参与者中都引⼊超时机制,并且把两阶段提交协议的第⼀个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
-
缺点:如果进⼊PreCommit后,Coordinator发出的是abort请求,假设只有⼀个Cohort收到并进⾏了abort操作,⽽其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发⽣不⼀致性。
-
-
柔性事务:
-
概念:所谓柔性事务是相对强制锁表的刚性事务⽽⾔。流程⼊下:服务器A的事务如果执⾏顺利,那么事务A 就先⾏提交,如果事务B也执⾏顺利,则事务B也提交,整个事务就算完成。但是如果事务B执⾏失败,事务B本身回滚,这时事务A已经被提交,所以需要执⾏⼀个补偿操作,将已经提交的事务A执⾏的操作作反操作,恢复到 未执⾏前事务A的状态。
-
缺点:业务侵⼊性太强,还要补偿操作,缺乏普遍性,没法⼤规模推⼴。
-
-
消息最终⼀致性解决⽅案之RabbitMQ实现:
-
实现:发送⽅确认+消息持久化+消费者确认。
-
什么情况下⽤到分布式开发?
-
优点:
-
模块解耦:把模块拆分,使⽤接⼝通信,降低模块之间的耦合度.
-
项⽬拆分,不同团队负责不同的⼦项⽬:把项⽬拆分成若⼲个⼦项⽬,不同的团队负责不同的⼦项⽬.
-
提⾼项⽬扩展性:增加功能时只需要再增加⼀个⼦项⽬,调⽤其他系统的接⼝就可以。
-
分布式部署:可以灵活的进⾏分布式部署.
-
提⾼代码的复⽤性:⽐如service层,如果不采⽤分布式rest服务⽅式架构就会在⼿机wap商城,微信商 城,pc,android,ios每个端都要写⼀个service层逻辑,开发量⼤,难以维护⼀起升级,这时候就可以采⽤分布式rest服务⽅式,公⽤⼀个service层。
-
-
缺点:
-
系统之间的交互要使⽤远程通信,接⼝开发增⼤⼯作量;
-
⽹络请求有延时;
-
事务处理⽐较麻烦,需要使⽤分布式事务。
-
jvm内存模型,各个部分的特点?
-
PC寄存器:
-
每个线程拥有⼀个pc寄存器;
-
指向下⼀条指令的地址。
-
-
⽅法区:
-
保存装载的类的元信息:类型的常量池,字段、⽅法信息,⽅法字节码;
-
通常和永久区(Perm)关联在⼀起;
-
-
堆:
-
应⽤系统对象都保存在java堆中;
-
所有线程共享java堆;
-
对分代GC来说,堆也是分代的;
-
-
栈:
-
线程私有;
-
栈由⼀系列帧组成(因此java栈也叫做帧栈);
-
帧保存⼀个⽅法的局部变量(局部变量表)、操作数栈、常量池指针;
-
-
每⼀次⽅法调⽤创建⼀个帧,并压栈。
类加载器,双亲委派模型?
-
BootStrap ClassLoader 启动ClassLoader
-
Extension ClassLoader 扩展ClassLoader
-
App ClassLoader 应⽤ClassLoader/系统ClassLoader
-
Custom ClassLoader ⾃定义ClassLoader
-
双亲委派机制:当⼀个类收到了类加载请求,他⾸先不会尝试⾃⼰去加载这个类,⽽是把这个请求委派给⽗类去 完成,每⼀个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当⽗类加载器反馈⾃⼰⽆法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),⼦类加载器才会尝试⾃⼰去加载。
类加载机制。
-
概念:虚拟机把描述类的数据⽂件(字节码)加载到内存,并对数据进⾏验证、准备、解析以及类初始化,最终形 成可以被虚拟机直接使⽤的java类型(java.lang.Class对象)。
-
类⽣命周期:
-
类加载过程:读取⼆进制字节流到jvm—>验证格式语义等—>为静态变量分配内存空间—>常量池引⽤解析—>执⾏static标识的代码
-
加载过程:通过⼀个类的全限定名来获取定义此类的⼆进制字节流,将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。在内存中(⽅法区)⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的 各种数据的访问⼊⼝;
-
验证过程:为了确保Class⽂件的字节流中包含的信息符合当前虚拟机的要求,⽂件格式验证、元数据验证、字节码验证、符号引⽤验证;
-
准备过程:正式为类属性分配内存并设置类属性初始值的阶段,这些内存都将在⽅法区中进⾏分配; 准备阶段,static对象会被设置默认值,static fifinal对象会被赋上给予的值。
-
解析阶段:虚拟机将常量池内的符号引⽤替换为直接引⽤的过程。
-
符号引⽤:字符串,引⽤对象不⼀定被加载;
-
直接引⽤:指针或者地址偏移量,引⽤对象⼀定在内存中。
-
初始化阶段:类初始化阶段是类加载过程的最后⼀步。初始化阶段就是执⾏类构造器<clint>()⽅法的过程。
-
使⽤阶段:
-
卸载阶段:
-
java堆的结构,⼀个bean被new出来之后,在内存空间的⾛向?、
-
JVM中堆空间可以分成三个⼤区,新⽣代、⽼年代、永久代
-
新⽣代可以划分为三个区,Eden区,两个Survivor区,在HotSpot虚拟机Eden和Survivor的⼤⼩⽐例为8c1
如何让栈溢出,如何让⽅法区溢出?
-
运⾏时产⽣⼤量的类去填满⽅法区,直到溢出。
写出⼏个jvm优化配置参数。
-
设定堆内存⼤⼩,这是最基本的。
-
-Xms:启动JVM时的堆内存空间。
-
-Xmx:堆内存最⼤限制。
-
设定新⽣代⼤⼩。
-
新⽣代不宜太⼩,否则会有⼤量对象涌⼊⽼年代。
-
-XX:NewRatio:新⽣代和⽼年代的占⽐。
-
-XX:NewSize:新⽣代空间。
-
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占⽐。
-
-XX:MaxTenuringThreshold:对象进⼊⽼年代的年龄阈值。
-
设定垃圾回收器
-
年轻代:-XX:+UseParNewGC。
-
⽼年代:-XX:+UseConcMarkSweepGC。
-
CMS可以将STW时间降到最低,但是不对内存进⾏压缩,有可能出现“并⾏模式失败”。⽐如⽼年代空间还有 300MB空间,但是⼀些10MB的对象⽆法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要 ⽐Parallel GC⻓很多。
-
G1采⽤”标记-整理“算法,解决了内存碎⽚问题,建⽴了可预测的停顿时间类型,能让使⽤者指定在⼀个⻓度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
-
有哪⼏种GC机制?
-
引⽤计数法(没有被java采⽤):
-
原理:对于⼀个对象A,只要有任何⼀个对象引⽤了A,则A的引⽤计数器就加1,当引⽤失效时,引⽤计数器就减1,只要对象A的引⽤计数器的值为0,则对象A就会被回收。
-
问题:
-
引⽤和去引⽤伴随加法和减法,影响性能;
-
很难处理循环引⽤。
-
-
标记清除法:
-
原理:现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。⼀种可⾏的实现是,在标记节点,⾸先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引⽤的垃圾对象。然后在清除阶段,清除所有未被标记的对象。
-
问题:
-
标记和清除两个过程效率不⾼,产⽣内存碎⽚导致需要分配较⼤对象时⽆法找到⾜够的连续内存⽽需要触发⼀次GC操作。
-
-
标记压缩法:
-
原理:适合⽤于存活对象较多的场合,如⽼年代。它在标记-清除算法的基础上做了⼀些优化。标记阶段⼀样,但之后,将所有存活对象压缩到内存的⼀端。之后,清除边界外所有的空间。
-
优点:
-
解决了标记- 清除算法导致的内存碎⽚问题和在存活率较⾼时复制算法效率低的问题。
-
-
复制算法:
-
原理:将原有的内存空间分为两块,每次只使⽤其中⼀块,在垃圾回收时,将正在使⽤的内存中的存活对象复制到未使⽤的内存块中,之后清除正在使⽤的内存块中的所有对象,交换两个内存的⻆⾊,完成垃圾回收。
-
问题:
-
不适⽤于存活对象⽐较多的场合,如⽼年代。
-
-
分代回收法:
-
原理:根据对象存活周期的不同将内存划分为⼏块,⼀般是新⽣代和⽼年代,新⽣代基本采⽤复制算法,⽼年代采⽤标记整理算法。
-
springboot启动过程。
-
通过 SpringFactoriesLoader加载 META-INF/spring.factories⽂件,获取并创建SpringApplicationRunListener对象
-
然后由 SpringApplicationRunListener来发出 starting 消息
-
创建参数,并配置当前 SpringBoot 应⽤将要使⽤的 Environment
-
完成之后,依然由 SpringApplicationRunListener来发出 environmentPrepared 消息
-
创建 ApplicationContext
-
初始化 ApplicationContext,并设置 Environment,加载相关配置等
-
由 SpringApplicationRunListener来发出 contextPrepared消息,告知SpringBoot 应⽤使⽤的 ApplicationContext已准备OK
-
将各种 beans 装载⼊ ApplicationContext,继续由 SpringApplicationRunListener来发出 contextLoaded 消息,告知 SpringBoot 应⽤使⽤的 ApplicationContext已装填OK
-
refresh ApplicationContext,完成IoC容器可⽤的最后⼀步
-
由 SpringApplicationRunListener来发出 started 消息
-
完成最终的程序的启动
-
由 SpringApplicationRunListener来发出 running 消息,告知程序已运⾏起来了