下午收到了腾讯北极光工作室的面试邀请,晚上进行面试。
面试记录
- 面试老师介绍了自己的团队,然后?问我 玩不玩游戏?
不玩,我更喜欢鼓捣些有趣的小玩意。(然后带跑了节奏)最近在研究GitHub的GitHub Actions - 这应该属于一个CI/CP的工具。你觉得它的技术难点主要在哪呢?
我觉得这个工具其实挺方便的,对于我而言可能阅读文档相对比较难。 - 你们说一下它的技术原理吗?
我最近在做一个Spring Boot的项目。在提交的时候,它会先使用gradlew bootjar
进行打包,然后使用SFTP
协议传输到我的服务器,最后使用SSH
协议执行jar包,完成部署。 - 揣测一下它是怎么实现提交时构建版本的?
我猜测是一个触发器的机制,在提交版本时进行处理。 - 如果让你来做,怎么让Git来调用我的编译服务?
这是GitHub提供的服务。GitHub会启动一个docker容器来进行构建操作。 - 编译时的环境部署信息怎么输入?
使用GitHub的secret
,将TOKEN
和RSA
的私钥上传。在yml
配置文件中进行调用。 - 考虑极端情况,让我做这样一个系统,在用户量比较大的时候,怎么来分配资源?
在收到请求时,挑选一台空闲的服务器进行运行。 - 我说的是资源管理的问题,假设我只能起100个
docker
容器,但是有一千个人要用,该怎么分配?
做一个FIFO
的队列,先来先服务。 - 那如果我要对资源进行限制,不能让一个人占用过多的资源呢?
给每个人分配时间片,时间片用完了换人。 - 如果时间片当前不在用,不是就浪费了嘛?
大家轮流用,不存在时间片空闲的情况。 - 如果人数是动态变化的呢?
如果有新的请求,就把它放到最空闲的服务器。因为时间随机分布,可以让每台服务器尽量忙碌。 - 在资源不够时该怎么协调资源,该怎么做呢?
那就将I/O密集型和CPU密集型的任务并在一起做,这样可以吗? - 怎么讲这些方法组合起来呢?
那就在收到请求时将他们放到一个FIFO
的队列里,如果有服务器是空闲的,就从队列取出一个服务,为其分配时间片。在时间片用完之后,将其移入队尾。 - 每个任务都要分配相同的时间片吗?
我觉得是的,如果想要做到公平,就要给每个任务分配相同的时间片,大概十分钟那样。 - 你是计算机专业的对嘛?你们学过操作系统吗?
学过 - 操作系统里进程和线程调度的算法有哪些?
FIFO,最短时间,最高响应比,时间片轮转 - 那你想一下这个case,在操作系统里怎么实现是最合理的?
我觉得是一个带优先级的优先队列,让优先级高的进程先进性工作 - 那怎么进行调度呢?
比如一个优先级高的进程进入,就让其抢占原有的进程。 - 那原有的进程呢?
将其挂起?那应该让之前的进程先做完,然后让新的服务执行。 - 我注意到你提到了使用
.NET CORE
完成了一个井字棋游戏,可以介绍一下吗?
这是一个前后端分离的桌面应用,后端用的是ASP .Net Core
- 前端呢?
用的是WinForm
- 这个游戏的规则怎么样?
在一个三乘三的棋盘上,黑白双方轮流下棋,如果一方获得了连续的三颗棋子,就获得胜利。 - 那你有遇到过什么技术难点吗?
开始时我使用了同步的网络请求,这样在请求的时候会导致主线程被挂起,让用户认为游戏卡死了。后来我使用了异步的网络请求,提高了用户体验。 - 会延迟多久
大概两三百毫秒 - 这个服务器在哪?
在上海的阿里云,但是我的数据库不在上海,所以延迟会比较久。 - 为什么ping命令30-40毫秒,为什么操作延迟会到200到300毫秒?
首先是TCP
握手和SSL
握手消耗的时间,然后要连接数据库。 - 那你觉得可以怎么优化呢?
使用连接池加速数据库连接,使用WebSocket
进行网络请求 - 怎么获取对方下棋的结果?
每隔一段时间向服务器请求,进行轮询。现在我应该会用WebSocket
来进行实现。 - 可以实现多少人同时对战
我觉得瓶颈应该在数据库的并发上 - 如果需要改呢?
讲棋局信息存储在内存中。 - 如果服务器
down
了呢?
游戏数据会丢失。那局游戏就没了。 - 连接还在吗?
session
会作废,连接应该也没了 - 为什么会作废呢?
原理不是非常了解。 - 那怎么实现重连呢?
使用token机制,重新验证登录状态。 - 那怎么实现数据的保存呢?
我认为存在前端是不合理的,因为前端是不安全的,所以应该存放在后端。我觉得可以使用类似于radis
的服务。 - 那你有用过redis吗?
了解过,但是没在项目里用过。 - 我对你项目的情况大概了解了。你现在倾向于读研还是工作呢?
倾向于工作,因为非专业课不占优势。 - 你现在学了那些专业课
计算机网络,数据库,组成原理和操作系统 - 那TCP和UDP有什么区别
TCP面向连接,URP面向无连接。TCP提供可靠传输,UDP不能提供可靠传输。TCP的延迟比UDP大。 - 为什么TCP的延迟大?
需要发送ACK报文,UDP不需要。 - TCP发包需要每发一个包,就接受一个ACK吗?
不一定,可以数个包进行一次ACK。 - TCP是怎么实现这样的呢?
是一个滑动窗口的机制。 - 如果报文丢失呢?
接收方就不会发送ACK报文,发送方会进行超时重传。 - 那后面没有丢失的报文呢?
会被丢弃,等待重传。 - 那为什么会被丢到呢?
(不是很清楚呢,有点忘了) - 栈内存和堆内存的区别
new
方法动态生成的对象会被放到堆里,使用类似int a=5
这样生成的变量会被放到栈里。 - 那class可以被放到栈里吗?
如果static
的可能可以?不了解。 - JVM实现了GC的机制,那GC什么时候会失效呢?
循环依赖? - 那怎么检查和解除呢?
类似于操作系统里解除死锁的算法? - 那对象怎么检查呢?
查引用的列表,找有没有他自己? - 那这是深度优先还是广度优先呢?
我觉得是深度优先。我对这块了解不深 - 那你有什么想问的呢?
你们是一个做游戏的部门,那后端应该是基于C++的,而我现在的开发大多基于Java,那应该怎么学习C++的后端开发比较好呢? - 我觉得应该从项目入手,做项目来进行学习。
- 你还有什么想问的吗?
没有了 辛苦了
你也辛苦了面试总结
待完善
什么是 GitHub Actions
GitHub Actions
是GitHub提供的一个持续集成,持续部署工具。您可以直接在 GitHub 仓库中通过 GitHub Actions 创建自定义持续集成 (CI) 和持续部署 (CD) 工作流程。
如何使用 GitHub Actions
在git项目中开启Actions功能
打开GitHub项目的主页,找到这个按钮,点击,即可进入Actions页面。
选择一个合适的配置文件,将其加入你的项目中,即完成了持续集成的配置工作。
配置文件的样例
这是一个配置文件的样例。使用该配置文件,可以用于gradle构建的Spring Boot
项目。在项目进行更新时,自动生成Spring Boot
的jar
文件,并发布release
。
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Modify gradle config file
run: |
sed -e '/maven.aliyun.com/d' build.gradle >> build.gradle.1
sed -e '/maven.aliyun.com/d' settings.gradle >> settings.gradle.1
mv build.gradle.1 build.gradle
mv settings.gradle.1 settings.gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew bootjar
- name: Get Release Info
id: get_info
run: |
VERSION=`grep version build.gradle | grep '=' | grep -Eo \'.*\' | grep -Eo '[a-z|A-Z|0-9|.|_]*'`
NAME=`grep "rootProject.name" settings.gradle | grep -Eo \'.*\' | grep -Eo '[a-z|A-Z|0-9|.|_]*'`
FILE_PATH="./build/libs/"$NAME"-"$VERSION".jar"
FILE_NAME=$NAME"-"$VERSION".jar"
VERSION=$VERSION.`git rev-parse --short HEAD`
echo ::set-output name=file_path::$FILE_PATH
echo ::set-output name=file_name::$FILE_NAME
echo ::set-output name=version::$VERSION
- name: Create Release
id: create_release
uses: actions/create-release@master
env:
GITHUB_TOKEN: ${{secrets.TOKEN}}
with:
tag_name: Release_${{steps.get_info.outputs.version}}
release_name: Release of version ${{steps.get_info.outputs.version}}
draft: false # 是否是草稿
prerelease: false # 是否是预发布
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@master
env:
GITHUB_TOKEN: ${{secrets.TOKEN}}
with:
upload_url: ${{steps.create_release.outputs.upload_url}}
asset_path: ${{steps.get_info.outputs.file_path}}
asset_name: ${{steps.get_info.outputs.file_name}}
asset_content_type: application/java-archive
- name: rename file
run: |
mv ./build/libs/${{steps.get_info.outputs.file_name}} ./build/libs/${{steps.get_info.outputs.file_name}}.`git rev-parse --short HEAD`
- name: deploy file
uses: wlixcc/SFTP-Deploy-Action@v1.0
with:
username: 'server_runner'
server: 'huhaorui.com'
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
local_path: './build/libs/*'
remote_path: '/www/wwwroot/fridge.huhaorui.com'
args: '-o ConnectTimeout=5'
- name: restart server
run: |
mkdir ~/.ssh
ssh-keyscan huhaorui.com >> ~/.ssh/known_hosts
echo "${{ secrets.SSH_PRIVATE_KEY }}" >> ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh 'server_runner@huhaorui.com' "kill -9 \`ps -x | grep fridge_server | sed -n '1p' | grep -Eo [0-9]{4}[0-9]+\`"
ssh 'server_runner@huhaorui.com' "nohup java -jar /www/wwwroot/fridge.huhaorui.com/${{steps.get_info.outputs.file_name}}.`git rev-parse --short HEAD` >/dev/null 2>&1 &"
如何写一个配置文件
通过研究上面的配置,可以发现,我们可以在配置中使用已有的action,如actions/upload-release-asset@master
,也可以自己编写bash脚本,用于实现特定的功能。
更多使用方法可以参阅GitHub提供的文档。
JetBrain全家桶,这是多少开发者梦寐以求的东西。可是,299美元一年的价格,让很多同学望而却步。殊不知,你的学校邮箱,可以让你获得免费的正版全家桶。带上邮箱,来吧!
你需要准备的东西:
- 一个.edu的教育邮箱
- 不错的网络连接
首先,我们当然要先拥有一个教育邮箱。以浙江工业大学的邮箱为例,打开邮箱(mail.zjut.edu.cn),用户名是你的学号,密码是身份证号的后八位。

接下来,打开JetBrain的网站( https://www.jetbrains.com/shop/eform/students )填写你的个人信息。注意,填写邮箱时,选择刚才的学校邮箱。
你肯定希望在收件箱看见你的邮件吧。但是由于神奇的原因,这封邮件会出现在你的拦截队列。

进入拦截队列,选择投递邮件,你就可以在收件箱看见它。

点击 Confirm Request

阅读协议,选择 I Accept,你的学生权限申请就通过了。接下来,在新出现的界面注册一个jetbrain账号。

输入信息,点击提交。你就拥有了一套全家桶。

下载你想要的那个吧!
用Java的方式打开数据库课程设计
准备工作
- JetBrains Intellij IDEA Ultimate
- JDK 1.8或11
新建项目
打开IDEA我们可以看见下面的起始页面
点击 Create New Page
新建项目,进入 Java
点击下一步,勾选Create project from template
,点击下一步设置项目名称、路径、包名等即可创建完成
添加依赖
前往mvnrepository.com下载适用于mssql的jdbc驱动用以连接数据库,将连接驱动放入lib文件夹下,右键项目选择Project Structure
,进入Libraries,点击+按钮,添加Java Lib,选择lib文件夹点击确定即可
初始数据
插入数据
//插入数据
//PreparedStatement预编译SQL语句确保安全
//?是占位符,用setString/setInt等方法插入对应占位符的值
try (PreparedStatement ps = conn.prepareStatement("insert into t_01 (name, info) values (?, ?)")) {
ps.setString(1, "stu10");
ps.setString(2, "135790");
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
插入结果
更新数据
//更新数据,将id=10的记录的info字段改为hello world
try (PreparedStatement ps = conn.prepareStatement("update t_01 set info=? where id=?")) {
ps.setString(1, "hello world");
ps.setInt(2, 10);
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
更新结果
删除数据
//删除数据,删除id=10的记录
try (PreparedStatement ps = conn.prepareStatement("delete from t_01 where id=?")) {
ps.setInt(1, 10);
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
删除结果
查询数据
//查询所有数据并输出
try (PreparedStatement ps = conn.prepareStatement("select * from t_01")) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String info = rs.getString("info");
System.out.println("id=" + id + ", name=" + name + ", info=" + info);
}
} catch (SQLException e) {
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
查询结果
用.Net Core
的方式打开数据库课程设计
准备工作
- Visual Studio 2019 Community
- 安装了.NET桌面开发组件
新建项目
打开Visual Studio点击新建项目,选择你要创建项目的类型(控制台应用、WinForm、WPF)并给项目起个名字
添加依赖
在依赖项右键点击管理NuGet程序包,搜索'sql server',选择System.Data.SqlClient并安装,中途跳出的提示框点击确定,许可证点击接受即可
数据库准备
create database db_01;
use database db_01;
create table t_01
(
id int identity constraint t_01_pk primary key nonclustered,
name varchar(32) not null,
info text
);
初始数据
用代码连接数据库
新建Connection.cs工具类用于提供SQL连接对象,可以通过Connection.GetConnection()调用,代码如下
public static SqlConnection GetConnection()
{
try
{
//构建连接字符串
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "数据库地址localhost或ip";
builder.UserID = "sa";
builder.Password = "你的密码";
builder.InitialCatalog = "目标数据库";
//根据SqlConnectionStringBuilder参数创建连接字符串
SqlConnection connection = new SqlConnection(builder.ConnectionString);
connection.Open();
return connection;
}
catch (SqlException e)
{
Console.WriteLine(e.ToString());
return null;
}
}
查询数据
查询id=2的记录
//查询t_01中id=2的记录
using (SqlCommand command = connection.CreateCommand())
{
//@id为占位符,用command.Parameters.AddWithValue("@id", 2)赋值
//即最后得到的SQL语句为'select * from t_01 where id=2'
command.CommandText = "select * from t_01 where id=@id";
command.Parameters.AddWithValue("@id", 2);
//获取SqlDataReader对象来读取数据
using (SqlDataReader reader = command.ExecuteReader())
{
//while循环内每次读一行,用reader["id"]读取改行id的值
while (reader.Read())
{
Console.WriteLine(reader["id"]);
Console.WriteLine(reader["name"]);
Console.WriteLine(reader["info"]);
}
}
}
查询结果
更新数据
将id=2的记录的info字段修改为helloworld
using (SqlCommand command = connection.CreateCommand())
{
//将id为2的记录的info字段改为helloworld,
command.CommandText = "update t_01 set info=@info where id=@id";
command.Parameters.AddWithValue("@info", "helloworld");
command.Parameters.AddWithValue("@id", 2);
//affectedRows保存受影响的行数
int affectedRows = command.ExecuteNonQuery();
Console.WriteLine(affectedRows);
}
更新结果
插入数据
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "insert into t_01 (name, info) values (@name, @info)";
command.Parameters.AddWithValue("@name", "stu05");
command.Parameters.AddWithValue("@info", "135790");
int affectedRows = command.ExecuteNonQuery();
Console.WriteLine(affectedRows);
}
插入结果
删除数据
删除id=8的记录
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "delete from t_01 where id=@id";
command.Parameters.AddWithValue("@id", 8);
int affectedRows = command.ExecuteNonQuery();
Console.WriteLine(affectedRows);
}