三年工作经验
三年面试思路整理
[toc]
参考文档: https://www.cnblogs.com/Zz-maker/p/11072103.html
# 一、面试考察点
# 1.1 常规三面
轮数 | 考察点 | 备注 |
---|---|---|
第一轮 | 基础知识(广度和深度)、算法 | |
第二轮 | 项目经验(和岗位的切合度)、设计和解决方案、算法 | |
第三轮 | 岗位匹配度、思维考察、价值观考察、算法 |
# 1.2 考察偏好
公司类型 | 考察偏好 | 备注 |
---|---|---|
常规 | 知识的广度和一定的深度。注重各种问题的解决能力 | |
大厂 | 知识的深度和一定的广度、算法要求高、高并发分布式的项目背景。注重知识的深度和算法 | |
外企 | 知识点的一定深度和一定的广度、算法要求中等、注重思维和价值观。注重思维能力和价值观 |
# 二、项目经验
项目经验在描述时,着重描述技术对项目产生的影响,选择该技术的原因,项目中的重难点的解决方案等等。
# 2.1 PayPal
# 1)Migration项目
亮点,主要从系统设计、高并发场景的考虑、大数据量场景的考虑
- 系统的设计。心跳机制、错误恢复、幂等
- 大数据量和高并发场景的考虑。多线程查和写(Spring reactor技术)、文件分片
- AsyncResponse的问题解决
# 2)PayPal China项目
亮点,主要从负责的业务定位、系统的交互原理、微服务拆分架构理解、表的设计和结构理解、跨境支付的原理、系统的整体架构的理解
- 使用GRPC的方式做微服务之间的调用
- notificationserv通过rabbitMQ消息队列发送验证码
- 数据加密这块的理解(非对称、对称等等)
- 封装过graphQL的serv
- Python通过panda和spark做日志分析
# 2.2 私活
# 1)星厨牧场
亮点,主要是压测模拟高并发场景的情况、服务的负载均衡、系统的架构设计
# 2.3 科达
# 1)海燕
亮点,主要是JVM调优、MYSQL优化、Redis原理、Kafka原理、ElasticSearch原理、前端技术、Spring原理(AOP)的应用
- JVM调优4G -> 1G
- MYSQL调优经验。索引覆盖、explain的使用、索引的设计原则
- SpringAOP做SQL的打印
- Redis存放配置缓存和热点数据(缓存击穿和缓存雪崩)。redis集群的设计
- Kafka
- ElasticSearch
- Feign做服务调用
# 三、专项突击
# 1)花旗C11(Manager)面试
重点:
- Java基础
- JVM调优
- 多线程原理
- Mysql调优
# 自我介绍
我毕业于xxxx大学,是2018届本科毕业生,主修专业是 计算机科学与技术。
毕业后在苏州科达的海燕产品开发部做全栈开发。
两年后在PayPal做后端研发,一致到现在。
在科达的两年中,我有幸参与了海燕系统的完整重构,从项目框架的搭建到最后的全国各省市部署上线。从项目的构建到功能实现,在从性能优化到实地部署,海燕产品不断完善的过程中,我的技术也在随之进步,并逐渐的成为了项目中核心研发成员,后来成为阿布扎比项目的负责人,来将海燕项目定制化的集成到阿布扎比的人车管控平台系统中,这里感谢组长的悉心培养和信任。这两年中,我深入了解过数据库的原理、JVM的原理和优化、分布式中间件的使用和优化(像Redis、Kafka、ElasticSearch、zookeeper等等)、前端技能、产品设计、交互设计、架构设计等等。在技术、设计以及管理上得到了全方位的培养与发展。在科达的两年,也是我工作以来成长最快的两年。后来因为部门变动,氛围发生变化,于是和组长一起选择了离职,寻找新的机会。于是来到了PayPal。
在PayPal这一年里,让我感受到了名企内部的技术规范性,无论从产品设计到研发测试,都有着严格的规范,有条不紊的进行着。公司的良好工作氛围,让我很快的融入其中。从开始的简单需求到后来的功能设计,再到最近的migration项目负责人,我也逐渐成为了组内的核心开发人员。
# 项目介绍:
# PayPal
在PayPal我主要有两部分项目,一个是Merchant account业务的主项目,另一个是和global team合作的Migration项目。
Merchant account业务主项目这部分分为两个阶段:
- 第一阶段是PayPal China的基础业务开发,从头构建符合中国业务的业务系统,技术栈和global基本独立。
- 第二阶段是PayPal China和global系统的集成,主要是对接global系统平台的核心组件和部分业务基础组件,保证跨境交易模式统一,和模块复用,加快系统上线。
在第一阶段,主要做的内容比较小而杂,主要是一些简单逻辑的开发和UT。
在第二阶段,刚开始是调研global的技术栈和业务模块,主要是了解global整个系统的设计和开发流程等等,后面就开始根据产品提出的具体需求,针对性的在global系统中找到可行的解决方案,原则就是复用优先,配置优先,尽量不改动或者少改动global业务代码。
而和global team合作的migration项目。
项目背景:是由于各个国家对数据的监管要求,很多数据都只能留存在国内,但是做跨境交易需要其他国家的商户数据,如果每次交易都需要从各个国家请求数据,是非常低效的方式,所以migration项目,则是定期同步跨境交易所需的最低数据要求数据集(当然是符合监管要求的可以传输的数据)到各个国家,这时做跨境交易的时候可以根据本地的数据来做好最基本的交易准备,一切就绪则请求到对应服务上,减少了跨地区系统之间的频繁交互带来的延迟。
具体内容:是基于migration管理平台voyage制定的交互规范实现我们domain的也就是merchant setting这部分数据的export和import。各个domain负责各自数据的export和import,而export和import的执行时间和策略都是管理平台voyage决定的。
# 海燕
项目的内容是基于道路卡口拍摄到的过车图片,做特征识别,如车牌、车牌颜色、车身颜色、车辆品牌、是否系安全带、打电话等等。然后基于这些特征去做具体的业务,比如车辆布控告警、犯罪嫌疑车辆追踪、全城车辆信息实时管控等等大数据分析业务。
我在项目中的负责业务模块的实现,也就是全栈,前后端代码、数据库设计、系统优化,代码封装等等。
工作第二年,有个海外项目,也就是阿布扎比这个城市需要部署我们的全城人车管控平台,我们这个车辆管控也需要对接其中,但由于需求是定制的,所以我们基于原有的海燕项目根据需求精简模块,以及特殊要求的一些定制模块,来接入整个阿布扎比管控平台。
# 面试记录
# 面试表格
公司 | 进度 | 备注 |
---|---|---|
得物 | (一、二、三面 通过)终面 | |
平安壹钱包 | (一、二、三面 通过)终面 |
# 得物
# 进度
- [x] 一面
- [x] 二面
- [x] 三面
# 特点
- 岗位:金融结算岗
- 工作时间: 10-10-5
# 需要了解的信息
- 薪资福利
- 工作时间和流程
- 工作地点和方式
- 业绩考核
- 调薪晋升
# 薪资话术
当前薪资 月薪:16K,年薪: 20W
期望薪资是30K ~ 35K
原因:
- 去年有个阿里的offer,薪资是22K * 15薪,但因为在杭州,想在上海定居的我,最后选择了PayPal。
- 我不喜欢跳槽,所以这次也希望找一个可以长期工作的公司,和公司一起成长发展。如果薪资不能符合我的能力的话,最后还是会离开。
- 目前我也有几个offer,开出的薪资也是30K左右。(平安、游卡)
- 在面试的过程中,也和面试官了解了很多关于公司的发展和部门的工作内容等等信息,对贵公司的当前岗位也是非常感兴趣,如果薪资合适,我也很荣幸加入。
选择PayPal的原因:
- 在上海
- 外企的氛围可以提升我的英语水平,提升自己的能力广度
- PayPal的面试体验给我很好,担心去阿里会被剥削
- 跨境交易的业务吸引我
- 没有加班,各种假期也很多,也是因为这个被HR说服接收当前的薪资
# 项目中遇到的问题
# JVM类
- 海燕项目缓存占用高
# 多线程类
- AsyncResponse
- Spring Reactor
# Spring 注解
- 自定义注解打印和修改jpql语句
- AOP拦截请求做统一异常处理
# 数据库类
- 多条件查询:JPQL
- DAL框架分析
# Redis类
- Redis时延问题,了解到redis刚开始没有做集群,所以有些耗时操作会阻塞redis,导致其他用户从redis获取数据会比较耗时。后来运维那边新增了集群配置后,时延问题得到了极大的改善。
# JVM
# 1.1 java 内存分为哪几个模块
# 1)程序计数器
用于指向下一个指令执行地址的 线程私有
# 2)Java栈、本地方法栈
- Java栈是方法执行的内存区域(线程私有), 主要包含
- 局部变量表 (基本数据类型、对象引用、return 地址)
- 操作数栈
- 动态链接
- 方法出口
- 本地方法栈是执行native方法使用的,线程私有
# 3)Java堆、元数据区
- 堆为内存GC的主要区域,分为老年代和年轻代,年轻代又分为eden区、survivor1和survivor2,两个survivor在Minor GC的时候会互相转换
- 元数据区又称为永久代,使用的是本地内存,非JVM申请的内存区域。 主要存放:
- 虚拟机加载的类信息
- 静态变量
- 常量等数据。
# 1.2 GC
堆为内存GC的主要区域,分为老年代和年轻代,年轻代又分为eden区、survivor1和survivor2,两个survivor在Minor GC的时候会互相转换。
Minor GC采用的是复制算法,老年代采用的是标记整理算法。
垃圾回收器:
- 年轻代
- Serial(串行)
- ParNew(多核CPU并行)
- Parallel Scavenge(根据吞吐量,并行)
- 老年代
- Serial Old(串行)
- Parallel Old(Parallel Scavenge老年版本)
- CMS
- 初始标记(STW)
- 并发标记
- 重新标记(STW)
- 并发清除
G1
分为一个个内存区域,有Eden、Survivor、Old和Humongous四个区域
- 并行和并发:充分利用多CPU,减少STW的停顿时间
- 分代收集:只不过将堆划分为一块块内存区域
- 空间整合:整体是标记-整理
# 1)垃圾判断方法
- 引用计数法: 通过在对象头中添加一个计数器,每当对象被引用时,在这个计数器上+1,如果为0,则代表对象可回收。但缺点是无法判断循环引用的对象。
- 可达性分析: 通过GCROOTs的对象作为搜索起点,向下搜索引用,如果没有被搜索的到对象便是可回收对象。 常用的GCROOTs的对象有:
- Java栈中引用的对象
- 元数据区中类的静态属性引用的对象
- 元数据区中常量引用的对象
- 本地方法栈中JNI引用的对象
关于GCROOTs遍历时,如果发生在young GC需要扫描老年代的对象,效率很低。在Hotspot虚拟机的实现中,有一个Card Table的概念,将老年代的内存分为一个个固定大小的内存卡片,如果其中有年轻代的对象引用,则将卡片标记为dirty card,然后在young GC的时候会从Card Table中找到dirty card的GCROOTs,这样就不用遍历老年代所有的GCROOTs了。
# 引用的几种类型:
- 强引用: 比如 对象 = new 一个对象,只要引用存在对象就不会被GC回收
- 软引用: 软引用通过SoftReference类来表示,只在内存不足时会回收这类引用的对象
- 弱引用: 通过WeakReference类来表示,只要JVM开始回收垃圾,这类对象就会被回收
- 虚引用: 通过PhantomReference类来表示,仅表示对象持有虚引用,随时可以被回收
# 1.3 JVM调优
- 通过JDK自带的JvisualVM来寻找堆内存的占用异常情况,尽量通过代码优化掉不合理的对象内存占用
- 合理的设置Xms Xmx对的大小
# 多线程
# 2.1 线程创建
- new Thread
- new Thread(new Runable)
- Callable and Future
- Excutor 线程池的方式
- 线程池创建参数 ThreadPoolExecutor
1. corePoolSize 核心线程数
2. maximumPoolSize 池中允许最大工作线程数
3. workQueue 存放无可用线程的新任务队列
4. keepAliveTime 超过等待时间的队列中的任务被回收
5. threadFactory 创建线程的线程工厂
6. handler 拒绝策略
1. AbortPolicy 抛出 RejectedExecutionException 来拒绝新任务
2. CallerRunsPolicy 将此任务交给调用者直接执行
3. DiscarPolicy 不处理,直接丢弃
4. DiscardOldestPolicy 对其最早的未处理任务
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.2 线程状态
- new
- runable
- running
- blocked
- terminated
# AQS(AbstractQueuedSynchronizer)
# 2.1 锁的种类
- synchronized
- Lock接口实现的锁
- ReentrantLock
- ReentrantReadWriteLock
- ReentrantLock
- CountdownLatch
- CyclicBarrier
- Semaphore
# 2.3 工具
检测死锁工具: jconsole
# 知识点散列
- 对象头 和 锁的膨胀: 对象头中有个treadID字段,第一次访问,线程获取的是偏向锁,然后将treadId设置为当前线程id。第二次判断线程id和threadid是否一致,一致直接使用,不一致则升级为轻量级锁,通过自旋获取锁。执行一定次数的自旋还没有获取锁对象,此时升级为重量级锁。此过程为synchronzied的锁膨胀。
- synchronized的底层实现: jvm中通过monitor enter和monitor exit,此处会有两个monitor exit,第二个是为了处理异常释放锁的
- CAS原理及缺陷和解决方案
- volatile关键字原理
- sleep、wait、join、notify、notifyAll作用
- ThreadLocal实现原理
- Lock接口及实现类
- 重入锁、公平锁、非公平锁、读写锁
- 重入锁: 当前线程可以反复获取相同锁,锁的state会+1, 释放会-1
- 公平锁: 按照排队的顺序获取锁
- 非公平锁: 抢占式的方式获取锁
- 读写锁: 读锁不会阻塞多个读线程。写锁会阻塞读线程和写线程。
- LockSupport类
- Condition接口
- ArrayBlockingQueue、PriorityBlockingQueue、DelayQueue
- Java 的并发容器有哪些
- 阻塞队列: 是支持了两个附加操作的队列,一个是队列为空时获取元素的线程会阻塞等待队列变为非空。另一个是队列满了的时候存储元素的线程会阻塞等待队列有空间。可以很好的解决生产者消费者问题。
- Fork/Join框架
- 工作窃取算法是什么
- 原子类型有哪些及作用
- 基本类型: AtomicBoolean,Integer, Long, reference
- 基本类型数组: AtomicIntegerArray, Long, reference
- 解决ABA问题的原子类: AtomicMarkableReference(引入一个boolean值判断中间是否发生值的变化)、AtomicStampedReference(引入int值累加反映值的版本)
- JDK并发包中提供了哪几个比较常见的处理并发的工具类?
- CountDownLatch与CyclicBarrier
- Semaphore
- Exchanger
- 线程池,优点、流程、参数及含义
- 如何合理设置线程池
# Java基础
# 集合与数据结构
- HashTable
- HashMap
- ConcurrentHashMap
- CopyOnWriteArrayList
# Spring
# bean的启动流程
容器启动阶段
- Bean的配置信息
- BeanDefinition
- BeanDefinitionReader
- BeanDefinitionRegistry
容器实例化阶段
- 原生bean实例
- BeanWrapper(装饰bean)
- Aware接口
- BeanPostProccessor.postProcessBeforeInitialization(bean的前置处理)
- InitializingBean(初始化bean所有属性后的一些处理)
- BeanPostProccessor.postProcessAfterInitialization(bean的后置处理)
- DisposableBean(在销毁bean的时候做一些处理)
- 使用
- 销毁接口调用
# bean的扩展点
BeanPostProcessor.postProcessBeforeInitialization: 前置扩展
BeanPostProcessor.postProcessAfterInitialization: 后置扩展
# 3.1 Spring IOC
# 循环依赖问题
通过三级缓存实现
# 3.2 Spring AOP
# 实现方式
使用动态代理实现
- JDK动态代理:
- 实现InvocationHandler接口。
- 利用反射机制生成一个实现代理接口的匿名类,JDK通过ProxyGenerator.generateProxyClass()生成字节码,在通过ClassLoader加载到内存。
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类。因为JDK动态代理底层代码实现继承了Proxy类,Java是单继承的,所以只能代理接口。
- cglib代理:
- 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法不要声明成final
- 动态生成的类是直接存在内存中的,通过ClassLoader加载即可
Spring AOP中在目标对象实现了接口,会默认使用JDK代理。实现了类,则使用cglib代理。
# 分布式中间件
# Reids
分布式缓存。有以下特点:
- 数据在内存,读写速度非常快
- 单进程、单线程,没有线程安全问题
- IO多路复用
- 数据类型丰富。支持 字符串、hashmap、list、集合 有序集合
- 数据可以持久化: RDB(Dump数据到文件)和 AOF(修改命令存到文件)
# 淘汰策略:
- 在设置了过期时间的数据中
- 最近最少使用
- 剩余时间最短
- 随机
- 在所有数据中
- 最近最少使用
- 随机
- 不淘汰。超出内存返回错误信息
# Redis事务
一组命令按顺序执行,命令是原子的,事务并非原子的。在编译时异常,命令均不被执行。在运行时异常,异常的命令失败,其他命令可以正常执行
# Redis缓存问题
- 缓存穿透(查不到数据。解决:为空的数据也做缓存)
- 缓存击穿(热点数据过期。解决:热点数据不设置过期时间、或者业务加锁)
- 缓存雪崩(一大片缓存集中失效。解决:尽量设置离散的过期时间)
- 缓存预热(系统上线前,先将缓存载入)
- 缓存降级(不查数据库,直接返回默认值给用户)
# Kafka
基本术语
- producer: 生产者
- broker: 一个kafka实例
- topic: 消息主题,一个broker可以创建多个topic
- partition: topic的分区。
- 每个topic可以有多个分区,分区的作用是负载。
- 同一个topic在不同分区数据是不重复的。
- 分区表现形式就是一个个文件夹。
- replication: 分区的副本。
- 每个分区有多个副本
- leader挂了,从副本中选取一个leader
- 副本数量不大于broker数量
- 同一个broker中只会存在一个副本(包括自己)
- message:消息实体
- customer: 消费者
- customer group: 消费者组
- kafka的设计中同一个分区只能被消费者组中的一个消费者消费
- 一个消费者组中的消费者可以消费同一个topic的不同分区
- zookeeper:kafka依赖zookeeper保存集群中的元信息,来保证系统可用性
数据流程分析
- 发送数据
- 生产者生产数据是主动push。
- push数据时,当 ack 为0,push后直接返回,不等leader是否写入成功
- push数据时,当ack为1,push后等待leader写入成功则返回,不等其他follower是否写入成功
- push数据时,当ack为-1,push后等待leader和follower均写入成功后返回
- 消费者消费数据是主动pull。
# RabbitMQ
# zookeeper
分布式协调服务,数据结构类似文件树。数据节点是叫znode。适合读多写少的情景,节点数据不大于1MB
Znode:
- data: 存储的数据
- ACL: 记录znode的访问权限
- stat: 记录znode的元数据。比如事务ID,版本号,时间戳,大小等等
- child: 当前节点的子节点引用
常用API:
- create:创建节点
- delete:删除节点
- exists:判断节点是否存在
- getData:获得一个节点的数据
- setData:设置一个节点的数据
- getChildren:获取节点下的所有子节点
可以通过注册watch触发器,来获取节点修改的异步通知,在业务模块做相应的处理。
zookeeper集群采用一主多从的方式。写数据是先写入leader节点,然后同步到follower节点。读数据则是从任意follower节点读取
ZAB协议, 用于集群同步和数据一致性的
应用:
- 分布式锁
- 服务注册和发现:通过watch机制
- 共享配置和状态信息
# ElasticSearch
核心概念
- 倒排索引 (反向索引)
- 索引 - 对应MSYQL中的 数据库 概念
- 类型 - 对应MYSQL中的 表 概念
- 文档 - 对应MYSQL中的 行 概念
数据类型
- keyword: 直接建立 反向索引
- text: 先分词再建立 反向索引
# 分布式系统
# Spring Cloud
# Spring Config
# 其他技术
# Spring Reactor
响应式编程框架。是通过事件驱动实现异步和非阻塞的方式发送和接收数据。生产者生成数据通过推模式给消费者,而不是通过消费者轮询或阻塞等待数据。
# Grpc
采用google自研的proto buf去序列化数据为二进制,在传输性能上高于json这类文本类型传输。协议采用的是HTTP2.0,可以很好的利用IO多路复用的特性。
# GraphQL
# Netty
# 架构设计
# 面向对象的六个原则
- 单一指责
- 开闭原则
- 里氏替换
- 接口隔离
- 依赖导致
# 领域驱动
领域驱动设计的思想是:轻 Service ,重 Domain。
我们平时开发时,大部分是 SQL 驱动,接到一个后端开发需求时,首先是去看接口如何对应到数据库中,要访问哪些表,哪些库,然后思考 SQL 如何写。这样会看到,类似的SQL 到处都是,通常为一个 需求写一个 SQL。
对于领域驱动,我们首先需要理清所有业务,包括定义模型包含的属性和方法,领域模型相当于可复用业务的中间层,新的需求需要基于之前定义好的业务来完成。