腾讯 IEG 游戏前沿技术 一面复盘

前言

投了个实习内推后台开发,本来要电话先交流的那天直接走流程下午面试了,对面两人,面了有一个小时,游戏本的构思续航忘记插电了最后还掉线了一下,趁着还记得面试内容复盘一下

自我介绍一下

答:

您好,我是深大26届 xxx 的 xxx,对贵公司后台开发的岗位很感兴趣。现在在 xxx 做后台开发实习生,负责项目的开发和运维相关,还有一部分系统设计相关的的工作,之前在学校中也做过 web 开发相关的项目。

了解到贵公司正在招聘后台开发,做与 AI 相关的交叉。考虑到自己在 xx,对经典机器学习算法还有其相关的论文也比较了解,加上自己在后台开发相关生态的实践较多,因此认为本人跟贵公司的岗位是比较契合的。

我听学长的介绍了解到贵公司会做分布式训练的内容,这块我也是比较感兴趣的。觉得能够将自己在后台开发中对分布式服务的实践与课程中机器学习的内容还有模型的训练的知识相结合,会是很美妙的事情。

很荣幸能有今天交流的机会,希望我们沟通愉快

问实习

SQL 优化

问:实习干了什么

答:会对已有的项目做一些修改,业务需求变动的时候对接口做修改。在开发过程中也出现过因为大数据量导致的性能问题,自己有对 SQL 优化慢查询,也有用上数据结构的知识解决问题

问:具体是怎么做的

答:SQL 慢查询的话,先用 explain 看查询过程,尽量让它走索引。然后看联表的情况,要让小表驱动大表 ,有过滤条件的话尽量在联表之前先过滤,叫谓词下推 ,然后尽量不要使用 select *,要将联合索引覆盖查询的字段,叫覆盖索引

问:可以举实际案例吗

答:用户表和部门表,一个大一个小的时候,小表放在 LEFT JOIN 的左边

问:覆盖索引详细讲下

答:先介绍下聚簇索引非聚簇索引,我们根据索引构建的树查找数据,最后叶子节点包含全部数据就是聚簇索引,不是则是非聚簇索引。MySQL 默认存储引擎和 Innodb 他们分别是其中之一。当我们查询的字段不在叶子节点时,就会增加一个回表查询的过程,所以尽量让索引覆盖我们查询的字段。

复盘:MyISAM 是非聚簇索引,Innodb 是聚簇索引。回表查询指 非主键索引树搜索回到主键索引树搜索的过程

问:覆盖索引是覆盖 select 后面的还是覆盖 where 后面的

答:是覆盖 select 后面的(完了,好像都要)

然后就被解释了一下也要覆盖 where 后面的

拓展:可以深挖 SQL 优化的其他方法,包括分页、分组、排序 等。以及生产环境建索引的问题

SQL 优化的方式有很多种,大体分为 物理查询优化逻辑查询优化 两种,前者利用 索引表连接方式 等技术进行优化,后者利用 等价变换 提升查询效率

最左前缀法则使select、where、order by走索引,谓词下推,小表驱动大表,覆盖索引。

深度分页时,有两种解决方案,一种是当索引是自增时,根据索引先定位到数据行,一种是先对索引做分页查询再回表。

如果有需要创建索引的情况,需要考虑生产环境下建索引可能导致长时间锁表,期间有数据更新插入导致的数据不一致,可以通过建新表建索引批量导入再批量执行期间更新插入的数据解决。

分布式定时任务

问:讲一下这个分布式的定时任务

答:一开始这边是有每天查数据库然后根据表的数据发送短信通知的定时任务的需求,可以启动一个单体的 Springboot 项目去执行。但是后面有各种其他的定时任务,比如备份数据库的需求,定期刷新 redis 缓存等,我们发现单体的定时任务不容易监控它的运行结果,也不容易做统一的管理,在运行失败时及时定位错误

所以就采用 XXL-JOB 这个开源项目作为我们的分布式定时任务的解决方案,我也在三台服务器上搭建了 XXL-JOB-ADMIN 定时任务调度中心的集群,目前已经投入生产

问:为什么要用分布式

答:单体的多种定时任务不容易做统一的管理,不能及时地定位错误

复盘:

参考文章: https://zhuanlan.zhihu.com/p/660320770

主要有以下原因

高可用:单体的定时任务只在一台机器运行,当程序或节点挂掉时就会导致功能不可用,对于一些核心功能来说是无法接受的

性能瓶颈:单台机器的CPU、磁盘还有内存有限,任务量过大时会处理不过来

分布式的解决方案就能实现高可用,将任务数据持久化到数据库,有完善的任务重做功能和告警机制

问:有任务执行太慢导致后面的任务延期执行怎么办

答:(没答上,实在不应该,之前看过相关文章的)

复盘:参考文章:@EnableScheduling 和 @Scheduled 实现定时任务的任务延期问题)

问题的根本在于只使用单个线程去执行定时任务,造成线程的阻塞

可以手动进行异步编排,交给某个自己配置的线程池执行。

Springboot 自带的定时任务 @EnableScheduling 会自动创建一个 corePoolSize 为 1 ,maxinumPoolSize 为 Integer.MAX_VALUE 的线程池。可通过自定义配置线程池异步执行任务

XXL-JOB 中使用快慢线程池,默认将一个 一分钟内触发超过 10 次慢执行的任务交给慢线程池

问:如果让你设计一个调度中心,会怎么去做,多个调度中心是怎么共享任务信息的

答:XXL-JOB 中使用的是建立数据库表,多个调度中心使用同一个数据库。

复盘:

参考:

官方文档:分布式任务调度平台XXL-JOB (xuxueli.com)

一篇讲的很好的技术博客:新来个架构师,把Xxl-Job原理讲的炉火纯青 - 三友的java日记 - 博客园 (cnblogs.com)

XXL-JOB 同一个集群的多个调度中心之间是没有直接通信的,需要共享同一个数据库表

XXL-JOB-ADMIN 调度中心需要提供 RESTful API,包括服务的注册,服务注册的摘除,还有任务结果的回调

执行器需要提供 RESTful API,包括心跳检测,忙碌检测,启动任务,终止任务,还有查看执行的日志

路由策略

当一个任务配置的执行器有多个时,调度中心在选择执行器的时候有不同的策略,包括

第一个/最后一个/轮询/随机/最近最少使用(LFU)/最不经常使用(LCU)/故障转移/忙碌转移/一致性Hash/分片广播

阻塞处理

处理一个任务的时间比较长的时候,可能导致后续的任务调度阻塞,需要配置阻塞处理策略。包括单机串行(默认)、丢弃后续调度、覆盖之前调度

失败重试

可以考虑配置重试次数,执行器执行失败时进行重试

分布式环境下使用分片广播时,如果多个任务之间互不影响,可以考虑使用告警,之后手动执行小段;如果互相影响的话需要利用分布式事务
拓展:分布式环境下考虑使用分布式锁 解决重复调度问题,还有分布式事务解决任务分片的原子性问题

XXL-JOB 中使用的是 select for update 的方式实现分布式锁

根据 CAP 理论进行取舍选择 刚性/柔性 分布式事务

问:任务挂掉了怎么排查

答:XXL-JOB-ADMIN 图形化的后台管理界面可以看到执行的日志,可以看到任务是怎么挂掉的

(对面并不满意)

复盘:

最直接的方式是查看执行器回调给调度中心的执行日志以及返回的执行状态

XXL-JOB 默认实现了邮件的告警,可以通过配置告警邮件接收告警信息

还可以通过实现 JobAlarm 的接口自定义告警方式

问项目

问:视频浓缩

答:这个项目原来呢有一个 Java 编写的后端,通过轮询数据库的方式检查任务的状态,然后通过接口访问一个 Python fastapi 编写的后端,通过启动 shell 监听进程,去调用 ffmpeg 还有 cpp 编写的 OCR 程序。

我跟小组成员探讨后决定对其接口的调用关系做简化,将 Python 部分的代码重写并合并到 Java 当中,利用 javacpp 包实现的 jni 接口调用 ffmpeg,再通过实现 native 方法对 OCR程序做调用。实现了代码风格的统一。

前面提到在 Java 使用轮询数据库的方式,这种方案对磁盘的 IO 压力会比较大,考虑到我们也没有将任务状态持久化存储的需求,于是决定改用事件驱动的方式,将任务的发布和消费解耦。

问:项目不像一个系统而是一个小工具,有前端吗

答:有的,项目有前端也有后端

问:两套架构是什么样

答:根据用户的两种不同的需求,分别设计了两种部署方式。

一种是单体式移动办公部署,在客户有 gpu 的电脑上开箱即用,数据的存储使用本地缓存 h2 内嵌数据库

一种是分布式服务器单体部署,把 yolo、opencv、ocr 部署在显存服务器上,把前端后端部署在静态服务器和其他普通服务器上,提供可访问的网页端

问:事件驱动是怎么实现

答:在单体的架构中,我们选择用 SpringBoot 自带的事件驱动模型;在分布式架构中,我们使用 MQ。

问:选的是什么 MQ,对比了哪些技术,为什么

答:选的是 RocketMQ,对比了 RabbitMQ 和 Kafka。

Kafka 的吞吐量很大,但是可靠性无法保证,RabbitMQ 吞吐量和可靠性都不错,但是我们的团队相对 MQ 做定制化的改进,由于 RabbitMQ 是 Erlang 编写,需要更多的学习成本,而 RocketMQ 是 Java 编写的。

所以我们基于吞吐量,可靠性,扩展性三个维度的考虑,最后选择了 RocketMQ

问:为什么说 Kafka 的可靠性无法保证

答:这一块还需要仔细研究。。

复盘:

Kafka 的可靠性是有保证的。

问专业技能

问:docker

答:在 docker 上的实践不多,有同事遇到 docker 部署 Python 项目的

问:你做的运维是指把应用部署在服务器上吗

答:包括把前端的 dist 包部署到静态服务器上,编写脚本运行后端的 jar 包,重定向日志输出。有考虑搭建 k8s 集群,由于需要重启服务器,迁移原有服务等危险的操作,暂时没有执行

问:那服务挂掉了怎么办

答:看运行的日志。如果是 k8s 的话可以对 pod 重启,还有在高压期自动扩缩容

问:redis 有用到是吧

答:有用到 redis 将访问数据库的大数据量的结果保存在缓存中。

反问:用的是什么数据结构

答:(没想起来叫做 string)就是最基本的键值对

问:说下 redis 其它数据结构

答:比如 哈希,可以存多个键值对

复盘:

redis 数据结构:

  1. string:一个 key 对应一个 value,当 value 为数字时可以用数值操作
  2. hash:底层使用哈希表存储,一个 key 可以对应多个 field,每个 field 映射到一个 value;典型应用在对象管理
  3. list:底层使用双向链表,一个 key 可以保存多个数据,并且体现数据进入的顺序
  4. set:和 hash 结构相同,存储键不存储值,对大数据量高效查询
  5. sorted_set:在 set 基础上增加排序字段

问:用的是 git 还是 svm

答:用的是 git,外网用 gitee,内网用 gitlab 私有仓库

问:有写过前端是吗

答:是的,在学校有写过 web 前端项目,实习的时候也偶尔写前端

写算法

说给我一道简单的题吧,然后就是 两数之和 (?

1. 两数之和

我就先说用暴力的解法两层循环复杂度O(n^2)可以实现

对面说那你不要用两层循环,用其他办法

然后就搓出来运行一下没什么问题

当时写的:

java 复制代码
public class Solution {  
    public List<Integer> func(int[] nums, int target) {  
        Map<Integer, Integer> map = new HashMap<>();  
        for (int i = 0; i < nums.length; ++i)
            map.put(nums[i], i);
        for (int i = 0; i < nums.length; ++i) {
            if (map.get(target - nums[i]) != null){  
                List<Integer> result = new ArrayList<>(); 
                result.add(i);
                result.add(map.get(target - nums[i]));
                return result;  
            }  
        }
        return null;  
    }  
    public static void main(String[] args) {  
          int[] nums = {3, 2, 7, 11};
          int target = 9;
          System.out.println(func(nums, target));
    }
}

其实标准题解如下,只要一次遍历就行:

java 复制代码
public class Solution1 {  
    public int[] twoSum(int[] nums, int target) {  
        int len = nums.length;  
        Map<Integer, Integer> map = new HashMap<>();  
        for (int i = 0; i < len; ++i){  
            if (map.get(target - nums[i]) != null){  
                return new int[]{map.get(target - nums[i]), i};  
            }  
            map.put(nums[i], i);  
        }  
        return null;  
    }  
}

问情况

问:你现在是在 xxx 上班,要是有腾讯的机会会怎么想

答:会尽快完成工作的交接,把握住眼前有挑战的机会

问:上课情况

答:自己的规划是在大三多一些实践的经历,所以有调整课表的情况,课比较少。现在还在选课阶段,可以根据后面实习的情况动态调整选课的情况

问:所以具体是?

答:能保证一周到勤 3 ~ 4 天

相关推荐
P.H. Infinity2 分钟前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天6 分钟前
java的threadlocal为何内存泄漏
java
caridle18 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^23 分钟前
数据库连接池的创建
java·开发语言·数据库
苹果醋326 分钟前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花31 分钟前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端34 分钟前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan41 分钟前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
全栈开发圈1 小时前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫