大家好,我是小林。
今天分享一位同学面试上海某游戏公司的面经,同学的技术栈是Java后端,虽然不是大厂,但是一面面试也被问了 25 多个问题,时长也接近 1 小时了
面试过程中,也问到了 Linux socket 编程,游戏公司都会对网络协议和网络编程这一块要求比较高,所以投游戏公司的同学,需要重点准备网络方面的知识。
还有一点,游戏公司的开发岗除了技术要求之外,可能还会问你一下你对游戏的兴趣,平常玩什么游戏,对游戏有什么看法,因为工作内容就是开发游戏,如果对游戏没有热情,会觉得工作缺失了激情。
问题记录
介绍你的项目
balabal 了几分钟
Redis 缓存一致性
说了旁路缓存策略
如果这个时候一波海量请求,你怎么保证他们能读到数据
- 数据延迟肯定是有的
- 我个人认为可以做流量控制,限制读请求数量
- 当然,如果非的读的话。可以采用把删除缓存策略改为更新缓存策略
说说Redis 数据结构
Redis 有五大基本数据类型和四大新类型
五大基本类型是:
- String
- Hash
- List
- Set
- zset
每一种数据结构根据自身的特性有不同的使用场景:
- string:
计数器,因为 Redis 是单线程模型的,所以redis执行命令时原子性,所以他可以用来做计数器,例如 点赞计数、转发、库存数量等
分布式锁:setnx key value ex 时间
- hash:
Hash 是 key-value 键值对,类似与 Java 的 HashMap, 查找时间复杂度是 o(l)
Hash 的底层数据结构是hashtale 和压缩表
当 元素个小于 512 并且所有元素大小小于 64 字节,采用压缩列表作为底层数据结构
反之采用 hashtable
它适合做购物车,用户作为 id、商品 id 位 field、商品数量为 value
List (说到 List 被面试面试官打断了,下一个)
事务了解吗?
了解,acid 事务四大特性说了一遍
事务隔离级别有哪几种
- 四种
读未提交
读已提交
可重复读
串行话
- 读未提交就是一个A事务能读到另一个B事务未提交的事务,当这个B事务发生回滚时, A 事务读到的是脏数据。它有脏读、不可重复读、幻读问题
- 读已提交就是只能读到对方事务已经提交的事务,它解决了脏读问题,但是有不可重复读和幻读问题(说到这里突然被面试官打断)
追问:隔离级别是由啥保证的
- mvvc 机制 和 锁机制
可重复读为什么完全不能解决幻读
在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。
图片
因为这种特殊现象的存在,所以我们认为 MySQL Innodb 中的 MVCC 并不能完全避免幻读现象。
进程与线程的区别
常规八股
线程池有哪几个类型的
- newSingleExecutor
只有一个核心线程,也是最大线程数。队列采用的是 LinkedblockingQueue 无界阻塞队列。极端情况下会有 OOM 问题
它的工作原理是当提交任务是当没有工作线程时,会将任务放入到阻塞队列中,
有核心线程时,获取阻塞队列取任务执行,执行完了接着从阻塞队列执行
Keepalive存活时间是 0,因为本来就没有非核心线程
它的场景是串行化的场景,因为他只有一个工作线程
- newCacheExecutor
核心线程数是 0,队列采用的是 SynchrousQueue 阻塞队列。最大线程数是 Integer.Max_value 的默认值,KeepAiveTime 是 60 s,也就是线程执行完了处于空闲状态时,过 60 s 就会销毁,如果频繁的创建线程会产生 OOM 问题
它的工作原理是提交任务,没有线程时,任务放到阻塞队列
创建核心线程时取队列执行任务,插入一个元素必须等工作线程取出消费,如果队列没有任务则会阻塞
它的吞吐量比 newFixedExecutor 更高,它适用于并发量大但是任务执行周期短的场景
newFixedExecutor
SheculedExecutor
周期性去执行任务。队列是 DeayQueue 延迟队列,
它的工作原理是 当工作线程数小于最大线程数,首先会去创建线程去执行任务
当达到核心线程数时,会将任务放入到阻塞队列,
所谓周期性就是 他去任务队列取出任务时,会修改一个 time 变量 位下次要执行的时间
然后放入到队列中
说说各层有哪些协议
- 应用层:DNS、HTTP、IP
- 传输层:TCP、UDP
- 网络层:IP、ICMP
- 数据链路层:ARP
- 物理层:不记得了
说说一个数据怎么在网络各层分割报文的
发送数据方
- 传输层:加上 TCP 报文头
- 网络层:加上 IP 头
- 数据链路层:加上帧头和帧尾
- 物理层:则是转换为包含0、1的二进制比特流
讲讲 TCP 三次握手
- 首先刚开始双方处于关闭连接状态,服务端处于监听端口状态,也就是 Listen 状态
- 第一次握手:客户端首先生成随机初始化序列号seq = x,并放到 TCP 头部的32位序号字段中,同时将 SYN 标志设置为 1,表示这是一个 SYN 报文,然后发送给服务端,接着客户端处于 SYN_SENT
- 第二次握手:服务端收到客户端发送过来的 SYN 报文后,首先也会生成随机初始化序列号seq = y,并放到 TCP 头部的32位序号字段中, 并对客户端的序列化 seq = x + 1 作未确认应答号,然后放到 TCP 头部的确认应答字段中,同时将 SYN 和 ACK 标志设置为 1,表示这是一个 SYN-ACK 报文。把该报文发送给客户端后,服务端处于 SYC_RCVD
- 第三次握手:客户端收到服务端发送过来的 SYN_ACK 报文,会发送确认报文给服务端,这个确认报文是对服务端的初始序列化 seq = y + 1, 客户端进入 ESATBLISH 状态
- 服务端收到后,也进入 ESTABLISHED 状态
你刚刚说的 Listen、SYN_SENT、SYN_RCVD、ESTABLISHED 状态有什么含义?
- LISTEN 状态表示监听是否有连接到来,当有连接到来时,它获得已经连接的 socket
- SYN_SENT 表示 客户端具备发送数据能力。但还不具备接受数据能力, 此时需要等待服务端的确认
- SYN_RCVD 表示服务端具备接受数据的能力和发送数据的能力,此时需要等待客户端的确认
- ESTABLISHED 表示我已经建立连接了,我可以发送数据了
客户端发送了数据给服务端,服务端返回对方成功确认收到的确认信息,这个时候是否可以肯定服务端收到了数据
- 不一定,服务端有一个接受缓存区,此时服务端还在处理前面的数据,有可能服务端发生异常了,导致接收缓冲区的数据未被处理
那怎么解决这个问题呢?
- 嗯,触发重传机制,客户端重新发送数据?(懵逼)
客户端想尽快关闭连接,应该怎么办?
- 发送 FIN 报文?
- RST 报文好像也可以断开连接
Socket 编程了解过吗,什么是 socket
- 了解过
- Socket 是一个套接字
socket 的流程
不会(我搞 java 的,没研究过 socket 编程,完了)
基本 socket 做好了封装,你了解吗
刚开始懵逼,后来想到才是 Netty 这个框架
Socket 和 http 有什么区别?
- Socket 是一个套接字接口
- Http 是请求连接,http 是 tcp 连接的管理器
你说说 spring 的生命周期?
大致分为五个阶段,创建前准备阶段、实例化阶段、依赖注入阶段、容器缓存阶段、实例销毁阶段
后面从说了每个阶段是干嘛的(面试官反应逻辑讲的不够清楚,这里我就不列出来的)
事后复习总结如下:
- 创建前准备阶段:
Spring 启动后,扫描 @ComponentScan 注解配置的路径下的所有 .class 文件,
类加载其根据类名加载获取类的 Class 对象
判断类上是否有 @Component、Service 等注解找出 bean 对象
给每个符合条件的 bean 创建 BeanDefintion 对象用于存放 Class 对象、作用域等信息,作用域包括 singletion、prototype、request 等,然后添加进 beanDefinitionMap, key 值存放 bean 的名字,value 是对应的 BeanDefition
扫描 bean 对象
遍历 beanDefinitionMap,创建
MyBatis 中 ${} 与 #{} 的区别
- 无法防止注入攻击,在开发中尽量使用{}
- #{} 是占位符,预编译处理,${} 是拼接符,字符串替换,没有预编译处理
感觉
面试官说我们是做游戏开发,然后又问我你平时喜欢打游戏吗,我说近些年很少打游戏的。以前很喜欢玩,后面觉得腻了,然后面试官说做游戏开发需要对游戏很了解的,对游戏很热爱的,否则难干下去
反问环节说我基础还算行,就是可能在业务方面可能不匹配
不足之处
socket编程不太熟悉,计网还需加强学习
Spring bean 的生命周期 没有让面试官听懂