记录一些实习期间学到的知识,本文有不少 AI 生成的内容,只经过有限的人工检查。

一些外部链接如果不能访问,可以尝试去 archive.org 查看。

Docker

参考: https://www.bilibili.com/video/BV14s4y1i7Vf

简介、安装

问题:在实际生产中,应用程序的部署、环境配置过程往往非常复杂。

Docker 是一个用于 build, run, share 应用程序的平台。

Docker 和 虚拟机的区别:

93-1.png

基本原理和概念:

93-2.png

镜像就是一个模板,容器就是这个模板的一个实例(可以有一个,也可以有多个)。

镜像如何分享给别人呢?Docker 仓库是用来存储 docker 镜像的地方。最流行的仓库就是 Dockerhub,我们可以在这个平台下载各种镜像,也可以将自己的镜像上传,这样就可以实现镜像的共享和复用。

下载安装不提。

启动 docker, 在命令行中键入 docker version,可以看到输出了 Client 和 Server 下面的一堆信息。

docker client 和 docker daemon 之间通过 Socket 或者 RESTful API 进行通信。

Docker Daemon 是服务端的守护进程,负责管理 Docker 的各种资源。

容器化、Dockerfile

容器化(containerization),就是将应用程序打包成容器,然后在容器中运行应用程序的过程。

  • 创建一个 Dockerfile
  • 使用 Dockerfile 构建镜像
  • 使用镜像创建和运行容器

Dockerfile 是一个文本文件,用来告诉 Docker 如何构建镜像。一个简单的示例:

93-3.png

实战

mkdir HelloDocker, touch file index.js :

1
console.log("hello Docker")

we can run this code with nodejs.

如果我们想要在另一个环境中运行这个应用程序,需要:

  1. 安装操作系统
  2. 安装 JavaScript 运行环境(NodeJS)
  3. 复制应用程序、依赖包、配置文件
  4. 执行启动命令运行程序

有了 Docker 之后,就可以把这些步骤写到 Dockerfile 中。 剩下的工作交给 Docker 自动完成。

我们需要先指定一个基础镜像,镜像是按层次结构来构建的,每一层都是基于上一层。

touch file HelloDocker/Dockerfile :

1
FROM node:14-alpine

14 表示 nodejs 的版本,alpine 表示这个镜像是基于 alpine 这个 Linux 发行版(一个非常轻量级的发行版)来构建的。

源路径:相对于 Dockerfile 文件的路径。
目标路径:相对于镜像的路径。

1
COPY index.js /index.js

然后我们需要在镜像中运行应用程序:

1
CMD [ "node", "/index.js" ]

完成。完整的 Dockerfile 如下:

1
2
3
FROM node:14-alpine
COPY index.js /index.js
CMD [ "node", "/index.js" ]

在 terminal 中键入命令:

1
docker build -t hello-docker .

如果一切顺利,镜像就构建完成了。

可以使用 docker image ls 查看所有的镜像。

运行:

1
docker run hello-docker

一些 docker 命令:

93-4.png

ElasticSearch

os : windows11

前提:需要 Java 环境。

下载:https://www.elastic.co/cn/downloads/elasticsearch

解压,配置环境变量。

启动:在 bin 目录下 .\elasticsearch.bat ,若配置了环境变量,也可以直接 elasticsearch

访问 https://localhost:9200/

若忘记密码,则需要重新配置密码:

1
2
3
4
5
6
7
8
9
PS D:\elasticsearch\elasticsearch-8.15.3\bin> elasticsearch-reset-password -u elastic
warning: ignoring JAVA_HOME=C:\Program Files\Microsoft\jdk-17.0.9.8-hotspot\; using bundled JDK
This tool will reset the password of the [elastic] user to an autogenerated value.
The password will be printed in the console.
Please confirm that you would like to continue [y/N]y


Password for the [elastic] user successfully reset.
New value: (here will be your new password)

注意,修改密码时,ElasticSearch 需要处于启动状态。

拓展资料:

MinIO

MinIO 是根据 GNU Affero 通用公共许可证 v3.0 发布的对象存储系统。它与 Amazon S3 云存储服务 API 兼容。它能够处理非结构化数据,例如照片、视频、日志文件、备份和容器映像,支持的最大对象大小为 50TB。

下载:https://min.io/download?license=agpl&platform=windows#/windows

注意不要下载到企业版本。

拓展资料:

什么是对象存储

对象存储也称为基于对象的存储,是一种计算机数据存储架构,旨在处理大量非结构化数据。与其他架构不同,它将数据指定为不同的单元,并捆绑元数据和唯一标识符,用于查找和访问每个数据单元。

这些单元(或对象)可以存储在本地,但通常存储在云端,以便于从任何地方轻松访问数据。由于对象存储具有横向扩容能力,它的可伸缩性几乎没有限制,并且存储大量数据的成本也低于块存储等其他存储方法。

如今的许多数据都是非结构化的,无法很好地存储在传统数据库中,包括电子邮件、媒体和音频文件、网页、传感器数据和其他类型的数字内容。因此,寻找高效且经济实惠的方法来存储和管理这类数据成为了一个难点。越来越多的企业将对象存储作为存储静态内容、数据架构和备份的首选方法。

对象存储的定义 :

对象存储是用于存储非结构化数据的数据存储架构,它将数据划分为单元(对象),并存储在结构扁平的数据环境中。每个对象都包含数据以及应用可用于轻松访问和检索对象的元数据和唯一标识符。

对象存储的工作原理

在对象存储中,一个文件的数据块被共同保存为一个对象,并连同其相关的元数据和自定义标识符放入被称为存储池的扁平数据环境中。

当您需要访问数据时,对象存储系统将使用唯一标识符和元数据来查找所需的对象,例如图片或音频文件。您还可以自定义元数据,从而添加可用于其他用途(例如用于数据分析的检索)的上下文。

您可以使用 RESTful API、HTTP 和 HTTPS 查询对象元数据,以查找和访问对象。由于对象存储在全局存储池中,因此您可以快速轻松地找到所需的确切数据。此外,扁平环境使您能够快速扩容,即便是 PB 或 EB 级负载也毫不费力。存储池可以分布在多个对象存储设备和地理位置中,因此规模不受限制。随着数据量的增长,您只需向池中添加更多存储设备即可。

对象存储的优势(例如弹性和可伸缩性)使其成为管理云基础设施中非结构化数据的理想选择。那么,什么是云对象存储呢?顾名思义,也就是作为按需云服务提供的基于对象的存储。事实上,对于大多数大型云服务提供商而言,云对象存储是主要的存储格式。

对象存储、文件存储、块存储

多年来,随着互联网的发展以及数据源和数据类型的不断丰富,全世界的数据存储需求也在不断演变。传统的文件存储和块存储并不能很好地处理产生的海量数据,尤其是天生不适合结构化数据存储方法的非结构化数据。

那么,对象存储与文件存储和块存储有什么区别呢?

文件存储

文件存储将数据存储和整理到文件夹中,类似于您保存在办公室的纸质文件系统中的物理文件。如果您需要某个文件中的信息,则需要知道哪个房间、文件柜、抽屉和文件夹包含该特定文件。文件存储使用相同的分层存储结构,文件被命名,以元数据标记,然后放入文件夹中。

要找到某个数据,您需要知道查找该数据的正确路径。随着时间的推移,文件变得越来越多,搜索和检索数据文件可能会变得非常耗时。虽然可伸缩性较为有限,但通过这种方法可以很容易地存储几乎任何类型的少量数据,并且可供多个用户同时访问。

块存储

块存储在文件存储的基础上提升了性能,它将文件拆分为多个单独的块并单独存储。块存储系统会为每个原始数据块分配一个唯一标识符,当您需要访问完整的文件时,系统将使用唯一标识符将数据块重组为完整的文件。块存储不需要单一的数据路径,因此您可以将其存储在最方便的位置,并且在需要时仍然能够快速检索。

块存储非常适合需要处理大量事务型数据或使用任务关键型应用的组织,可提供低延迟和一致的性能。但是,块存储费用高昂,不提供元数据功能,并且需要操作系统才能访问块。

对象存储

如前所述,对象存储将文件存储为扁平数据环境(即存储池)中的独立对象,对象包含全部数据、唯一标识符和详细元数据(元数据包含关于数据、权限、政策和其他应急情况的信息)。对象存储最适合非结构化数据的静态存储,您只写入一次数据,但可能需要多次读取。

虽然对象存储不需要目录、文件夹和其他复杂的分层结构,但却不适合用来存储不断变化的动态数据,因为修改对象需要重写整个对象。根据您的速度和性能要求,在某些情况下文件存储和块存储可能仍然适合您的需求。

对象存储有哪些优势?

  • 高可伸缩性
    • 您可以轻松横向扩容对象存储的扁平架构,而不会受到文件存储或块存储那样的限制。对象存储基本没有大小限制,因此只需添加新设备,即可将数据扩大到 EB 级。
  • 降低复杂性
    • 对象存储没有文件夹或目录,也就不具有层次结构系统的大多数复杂性。由于没有复杂的树或分区,检索文件变得十分轻松,因为您不需要知道确切位置。
  • 易于搜索
    • 元数据是对象的一部分,使您无需借助单独的应用即可轻松搜索和导航。它也更加灵活,并且可以深度自定义。您可以使用各种属性和信息来为对象添加标记,例如用量、费用以及自动删除、保留和分层的政策。
  • 弹性
    • 对象存储可以自动复制数据并存储在多个设备和多个地理位置。这有助于防范服务中断和数据丢失,并可为灾难恢复策略提供支持。
  • 成本效益
    • 对象存储在设计时考虑了成本因素,与基于文件和块的系统相比,能够以更低的价格存储大量数据。使用对象存储时,您只为需要的容量付费,即使存储大量数据,也能很好地控制费用。

RabbitMQ

os: win11

https://developer.aliyun.com/article/1134566

启用管理插件:

1
rabbitmq-plugins enable rabbitmq_management

访问管理界面: http://localhost:15672 .

默认的用户名和密码是:

  • 用户名: guest
  • 密码: guest

默认的 RabbitMQ 服务端口通常是 5672(用于 AMQP 协议),而 15672 是管理界面的端口。

拓展资料:

git 操作

一些平时自娱自乐时不会遇到的 git 操作,直接将情况描述清楚给 AI,AI 的回复一般没什么问题。VSCode 的 git 插件很好,一定要装。

同步远程分支到本地分支

你当前在本地分支 dev-test 上,而你想同步远程分支 dev-annotation-data 的更新到本地。有以下步骤可以完成这个需求:

步骤 1:拉取远程分支到本地

首先,你需要确保远程分支 dev-annotation-data 是最新的。使用以下命令:

1
git fetch origin dev-annotation-data

结果:

  • git fetch 会从远程仓库获取 dev-annotation-data 的最新代码,但不会影响你的工作目录。
  • 现在,远程分支 origin/dev-annotation-data 的最新代码已在本地缓存。

步骤 2:合并远程分支的内容到当前分支

如果你想直接将 dev-annotation-data 的更新合并到当前分支 dev-test,运行以下命令:

1
git merge origin/dev-annotation-data

效果:

  • dev-annotation-data 的更新会合并到当前分支 dev-test
  • 如果有冲突,Git 会提示你解决冲突,解决后需要 git addgit commit

步骤 3:检查结果

查看合并后的状态:

1
git status

确认是否还有未解决的冲突或未提交的文件。

其他场景:如果你需要单独创建本地分支来跟踪远程分支

如果你想在本地独立处理 dev-annotation-data 的更新,可以新建一个分支:

1
git checkout -b dev-annotation-data origin/dev-annotation-data

这会创建一个新的本地分支 dev-annotation-data,并与远程分支保持同步。随后你可以选择是否将它的更改合并到 dev-test

其他场景:如果你的工作有未提交的改动

你当前有修改的文件(cache.py)和未跟踪的文件(requirements_copy.txt)。在执行合并前,建议保存这些改动:

  1. 提交改动

    1
    2
    git add .
    git commit -m "Save local changes"
  2. 或者临时保存改动(Stash)

    1
    git stash

    合并完成后再恢复:

    1
    git stash pop

按照这些步骤操作后,应该可以成功同步 dev-annotation-data 的更新到你的本地环境。

git merge origin/dev-annotation-data
error: Your local changes to the following files would be overwritten by merge:
该路径已和谐/cache.py
Please commit your changes or stash them before you merge.
Aborting

出现这个错误是因为你本地对 cache.py 的修改未提交,而远程分支 origin/dev-annotation-data 对这个文件也有改动。Git 不允许直接合并,以免丢失本地未提交的更改。

以下是解决方案:

选项 1:提交本地更改然后合并

  1. 查看本地更改
    确保你的本地更改是你想要保留的:

    1
    git diff 该路径已和谐/cache.py
  2. 提交本地更改
    如果更改是需要的,可以先提交:

    1
    2
    git add 该路径已和谐/cache.py
    git commit -m "Save local changes to cache.py"
  3. 执行合并
    现在可以合并远程分支了:

    1
    git merge origin/dev-annotation-data

    如果有冲突,Git 会提示你解决冲突。解决冲突后,需要重新提交:

    1
    2
    git add <冲突文件>
    git commit -m "Resolve merge conflict"

选项 2:暂存本地更改(Stash)然后合并

如果你不想立即提交本地更改,可以使用 git stash 临时保存更改:

  1. 保存更改到 Stash

    1
    git stash

    这样本地的未提交更改会被暂时存储。

  2. 合并远程分支

    1
    git merge origin/dev-annotation-data
  3. 恢复 Stash
    合并完成后,可以恢复之前保存的更改:

    1
    git stash pop

    如果有冲突,Git 会提示你解决冲突。

选项 3:放弃本地更改

如果你不需要保留本地更改,可以直接丢弃:

  1. 放弃更改

    1
    git restore 该路径已和谐/cache.py
  2. 执行合并

    1
    git merge origin/dev-annotation-data

推荐
根据你的具体需求选择:

  • 需要保留更改:选项 1 或选项 2。
  • 不需要保留更改:选项 3。

如果不确定,可以先使用选项 2,将更改暂存后再决定如何处理。

gRPC

“模块与模块之间通过 grpc 调用”,这句话通常是什么意思

“模块与模块之间通过 gRPC 调用” 通常是指在一个系统中,不同的模块(或服务、组件)通过 gRPC(Google Remote Procedure Call) 协议进行通信。

gRPC 是 Google 开发的一个开源、高性能的 RPC 框架,全称是 gRPC Remote Procedure Call。它基于 HTTP/2 协议,使用 Protocol Buffers(简称 Protobuf) 作为数据序列化格式。简单来说,gRPC 让你可以像调用本地函数一样调用远程服务,但背后是通过网络传输完成的。

和传统的 REST API(基于 HTTP/1.1 和 JSON)相比,gRPC 有更高的性能更小的消息体积更强的类型安全性,非常适合微服务架构或需要高效通信的场景。

Protocol Buffers(Protobuf),这是一个二进制序列化工具,比 JSON 或 XML 更紧凑、更快。

先定义一个 .proto 文件,里面描述服务和数据结构(类似接口定义)。比如:

1
2
3
4
5
6
7
8
9
10
11
syntax = "proto3";
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
}
message UserRequest {
int32 id = 1;
}
message UserResponse {
string name = 1;
int32 age = 2;
}

然后用 Protobuf 编译器生成代码(支持多种语言,比如 Java、Go、Python),这些代码会帮你处理序列化和反序列化。

HTTP/2

  • gRPC 用 HTTP/2 代替了 HTTP/1.1,支持多路复用(多个请求复用一个连接)、头部压缩和双向流(客户端和服务器可以同时发消息)。
  • 这比 REST 的每次请求都开新连接效率高很多。

服务端和客户端

  • 服务端实现 .proto 文件定义的服务逻辑。
  • 客户端通过生成的代码直接调用服务端的方法,就像调用本地函数一样。

四种通信模式

  • 一元 RPC:类似 REST 的请求-响应模型(客户端发请求,服务端返回结果)。
  • 服务端流式 RPC:客户端发一次请求,服务端返回一个数据流(比如实时日志)。
  • 客户端流式 RPC:客户端持续发送数据流,服务端处理后返回结果(比如上传大数据)。
  • 双向流式 RPC:客户端和服务端都可以随时发送和接收数据(比如聊天应用)。
特性 gRPC REST
协议 HTTP/2 HTTP/1.1
数据格式 Protobuf(二进制) JSON/XML(文本)
性能 高(体积小、速度快) 相对低
类型安全 强(有严格定义) 弱(依赖文档)
双向通信 支持 不支持
学习曲线 稍陡(需要学 Protobuf) 简单(JSON 直观)

fastapi

同步异步

普通函数(同步):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from typing import Union
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}

这里的 read_rootread_item 是普通的同步函数。它们会被 FastAPI 按同步方式运行,直到函数执行完毕后返回响应。

异步函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from typing import Union
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_root():
return {"Hello": "World"}


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}

这里的 read_rootread_item 是异步函数(async def),它们允许在函数内部使用 await 调用其他异步操作,例如异步数据库查询或 HTTP 请求。

对于简单的逻辑(如返回静态数据),同步和异步函数性能没有明显差别。IO 密集型任务(如数据库操作或 HTTP 请求),异步函数通常表现更优。

FastAPI 内部使用了 Starlette 框架,而 Starlette 支持异步和同步函数。

  • 同步函数:会在后台被包装为异步协程来执行。
  • 异步函数:原样执行,完全异步处理。

parse_obj、from_orm

注意使用 Pydantic 的 parse_obj 和 from_orm 方法简化代码。

下面这段代码是否可以使用 orm 技术简化代码?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 和 AI 的聊天记录中,原字段已经替换为 aaa
def count_non_empty_fields(self, db: Session) -> CompletenessOut:
"""
统计 CompletenessOut 对应字段非空值的个数
"""
# 使用 SQLAlchemy 的 func 来统计每个字段的非空值
result = db.query(
func.count(self.model.id_photo).label("aaa"),
func.count(self.model.cn_name).label("aaa"),
func.count(self.model.en_name).label("aaa"),
func.count(self.model.birthdate).label("aaa"),
func.count(self.model.email).label("email"),
func.count(self.model.tel).label("aaa"),
func.count(self.model.id_type).label("aaa"),
func.count(self.model.id_number).label("aaa"),
func.count(self.model.gender).label("aaa"),
func.count(self.model.nationality).label("aaa"),
func.count(self.model.political_status).label("aaa"),
func.count(self.model.technical_title).label("aaa"),
func.count(self.model.personal_profile).label("aaa"),
func.count(self.model.subject).label("aaa")
).filter(
# 排除删除标志的条件(如果有逻辑删除字段)
# self.model.is_deleted == False
).first()

# 将结果转换为 CompletenessOut 格式
return CompletenessOut(
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
email=result.email,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
aaa=result.aaa,
)

改进版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def count_non_empty_fields(self, db: Session) -> CompletenessOut:
"""
统计 CompletenessOut 对应字段非空值的个数,排除已软删除的数据
"""
# 获取所有需要统计的字段名(排除特殊字段)
fields = [
field for field in self.model.__table__.columns.keys()
if field in CompletenessOut.__fields__
]

# 动态构建统计表达式
count_expressions = [
func.count(getattr(self.model, field)).label(field) for field in fields
]

# 执行查询,过滤掉已软删除的记录
result = (
db.query(*count_expressions)
.filter(self.model.deleted_at.is_(None)) # 过滤掉 deleted_at 非空的数据
.first()
)

# 使用字典解构和 **kwargs 动态生成 CompletenessOut 实例
return CompletenessOut(**{field: getattr(result, field) for field in fields})

优势:

  1. 可维护性高:如果 CompletenessOut 的字段发生变化,只需更新 Pydantic 模型,无需修改查询逻辑。
  2. 代码简洁:减少显式列出字段的重复代码,逻辑更易读。
  3. 通用性强:如果需要统计其他模型的字段,几乎可以直接复用这段代码。

心得体会

虽然面试的时候问了一大堆,实际工作的时候并不会用到很复杂的知识。

才来的几天,暴露出的问题是沟通成本高:

  • 睡眠不足,导致神志不清。这个主要是由于熬夜的习惯,不过后来改正了一些,就还好。
  • 思维方式偏向文字而不是说话。
    • 具体例子:我在飞书上打了一段不算长的文字,向产品经理详细描述了我的疑问,但是产品经理更倾向于直接跑到我工位上口头沟通。

环境问题:

  • 小众的 linux 发行版可能会遇到大问题,例如 debian 稳定版的依赖比较过时,很多东西装起来很麻烦。如果要用 linux ,或许 Ubuntu 这种流行的东西要好一点。
  • 即便是 Windows,也有一些装不上的包。但是我负责编写的代码并不会用到这个包,因此暂时就没管。
  • 得出结论:也许应该转向 Ubuntu 或者依赖比较新的 linux 发行版。
  • 安装不同的 Python 版本,Windows 比较方便,debian 则比较麻烦(我还自己编译过,但是出了一堆问题),Ubuntu 则非常方便。
  • 安装 requirements 遇到冲突会很麻烦,有时需要到官网上查看,实在不行就换个相近的版本吧。
  • 写 Python 代码,PyCharm 比 VSCode 更方便,具体体现在代码规范,和一些辅助功能上。但是实际上我还是用了 VSCode,换过来有点适应成本。

Apifox 非常方便,和 postman 相比集成了更全面一体的功能,而且易于管理。右上角可以选择环境。

用飞书沟通远比国内流行的微信强,会议还有 AI 摘要功能,可用性挺高的。

AI 辅助编程:

  • 关于 AI 使用,带教老师的原话是,这个项目,除了那些带着 token, password 之类的你不能丢上去,其他无所谓。
  • 现在免费 AI 中也许 deepseek 更好, 当时我使用的是 chatgpt 对话,同时在 VSCode 中有补全代码的 AI 插件(真的非常方便)。

一段我在实习中写的提示词:

- - - - - prompt start - - - - -

现在有一个新的需求,统计 ?? 的人数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@router.get('/你猜/是什么/链接', response_model=一个class)
def ???1(
*,
request: Request,
db: Session = Depends(deps.get_db),
) -> ???1Out:
auth_action.auth_path(request)
return ???_action.get_???1(db)

def get_???1(db: Session) -> ???1Out:
return ???_crud.get_???1(db)

class CRUD???Static(CRUDBase[???, BaseModel, BaseModel]):
# ... 省略
def get_???1(self, db: Session) -> ???1Out:
pass

相关结构:

1
# 和谐

通过表 ?? 统计双一流的时候,对于每一个 ??(即表中数据的主键),通过 ?? 的外键统计 ?? 中,是否含有 ?? ,若有则计数加一。

?? 表的结构:

1
# 河蟹

?? 表的结构,?? 是你需要关注的字段:

1
# 和谐

?? 的信息在列表 ???1_?? 中,你可以直接使用,格式为:

1
2
3
4
5
6
???1_?? = [
"你知道",
"的",
# ...
"太多了"
]

根据这段提示词,AI 一次就给出了可以正确运行的代码。

- - - - - prompt end - - - - -

基本上,只要描述地足够清晰,对于简单任务,AI 的可用性还是不错的。