今天分享一位同学面试上海某游戏公司的面经,同学的技术栈是Java后端,虽然不是大厂,但是一面面试也被问了 25 多个问题,时长也接近 1 小时了
面试过程中,也问到了 Linux socket 编程,游戏公司都会对网络协议和网络编程这一块要求比较高,所以投游戏公司的同学,需要重点准备网络方面的知识。
还有一点,游戏公司的开发岗除了技术要求之外,可能还会问你一下你对游戏的兴趣,平常玩什么游戏,对游戏有什么看法,因为工作内容就是开发游戏,如果对游戏没有热情,会觉得工作缺失了激情。
balabal 了几分钟
说了旁路缓存策略
数据延迟肯定是有的
我个人认为可以做流量控制,限制读请求数量
当然,如果非的读的话。可以采用把删除缓存策略改为更新缓存策略
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的二进制比特流
首先刚开始双方处于关闭连接状态,服务端处于监听端口状态,也就是 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 状态表示监听是否有连接到来,当有连接到来时,它获得已经连接的 socket
SYN_SENT 表示 客户端具备发送数据能力。但还不具备接受数据能力, 此时需要等待服务端的确认
SYN_RCVD 表示服务端具备接受数据的能力和发送数据的能力,此时需要等待客户端的确认
ESTABLISHED 表示我已经建立连接了,我可以发送数据了
不一定,服务端有一个接受缓存区,此时服务端还在处理前面的数据,有可能服务端发生异常了,导致接收缓冲区的数据未被处理
嗯,触发重传机制,客户端重新发送数据?(懵逼)
发送 FIN 报文?
RST 报文好像也可以断开连接
了解过
Socket 是一个套接字
不会(我搞 java 的,没研究过 socket 编程,完了)
刚开始懵逼,后来想到才是 Netty 这个框架
Socket 是一个套接字接口
Http 是请求连接,http 是 tcp 连接的管理器
大致分为五个阶段,创建前准备阶段、实例化阶段、依赖注入阶段、容器缓存阶段、实例销毁阶段
后面从说了每个阶段是干嘛的(面试官反应逻辑讲的不够清楚,这里我就不列出来的)
事后复习总结如下:
创建前准备阶段:
Spring 启动后,扫描 @ComponentScan 注解配置的路径下的所有 .class 文件,
类加载其根据类名加载获取类的 Class 对象
判断类上是否有 @Component、Service 等注解找出 bean 对象
给每个符合条件的 bean 创建 BeanDefintion 对象用于存放 Class 对象、作用域等信息,作用域包括 singletion、prototype、request 等,然后添加进 beanDefinitionMap, key 值存放 bean 的名字,value 是对应的 BeanDefition
扫描 bean 对象
遍历 beanDefinitionMap,创建
无法防止注入攻击,在开发中尽量使用{}
#{} 是占位符,预编译处理,${} 是拼接符,字符串替换,没有预编译处理
面试官说我们是做游戏开发,然后又问我你平时喜欢打游戏吗,我说近些年很少打游戏的。以前很喜欢玩,后面觉得腻了,然后面试官说做游戏开发需要对游戏很了解的,对游戏很热爱的,否则难干下去
反问环节说我基础还算行,就是可能在业务方面可能不匹配
socket编程不太熟悉,计网还需加强学习
Spring bean 的生命周期 没有让面试官听懂