HHR的小站
享受代码带来的快乐吧
首页
您正在查看: 杂谈 分类下的文章

下午收到了腾讯北极光工作室的面试邀请,晚上进行面试。

面试记录

  • 面试老师介绍了自己的团队,然后?问我 玩不玩游戏
    不玩,我更喜欢鼓捣些有趣的小玩意。(然后带跑了节奏)最近在研究GitHub的GitHub Actions
  • 这应该属于一个CI/CP的工具。你觉得它的技术难点主要在哪呢?
    我觉得这个工具其实挺方便的,对于我而言可能阅读文档相对比较难。
  • 你们说一下它的技术原理吗?
    我最近在做一个Spring Boot的项目。在提交的时候,它会先使用gradlew bootjar进行打包,然后使用SFTP协议传输到我的服务器,最后使用SSH协议执行jar包,完成部署。
  • 揣测一下它是怎么实现提交时构建版本的?
    我猜测是一个触发器的机制,在提交版本时进行处理。
  • 如果让你来做,怎么让Git来调用我的编译服务?
    这是GitHub提供的服务。GitHub会启动一个docker容器来进行构建操作。
  • 编译时的环境部署信息怎么输入?
    使用GitHub的secret,将TOKENRSA的私钥上传。在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页面。
屏幕截图 2021-02-24 194455.jpg
选择一个合适的配置文件,将其加入你的项目中,即完成了持续集成的配置工作。

配置文件的样例

这是一个配置文件的样例。使用该配置文件,可以用于gradle构建的Spring Boot项目。在项目进行更新时,自动生成Spring Bootjar文件,并发布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美元一年的价格,让很多同学望而却步。殊不知,你的学校邮箱,可以让你获得免费的正版全家桶。带上邮箱,来吧!

你需要准备的东西:

  1. 一个.edu的教育邮箱
  2. 不错的网络连接

首先,我们当然要先拥有一个教育邮箱。以浙江工业大学的邮箱为例,打开邮箱(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,点击下一步设置项目名称、路径、包名等即可创建完成
img
img
img
img

添加依赖

前往mvnrepository.com下载适用于mssql的jdbc驱动用以连接数据库,将连接驱动放入lib文件夹下,右键项目选择Project Structure,进入Libraries,点击+按钮,添加Java Lib,选择lib文件夹点击确定即可
img
img
img
img

初始数据

img

插入数据

//插入数据
//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();
}

插入结果

img

更新数据

//更新数据,将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();
}

更新结果

img

删除数据

//删除数据,删除id=10的记录
try (PreparedStatement ps = conn.prepareStatement("delete from t_01 where id=?")) {
    ps.setInt(1, 10);
    ps.execute();
} catch (SQLException e) {
    e.printStackTrace();
}

删除结果

img

查询数据

//查询所有数据并输出
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();
}

查询结果

img

.Net Core的方式打开数据库课程设计

准备工作

  • Visual Studio 2019 Community
  • 安装了.NET桌面开发组件

选择.NET桌面开发组件

新建项目

打开Visual Studio点击新建项目,选择你要创建项目的类型(控制台应用、WinForm、WPF)并给项目起个名字
img
img

添加依赖

在依赖项右键点击管理NuGet程序包,搜索'sql server',选择System.Data.SqlClient并安装,中途跳出的提示框点击确定,许可证点击接受即可
img
img
img
img
img

数据库准备

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
);

初始数据

img
img

用代码连接数据库

新建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"]);
        }
    }
}

查询结果

img

更新数据

将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);
}

更新结果

img

插入数据

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);
}

插入结果

img

删除数据

删除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);
}

删除结果

img