Linux#
磁盘空间还有,但数据无法写入是什么原因?
SQL#
练习表
-- 创建部门表
CREATE TABLE departments (
department_id INT PRIMARY KEY,
department_name VARCHAR(100) NOT NULL
);
-- 插入部门数据
INSERT INTO departments VALUES
(1, 'Engineering'),
(2, 'Human Resources'),
(3, 'Marketing'),
(4, 'Finance');
-- 创建员工表
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
gender CHAR(1),
hire_date DATE,
department_id INT,
FOREIGN KEY (department_id) REFERENCES departments(department_id)
);
-- 插入员工数据
INSERT INTO employees VALUES
(101, 'Alice', 'Smith', 'F', '2015-03-01', 1),
(102, 'Bob', 'Johnson', 'M', '2017-07-15', 1),
(103, 'Carol', 'Williams', 'F', '2019-01-10', 2),
(104, 'David', 'Brown', 'M', '2018-11-23', 3),
(105, 'Eve', 'Davis', 'F', '2020-06-17', 4);
-- 创建薪资表
CREATE TABLE salaries (
salary_id INT PRIMARY KEY,
employee_id INT,
salary DECIMAL(10,2),
from_date DATE,
to_date DATE,
FOREIGN KEY (employee_id) REFERENCES employees(employee_id)
);
-- 插入薪资数据
INSERT INTO salaries VALUES
(1, 101, 80000, '2020-01-01', '2021-12-31'),
(2, 101, 85000, '2022-01-01', '9999-12-31'),
(3, 102, 75000, '2019-01-01', '9999-12-31'),
(4, 103, 60000, '2019-01-10', '9999-12-31'),
(5, 104, 72000, '2018-11-23', '9999-12-31'),
(6, 105, 68000, '2020-06-17', '9999-12-31');
-- 创建项目表
CREATE TABLE projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100),
start_date DATE,
end_date DATE
);
-- 插入项目数据
INSERT INTO projects VALUES
(1, 'Project Apollo', '2021-01-01', '2021-12-31'),
(2, 'Project Zephyr', '2022-03-01', NULL),
(3, 'Project Orion', '2020-06-01', '2021-06-30');
-- 创建员工-项目关系表(多对多)
CREATE TABLE employee_projects (
employee_id INT,
project_id INT,
role VARCHAR(50),
PRIMARY KEY (employee_id, project_id),
FOREIGN KEY (employee_id) REFERENCES employees(employee_id),
FOREIGN KEY (project_id) REFERENCES projects(project_id)
);
-- 插入员工项目关系数据
INSERT INTO employee_projects VALUES
(101, 1, 'Developer'),
(101, 2, 'Lead Developer'),
(102, 1, 'Tester'),
(103, 2, 'HR Manager'),
(104, 3, 'Marketing Lead'),
(105, 2, 'Accountant');
练习题 1:查询所有员工的姓名及其所在部门名称
- 查询每位员工的 first_name、last_name 和其对应的 department_name。
SELECT e.first_name, e.last_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
练习题 2:查询当前薪资最高的员工信息
- 找出当前薪资(to_date = ‘9999-12-31’)最高的员工的姓名和薪资。
SELECT e.first_name, e.last_name, s.salary
FROM salaries s
JOIN employees e ON s.employee_id = e.employee_id
WHERE s.to_date = '9999-12-31'
ORDER BY s.salary DESC
LIMIT 1;
练习题 3:查询每个部门的平均薪资
SELECT d.department_name, AVG(s.salary) AS average_salary
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN salaries s ON e.employee_id = s.employee_id
WHERE s.to_date = '9999-12-31'
GROUP BY d.department_name;
练习题 4:查询参与了“Project Zephyr”的员工及其角色
- 列出参与了名为“Project Zephyr”的员工的姓名和他们的角色。
SELECT e.first_name, e.last_name, ep.role
FROM employee_projects ep
JOIN employees e ON ep.employee_id = e.employee_id
JOIN projects p ON ep.project_id = p.project_id
WHERE p.project_name = 'Project Zephyr';
练习题 5:查询至少参与了两个项目的员工
SELECT e.first_name, e.last_name, COUNT(ep.project_id) AS project_count
FROM employee_projects ep
JOIN employees e ON ep.employee_id = e.employee_id
GROUP BY e.employee_id
HAVING COUNT(ep.project_id) >= 2;
练习题 6:查询“Engineering”部门的所有员工当前薪资
- 显示 Engineering 部门所有员工的姓名及其当前薪资。
SELECT e.first_name, e.last_name, s.salary
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN salaries s ON e.employee_id = s.employee_id
WHERE d.department_name = 'Engineering'
AND s.to_date = '9999-12-31';
练习题 7:查询所有项目的负责人(Lead Developer 或 Manager)
- 列出所有项目的名称、员工姓名及他们担任的领导角色(如包含“Lead”或“Manager”的角色)。
SELECT p.project_name, e.first_name, e.last_name, ep.role
FROM employee_projects ep
JOIN employees e ON ep.employee_id = e.employee_id
JOIN projects p ON ep.project_id = p.project_id
WHERE ep.role LIKE '%Lead%' OR ep.role LIKE '%Manager%';
练习题 8:查询未参与任何项目的员工
SELECT e.first_name, e.last_name
FROM employees e
LEFT JOIN employee_projects ep ON e.employee_id = ep.employee_id
WHERE ep.project_id IS NULL;
访问一个网站背后的过程?
- 首先通过DNS解析,将域名解析为对应的IP地址,解析顺序依次为 “浏览器缓存” → “本地 hosts 文件” → “递归查询(本地 DNS)” → “迭代查询(根→顶级→权威)”;
- 获取到IP地址后,进行TCP三次握手建立连接;
- 如果请求的域名是HTTPS的,客户端和服务器还会通过 TLS 握手 建立加密通道,握手过程包括证书验证、密钥协商等;之后所有 HTTP 报文将通过 TLS 进行加密传输。
- 之后向服务端发送HTTP请求报文,请求报文中包含方法(GET/POST/PUT/DELETE等)、路径、HTTP版本号、首部字段(一组键值对,如 Host、User-Agent、Accept 等 )、请求体(可选,例如在 POST 请求中携带 JSON 数据);
- 服务端收到后,返回HTTP响应报文,其中包含版本号、状态码、短语、首部字段(Content-Type、Server等)、空行、数据实体;
DNS解析流程?
- 将域名解析为对应的IP地址,解析顺序依次为 “浏览器缓存” → “本地 hosts 文件” → “递归查询(本地 DNS)” → “迭代查询(根→顶级→权威)
- DNS相关的测试工具有 dig、nslookup
什么是递归查询?什么是迭代查询?
- 假设宿主机的 DNS 服务器指向了 223.5.5.5(阿里云DNS),本机向这个 DNS 服务器发起的查询就是递归查询;
- 而这个DNS服务器(223.5.5.5),如果没有命中缓存,它会向根服务器、顶级服务器、权威服务器发起的查询,就叫迭代查询。
迭代查询的细节简述?
- 访问 www.baidu.com 时,如果本地 DNS 没有缓存,它会向根服务器查询,根服务器返回 .com 的 DNS 地址;
- 接着去问 .com 顶级域服务器,它告诉你 baidu.com 的权威服务器地址;
- 最后向权威服务器请求 www.baidu.com 的 IP,获取成功后返回给客户端。这个过程就是迭代查询。
TCP三次握手的过程?
- 第一次握手(SYN):客户端向服务器发送一个 SYN。此时,客户端进入 SYN_SENT 状态;
- 第二次握手(SYN-ACK):服务器收到客户端的 SYN 报文后,会发送一个 SYN-ACK(同步确认)报文段作为响应。此时,服务器进入 SYN_RCVD 状态;
- 第三次握手(ACK):客户端收到服务器的 SYN-ACK 报文后,发送一个 ACK(确认)报文段作为最终确认。此时,客户端进入 ESTABLISHED 状态。服务器收到客户端的 ACK 后也进入 ESTABLISHED 状态,连接正式建立。
TCP和UDP的区别?
- 连接与可靠性:TCP是面向连接的,会通过三次握手、流量和拥塞控制、重传等机制使传输更可靠 ;而UDP仅提供最基本的数据传输能力,只管发送,不管对方是否收到。
- 传输速度:UDP要高于TCP。
- 首部结构:
- TCP首部最小为20字节,最大为60字节(其中包括源端口、目标端口、序列号、确认号、6个标志位、效验和、滑动窗口、紧急指针、数据实体等)
- UDP首部为8个字节(其中包括:源端口、目标端口、长度、效验和、数据实体)
- 通信方式:TCP 仅支持一对一通信(因为只能和一台主机建立三次握手);UDP支持一对一、一对多的方式通信。
- 应用场景:TCP 适合下载文件等对数据传输可靠性要求高的场景;而 UDP 更多的用于在线视频、语音等对数据实时传输效率要求高的场景,像 DNS、DHCP 协议是基于 UDP 的。
OSI七层模型指的是哪几层?分别有什么协议?
常见服务的端口号
- 未特别说明的,均为TCP,但有些服务,既支持UDP,又支持TCP
Port | Service |
---|
20 | ftp-data |
21 | ftp |
22 | ssh |
23 | telnet |
25 | smtp |
53/udp | dns |
67/udp | dhcp |
68/udp | dhcp-client |
69/udp | tftp |
80 | http |
110 | pop3 |
119 | nntp |
123/udp | ntp |
443 | https |
465 | smtps |
514/udp | syslog |
873 | rsync |
995 | pop3s |
1521 | oracle |
2049 | nfs |
3306 | mysql |
3389 | rdp |
5432 | postgresql |
5672 | RabbitMQ |
6379 | redis |
8080 | tomcat |
9092 | Kafka |
9200 | elasticsearch |
11211 | memcached |
监控与告警#
如何设计一个有效的监控系统?
- 全面覆盖:监控系统应覆盖所有关键组件,包括服务器、网络设备、数据库、应用层等。
- 多维度监控:不仅要监控硬件资源(如CPU、内存、磁盘),还要监控应用性能(如请求延迟、错误率)和服务健康状态(如服务可用性)。
- 实时性:监控数据应尽可能实时更新,以便及时发现问题。
- 告警机制:设置合理的告警阈值和通知渠道(如邮件、短信、Slack等),确保问题能够被及时发现和处理。
- 可视化:通过仪表盘展示关键指标,便于快速了解系统状态。
- 历史数据分析:保留历史数据,便于分析趋势和排查问题。
如何设置合理的告警阈值?
- 基于历史数据:分析历史数据,了解系统的正常运行范围,确定合适的阈值。
- 区分紧急和非紧急告警:对于影响用户体验的关键指标(如请求失败率),设置较为严格的阈值;对于次要指标(如磁盘使用率),可以适当放宽。
- 动态调整:随着系统负载的变化,定期调整告警阈值,确保其始终处于合理范围内。
- 避免误报:设置合理的触发条件,避免频繁的误报。例如,可以通过多次采样确认异常后再触发告警。
CI/CD#
容器与虚拟机的区别?
- 容器使用宿主机的内核,而每个虚拟机都有自己独立的内核。
- 容器更加轻量,迁移和部署都比虚拟机更加方便,并且启动速度也要比虚拟机快。
- 容器的隔离性不如虚拟机,因为容器是公用宿主机的内核 + 通过内核的 namespace 实现隔离。
namespace 有哪些?
- PID namespace:隔离进程 ID(每个 namespace 中的进程看到的 PID 是独立的)
- Network namespace:隔离网络资源(如网卡、IP 地址、端口等)
- Mount namespace:隔离文件系统挂载点
- UTS namespace:隔离主机名和域名
- User namespace:隔离用户和组 ID
- IPC namespace:隔离进程间通信(如信号量、消息队列等)
Dockerfile 的指令有哪些?
- FROM 指定基础镜像。
- LABEL 给镜像打标签。
- COPY 将宿主机文件。
- COPY 和 ADD,都可以将宿主机的文件拷贝至容器,但 ADD 还可以将压缩包解压缩拷贝,以及从 URL 下载文件拷贝至镜像。
- RUN 指定镜像构建时执行的shell命令,每个 RUN 指令都会在镜像中新增一层。
- ARG 和 ENV,ARG 是 “构建时变量”,ENV 是 “运行时变量”。
- USER 指定容器运行时的用户。
- WORKDIR 为后续的RUN、COPY、ADD、CMD、ENTRYPOINT等指令设置工作目录”(构建和运行阶段均生效)
- EXPOSE 声明容器运行时 “监听的端口”(仅为元数据说明,不实际映射端口)。
- VOLUME 定义容器中的 “匿名卷”(持久化数据的目录),避免容器内的数据随容器删除而丢失。
- ONBUILD 定义 “触发器”,当当前镜像被用作其他镜像的基础镜像时,自动执行ONBUILD后的指令。
- STOPSIGNAL 指定容器停止时发送的系统信号(默认是SIGTERM),用于优雅关闭应用。
- CMD 和 ENTRYPOINT,都是指定容器启动时的命令,但 ENTRYPOINT 不可变。CMD 通常作为容器启动时的参数,可以被覆盖。
Dockerfile 中的 COPY 和 ADD 有什么区别?
- COPY 和 ADD,都可以将宿主机的文件拷贝至容器,但 ADD 还可以将压缩包解压缩拷贝,以及从 URL 下载文件拷贝至镜像。
Dockerfile 中的 CMD 和 ENTRYPOINT 有什么区别?
- CMD 和 ENTRYPOINT,都是指定容器启动时的命令,但 ENTRYPOINT 不可变。CMD 通常作为容器启动时的参数,可以被覆盖。
使用 Dockerfile 制作镜像时,如何减少镜像的大小?
- 使用轻量级镜像,如 alpine。
- 清理无用的缓存文件。
- 尽量减少 RUN 指令的使用,以减少镜像分层。
- 使用多阶段构建,比如第一阶段使用nodejs镜像构建前端代码,第二阶段仅把前端构建结果拷贝至nginx镜像。