wenmo8 发布的文章

1 介绍

RocketMQ是一款分布式消息中间件,其核心技术涉及多个核心概念,包括主题、队列、消息、生产者、消费者、消费者分组以及订阅关系。以下是对这些核心技术的详细说明:

2 核心技术实现

2.1 主题(Topic)

1. 定义
:主题是RocketMQ中消息传输和存储的顶层容器,用于标识同一类型业务逻辑的消息。它是一个逻辑概念,并非实际的消息容器。
2. 作用

  • 数据分类隔离:RocketMQ建议将不同业务类型的数据拆分到不同的主题中,以实现隔离存储和订阅。
  • 数据身份和权限管理:RocketMQ中的消息本身是匿名无身份的,同一分类的消息使用相同的主题来做身份识别和权限管理。

3. 名称
:主题名称用于标识主题,且在集群内部全局唯一。
4. 队列列表
:作为主题的组成单元,是消息存储的实际容器,一个主题内包含一个或多个队列,消息实际存储在主题的各队列内。
4. 消息类型
:在创建主题时,可以指定主题中存放的消息类型,包括Normal(普通消息)、FIFO(顺序消息)、Delay(定时/延迟消息)、Transaction(事务消息)。

image

2.2 队列(MessageQueue)

1. 定义
:队列是RocketMQ中消息传输和存储的实际容器,也是RocketMQ消息的最小存储单元。
2. 特点

  • RocketMQ中所有主题都是由多个队列组成,以此实现队列数量的水平拆分和队列内部的流式存储。
  • 队列天然具备顺序性,即消息按照进入队列的顺序写入存储,同一队列间的消息天然存在顺序关系。
  • 消息在队列中的位置和消息之间的顺序通过位点(Offset)进行标记管理。

image

2.3 消息(Message)

1. 定义
:消息是RocketMQ中最小的数据传输单元,生产者将需要发送的数据包装成消息发送给RocketMQ的服务端。
2. 特点

  • 不可变性:消息一旦产生,其内容即不可改变
  • 持久化:RocketMQ默认会对消息进行持久化,保存到服务端的存储文件中,以保证消息的可回溯性和系统故障场景下的可恢复性。

image

2.4 生产者(Producer)

1. 定义
:生产者负责将消息发送到RocketMQ的指定主题和队列中。
2. 发送方式

  • 同步发送:有返回值,必须等待消息发送成功才会结束。
  • 异步发送:在发送消息后立即返回,同时异步接收传递过去的消息状态。
  • 单向发送:单方面发送消息,不需要等待任何回应。

image

2.5 消费者(Consumer)

1. 定义
:消费者负责从RocketMQ中订阅并消费指定主题的消息。
2. 消费过程

  • 消费者先从Broker拉取消息到客户端,然后启动消费线程消费这些消息。
  • 消费者可以使用PullMessageService线程不断从Broker拉取消息,并使用ConsumeMessageService线程进行消费。

image

2.6 消费者分组(Consumer Group)

1. 定义
:消费者分组是RocketMQ中用于管理消费者的一个概念,同一分组内的消费者共同分摊消息并进行消费。
2. 作用

  • 通过消费者分组,可以实现消息的负载均衡和容错处理。
  • RocketMQ要求同一个消费者分组下所有消费者的消费行为要保持一致。

image

2.7 订阅关系(Subscription)

1. 定义
:订阅关系是指消费者分组与主题之间的关联关系,以及消费者订阅消息时的过滤规则。
2. 作用

  • 通过订阅关系,消费者可以指定要消费的主题和消息类型。
  • RocketMQ通过订阅关系来实现消息的过滤和分发。

image

3 总结

综上所述,RocketMQ的核心技术涉及多个核心概念,这些概念相互协作,共同实现了RocketMQ的高性能、高可用性和高可扩展性。

还记得在第1章,我们利用ChatGPT生成的业务用例吗?这种业务用例生成方式其实和场景法用例设计十分相似,我们是不是也可以直接将业务用例输入ChatGPT,让它输出测试用例呢?笔者输入相关提示词让其补充以需求规格说明书中的业务用例为依据的测试用例,ChatGPT输出如图3-5所示。

图3-5  ChatGPT输出的部分基于需求规格说明书中的业务用例补充的测试用例

以下为ChatGPT输出的完整的基于需求规格说明书中的业务用例补充的测试用例。

ChatGPT
输出的完整的基于需求规格说明书中的业务用例补充的测试用例

根据业务用例UC-01,以下是一些与该用例相关的测试用例,以确保百度关键字搜索功能的正确性和完整性:

测试编号

测试目标

前置条件

测试步骤

预期结果

TC019

基本关键字搜索 - 有效输入

用户已进入百度搜索页面

1. 在搜索框中输入一个有效关键字(例如:"测试")。<br> 2. 点击搜索按钮。

显示与关键字相关的搜索结果页面。

TC020

基本关键字搜索 - 多个有效输入

用户已进入百度搜索页面

1. 在搜索框中输入多个有效关键字(例如:"软件测试方法")。<br> 2. 点击搜索按钮。

显示与多个关键字相关的搜索结果页面。

TC021

基本关键字搜索 - 无相关结果

用户已进入百度搜索页面

1. 在搜索框中输入一个关键字,但系统无法找到相关结果(例如:"未知关键字")。<br> 2. 点击搜索按钮。

显示相应的通知给用户(例如:"未找到相关结果")。

TC022

基本关键字搜索 - 取消搜索

用户已进入百度搜索页面

1. 在搜索框中输入一个有效关键字(例如:"测试")。<br> 2. 在搜索操作执行前取消搜索操作。

搜索操作被取消,用例结束。

TC023

基本关键字搜索 - 多次搜索

用户已进入百度搜索页面

1. 在搜索框中输入一个有效关键字(例如:"测试")。<br> 2. 点击搜索按钮。<br> 3. 重复步骤1和2多次。

搜索结果正确显示,搜索历史被保留。

TC024

基本关键字搜索 - 边界值测试

用户已进入百度搜索页面

1. 在搜索框中输入一个极小的关键字(例如:"a")。<br> 2. 点击搜索按钮。

显示与关键字相关的搜索结果页面。

TC025

基本关键字搜索 - 边界值测试

用户已进入百度搜索页面

1. 在搜索框中输入一个极长的关键字(超出限制字符数,例如:"a" * 1000)。<br> 2. 点击搜索按钮。

显示错误消息提示。

这些测试用例覆盖了主要业务用例UC-01中的不同情况,包括有效输入、无相关结果、取消搜索操作、多次搜索以及边界值测试。它们有助于验证百度关键字搜索功能是否按照规格说明书的要求正常运行。

ChatGPT根据业务用例UC-01补充了测试用例,这使得测试用例在操作流程的使用场景方面覆盖得更加全面。

4)    用例评审和改进:对测试用例的评审和改进是确保软件质量的关键环节。结合前面ChatGPT生成的功能和安全测试用例,进行测试团队内部测试用例评审,在进行内部评审时,测试团队成员发现以下问题。

  • 有的测试用例的测试步骤中夹杂着<br>HTML标签。
  • 有的测试用例的测试步骤仅有1个步骤,却有序号。
  • 有的测试用例重复,如TC001(参见图3-3)和TC019测试用例就非常相似。
  • 有的测试用例不一定包含真实数据库表,如TC011 SQL注入攻击测试用例,可能就不存在users表等,需要和研发人员进一步确认是否存在该表。
  • 存在文本输入域需明确的情况,如关键字文本输入框中的最大输入字符数是1000个吗?是允许输入1000个英文字符,还是1000个中文或英文字符?这些需要和产品人员进一步明确。

限于篇幅,笔者只罗列几点内容,测试团队针对评审意见进行了相关测试用例的问题修正与疑问明确,经过测试用例去重以后,形成基于百度关键字搜索业务的功能和安全测试用例集,如表3-1所示。


3-1
基于百度关键字搜索业务的功能和安全测试用例集

测试编号

测试目标

前置条件

测试步骤

预期结果

TC001

搜索输入框合法关键字的测试

用户打开百度搜索页面

1. 在搜索输入框中输入一个有效关键字(例如:"测试")

2. 点击搜索按钮

显示搜索结果页面。

TC002

搜索输入框多个合法关键字的测试

用户打开百度搜索页面

1. 在搜索输入框中输入多个有效关键字(例如:"软件测试 方法")

2. 点击搜索按钮

显示搜索结果页面。

TC003

搜索输入框包含特殊字符的测试

用户打开百度搜索页面

1. 在搜索输入框中输入特殊字符(例如:"#$%^")

2. 点击搜索按钮

显示错误消息提示。

TC004

基本关键字搜索 - 无相关结果

用户已进入百度搜索页面

1. 在搜索框中输入一个关键字,但系统无法找到相关结果(例如:"未知关键字")。

2. 点击搜索按钮。

显示相应的通知给用户(例如:"未找到相关结果")。

TC005

基本关键字搜索 - 取消搜索

用户已进入百度搜索页面

1.在搜索框中输入一个有效关键字(例如:"测试")。

2. 在搜索操作执行前取消搜索操作。

搜索操作被取消,用例结束。

TC006

基本关键字搜索 - 多次搜索

用户已进入百度搜索页面

1.在搜索框中输入一个有效关键字(例如:"测试")。

2. 点击搜索按钮。

3. 重复步骤1和2多次。

搜索结果正确显示,搜索历史被保留。

TC007

基本关键字搜索 - 边界值测试

用户已进入百度搜索页面

1.在搜索框中输入一个极小的关键字(例如:"a")。

2. 点击搜索按钮。

显示与关键字相关的搜索结果页面。

TC008

基本关键字搜索 - 边界值测试

用户已进入百度搜索页面

1.在搜索框中输入一个极长的关键字(超出限制字符数,例如:"a" * 1000)。

2. 点击搜索按钮。

显示错误消息提示。

TC009

搜索建议显示测试

用户打开百度搜索页面

在搜索输入框中输入部分关键字(例如: "测")

显示搜索建议列表。

TC010

搜索建议多个部分关键字的测试

用户打开百度搜索页面

在搜索输入框中输入多个部分关键字(例如: "软件测")

显示搜索建议列表。

TC011

针对搜索结果的验证

用户输入合法关键字并点击搜索按钮

检查搜索结果页面

显示相关的搜索结果。

TC012

下一页按钮测试

用户输入合法关键字并点击搜索按钮

点击下一页按钮

显示下一页的搜索结果。

TC013

上一页按钮测试

用户输入合法关键字并点击搜索按钮

点击上一页按钮

显示上一页的搜索结果。

TC014

按相关性排序测试

用户输入合法关键字并点击搜索按钮

选择按相关性排序

结果按相关性排序。

TC015

按时间排序测试

用户输入合法关键字并点击搜索按钮

选择按时间排序

结果按时间排序。

TC016

SQL注入攻击测试

用户在搜索输入框中输入恶意SQL查询(例如: "'; DROP TABLE Users --")

检查搜索结果或页面行为

拒绝查询并显示错误消息。

TC017

XSS攻击测试

用户在搜索输入框中输入包含XSS脚本的关键字(例如: "<script>alert('XSS Attack')</script>")

检查搜索结果或页面行为

过滤并显示普通文本。

TC018

搜索输入框的最小边界值测试

用户打开百度搜索页面

在搜索输入框中输入一个空格

显示搜索建议或搜索历史。

TC019

搜索输入框的最大边界值测试

用户打开百度搜索页面

在搜索输入框中输入一个长字符串,超出限制字符数(例如:1000个字符,500个汉字)

显示错误消息提示。

TC020

搜索建议的最小边界值测试

用户打开百度搜索页面

在搜索输入框中输入一个字符(例如:"a")

显示搜索建议列表。

TC021

搜索建议的最大边界值测试

用户打开百度搜索页面

在搜索输入框中输入多个字符,但不足以显示搜索建议(例如:"abcde")

不显示搜索建议列表。

TC022

搜索结果的最小边界值测试

用户输入合法关键字并点击搜索按钮

检查搜索结果页面

显示相关的搜索结果。

TC023

搜索结果的最大边界值测试

用户输入合法关键字并点击搜索按钮

输入关键字,使得搜索结果数超过限制(例如:超过1000个结果)

显示错误消息提示。

通常情况下还应该进行测试用例外不评审。将已完成的基于百度关键字搜索业务的功能和安全测试用例集的存放位置告知项目团队成员,需要预留出一定的时间,便于项目组研发、产品人员阅读,以免在项目团队测试用例评审会议上占用过多时间熟悉相关测试用例内容。在项目团队测试用例评审会议上,相关人员积极进行测试用例评审,提出如下几点意见。

1)    针对测试用例TC008、TC019和TC023,产品人员对搜索输入框的字符进行了明确,即无论是中文字符还是英文字符,最大输入的字符数就是100个,无论是中文字符、英文字符或者中英文字符混合,都需要前后端做校验,若前端输入超过100个字符,则自动截取前100个字符。

2)    针对测试用例TC016,产品人员对需求进行了明确,即将SQL注入的语句进行过滤处理,将其作为普通文本处理,同时后端研发人员也明确users表存在。

3)    针对测试用例TC023,产品人员对需求进行了明确,即搜索结果数没有限制。

前言

前面介绍了在C#中使用Progress类调用Python脚本的方法,但是这种方法在需要频繁调用并且需要进行数据交互的场景效果并不好,因此今天分享的是C#调用Python代码的方式(二):使用pythonnet调用Python代码。

pythonnet介绍

Python.NET 是一个包,为 Python 程序员提供了与 .NET 公共语言运行时 (CLR) 几乎无缝的集成,并为 .NET 开发者提供了一个强大的应用程序脚本工具。它允许 Python 代码与 CLR 交互,也可以用于将 Python 嵌入到 .NET 应用程序中。

image-20241217120710093

使用pythonnet,以PaddleOCR-GUI为例

在使用pythonnet之前,需要搞清楚它的三个概念,分别是
Runtime.PythonDLL

PythonEngine.PythonHome

PythonEngine.PythonPath
,搞清楚了这些,使用起来就很方便了。

image-20241217120547972

先来看Runtime.PythonDLL如何指定。

比如你用Python3.12创建了一个虚拟环境,但是在这个虚拟环境,找不到DLL文件,这时候你需要去原始的那个Python3.12文件夹中去找:

image-20241217120934674

我这里对应的路径是:C:\Users\25398\AppData\Local\Programs\Python\Python312\python312.dll。

再来看PythonEngine.PythonHome如何指定。

PythonEngine.PythonHome写你创建的虚拟环境中的python.exe,这里我对应的是:

image-20241217121302447

最后再来看看PythonEngine.PythonPath如何指定。

PythonEngine.PythonPath是指运行你的python代码所需的所有目录,包括你写的python代码所在的目录,虚拟环境的一些目录,原始环境的一些目录,这里我对应的如下所示:

D:\Learning\PaddleOCR\;
D:\Learning\PaddleOCR\PaddleOCRVENV\Lib;
D:\Learning\PaddleOCR\PaddleOCRVENV\Lib\site-packages;
C:\Users\25398\AppData\Local\Programs\Python\Python312\Lib;
C:\Users\25398\AppData\Local\Programs\Python\Python312\Lib\site-packages;
C:\Users\25398\AppData\Local\Programs\Python\Python312\DLLs"

当你运行时提示没有叫XX的模块的时候,需要看看是否都包含了,比如刚开始我没有包含C:\Users\25398\AppData\Local\Programs\Python\Python312\DLLs的时候,会报一个没有XX模块的错误,加上之后就不会了。

现在ViewModel中对这些量进行赋值:

image-20241217122302883

这样使用即可:

 using (Py.GIL())
 {
     dynamic example = Py.Import("test4");
     if (SelectedFilePath == null)
     {
         return;
     }
     string image_path = SelectedFilePath;
     string selected_language = selectedLanguage;
     string result = example.use_paddleocr(image_path, selected_language);
     OCRText = result;
 }

image-20241217122342153

对应的Python代码如下:

import logging
from paddleocr import PaddleOCR

def use_paddleocr(image_path,selected_language):
    # 配置日志级别为 WARNING,这样 DEBUG 和 INFO 级别的日志信息将被隐藏
    logging.basicConfig(level=logging.WARNING)

    # 创建一个自定义的日志处理器,将日志输出到 NullHandler(不输出)
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

    # 获取 PaddleOCR 的日志记录器
    ppocr_logger = logging.getLogger('ppocr')

    # 移除所有默认的日志处理器
    for handler in ppocr_logger.handlers[:]:
        ppocr_logger.removeHandler(handler)

    # 添加自定义的 NullHandler
    ppocr_logger.addHandler(NullHandler())

    ocr = PaddleOCR(use_angle_cls=True, lang=selected_language)  # need to run only once to download and load model into memory
    result = ocr.ocr(image_path, cls=True)
    result1 = ""
    for idx in range(len(result)):
        res = result[idx]   
        for line in res:            
            result1 += line[1][0]
    return result1

实现的效果如下所示:

image-20241217123301884

还遇到的一个坑,就是同步运行没问题,但是改成异步运行,前两次可以,后面就不行了,需要加上这个:

image-20241217123639735

以上就是本次分享的全部内容,全部源代码已上传到GitHub,地址:
https://github.com/Ming-jiayou/PaddleOCR-GUI。

前言

最近线上服务经常出现一些奇奇怪怪的问题,比如网页上的静态资源加载不出来,或者请求后端莫名报错,又或者 Redis 报错…

当我 SSH 登录到服务器上时,更不对劲了,敲个命令都卡顿…

如果是以前没经验,或许会以为遇到了疑难杂症,但作为多年的 Linux 用户,我已经知道了这种种异常的背后是「存储空间已满」在作祟!

那么问题就来到了「硬盘空间去哪儿了?」

排查

先用 df 看个大概

首先是最常用的命令

df -h

先来看看各个磁盘和挂载点的情况

在我的服务器上执行这个命令之后发现,根目录的可用空间已经只剩下几百 KB 了…

再用 du 看具体

使用 du 命令可以查看各个子目录占用的空间,然后再结合 sort 命令排个序

sudo du -h --max-depth=1 / | sort -hr

参数说明:

  • 因为要直接统计根目录,所以需要有 root 权限
  • --max-depth=1
    :仅显示当前目录及其下一级子目录的占用情况。
  • sort -hr
    :按照大小进行降序排列。

查看目录的总占用空间:

du -sh /

更好的工具

前面的 du 命令,还是没那么好用,主要是列出来的数据太多了。

这次我用上了 ncdu 工具

ncdu
是一个更加用户友好的磁盘使用分析工具,支持交互式界面。

不过很多发行版没有内置,需要先安装:

sudo apt install ncdu

使用 ncdu 分析根目录占用

sudo ncdu -x /

使用
-x
参数可以限制扫描范围为当前文件系统,不跨越挂载点。因为根目录下有很多其他硬盘的挂载点,根据前面使用 df 命令的分析,是主硬盘满了,所以我只要看主硬盘的就行。

ncdu 启动之后会扫描各个文件的存储空间,然后进入一个交互式界面,可以很直观的看到各个目录的占用空间大小,从大到小排序。

罪魁祸首

在 ncdu 里可以很直观看到
/var/lib/docker
这个目录占用了 70% 以上的存储空间,妥妥的答辩啊!

使用 du 分析

sudo du -h --max-depth=1 /var/lib/docker | sort -hr

之后大概的占用情况是这样(只是例子,不是真实数据)

10G     /var/lib/docker/overlay2
5G      /var/lib/docker/volumes
1G      /var/lib/docker/containers
500M    /var/lib/docker/images

基本就是 docker 的镜像、容器日志、卷之类的把硬盘吃掉了

清理 Docker 的未使用资源

找到了问题,那就好办了

首先把没用的 docker 容器停掉

然后执行一下 docker 提供的一些清理命令。

清理未使用的资源(镜像、容器、卷和网络)

Docker 提供了
docker system prune
命令,能清理未使用的资源。

docker system prune -a
  • -a
    :删除所有未使用的镜像(包括没有关联到容器的镜像)。
  • 注意
    :这个命令不会删除被正在运行的容器依赖的资源,请小心操作。

仅清理未使用的卷

如果是
/var/lib/docker/volumes
占用空间较多:

docker volume prune

仅清理未使用的网络

如果是
/var/lib/docker/network
占用较多:

docker network prune

删除不需要的容器、镜像和卷

删除未运行的容器

docker container prune

删除无用的镜像

docker image prune -a

删除无用的卷

docker volume prune

清理旧的镜像和未使用的标签

如果在使用大量镜像,很多旧版本可能已经没有用了。

列出镜像按大小排序

docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | sort -k2 -h

删除指定镜像

docker rmi <image-id>

啥玩意这么大?

在分析存储空间的占用过程中,我还发现有个文件特别离谱,下面这个文件,500多个G…

/var/lib/docker/containers/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df-json.log

找出拉屎的容器

来看看是哪个容器拉的屎。

日志文件的路径中包含了容器的 ID:
e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df

来找一下是哪个容器:

docker ps | grep e4b5a99b429a

在容器列表中找不到该容器,可能它已经被停止或删除了。在这种情况下,可以使用
docker inspect
检查具体信息:

docker inspect e4b5a99b429a

分析日志内容

可以通过
tail

less
查看日志内容,检查是否有异常输出:

sudo tail -n 50 /var/lib/docker/containers/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df/e4b5a99b429a612885417460214ea40a6a49a3360c29180af800ff7aef4c03df-json.log

日志的具体内容我就不贴了,看起来应该没啥问题,就是运行久了,日积月累…

处理这个问题的方法,见下一小节。

清理 docker 日志文件

如果
/var/lib/docker/containers
占用大量空间,可能是容器日志文件过大。

查看日志文件

每个容器的日志存储在
/var/lib/docker/containers/<container-id>/<container-id>-json.log

使用以下命令找到最大的日志文件:

sudo find /var/lib/docker/containers/ -type f -name "*.log" -exec du -h {} + | sort -hr | head -n 10

手动清理日志

清空一个特定容器的日志文件:

sudo truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log

设置日志文件大小限制

在 Docker 的配置文件中限制日志大小(推荐):

  1. 编辑 Docker 配置文件(通常是
    /etc/docker/daemon.json
    ):

    sudo nano /etc/docker/daemon.json
    
  2. 添加或修改以下配置:

    {
      "log-driver": "json-file",
      "log-opts": {
        "max-size": "10m",
        "max-file": "3"
      }
    }
    

    • max-size
      :单个日志文件最大 10 MB。
    • max-file
      :保留 3 个日志文件。
  3. 重新加载 Docker:

    sudo systemctl restart docker
    

迁移 /var/lib/docker 到新磁盘

可以将
/var/lib/docker
挂载到其他磁盘,从而缓解当前磁盘的存储压力。这是一个常见的做法,尤其在有多块磁盘的情况下。

方法一:直接迁移
/var/lib/docker
到新磁盘

停止 Docker 服务

在迁移数据之前,需要先停止 Docker 服务:

sudo systemctl stop docker

移动
/var/lib/docker
到新位置

假设新磁盘挂载在
/mnt/new-disk
,执行以下命令:

sudo mv /var/lib/docker /mnt/new-disk/docker

创建软链接

将新的路径链接回
/var/lib/docker
,让 Docker 继续按默认路径工作:

sudo ln -s /mnt/new-disk/docker /var/lib/docker

启动 Docker 服务

sudo systemctl start docker

验证

运行以下命令确保 Docker 正常工作:

docker info

方法二:修改 Docker 配置文件,指定新存储位置

停止 Docker 服务

sudo systemctl stop docker

移动
/var/lib/docker
到新磁盘

将现有数据迁移到新磁盘挂载点。例如,新磁盘挂载在
/mnt/new-disk

sudo mv /var/lib/docker /mnt/new-disk/docker

修改 Docker 配置

编辑 Docker 的配置文件(通常是
/etc/docker/daemon.json
),指定新的存储路径:

sudo nano /etc/docker/daemon.json

添加或修改以下内容:

{
  "data-root": "/mnt/new-disk/docker"
}

启动 Docker 服务

sudo systemctl start docker

验证

再次检查 Docker 是否正常运行:

docker info | grep "Docker Root Dir"

你应该能看到新的路径(如
/mnt/new-disk/docker
)。

方法三:直接挂载新磁盘到
/var/lib/docker

如果想直接将新磁盘作为
/var/lib/docker
的挂载点,可以使用以下方法

格式化新磁盘

假设新磁盘为
/dev/sdb1
,先格式化并创建文件系统(如
ext4
):

sudo mkfs.ext4 /dev/sdb1

挂载新磁盘

将新磁盘挂载到
/var/lib/docker

sudo mount /dev/sdb1 /var/lib/docker

迁移现有数据

如果
/var/lib/docker
目录下已有数据,需要先复制到新磁盘:

sudo rsync -a /var/lib/docker/ /mnt/new-disk/

然后再将新磁盘挂载回
/var/lib/docker

修改
/etc/fstab
确保开机自动挂载

编辑
/etc/fstab
文件,添加一行挂载配置:

/dev/sdb1  /var/lib/docker  ext4  defaults  0  2


注意事项

  1. 数据迁移风险
    :在迁移或重建
    /var/lib/docker
    时,务必备份重要数据(如持久卷数据)。

  2. 权限问题:确保新目录的权限与原始目录一致:

    sudo chown -R root:root /mnt/new-disk/docker
    
  3. 检查挂载点
    :确保新磁盘挂载成功,并设置自动挂载,避免系统重启后路径丢失。

通过以上方法,可以成功将
/var/lib/docker
挂载到其他磁盘,缓解存储压力并优化存储布局。

重建 /var/lib/docker

如果清理后空间仍然不足,可以重建 Docker 的存储目录(会删除所有容器、镜像和数据)

停止 Docker 服务:

sudo systemctl stop docker

备份现有 Docker 数据(可选):

sudo mv /var/lib/docker /var/lib/docker.bak

创建一个新的空目录:

sudo mkdir /var/lib/docker

启动 Docker 服务:

sudo systemctl start docker

总结

在这次 Linux 服务器硬盘空间消失问题的排查过程中,我经历了一次完整的存储分析和优化实战。

关键步骤概括:

  1. 初步排查存储占用情况
    • 使用
      du

      ncdu
      等工具,快速定位占用空间较大的目录。
    • 发现
      /var/lib/docker
      目录占用了大量存储空间。
  2. 深入定位具体问题
    • 找到具体的容器日志文件路径,通过容器 ID 确认了是哪个容器产生了大量日志。
    • 使用
      docker inspect

      docker logs
      ,进一步分析日志内容。
  3. 解决问题
    • 清空了过大的容器日志文件,通过
      truncate
      命令立即释放空间。
    • 修改 Docker 配置文件(
      daemon.json
      )限制了日志文件的大小,避免类似问题再次发生。
  4. 验证与优化
    • 重启 Docker 服务后,验证了服务正常运行。
    • 使用
      docker system prune
      清理了无用资源,并规划了日志管理策略。

个人收获与思考:

这次问题的解决让我深刻体会到以下几点:

  • 系统监控的重要性
    :及时监控存储使用情况,可以避免问题扩大化。
  • 日志管理最佳实践
    :过度增长的日志文件是常见的存储占用原因,必须设置合理的日志大小限制。
  • 工具的高效使用

    du

    ncdu
    和 Docker 命令等工具在排查问题中大大提升了效率。
  • 日常维护习惯
    :定期清理无用的容器资源(例如停止的容器、未使用的镜像),可以保持系统健康运行。

这次实践不仅解决了磁盘空间问题,也让我对 Linux 系统管理和 Docker 的运维有了更深的理解。在未来的运维工作中,我将更加注重系统的监控与优化,提前预防类似问题的发生。

正文

最近在学习JavaScript里的async、await异步,对于其中的Promise状态以及背后的Js引擎实际运行状态不大理解且很感兴趣,因此花了一点时间仔细研究了一下。

从Example说起

const createImg = function (path) {
  return new Promise((resolve, reject) => {
    const img = document.createElement('img');
    img.src = path;
    img.classList.add('images');
    img.addEventListener('load', () => {
      imgContainer.append(img);
      resolve(img);
    });

    img.addEventListener('error', () => {
      reject(new Error('image has not found!'));
    });
  });
};

const loadImg = async function(imgPath){
	const imgs = imgPath.map(async img => await renderImg(img));
	console.log(imgs);  // Question
}

loadImg(['./img/img-1.jpg', './img/img-2.jpg', './img/img-3.jpg'])

其中imgPath是图片地址数组,loadImg是遍历渲染图片的异步函数,renderImg是在当前页面插入并渲染图片的一个异步函数。我的疑问从Question Line开始,
为什么控制台打印出的是Promise < fulfilled: undefined>

async机制

我开始从async在Js引擎中的执行逻辑学起:async会
开辟一个单独的协程
,并且当执行到其中await行(
前提是返回Promise
)时,将await后表达式放入Web APIs后台运行。

遵循这个执行逻辑分析,loadImg首先开辟一个
loadImg协程
,并且在执行到imgPath.map(async img...)行时,单独为几个img异步函数分别开辟了协程,为方便后文称
img1、img2、img3协程

image.png

图中箭头表示
当前CALLSTACK中执行的是哪个协程
的代码内容,由于await createImg(img)中createImg是一个返回Promise的异步函数,因此会把createImg放入Web APIs中,并立即返回一个Promise< pending >。

[!NOTE]

这里值得注意的是,createImg返回的Promise< pending>是
返回在Img协程内部
的,也就是说如果有一个变量可以接收,例如 const tmp = await createImg(img),则这个
Promise是赋值给tmp的
;但是
async调用之后会立即返回一个Promise< pending >对象
(后文详细描述为什么打印出来的是Promise< fulfilled: undefined >,与之不同)。

最终可以看见在LoadAll协程处返回了3个Promise< pending >对象,但是console.log出来的还是Promise< fulfilled: undefined>,很奇怪。

我反思可能有两个问题:

  1. createImg(img)的响应速度太快了,这导致在不同协程间切换的时候,已经fulfilled了;
  2. 又或者是async返回的永远都是Promise< fulfiiled>?
const loadAll = async function (ImgArr) {
  const imgs = ImgArr.map(async Img => {
    console.log('start rendering');
    await new Promise(resolve => setTimeout(resolve, 2000));
    await createImg(Img);
    console.log('end rendering');
    return Img;
  });
  console.log(imgs);
};

这次我在createImg前加了2s的阻塞,这样就能知道到底是谁的问题了。

image.png

结果还是Promise< fulfilled: [[value]]>? 实话说真有点懵了。
理性分析来说,
Promise是即刻返回的
,所以不存在看后续代码中是否能跑通,是否有bug等,所以理论上来说可以直接
排除问题2
,因为通常来说
pending可以到fulfilled/reject两个状态

在我多次尝试之后,发现...

console控制台实时渲染

直接上结论:

Promise
返回的就是Promise< pending>
,也只能返回这个对象,而其中的fulfilled /reject状态是当async中的
异步代码后台跑完后
返回给console,并
由console去动态渲染替换的

证据:

image.png

在阻塞的2s期间内点击控制台的Promise对象数组,可以看到在
async整体代码没跑完
的时候,每个
Promise都是pending的状态
,等2s
阻塞期过后
,由于createImg的速度很快,一下就可以由console完成由pending ->fulfilled状态的动态渲染替换。

问题

None

总结

至此破案,也让我更进一步的理解async、Promise、await的机制。