查看原文
其他

上海某游戏小厂面试,也扛不住了...

脚本之家 2023-12-27

The following article is from 小林coding Author 小林coding

将 脚本之家 设为“星标
第一时间收到文章更新

文 | 小林coding

出品 | 小林coding(ID:CodingLin )

已获得原公众号的授权转载

今天分享一位同学面试上海某游戏公司的面经,同学的技术栈是Java后端,虽然不是大厂,但是一面面试也被问了 25 多个问题,时长也接近 1 小时了

面试过程中,也问到了 Linux socket 编程,游戏公司都会对网络协议和网络编程这一块要求比较高,所以投游戏公司的同学,需要重点准备网络方面的知识。

还有一点,游戏公司的开发岗除了技术要求之外,可能还会问你一下你对游戏的兴趣,平常玩什么游戏,对游戏有什么看法,因为工作内容就是开发游戏,如果对游戏没有热情,会觉得工作缺失了激情。

问题记录

介绍你的项目

balabal 了几分钟

Redis 缓存一致性

说了旁路缓存策略

如果这个时候一波海量请求,你怎么保证他们能读到数据
  1. 数据延迟肯定是有的
  2. 我个人认为可以做流量控制,限制读请求数量
  3. 当然,如果非的读的话。可以采用把删除缓存策略改为更新缓存策略
说说Redis 数据结构

Redis 有五大基本数据类型和四大新类型

五大基本类型是:

  1. String
  2. Hash
  3. List
  4. Set
  5. zset

每一种数据结构根据自身的特性有不同的使用场景:

  1. string:
    1. 计数器,因为 Redis 是单线程模型的,所以redis执行命令时原子性,所以他可以用来做计数器,例如 点赞计数、转发、库存数量等
    2. 分布式锁:setnx key value ex 时间
  2. hash:
    1. Hash 是 key-value 键值对,类似与 Java 的 HashMap, 查找时间复杂度是 o(l)
    2. Hash 的底层数据结构是hashtale 和压缩表
    3. 当 元素个小于 512 并且所有元素大小小于 64 字节,采用压缩列表作为底层数据结构
    4. 反之采用 hashtable
    5. 它适合做购物车,用户作为 id、商品 id 位 field、商品数量为 value
  3. List (说到 List 被面试面试官打断了,下一个)
事务了解吗?

了解,acid 事务四大特性说了一遍

事务隔离级别有哪几种
  1. 四种
    1. 读未提交
    2. 读已提交
    3. 可重复读
    4. 串行话
  2. 读未提交就是一个A事务能读到另一个B事务未提交的事务,当这个B事务发生回滚时, A 事务读到的是脏数据。它有脏读、不可重复读、幻读问题
  3. 读已提交就是只能读到对方事务已经提交的事务,它解决了脏读问题,但是有不可重复读和幻读问题(说到这里突然被面试官打断)
追问:隔离级别是由啥保证的
  1. mvvc 机制 和 锁机制
可重复读为什么完全不能解决幻读

在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。

因为这种特殊现象的存在,所以我们认为 MySQL Innodb 中的 MVCC 并不能完全避免幻读现象。

进程与线程的区别

常规八股

线程池有哪几个类型的
  1. newSingleExecutor
    1. 只有一个核心线程,也是最大线程数。队列采用的是 LinkedblockingQueue 无界阻塞队列。极端情况下会有  OOM 问题
    2. 它的工作原理是当提交任务是当没有工作线程时,会将任务放入到阻塞队列中,
    3. 有核心线程时,获取阻塞队列取任务执行,执行完了接着从阻塞队列执行
    4. Keepalive存活时间是 0,因为本来就没有非核心线程
    5. 它的场景是串行化的场景,因为他只有一个工作线程
  2. newCacheExecutor
    1. 核心线程数是 0,队列采用的是 SynchrousQueue 阻塞队列。最大线程数是 Integer.Max_value 的默认值,KeepAiveTime 是 60 s,也就是线程执行完了处于空闲状态时,过 60 s 就会销毁,如果频繁的创建线程会产生 OOM 问题
    2. 它的工作原理是提交任务,没有线程时,任务放到阻塞队列
    3. 创建核心线程时取队列执行任务,插入一个元素必须等工作线程取出消费,如果队列没有任务则会阻塞
    4. 它的吞吐量比 newFixedExecutor 更高,它适用于并发量大但是任务执行周期短的场景
  3. newFixedExecutor
  4. SheculedExecutor
    1. 周期性去执行任务。队列是 DeayQueue 延迟队列,
    2. 它的工作原理是 当工作线程数小于最大线程数,首先会去创建线程去执行任务
    3. 当达到核心线程数时,会将任务放入到阻塞队列,
    4. 所谓周期性就是 他去任务队列取出任务时,会修改一个 time 变量 位下次要执行的时间
    5. 然后放入到队列中
说说各层有哪些协议
  1. 应用层:DNS、HTTP、IP
  2. 传输层:TCP、UDP
  3. 网络层:IP、ICMP
  4. 数据链路层:ARP
  5. 物理层:不记得了
说说一个数据怎么在网络各层分割报文的

发送数据方

  1. 传输层:加上 TCP 报文头
  2. 网络层:加上 IP 头
  3. 数据链路层:加上帧头和帧尾
  4. 物理层:则是转换为包含0、1的二进制比特流
讲讲 TCP 三次握手
  1. 首先刚开始双方处于关闭连接状态,服务端处于监听端口状态,也就是 Listen 状态
  2. 第一次握手:客户端首先生成随机初始化序列号seq = x,并放到 TCP 头部的32位序号字段中,同时将 SYN 标志设置为 1,表示这是一个 SYN 报文,然后发送给服务端,接着客户端处于 SYN_SENT
  3. 第二次握手:服务端收到客户端发送过来的 SYN 报文后,首先也会生成随机初始化序列号seq = y,并放到 TCP 头部的32位序号字段中, 并对客户端的序列化 seq = x + 1 作未确认应答号,然后放到 TCP 头部的确认应答字段中,同时将 SYN 和 ACK 标志设置为 1,表示这是一个 SYN-ACK 报文。把该报文发送给客户端后,服务端处于 SYC_RCVD
  4. 第三次握手:客户端收到服务端发送过来的 SYN_ACK 报文,会发送确认报文给服务端,这个确认报文是对服务端的初始序列化 seq = y + 1,  客户端进入 ESATBLISH 状态
  5. 服务端收到后,也进入 ESTABLISHED 状态
你刚刚说的 Listen、SYN_SENT、SYN_RCVD、ESTABLISHED 状态有什么含义?
  1. LISTEN 状态表示监听是否有连接到来,当有连接到来时,它获得已经连接的 socket
  2. SYN_SENT 表示 客户端具备发送数据能力。但还不具备接受数据能力, 此时需要等待服务端的确认
  3. SYN_RCVD 表示服务端具备接受数据的能力和发送数据的能力,此时需要等待客户端的确认
  4. ESTABLISHED 表示我已经建立连接了,我可以发送数据了
客户端发送了数据给服务端,服务端返回对方成功确认收到的确认信息,这个时候是否可以肯定服务端收到了数据
  1. 不一定,服务端有一个接受缓存区,此时服务端还在处理前面的数据,有可能服务端发生异常了,导致接收缓冲区的数据未被处理
那怎么解决这个问题呢?
  1. 嗯,触发重传机制,客户端重新发送数据?(懵逼)
客户端想尽快关闭连接,应该怎么办?
  1. 发送 FIN 报文?
  2. RST 报文好像也可以断开连接
Socket 编程了解过吗,什么是 socket
  1. 了解过
  2. Socket 是一个套接字
socket 的流程

不会(我搞 java 的,没研究过 socket 编程,完了)

基本 socket 做好了封装,你了解吗

刚开始懵逼,后来想到才是 Netty 这个框架

Socket 和 http 有什么区别?
  1. Socket 是一个套接字接口
  2. Http 是请求连接,http 是 tcp 连接的管理器
你说说 spring 的生命周期?

大致分为五个阶段,创建前准备阶段、实例化阶段、依赖注入阶段、容器缓存阶段、实例销毁阶段

后面从说了每个阶段是干嘛的(面试官反应逻辑讲的不够清楚,这里我就不列出来的)

事后复习总结如下:

  1. 创建前准备阶段:
    1. Spring 启动后,扫描 @ComponentScan 注解配置的路径下的所有 .class 文件,
    2. 类加载其根据类名加载获取类的 Class 对象
    3. 判断类上是否有 @Component、Service 等注解找出 bean 对象
    4. 给每个符合条件的 bean 创建 BeanDefintion 对象用于存放 Class 对象、作用域等信息,作用域包括 singletion、prototype、request 等,然后添加进 beanDefinitionMap, key 值存放 bean 的名字,value 是对应的 BeanDefition
    5. 扫描 bean 对象
    6. 遍历 beanDefinitionMap,创建
MyBatis 中 ${} 与 #{} 的区别
  1. 使{}
  2. #{} 是占位符,预编译处理,${} 是拼接符,字符串替换,没有预编译处理

感觉

面试官说我们是做游戏开发,然后又问我你平时喜欢打游戏吗,我说近些年很少打游戏的。以前很喜欢玩,后面觉得腻了,然后面试官说做游戏开发需要对游戏很了解的,对游戏很热爱的,否则难干下去

反问环节说我基础还算行,就是可能在业务方面可能不匹配

不足之处

socket编程不太熟悉,计网还需加强学习

Spring bean 的生命周期 没有让面试官听懂

  推荐阅读:
  1. 当面试官问你为什么要离职,他在问什么?
  2. 谁家面试一直问 Swagger 啊?
  3. 在面试了一些人之后,我整理了16条关于面试的小建议
  4. 面试近2个月的经验与踩坑
  5. 面试通过,背调凉了。。
继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存