Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

采购管理知识学习笔记

发表于 2025-04-23

项目采购管理包括从项目团队外部采购或获取所需产品、服务或成果的各个过程。

项目采购管理过程

项目采购管理过程包括:

  • 规划采购管理
  • 实施采购
  • 控制采购

规划采购管理

规划采购管理是记录采购决策、明确采购方法,及识别潜在卖方的过程。

实施采购

实施采购是获取卖方应答、选择卖方并授予合同的过程。

评标委员会

第二章 评标委员会

第七条 评标委员会依法组建,负责评标活动,向招标人推荐中标候选人或者根据招标人的授权直接确定中标人。

第八条 评标委员会由招标人负责组建。评标委员会成员名单一般应于开标前确定。评标委员会成员名单在中标结果确定前应当保密。

第九条 评标委员会由招标人或其委托的招标代理机构熟悉相关业务的代表,以及有关技术、经济等方面的专家组成,成员人数为五人以上单数,其中技术、经济等方面的专家不得少于成员总数的三分之二。

评标委员会设负责人的,评标委员会负责人由评标委员会成员推举产生或者由招标人确定。评标委员会负责人与评标委员会的其他成员有同等的表决权。

注:评标委员会负责人很重要,负责评标过程节奏的把控,成员工作的分配,因此应当由经验丰富、德高望重的老前辈负责。

评标的准备与初步评审

第三章 评标的准备与初步评审

第二十二条 投标人资格条件不符合国家有关规定和招标文件要求的,或者拒不按照要求对投标文件进行澄清、说明或者补正的,评标委员会可以否决其投标。

第二十三条 评标委员会应当审查每一投标文件是否对招标文件提出的所有实质性要求和条件作出响应。未能在实质上响应的投标,应当予以否决。

注:本阶段评标委员会将对投标人资质和合规性进行审查。

详细评审

第四章 详细评审

第二十八条 经初步评审合格的投标文件,评标委员会应当根据招标文件确定的评标标准和方法,对其技术部分和商务部分作进一步评审、比较。

注:本阶段评标委员将依据评估标准和方法对招标文件进行详细评审。评估标准可分为客观部分和主观部分。客观部分由可量化的评估标准构成,因此,对同一份招标文件,所有评标委员会成员的打分应该是一致的,评标委员会负责人可以在此处进行成员分工。

控制采购

控制采购是管理采购关系、监督合同绩效、实施必要的变更和纠偏,以及关闭合同的过程。

参考链接

  1. 信息系统项目管理师教程(第4版),by douban.
  2. 评标委员会和评标方法暂行规定,by gov.

__acrt_first_block==header错误的原因及解决方法

发表于 2025-04-17 | 更新于 2025-04-18

同样遇到以下链接所示错误:

  • __acrt_first_block == header错误的一种办法

通过单步跟踪,定位到运行函数 std::vector.push_back(Object)时,就报__acrt_first_block==header错误。最终通过给std::vector对象预留内存解决,即在调用push_back()前先调用reserve()。

参考链接

  1. __acrt_first_block == header错误的一种办法,by hansen_fu.
  2. vector 的push_back() 报错,by 爱钓鱼的歪猴.

Themida & WinLicense 2.0 - 2.4.6 脱壳的方法

发表于 2025-04-17

最近遇到一个软件,使用exeinfo pe查看,发现其使用 Themida & WinLicense 2.0 - 2.4.6 加了壳,于是想办法给该软件脱壳,记录一下过程。

PE文件格式

壳是一种专用的加密软件技术,它能够保护软件的二进制程序,避免其直接暴露在不怀好意的同行面前。要理解壳,要脱壳,首先得了解一些基本知识。

壳所保护的软件二进制程序通常采用PE文件格式,包括壳自身也是如此。因此有必要了解PE文件格式。

壳的分类

常用的壳可分为压缩壳、加密壳、虚拟机保护壳。

脱壳技术

脱壳技术的核心是寻找目标程序的 OEP(Original Entry Point,原程序入口点),然后将目标程序恢复出来。

脱壳过程

请参考[分享]Themida & WinLicense 2.0 - 2.4.6 脱壳。脱壳过程需要注意的是:

  • ODBGScript v1.82.6,ODBGScript插件版本必须不小于v1.82.6,否则 Themida & WinLicense 2.0 - 2.4.6 的脱壳脚本将运行报错。
  • 需要在 32 位系统中运行 OllyDBG,否则将报 StrongOD 插件不可用错误。

参考链接

  1. [分享]Themida & WinLicense 2.0 - 2.4.6 脱壳,by Hasic.
  2. 软件破解之脱壳八法,by 爱笑的程序狗.

Ubuntu下交叉编译ARM程序方法

发表于 2025-03-03

需要在 X64 的 Ubuntu 20.04 平台交叉编译 ARM64 架构的程序,研究一下交叉编译工具链的使用方法。

交叉编译工具链

交叉编译工具链是一个由编译器、连接器和解释器组成的综合开发工具集,交叉编译工具链主要由binutils、gcc和glibc三个部分组成。有时出于减小 libc 库大小的考虑,也可以用别的 c 库来代替 glibc,例如 uClibc、dietlibc 和 newlib。

Ubuntu ARM 交叉编译工具链安装

目前主流的 ARM 交叉编译工具链主要有三种:

  • Linaro 提供的 ARM 交叉编译工具链,可以到 Linaro 官网 Linaro Releases 去下载已经制作好的交叉编译工具链
  • GNU(提供源码,自行编译制作)
  • Codesourcery

参考连接

  1. Linux Clash 最速安装使用,by 工科生的困意.
  2. Ubuntu 交叉编译工具链安装,by 孤情剑客.
  3. 【一】ubuntu交叉编译工具链选择与安装,by UNI-少林寺武功.

DeepSeek大模型本地部署方法

发表于 2025-02-26 | 更新于 2025-03-01

DeepSeek如此火热,不禁也研究一下本地部署DeepSeek的方法。

Ollama 安装和使用

Ollama是一款启动并运行大型语言模型的工具。

安装完成后,我们直接打开命令行执行 ollama –version 判断是否安装成功。

ollama 会在我们本地服务监听 11434 端口。

在线部署大模型

我们可以直接使用 ollama run 模型名称 来下载和运行我们想要的模型。例如:

1
2
3
4
5
6
# 查看ollama版本
ollama --version
# 下载大模型
ollama pull deepseek--r1:1.5b
# 运行大模型
ollama run deepseek-r1:1.5b

离线部署大模型

1
2
3
4
5
6
7
8
# 在联网生成大模型描述文件
ollama show deepseek-r1:1.5b --modelfile > modelfile.txt

# 复制大模型和大模型描述文件到离线电脑某个文件夹,然后使用如下命令离线部署
ollama create deepseek-r1:1.5b -f modelfile.txt

# 验证是否离线部署成功
ollama list

与AI交互的客户端

ChatBox 桌面客户端

Chatbox 是一款开源的 AI 客户端,专为与各种大型语言模型进行交互而设计。它支持包括 Ollama 在内的多种主流模型的 API 接入,无论是本地部署的模型,还是其他服务提供商的模型,都能轻松连接。

连接本地 Ollama 服务

连接远程 Ollama 服务

请参考:

  • 如何将 Chatbox 连接到远程 Ollama 服务:逐步指南

Open-webui Web客户端

Open-WebUI是一个可扩展、功能丰富且用户友好的自托管人工智能平台,设计上完全离线运行。它支持各种大语言模型(LLM)执行器,如 Ollama和兼容OpenAI的APIs ,并内置了用于检索增强生成(RAG)的推理引擎,使其成为一个强大的AI部署解决方案 。

部署过程请参考:

  • llama3本地环境配置(Ollama+open-webui)
  • Open WebUI

Page Assist + Ollama - 实现本地模型联网

Chatbox 提供了联网功能,但是 DeepSeek R1 模型是没办法联网。

想要让本地模型联网我们还得靠一个浏览器插件 Page Assist。

Page Assist 是一款开源的浏览器扩展程序,旨在为本地 AI 模型(如 Ollama)提供便捷的交互界面。

RAG 打造知识库

RAG 架构介绍

RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索与生成模型的人工智能技术,旨在通过检索外部知识库中的信息来增强语言模型的生成能力。

LLM(Large Language Model,大语言模型)

Embedding(嵌入):通过一个专门的模型来把你上传的文本、表格数据等本地文件处理成机器能理解的 “数字代码”。相似的文本在这个向量空间中距离会更近。比如,“苹果”和“水果”这两个词的嵌入向量就会比较接近,而“苹果”和“汽车”就会离得很远。

Vector Database(向量数据库):用来存储上一步处理好的 “数字代码” 的数据库,它能够高效地存储和检索这些向量。当你有一个问题时,它会把问题转换成向量,然后在这个仓库里快速找到和问题最相关的向量。比如,你问“苹果是什么?”它会在这个仓库里找到和“苹果”相关的向量,比如“水果”“红色”“圆形”等。

Anything LLM

Anything LLM 是一款基于 RAG架构的本地知识库工具,能够将文档、网页等数据源与本地运行的大语言模型(LLM)相结合,构建个性化的知识库问答系统。

知识库的构建请参考:

  • 跟着DW学习大语言模型-什么是知识库,如何构建知识库
  • 从零开始构建一个基于大模型和 RAG 的知识库问答系统

大模型微调

Fine-tuning(微调):通过特定领域数据对预训练模型进行针对性优化,以提升其在特定任务上的性能。

请参考:

  • 【大模型开发 】 一文搞懂Fine-tuning(大模型微调)
  • 深入理解大语言模型微调技术

参考链接

  1. 如何拥有一个无限制、可联网、带本地知识库的私人 DeepSeek?,by ConardLi​.
  2. 如何把你的 DeePseek-R1 微调为某个领域的专家?,by ConardLi​.
  3. DeepSeek + Cherry Studio:简捷搭建个人知识库,by 苏点点.
  4. 打造DeepSeek最强外挂!一篇文章教会你搭建「个人知识库」(喂饭版),by m0_63171455.
  5. LLM context 长度分析与提升方法总结,by phynlp.
  6. What do you mean exactly with “embedding length” ?,by reddit.
  7. What is Quantization in LLM,by Nithin Devanand.
  8. 什么是大语言模型量化? 每个量化精度都代表什么?,by karminski.
  9. 大模型|“上下文长度”和“上下文窗口”不再傻傻分不清楚!,by 智识可乐.
  10. 本地部署deepseek模型保姆级教程,by lovefc.
  11. 跟着DW学习大语言模型-什么是知识库,如何构建知识库,by 羞儿.
  12. DeepSeek +Kimi 免费快速生成PPT!,by UIED用户体验交流学习.

QT_GUI插件系统构建方法研究

发表于 2025-02-04

构建一个较大桌面GUI应用程序,采用插件架构是必由之路。首先插件架构有利于多人分工协作,有利于系统功能的扩展和维护,有利于用户需求的快速响应。

插件架构

插件架构系统主要由以下三部分构成:

  • 主系统,通过插件管理器加载插件,并创建插件对象。
  • 插件管理器,用于管理插件的生命周期,并将其暴露给主系统。
  • 插件,插件本身应符合插件管理器协议,并提供符合主系统期望的对象。

QT插件示例

请参考:

  • Echo Plugin Example

参考链接

  1. 构建自己的Qt插件系统,by aliyun.
  2. 深入理解插件系统,by qq_33055735.
  3. Echo Plugin Example ,by qt.

麒麟V10设置VNC连接的方法

发表于 2025-01-17 | 更新于 2025-03-10

最近研究了一下麒麟V10 Linux设置VNC连接的方法,记录一下。

VNC Server配置

VNC Server应该能够以服务的形式开机自启动,对外以 80 端口提供VNC远程连接服务。

创建 VNC Systemd 服务

创建VNC Systemd 服务,实现麒麟 V10 开机后 VNC 服务器自启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cd /lib/systemd/system/
sudo vim vncserver@:1.service

# 以下为vnc服务配置文件内容,编辑后保存
[Unit]
Description=Remote desktop service (VNC)
After=syslog.target network.target

[Service]
Type=forking
WorkingDirectory=/home/jack
User=jack
Group=jack
PIDFile=/home/jack/.vnc/%H%i.pid

ExecStartPre=/bin/sh -c '/usr/bin/vncserver -kill %i > /dev/null 2>&1 || :'
ExecStart=/usr/bin/vncserver -autokill %i
ExecStop=/usr/bin/vncserver -kill %i
Restart=on-success
RestartSec=15

[Install]
WantedBy=multi-user.target

设置 VNC Systemd 服务开机自启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 重新加载配置文件
systemctl daemon-reload
# 设置 VNC 客户端连接密码
vncpasswd
# 启动 vnc 服务
systemctl start vncserver@:1.service
# 查看 vnc 服务状态
systemctl status vncserver@:1.service
journalctl -u vncserver@:1.service
cat /var/log/syslog | grep vnc
# 设置开机自启动
systemctl enable vncserver@:1.service
# 禁止开机自启动
systemctl disable vncserver@:1.service

修改 vncserver 配置

修改 vncserver 配置,使 :1 对应 80 端口。

1
2
3
4
5
# 查看 vncserver 的位置
which vncserver

# 查看 vncserver 软连接到哪个文件
file /usr/bin/vncserver

最终找到麒麟 V10 使用的 vncserver 为 TigerVNC,使用 Vim 修改 TigerVNC配置文件,将 5900 替换为 79。

1
2
3
4
vim /usr/bin/tigervncserver

# 逐个替换并确认
:%s/5900/79/gc

修改 sysctl.conf 配置

Linux 对于非 root 用户禁止使用 1024 以下端口,但为了避免 VNC 服务被防火墙阻挡,应将 VNC 服务端口调整到 80 。修改 sysctl.conf 文件配置达到此目的。

1
2
3
4
5
6
#临时生效
sysctl net.ipv4.ip_unprivileged_port_start=0

#永久生效
echo "net.ipv4.ip_unprivileged_port_start=0" >> /etc/sysctl.conf
sysctl -p

启动 VNC 服务

1
2
3
4
5
6
7
# 设置 VNC 客户端连接密码
vncpasswd

# 启动 VNC 服务
vncserver -kill :1
vncserver :1
vncserver -list

连接 VNC 服务

使用各种 VNC 客户端,填入相关配置连接即可。连接 VNC 服务器遇到一些问题,记录如下。

vnc viewer 连接黑屏问题

基于 Systemd 设置 VNC 服务开机自启动成功,但是使用 RealVNC Viewer 客户端连接成功后显示黑屏,目前没有解决。但是使用 vncserver 直接创建 vnc 服务,然后使用 RealVNC Viewer 客户端连接成功后没有出现该问题。

中文输入法问题

打开终端,输入如下命令:

1
2
fcitx-autostart
fcitx restart

输入正确用户名和密码仍旧跳回登录界面

原因是主目录下的 .Xauthority 文件拥有者变成了 root ,导致用户登录时无法获取该文件,解决方法如下:

1
sudo chown user:user .Xauthority

麒麟 V10 自带的 Remmina 无法连接 VNC 服务器的问题

麒麟 V10 自带的 Remmina 能够连接 VNC 服务器的默认的 5901 端口,但是无法连接 VNC 服务器的 80 端口。该问题暂未解决。

建议使用 RealVNC Viewer 客户端,麒麟 v10 自带的 Remmina 客户端连接 80 端口有问题,暂时无法解决。

参考链接

  1. Linux 查看端口占用情况,by runoob.
  2. 普通用户无法监听 80,443等低于1024端口解决办法,lenglingx.
  3. Ubuntu Kylin 输入正确的账号密码,登陆后又跳回到登录界面 解决方案,by YoungHonker.
  4. Systemd 入门教程:命令篇,by 阮一峰.
  5. Systemd 定时器教程,by 阮一峰.
  6. Systemd 入门教程:实战篇,by 阮一峰.
  7. Node 应用的 Systemd 启动,by 阮一峰.
  8. Kylin V10 环境下配置VNC远程桌面访问,by 丷月亮是指路牌.

SQL判断字段值是否连续与是否从1开始

发表于 2025-01-14 | 更新于 2025-01-17

最近遇到一个运维问题,编写SQL语句判断某个字段是否从1开始,是否是连续的,记录一下问题解决过程。

目标数据表结构

目标数据表 Department 结构如下:

departId personId
7523 7523001
7523 7523002
7523 7523003
7524 7524001
7524 7524002
7524 7524003

判断字段是否从1开始

1
2
select * from Department a 
where a.personId in ( select * from ( select min(personId) as personId from Department b group by b.departId )) and mod(personId, 1000) !=1

判断字段是否连续

1
2
3
4
5
6
7
select * from 
( select b.personId,
( select max(personId)
from Department a
where a.personId < b.personId and a.departId = b.departId ) as prePersonId
from Department b )
where personId - 1 > prePersonId

参考连接

  1. 在论坛中出现的比较难的sql问题:7(子查询 判断某个字段的值是否连续),by LongRui888.

深入高可用系统原理与设计读书笔记

发表于 2024-12-25 | 更新于 2025-02-09

第一章:云原生技术概论

云计算的演进变革

物理机时代

虚拟化技术成熟

从虚拟化技术的发展中,我们看到业务的工作负载由物理机转向虚拟机,资源有了初级的隔离,并且分配/利用更加合理。

云计算技术成熟

对于 XaaS 的一路演进,可以简单归纳为:

  • 有了 IaaS(Infrastructure as a Service,基础设施即服务),客户不用关注物理机器,只需关注基础架构及应用程序。
  • 有了 PaaS(Platform as a Service,平台即服务),客户不用关注基础架构,只需关注应用程序。
  • 有了 FaaS(Function as a Service,功能即服务),客户只需关注功能和数据。

容器技术的兴起

Docker 创新性地提出了“镜像”(image)的概念,实现了一种新型的应用打包、分发和运行机制,开发人员能够在几秒钟内完成应用程序的部署、运行,无需再担心环境不一致的问题。

云原生出现的背景

部分软件已经变成水电煤一样的社会经济中的基础设施。

过去二十年间,云的底层基础设施和平台越来越强大,软件架构的发展也逐渐和云匹配:

  • 通过不可变基础设施(镜像)解决本地和远程一致性问题;
  • 通过服务网格(ServiceMesh)将非业务逻辑从应用程序中剥离;
  • 通过声明式 API 描述应用程序的状态,而不用管中间的处理过程;
  • 通过 DevOps 方法论以及一系列工具来提升研发/运维效率…

应用程序中的非业务逻辑不断被剥离,并下沉到云/基础设施层,代码越来越轻量。由此,工程师的开发工作回归本质(软件开发的本质是解决业务需求,各类“高深”、“复杂”的技术难题是业务需求的副产物,并不是软件开发的主题)。

云原生的定义

CNCF 云原生的定义 v1.0 版本

云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式 API。

这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。

云原生的目标

云原生的几个关键目标:

  • 可用(Available):通过各种机制来实现应用的高可用,以保证服务提供的连续性。
  • 规模(Scale):要求云原生服务能够适应不同的规模(包括但不限于用户规模/部署规模/请求量),并能够在部署时动态分配资源,以便在不同的规模之间快速和平滑的伸缩。
  • 敏捷(Agility):快速响应市场需求。
  • 成本(Cost):充分有效的利用资源。

云原生代表技术

云原生的代表技术:容器技术、微服务、服务网格、不可变基础设施、声明式设计以及 DevOps。

容器技术

Google Cloud 对容器的定义: 容器是轻量级应用代码包,它还包含依赖项,例如编程语言运行时的特定版本和运行软件服务所需的库。

容器技术发展历程:

chroot 阶段:隔离文件系统

chroot 被认为是最早的容器技术之一,它能将进程的根目录重定向到某个新目录,复现某些特定环境,同时也将进程的文件读写权限限制在该目录内。

LXC 阶段:封装系统

至 2013 年,Linux 虚拟化技术已基本成型,通过 cgroups、namespace 以及安全防护机制,大体上解决了容器核心技术“运行环境隔离”,但此时仍需等待另一项关键技术的出现,才能迎来容器技术的全面繁荣。

Docker 阶段:封装应用

Docker 的核心创新“容器镜像(container image)”:

  • 容器镜像打包了整个容器运行依赖的环境,以避免依赖运行容器的服务器的操作系统,从而实现“build once,run anywhere”。
  • 容器镜像一但构建完成,就变成只读状态,成为不可变基础设施的一份子。
  • 与操作系统发行版无关,核心解决的是容器进程对操作系统包含的库、工具、配置的依赖(注意,容器镜像无法解决容器进程对内核特性的特殊依赖)。

OCI 阶段:容器标准化

OCI 组织着力解决容器的构建、分发和运行标准问题,其宗旨是制定并维护 OCI Specifications(容器镜像格式和容器运行时的标准规范)。

OCI 有了三个主要的标准:

  • OCI Runtime Spec(容器运行时标准):定义了运行一个容器,如何管理容器的状态和生命周期,如何使用操作系统的底层特性(namespace、cgroups、pivot_root 等)。
  • OCI Image Spec(容器镜像标准):定义了镜像的格式,配置(包括应用程序的参数、依赖的元数据格式、环境信息等),简单来说就是对镜像文件格式的描述。
  • OCI Distribution Spec(镜像分发标准):定义了镜像上传和下载的网络交互过程的规范。

容器编排阶段:封装集群

以 Kubernetes 为代表的容器编排框架,就是把大型软件系统运行所依赖的集群环境也进行了虚拟化,让集群得以实现跨数据中心的绿色部署,并能够根据实际情况自动扩缩。

就像用 docker run 可以启动单个程序一样,现在用 kubectl apply -f 就能部署和运行一个分布式集群应用,而无需关心是在私有云还是公有云或者具体哪家云厂商上。

云原生阶段:百花齐放

微服务

Netflix 云架构师 Adrian Cockcroft定义:微服务架构是一种面向服务的架构,由松耦合的具有有限上下文的元素组成。

Adrian Cockcroft 的观点中有两个核心概念:

  • 松耦合(Loosely Coupled):意味着每个服务可以独立的更新,更新一个服务无需要求改变其他服务。
  • 限界上下文(Bounded Contexts):意味着每个服务要有明确的边界性,你可以只关注自身软件的发布,而无需考虑谁在依赖你的发布版本。微服务和它的消费者严格通过 API 进行交互,不共享数据结构、数据库等。基于契约的微服务规范要求服务接口是稳定的,而且向下兼容。

微服务架构中,有一些必须解决的问题,如负载均衡、伸缩扩容、传输通讯等等,这些问题可以说只要是分布式架构的系统就无法完全避免。

Kubernetes 在基础设施层面,解决分布式系统问题的方案:

  • Kubernetes 用 CoreDNS 替代 Spring Cloud 服务发现组件 Eureka。
  • Kubernetes 用 Service/Load Balancer 替代 Spring Cloud 中的负载均衡组件 Ribbon。
  • Kubernetes 用 ConfigMap 替代 Spring Cloud 的配置中心 Config。
  • Kubernetes 用 Ingress 代替 Spring Cloud 的网关组件 Zuul。

当虚拟化的基础设施从单个服务的容器扩展至由多个容器构成的服务集群,并开始解决分布式的问题。

服务网格

服务网格(Service Mesh)的概念最早由 Buoyant 公司的创始人 William Morgan 于 2016 年提出。

服务网格的定义

服务网格(ServiceMesh)是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,服务网格保证请求在这些拓扑中可靠地穿梭。在实际应用当中,服务网格通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但对应用程序透明。

—— by William Morgan

业内绝大部分服务网格产品通常由“数据平面”和“控制平面”两部分组成:

  • 数据平面(Data plane):通常采用轻量级的网络代理(如 Envoy)作为 Sidecar,网络代理负责协调和控制服务之间的通信和流量处理,解决微服务之间服务熔断、负载均衡、安全通讯等问题。
  • 控制平面(Control plane):包含多个控制组件,它们负责配置和管理 Sidecar ,并提供服务发现(Discovery)、配置管理(Configuration)、安全控制(Certificates)等功能。

不可变基础设施

可变的基础设施会导致以下问题:

  • 重大故障时,难以快速重新构建服务:持续过多的手动操作并且缺乏记录,会导致很难由标准初始化的服务器来重新构建起等效的服务;
  • 不一致风险:类似于程序变量因并发修改而带来的状态不一致风险。服务运行过程中,频繁的修改基础设施配置,同样会引入中间状态,导致出现无法预知的问题。

不可变基础设施思想的核心是,任何基础设施的运行实例一旦创建之后就变成只读状态。如需修改或升级,应该先修改基础设施的配置模版(例如 yaml、Dockerfile 配置),之后再使用新的运行实例替换。

声明式设计

声明式设计是指一种软件设计理念:“我们描述一个事物的目标状态,而非达成目标状态的流程”。至于目标状态如何达成,则由相应的工具在其内部实现。

和声明式设计相对的是命令式设计(又叫过程式设计),两者的区别是:

  • 命令式设计:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现;
  • 声明式设计:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

DevOps

DevOps 是个很复杂的概念,几句话很难解释清楚。我们延用之前的惯例,如果要理解一个复杂的概念,就先去了解它出现的背景,以及发展的历史。

DevOps 核心本质是解决软件开发生命周期中的管理问题,我们先从一种名为“瀑布模型”的项目管理方法说起。

虽然敏捷开发提升了开发效率,但它的范围仅限于开发和测试环节,并没有覆盖到部署环节。显然,运维部门并没有收益。相反的,甚至可以说“敏捷”加重了运维的负担。运维追求的目标是稳定,频繁变更是破坏稳定的根源。

DevOps 运动始于 2007 年左右,当时技术社区对开发与运维之间分开工作的方式以及由此引发的冲突感到担忧。

DevOps 的定义

DevOps(Development 和 Operations 的合成词)是一种重视“软件开发人员(Dev)”和“IT 运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。

通过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。

—— from 维基百科

DevOps 的成功实践离不开工具上的支持,这其中包括最重要的自动化 CI/CD 流水线,通过自动化的方式打通软件从构建、测试到部署发布的整个流程。还有实时监控、事件管理、配置管理、协作平台等一系列工具/系统的配合。

第二章: 构建“足够快”的网络服务

HTTPS 请求优化分析

一个完整、未复用连接的 HTTPS 请求需要经过以下 5 个阶段:DNS 域名解析、TCP 握手、SSL 握手、服务器处理、内容传输。

RTT(Round-Trip Time)一个网络数据包从起点到目的地然后再回到起点所花费的时长。

TTFB(Time To First Byte,首字节时间)指从浏览器请求页面到接收来自服务器发送的信息的第一个字节的时间。

网络拥塞控制原理与实践

网络吞吐量与 RTT、带宽密切相关:

  • RTT 越低,数据传输的延迟越低;
  • 带宽越高,网络在单位时间内传输的数据越多。

一些术语:

  • RTprop (Round-Trip propagation time,两个节点之间最小时延):该值取决于物理距离,距离越长,时延越大。
  • BtlBw(Bottleneck Bandwidth,瓶颈带宽):如果把网络链路想象成水管,RTprop 是水管的长度,BtlBw 则是水管最窄处的直径。
  • BDP(Bandwidth-Delay Product,带宽、时延的乘积):它代表了网络上能够同时容纳的数据量(水管中有多少流动的水)。 BDP 的计算公式是:BDP = 带宽 × 时延。其中,带宽以比特每秒(bps)为单位,时延以秒为单位。
  • inflight 数据:指已经发送出去但尚未收到确认的数据包。这些数据包仍在网络中传输,等待接收方的处理或确认。

QUIC 设计原理与实践

QUIC(Quick UDP Internet Connection,快速 UDP 网络连接)是一种基于 UDP 封装的安全可靠传输协议,旨在取代 TCP,成为新一代互联网的主流传输协议。

第三章:深入 Linux 内核网络技术

Linux 内核网络框架

从 Linux 内核 2.4 版本开始,内核引入了一套通用的过滤框架 —— Netfilter,允许外界对网络数据包在内核协议栈流转过程中进行代码干预。

Linux 系统中的各类网络功能,如地址转换、封包处理、地址伪装、协议连接跟踪、数据包过滤、透明代理、带宽限速和访问控制等,都是基于 Netfilter 提供的代码拦截机制实现的。可以说,Netfilter 是整个 Linux 网络系统最重要(没有之一)的基石。

内核旁路技术

对于网络密集型应用,内核态与用户态的频繁切换、复杂的网络协议栈处理,常常使 Linux 内核成为性能瓶颈。

在人们想办法提升 Linux 内核性能的同时,另外一批人抱着它不行就绕开它想法,提出了一种“内核旁路“(Kernel bypass)思想的技术方案。其中,DPDK 和 XDP 是主机内“内核旁路”思想的代表技术,RDMA 是主机之间“内核旁路”思想的代表技术。

Linux 网络虚拟化

虚拟网络通信技术

基于物理设备实现的网络拓扑结构是相对固定的,很难跟得上云原生时代下系统频繁变动的频率。例如,容器的动态扩缩容、集群跨数据中心迁移等等,都要求网络拓扑随时做出调整。正因为如此,软件定义网络(Software Defined Networking,SDN)的需求变得前所未有的迫切。

SDN 思想的核心是,在现有的物理网络之上新增一层虚拟网络,将控制平面(操作系统和各类网络控制软件等)和数据平面(底层通信的物理设备,以及各类通信协议等)解耦,将网络服务从底层硬件设备中抽象出来,由代码直接编程控制。

SDN 网络模型如图 3-16 所示:

  • 位于下层的网络称 Underlay 网络,它是由路由器、交换机等硬件设备互联而成的物理网络,负责网络之间的数据传输;
  • 位于上层的网络称 Overlay 网络,它是采用多种网络虚拟化技术在 Underlay 网络之上创建的虚拟网络。

第四章:负载均衡与代理技术

负载均衡与代理

从处理请求的网络层次角度看,所有的负载均衡器可归纳为两大类:四层负载均衡和七层负载均衡,分别对应 OSI 模型的第四层(传输层)和第七层(应用层)。

“四层负载均衡”并非严格限定于 OSI 模型的第四层(传输层)。实际上,它的工作模式涉及多个网络层次:

  • 第二层(数据链路层):通过修改帧头中的 MAC 地址,将请求从一个物理网络节点转发到另一个节点。这种方式通常用于同一广播域内的转发,例如交换机或桥接设备完成的二层转发操作。
  • 第三层(网络层):通过修改 IP 地址,实现跨子网的请求路由和转发。这是路由器的核心功能,通过修改数据包的源或目的 IP 地址,实现子网之间的通信和流量转发。
  • 第四层(传输层):通过修改 TCP/UDP 端口号或连接的目标地址,利用网络地址转换(NAT)技术隐藏内部网络结构,将请求从一个入口转发至多个后端服务。

七层负载均衡器工作在应用层,这意味着负载均衡器必须与后端服务器建立新的传输层连接,并将客户端的请求代理到后端服务器。

七层负载均衡器工作在应用层,因此能够检测和处理请求内容,包括:

  • 安全层 TLS 协议:TLS 的归属层次在网络领域存在争议,本文为便于讨论假设属于应用层。
  • 物理 HTTP 协议:涵盖 HTTP/1、HTTP/2、HTTP/3 等版本。
  • 逻辑 HTTP 协议:包括请求的头部、主体和尾部数据。
  • 消息协议:如 gRPC、RESTful API、SOAP、AMQP、MQTT 等。

负载均衡部署拓扑

四层负载均衡技术

四层负载均衡器的典型代表是 LVS(Linux Virtual Server,Linux 虚拟服务器),由中国程序员章文嵩于 1998 年发起和开发。

第五章:数据一致性与分布式事务

数据一致性

引入事务的目的,是为了保证数据的“一致性”(Consistency)。

这里的一致性指的是,对数据有特定的预期状态,任何数据更改操作必须满足这些状态约束(或者恒等条件)。

想要达成数据的一致性,需要 3 个方面的努力:

  • 原子性(Atomic):“原子”通常指不可分解为更小粒度的东西。这里原子性描述的是,客户端发起一个请求(请求包含多个操作)在异常情况下的行为。例如,只完成了一部分写入操作,系统出现故障了(进程崩溃、网络中断、节点宕机)。把多个操作纳入到一个原子事务,万一出现上述故障导致无法完成最终提交时,则中止事务,丢弃或者撤销那些局部修改。
  • 隔离性(Isolation): 同时运行的事务不应互相干扰。例如,当一个事务执行多次写入操作时,其他事务应仅能观察到该事务的最终完成结果,而非中间状态。隔离性旨在防止多个事务交叉操作导致的数据不一致问题。
  • 持久性(Durability):事务处理完成后,对数据的修改应当是永久性的,即使系统发生故障也不会丢失。在单节点数据库中,持久性意味着数据已写入存储设备(如硬盘或 SSD)。而在分布式数据库中,持久性要求数据成功复制到多个节点。为确保持久性,数据库必须在完成数据复制后,才能确认事务已成功提交。

这也就是常说的事务的“ACID 特性”。值得一提的是,对于一致性而言,更多的是指数据在应用层的外部表现。应用程序借助数据库提供的原子性、隔离性和持久性,来实现一致性目标。也就是说,A、I、D 是手段,C(Consistency)是 3 者协作的目标,弄到一块完全是为了读起来更顺口。

一致性与可用性的权衡

CAP 是一致性与可用性权衡的理论,是理解分布式系统的起点。

1999 年,美国工程院院士 Eric A.Brewer 发表了论文《Harvest, Yield, and Scalable Tolerant Systems》[1] ,首次提出了“CAP 原理”(CAP Principle)。不过,彼时的 CAP 仅是一种猜想,尚未得到理论上的证明。2002 年,麻省理工学院的 Seth Gilbert 和 Nancy Lynch 用严谨的数学推理证明了 CAP 的正确性。此后,CAP 从原理转变成定理,在分布式系统领域产生了深远的影响。

CAP 定理描述的是一个分布式系统中,涉及共享数据问题时,以下三个特性最多只能满足两个。

  • 一致性(Consistency):意味着数据在任何时刻、任何节点上看到的都是符合预期的。为了确保定义的严谨性,学术研究中通常将一致性定义为“强一致性”(Strong Consistency),也称为“线性一致性”(Linearizability)。
  • 可用性(Availability):可用性代表系统持续提供服务的能力。要理解可用性,首先需要了解两个密切相关的指标:可靠性(Reliability)和可维护性(Serviceability)。可靠性通过平均无故障时间(MTBF, Mean Time Between Failure)度量;可维护性通过平均修复时间(MTTR, Mean Time To Repair)度量。可用性衡量系统在总时间内可以正常使用的时间比例,计算公式为 A = MTBF / (MTBF + MTTR)。因此,可用性是通过可靠性和可维护性计算得出的比例。例如,99.9999% 的可用性意味着平均每年系统故障修复时间为 32 秒。
  • 分区容错性(Partition tolerance):当部分节点由于网络故障或通信中断而无法相互联系,形成“网络分区”时,系统仍能够继续正确地提供服务。

由于 CAP 定理已有严格的证明,我们不再探讨为何 CAP 不可兼得,直接分析舍弃 C、A、P 时所带来的不同影响。

  • 放弃分区容忍性(CA without P):意味着我们将假设节点之间通信永远可靠。永远可靠的通信在分布式系统中必定不成立,只要依赖网络共享数据,分区现象就不可避免地存在。如果没有 P(分区容错性),也就谈不上是真正的分布式系统。
  • 放弃可用性(CP without A):意味着我们将假设一旦网络发生分区,节点之间的信息同步时间可以无限制延长。在现实中,选择放弃可用性系统(又称为 CP 系统)适用于对数据一致性有严格要求的场景,如金融系统、库存管理系统等。这些应用场景中,数据的一致性和准确性通常比系统的可用性更为重要。
  • 放弃一致性(AP without C):意味着在网络分区发生时,节点之间的数据可能会出现不一致。这种情况下,系统会优先保证可用性,而不是一致性。选择放弃一致性系统(又称 AP 系统)已经成为设计分布式系统的主流选择,因为分区容错性(P)是分布式网络的固有属性,不可避免;而可用性(A)通常是建设分布式系统的目标。如果系统在节点数量增加时可用性降低,则其分布式设计的价值也会受到质疑。除了像银行和证券这样的金融交易服务,这些场景中数据一致性至关重要,通常需要保证一致性而可能接受部分中断之外,大多数系统更倾向于在节点增多时保持高可用性,而不是牺牲可用性以维持一致性。

对于分布式系统而言,必须实现分区容错性(P)。因此,CAP 定理实际上要求在可用性(A)和一致性(C)之间选择,即在 AP 和 CP 之间权衡取舍。

工程师们又重新给一致性下了定义,将 CAP、ACID 中讨论的一致性(C)称为“强一致性”,而把牺牲了 C 的 AP 系统但又要尽可能获得正确结果的行为称为追求“弱一致性”。不过,若只是单纯地谈论“弱一致性”,通常意味着不保证一致性。在弱一致性中,工程师们进一步总结出了一种较强的特例,称为“最终一致性”(Eventual Consistency),它由 eBay 的系统架构师 Dan Pritchett 在 BASE 理论中提出。

分布式事务模型

可靠事件队列

2008 年,eBay 架构师 Dan Pritchett 在 ACM 发表了论文《Base: An Acid Alternative》[1]。这篇论文中,作者基于实践总结出一种独立于 ACID 之外,通过消息队列和幂等机制来达成数据一致性的技术手段,并提出了“最终一致性”的概念。

BASE 是“Basically Available”、“Soft State”和“Eventually Consistent”的缩写,其核心理念为:

  • 基本可用(Basically Available):系统保证在大多数情况下能够提供服务,即使某些节点出现故障时,仍尽可能保持可用性。这意味着系统优先保障可用性,而非一致性。
  • 柔性状态(Soft state):系统状态允许在一段时间内处于不一致状态。与 ACID 强一致性的要求不同,BASE 允许系统在更新过程处于“柔性”状态,即数据在某些节点上可以暂时不一致。
  • 最终一致性(Eventually consistent):最终一致性强调,即使在网络分区或系统故障的情况下,在经过足够的时间和多次数据同步操作后,所有节点的数据一定会一致。

服务幂等性设计

幂等性是一个数学概念,后来被引入计算机科学中,用来描述某个操作可以安全地重试,即多次执行的结果与单次执行的结果完全一致。

第六章:分布式共识及算法

世界上只有一种共识算法,就是 Paxos,其他所有的共识算法都是 Paxos 的退化版本。

什么是共识

尽管“共识”和“一致”在汉语中含义相近,但在计算机领域,这两个术语具有截然不同的含义:

  • 共识(Consensus):指所有节点就某项操作(如选主、原子事务提交、日志复制、分布式锁管理等)达成一致的实现过程。
  • 一致性(Consistency):描述多个节点的数据是否保持一致,关注数据最终达到稳定状态的结果。

在分布式系统中,节点故障不可避免,但部分节点故障不应该影响系统整体状态。通过增加节点数量,依据“少数服从多数”原则,只要多数节点(至少 N/2+1 )达成一致,其状态即可代表整个系统。这种依赖多数节点实现容错的机制称为 Quorum 机制。

Quorum 机制

  • 3 节点集群:Quorum 为 2,允许 1 个节点故障;
  • 4 节点集群:Quorum 为 3,允许 1 个节点故障;
  • 5 节点集群:Quorum 为 3,允许 2 个节点故障。

    节点个数为 N 的集群,能容忍 (N−1)/2 个节点故障。你注意到了么?3 节点和 4 节点集群的故障容忍性一致。所以,一般情况下,以容错为目的的分布式系统没必要使用 4 个节点。

根据上述的讨论,基于 Quorum 的机制,在不可靠的环境下,通过“少数服从多数”协商机制达成一致的决策,从而对外表现为一致的运行结果。这一过程被称为节点间的“协商共识”。

一旦解决共识问题,便可提供一套屏蔽内部复杂性的抽象机制,为应用层提供一致性保证,满足多种需求,例如:

  • 主节点选举
  • 原子事务提交
  • 分布式锁管理
  • 日志复制

Paxos 算法

Paxos 算法由 Leslie Lamport[1] 于 1990 年提出,是一种基于消息传递、具备高度容错特性的共识算法,是当今分布式系统中最重要的理论基础,几乎就是“共识系统”的代名词。
Lamport 在分布式系统理论方面有非常多的成就,比如 Lamport 时钟、拜占庭将军问题、Paxos 算法等等。除了计算机领域之外,其他领域的无数科研工作者也要成天和 Lamport 开发的一套软件打交道,目前科研行业应用最广泛的论文排版系统 —— LaTeX (名字中的 La 就是指 Lamport)

在 Paxos 算法中,节点分为三种角色:

  • 提议者(Proposer):提议者是启动共识过程的节点,它提出一个值,请求其他节点对这个值进行投票,提出值的行为称为发起“提案”(Proposal),提案包含提案编号 (Proposal ID) 和提议的值 (Value)。注意的是,Paxos 算法是一个典型的为“操作转移”模型设计的算法,为简化表述,本文把提案类比成“变量赋值”操作,但你应该理解它是“操作日志”相似的概念,后面介绍的 Raft 算法中,直接就把“提案”称做“日志”了。
  • 决策者(Acceptor):接受或拒绝提议者发起的提案,如果一个提案被超过半数的决策者接受,意味着提案被“批准”(accepted)。提案一旦被批准,意味着在所有节点中达到共识,便不可改变、永久生效。
  • 记录者(Learner):记录者不发起提案,也不参与决策提案,它们学习、记录被批准的提案。

Raft 算法

Paxos 算法中“节点众生平等”,每个节点都可以发起提案。多个提议者并行发起提案,是活锁、以及其他异常问题的源头。那如何不破坏 Paxos 的“节点众生平等”基本原则,又能在提案节点中实现主次之分,限制节点不受控的提案权利?

Raft 算法的设计机制是明确领导者、增加选举机制交接提案权利。Raft 算法中,节点分为以下三种角色:

  • 领导者(Leader):负责处理所有客户端请求,将请求转换为“日志”复制到其他节点,不断地向所有节点广播心跳消息:“你们的领导还在,不要发起新的选举”。
  • 跟随者(Follower):接收、处理领导者的消息,并向领导者反馈日志的写入情况。当领导者心跳超时时,他会主动站起来,推荐自己成为候选人。
  • 候选人(Candidate):候选人属于过渡角色,他向所有的节点广播投票消息,如果他赢得多数选票,那么他将晋升为领导者;

第七章:容器编排技术

为了减轻管理容器的心智负担,实现容器调度、扩展、故障恢复等自动化机制,容器编排系统应运而生。过去十年间,Kubernetes 发展成为容器编排系统的事实标准,成为大数据分析、机器学习以及在线服务等领域广泛认可的最佳技术底座。

Kubernetes 在解决复杂问题的同时,本身也演变成当今最复杂的软件系统之一。目前,包括官方文档在内的大多数 Kubernetes 资料都聚焦于“怎么做”,鲜有解释“为什么这么做”。自 2015 年起,Google 陆续发布了《Borg, Omega, and Kubernetes》及《Large-scale cluster management at Google with Borg》等论文,分享了 Google 内部开发和运维 Borg、Omega 和 Kubernetes 系统的经验与教训。

容器编排系统的演进

Google 先后设计了三套不同的容器管理系统,Borg、Omega 和 Kubernetes,并向外界分享了大量的设计思想、论文和源码,直接促进了容器技术的普及和发展,对整个行业的技术演进产生了深远的影响。

从 Borg 到 Kubernetes,容器技术的价值早已超越了单纯提升资源利用率。更深远的影响在于,系统开发和运维的理念从“以机器为中心”转变为“以应用为中心”:

  • 容器封装了应用程序的运行环境,屏蔽了操作系统和硬件的细节,使得业务开发者不再需要关注底层实现;
  • 基础设施团队可以更灵活地引入新硬件或升级操作系统,最大限度减少对线上应用和开发者的影响;
  • 每个设计良好的容器通常代表一个应用,因此管理容器就等于管理应用,而非管理机器;
  • 将收集的性能指标(如 CPU 使用率、内存用量、QPS 等)与应用程序而非物理机器关联,显著提高了应用监控的精确度和可观测性。

容器技术的原理与演进

文件系统隔离

chroot 是“change root”的缩写,它允许管理员将进程的根目录锁定在特定位置,从而限制进程对文件系统的访问范围。chroot 的隔离功能对安全性至关重要。

1
2
3
4
$ mkdir -p new-root/{bin,lib64,root}
$ cp /bin/bash new-root/bin
$ cp /lib64/{ld-linux-x86-64.so*,libc.so*,libdl.so.2,libreadline.so*,libtinfo.so*} new-root/lib64
$ sudo chroot new-root

除了 /bin 之外,如果我们将程序依赖的 /etc、/proc 等目录一同打包进去,实际上就得到了一个 rootfs 文件。因为 rootfs 包含的不仅是应用,还有整个操作系统的文件和目录,这意味着应用及其所有依赖都被封装在一起,这正是容器被广泛宣传为一致性解决方案的由来。

资源全方位隔离

从 Linux 内核 2.6.19 起,逐步引入了 UTS、IPC、PID、Network 和 User 等命名空间功能。到了 3.8 版本,Linux 实现了容器所需的六项最基本的资源隔离机制。Linux 4.6 版本起,新增了 Cgroup 和 Time 命名空间。

资源全方位限制

进程的资源隔离已经完成,如果再对使用资源进行额度限制,就能对进程的运行环境实现“进乎完美”的隔离。这就要用 Linux 内核的第二项技术 —— Linux Control Cgroup(Linux 控制组群,简称 cgroups)。

cgroups 是 Linux 内核用于隔离、分配并限制进程组使用资源配额的机制。例如,它可以控制进程的 CPU 占用时间、内存大小、磁盘 I/O 速度等。

设计容器协作的方式

登录到 Linux 机器后,执行 pstree -g 命令可以查看当前系统中的进程树状结构。

1
2
3
4
$ pstree -g
|-rsyslogd(1089)-+-{in:imklog}(1089)
| |-{in:imuxsock) S 1(1089)
| `-{rs:main Q:Reg}(1089)

超亲密容器组 Pod

在 Kubernetes 中,与“进程组”对应的设计概念是 Pod。Pod 是一组紧密关联的容器集合,它们共享 IPC、Network 和 UTS 等命名空间,是 Kubernetes 管理的最基本单位。

容器镜像的原理与应用

所谓的“容器镜像”,其实就是一个“特殊的压缩包”,它将应用及其依赖(包括操作系统中的库和配置)打包在一起,形成一个自包含的环境。

1
2
3
$ docker build 镜像名称

$ docker run 镜像名称

Docker 镜像利用联合文件系统的分层设计。整个镜像从下往上由 6 个层组成:

  • 最底层是基础镜像 Debian Stretch,相当于“base rootfs”,所有容器可以共享这一层;
  • 接下来的 3 层是通过 Dockerfile 中的 ADD、ENV、CMD 等指令生成的只读层;
  • Init Layer 位于只读层和可写层之间,存放可能会被修改的文件,如 /etc/hosts、/etc/resolv.conf 等。这些文件原本属于 Debian 镜像,但容器启动时,用户往往会写入一些指定的配置,因此 Docker 为其单独创建了这一层;
  • 最上层是通过 CoW(写时复制)技术创建的可写层(Read/Write Layer)。

通过镜像分层设计,以 Docker 镜像为核心,不同公司和团队的开发人员可以紧密协作。每个人不仅可以发布基础镜像,还可以基于他人的基础镜像构建和发布自己的软件。镜像的增量操作使得拉取和推送内容也是增量的,这远比操作虚拟机动辄数 GB 的 ISO 镜像要更敏捷。

容器持久化存储设计

Docker 通过将宿主机目录挂载到容器内部的方式,实现数据持久化存储。如图 7-21 所示,目前它支持三种挂载方式:bind mount、volume 和 tmpfs mount。

容器间通信的原理

资源弹性伸缩

第八章:服务网格技术

什么是服务网格

服务网格是一个处理服务通讯的专门的基础设施层。它的职责是在由云原生应用组成服务的复杂拓扑结构下进行可靠的请求传送。在实践中,它是一组和应用服务部署在一起的轻量级的网络代理,对应用服务透明。

—— What’s a service mesh?And why do I need one?,William Morgan

服务网格是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,服务网格保证请求在这些拓扑中可靠地穿梭。在实际应用当中,服务网格通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但对应用程序透明。

服务间通信的演化

实施微服务架构时,需要解决问题(服务注册、服务发现、负载均衡、熔断、限流等)的本质是保证服务间请求的可靠传递。站在业务的角度来看,无论上述逻辑设计的多么复杂,都不会影响业务请求本身的业务语义与业务内容发生任何变化,实施微服务架构的技术挑战和业务逻辑没有任何关系。

回顾前面提到的 TCP/IP 协议案例,我们思考是否服务间的通信是否也能像 TCP 协议栈那样:“人们基于 HTTP 协议开发复杂的应用,无需关心底层 TCP 协议如何控制数据包”。如果能把服务间通信剥离、并下沉到微服务基础层,工程师将不再浪费时间编写基础设施层的代码,而是将充沛的精力聚焦在业务逻辑上。

第一代服务网格由一系列独立运行的代理型服务(Sidecar)构成,但并没有思考如何系统化管理这些代理服务。为了提供统一的运维入口,服务网格继续演化出了集中式的控制面板(Control Plane)。

典型的第二代服务网格以 Google、IBM 和 Lyft 联合开发的 Istio 为代表。根据 Istio 的总体架构(见图 8-8),第二代服务网格由两大核心组成部分:一系列与微服务共同部署的边车代理(称为数据平面),以及用于管理这些代理的控制器(称为控制平面)。控制器向代理下发路由、熔断策略、服务发现等策略信息,代理根据这些策略处理服务间的请求。

数据平面的设计

控制平面的设计

第九章:系统可观测性

什么是可观测性

遥测数据(telemetry data)是指采样和汇总有关软件系统性能和行为的数据,这些数据(接口的响应时间、请求错误率、服务资源消耗等)用于监控和了解系统的当前状态。

实际上,软件领域的观测与上述火箭发射系统相似,都是通过全面收集系统运行数据(遥测数据),以了解内部状态。因此,观测本质上是一种数据收集与分析的科学,旨在帮助解决复杂系统中的故障检测、性能优化和风险预警等问题。

可观测性与传统监控

可观测性与监控的关系

监控告诉我们系统哪些部分是正常的,可观测性告诉我们系统为什么不正常了。

——by《高性能 MySQL》作者 Baron Schwartz

遥测数据的分类与处理

业界将系统输出的数据总结为三种独立的类型,它们的含义与区别如下:

  • 指标(metric):量化系统性能和状态的“数据点”,每个数据点包含度量对象(如接口请求数)、度量值(如 100 次/秒)和发生的时间,多个时间上连续的数据点便可以分析系统性能的趋势和变化规律。指标是发现问题的起点,例如你半夜收到一条告警:“12 点 22 分,接口请求成功率下降到 10%”,这表明系统出现了问题。接着,你挣扎起床,分析链路追踪和日志数据,找到问题的根本原因并进行修复。

  • 日志(log):系统运行过程中,记录离散事件的文本数据。每条日志详细描述了事件操作对象、操作结果、操作时间等信息。例如下面的日志示例,包含了时间、日志级别(ERROR)以及事件描述。日志为问题诊断提供了精准的上下文信息,与指标形成互补。当系统故障时,“指标”告诉你应用程序出现了问题,“日志”则解释了问题出现的原因。

  • 链路追踪(trace):记录请求在多个服务之间的“调用链路”(Trace),以“追踪树”(Trace Tree)的形式呈现请求的“调用”(span)、耗时分布等信息。

指标的处理

为便于理解和使用不同类型的指标,Prometheus 定义了四种指标类型:

  • 计数器(Counter):一种只增不减的指标类型,用于记录特定事件的发生次数。常用于统计请求次数、任务完成数量、错误发生次数等。在监控 Web 服务器时,可以使用 Counter 来记录 HTTP 请求的总数,通过观察这个指标的增长趋势,能了解系统的负载情况;
  • 仪表盘(Gauge):一种可以任意变化的指标,用于表示某个时刻的瞬时值。常用于监控系统的当前状态,如内存使用量、CPU 利用率、当前在线用户数等;
  • 直方图(Histogram):用于统计数据在不同区间的分布情况。它会将数据划分到多个预定义的桶(bucket)中,记录每个桶内数据的数量。常用于分析请求延迟、响应时间、数据大小等分布情况。比如监控服务响应时间时,Histogram 可以将响应时间划分到不同的桶中,如 0-100ms、100-200ms 等,通过观察各个桶中的数据分布,能快速定位响应时间的集中区间和异常情况;
  • 摘要(Summary):和直方图类似,摘要也是用于统计数据的分布情况,但与直方图不同的是,Summary 不能提供数据在各个具体区间的详细分布情况,更侧重于单一实例(例如单个服务实例)的数据进行计算。

日志的索引与存储

处理日志本来是件稀松平常的事情,但随着数据规模的增长,量变引发质变,高吞吐写入(GB/s)、低成本海量存储(PB 级别)以及亿级数据的实时检索(1 秒内),已成为软件工程领域最具挑战性的难题之一。

分布式链路追踪

2010 年 4 月,Google 工程师发表了论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》[1],论文总结了他们治理分布式系统的经验,并详细介绍了 Google 内部分布式链路追踪系统 Dapper 的架构设计和实现方法。

如今的链路追踪系统大多以 Dapper 为原型设计,因为它们也统一继承了 Dapper 的核心概念:

  • 追踪(trace):Trace 表示一次完整的分布式请求生命周期,它是一个全局上下文,包含了整个调用链所有经过的服务节点和调用路径。例如,用户发起一个请求,从前端服务到后端数据库的多次跨服务调用构成一个 Trace。
  • 跨度(Span):Span 是 Trace 中的一个基本单元,表示一次具体的操作或调用。一个 Trace 由多个 Span 组成,按时间和因果关系连接在一起。Span 内有描述操作的名称 span name、记录操作的开始时间和持续时间、Trace ID、当前 Span ID、父 Span ID(构建调用层级关系)等信息。

性能剖析

可观测性领域的性能剖析(Profiling)的目标是分析运行中的应用,生成详细的性能数据(Profiles),帮助工程师全面了解应用的运行行为和资源使用情况,从而识别代码中的性能瓶颈。

性能数据通常以火焰图或堆栈图的形式呈现,分析这些数据是从“是什么”到“为什么”过程中的关键环节。

火焰图分析说明

  • 纵轴:表示函数调用的堆栈深度(或层级)。纵向越高表示调用链越深,底部通常是程序的入口函数(如 main 函数),上层是被下层函数调用的函数。
  • 横轴:表示函数在特定时间段内所占用的 CPU 时间或内存空间,条形的宽度越大,表示该函数消耗的时间或资源越多。

分析火焰图的关键是观察横向条形的宽度,宽度越大,函数占用的时间越多。如果某个函数的条形图出现“平顶”现象,表示该函数的执行时间过长,可能成为性能瓶颈。

核心转储

核心转储(Core dump)中的 “core” 代表程序的关键运行状态,“dump” 的意思是导出。

第十章 应用封装与交付

“以应用为中心”的设计思想

正是因为以应用为中心,整个云原生技术体系无限强调基础设施更好地服务于应用,以更高效的方式为应用提供基础设施能力,而不是反其道行之。而相应的,Kubernetes 也好、Docker 也好、Istio 也好,这些在云原生生态中起到了关键作用的开源项目,就是让这种思想落地的技术手段。

声明式管理的本质

分析 Kubernetes 的工作原理可以发现,无论是 kube-scheduler 调度 Pod,还是 Deployment 管理 Pod 部署,亦或是 HPA 执行弹性伸缩,它们的整体设计都遵循“控制器模式”。

从“封装配置”到“应用模型”

参考链接

  1. 深入高可用系统原理与设计,by thebyte.

VC6之MFC遗留程序升级方法

发表于 2024-12-24 | 更新于 2025-01-05

最近拿到一个VC6 MFC编写的遗留程序源代码,需要将其升级到最新的Windows系统,记录一下升级过程。

MFC程序执行流程

程序是从Main(WinMain)开始和结束。 但是在Main(WinMain)开始之前会对全局的变量或者对象进行初始化。

故在MFC中,通过全局类(theApp) 的初始化, 进入theApp的构造函数,根据类的规则,初始子类的构造函数,必须先要运行父类的构造函数。 在这过程把包含WinMain的文件连接了进来。 这样就隐藏了WinMain函数。

在WinMain函数中调用了AfxWinMain函数,在AfxWinMain函数中通过调用InitInstance函数完成了窗口的设计 窗口的注册 窗口的创建 窗口的显示和更新。然后在AfxWinMain中调用了Run方法,完成消息循环。

MFC扩展库BCGControlBar

遗留程序依赖 MFC 扩展库BCGControlBar,因此需要安装和配置 BCGControlBar,否则编译链接出错。例如:

BCGCBProInc.h : No such s file or dictionary. 解决方案:安装BGB界面库

安装

BCGControlBar是一个基于MFC的扩展库,您可以通过完全的用户化操作构成一些类似于Microsoft Office 2000/XP/2003和Microsoft Visual Studio.NET的应用程序(用户工具栏、菜单、键盘等等)。BCGControlBar库包含了大约150多个经过精心设计,测试和具有完备文档的MFC扩展类。这些都可以很容易的应用于您的应用程序,节省大量的开发和调试时间。

配置

在编译之前,要先设置其附加包含目录和附加库目录。

vc6.0在其tools>>options>>directoris下进行设置。在show directories for选择include files,添加BCGControlBar安装目录下的BCGCBPro目录。

然后再选择show directories for下的Library files并添加BCGControlBar安装目录下的bin目录。

MFC扩展库MapX

遗留程序依赖 MFC 扩展库 MapX,因此需要安装和配置 MapX,否则编运行出错。

MapX简介

MapX控件是Map Info公司向用户提供的具有强大地图分析功能的ActiveX控件,适用于大多数面向对象语言,可以无缝嵌入到各个领域的应用系统当中去。

参考链接

  1. 深入跟踪MFC程序的执行流程,by wenluderen
  2. 【C++】MFC 程序入口和执行流程,by 不溯流光.
  3. MFC专业扩展库BCGControlBar:使用方法入门,by 不如温暖过生活.
  4. MFC BCGControlBar 安装及使用,by VIPWTL.
  5. MFC框架——单文档/多文档/对话框/多顶级文档的区别,by 郝红升.
  6. MFC建立空文档失败分析,by 咖啡熊猫.
  7. 关于VC++6.0重新安装falsed时出现的DCOM user account解决方案,by miracle_dan.
  8. VC下MapX开发环境建立,by 风一兮.
  9. 用VC++对MapX进行二次开发总结,by 风一兮.
12…52下一页

Jack Huang

519 日志
66 标签
© 2025 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse