2024年11月

问题的提出

在软件开发中,我们为了减少软件的复杂度,是不会把所有的功能都塞进一个模块之中的,塞在一个模块之中对于软件的管理无疑是极其困难且复杂的。所以把一个项目拆分为模块无疑是一个好方法

                        ┌ ─ ─ ─ ─ ─ ─ ┐
                          ┌─────────┐
                        │ │Module A │ │
                          └─────────┘
┌──────────────┐ split  │ ┌─────────┐ │
│Single Project│───────▶  │Module B │
└──────────────┘        │ └─────────┘ │
                          ┌─────────┐
                        │ │Module C │ │
                          └─────────┘
                        └ ─ ─ ─ ─ ─ ─ ┘

对于Maven工程来说,原来是一个大项目:

single-project
├── pom.xml
└── src

现在可以分拆成3个模块:

multiple-projects
├── module-a
│   ├── pom.xml
│   └── src
├── module-b
│   ├── pom.xml
│   └── src
└── module-c
    ├── pom.xml
    └── src

我们能看到的是每一个模块都有属于自己的
pom.xml
,然后模块A的
pom.xml
是这样的:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>module-a</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>module-a</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.28</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

之后B的
pom.xml
也大同小异,只用把
<artifactId>module-a</artifactId>

<name>module-a</name>
改为自己的就行。这个时候我们就会发现一个很麻烦的事,我们很多地方都是一样的,但是每一个模块的pom都需要我们重复声明出来,那我们能不能用像对象那样继承下来,这样就不用重复声明了呢?Maven无疑是有这样的功能的

问题的解决

简化后的结构

我们现在看看简化后的模块结构式如何的

multiple-project
├── pom.xml
├── parent
│   └── pom.xml
├── module-a
│   ├── pom.xml
│   └── src
├── module-b
│   ├── pom.xml
│   └── src
└── module-c
    ├── pom.xml
    └── src

与之对比的是根目录多了一个pom,然后多加了一个"模块"parent,里面没有代码src,只有一个裸的pom。

看了对比之后我们一个一个讲是怎么修改的,结构又是怎么样的

修改细则

parent

我们先来看parent里面的pom是怎么个事

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>parent</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>

    <name>parent</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.28</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

我们能发现的是,对于之前的模块A修改其实也不多,修改的分别是
<artifactId>parent</artifactId>
<packaging>pom</packaging>
<name>parent</name>

这里我们着重讲一下
<packaging>pom</packaging>
,首先我们先明白
<packaging>
这个标签代表了什么,

<packaging>
这个标签他表示打包的方式,常见的值为
jar
(Java库)、
war
(Web应用)、
pom
(父项目)等。这个地方
parent

packaging
设置为
pom
,因为它不生成任何可执行的JAR文件,仅提供配置和依赖管理。

其他模块的简化思路

看完了parent的代码之后我们就慢慢地去理清简化的思路

编码与java版本配置

首当其冲的无疑就是这个部分,这个地方模块AB都是需要的,而且都是一样的,那么这个元素就是可以被继承的,也就是
是可以省略的

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>
    </properties>
公共依赖项

其次就是AB都需要的依赖项如
slf4j-api

logback-classic

junit-jupiter-engine
,以及作用域的设置

<dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.28</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

修改后的模块A的pom

我们再开看看修改后模块A的pom是怎么样的

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.itranswarp.learnjava</groupId>
        <artifactId>parent</artifactId>
        <version>1.0</version>
        <relativePath>../parent/pom.xml</relativePath>
    </parent>

    <artifactId>module-a</artifactId>
    <packaging>jar</packaging>
    <name>module-a</name>
</project>

不得不说有了parent之后,整个模块都变得简洁了起来

在设置好
parent
模块后,我们只需要引用
parent
作为其他模块的父模块。

首先通过


<parent>


标签引用


parent


模块

    <parent>
        <groupId>com.itranswarp.learnjava</groupId>
        <artifactId>parent</artifactId>
        <version>1.0</version>
        <relativePath>../parent/pom.xml</relativePath>
    </parent>

有了这些之后就相当于继承了parent里面的元素了。

之后我们再导入自己独有的元素就基本上完成了对此模块的配置

<artifactId>module-a</artifactId>
<packaging>jar</packaging>
<name>module-a</name>

继承
parent
模块后,模块A和模块B的
pom.xml
文件已经大幅简化。所有公共配置项,如
UTF-8
编码、Java编译版本、以及日志和测试的依赖库,均已在
parent
中配置好。这样,模块A和模块B仅需保留独有的内容,简化了配置并降低了维护成本。

相互的引用

如果模块A需要引用模块B的代码,可以在模块A的
<dependencies>
中增加对模块B的依赖项,如下:

<dependencies>
    <dependency>
        <groupId>com.itranswarp.learnjava</groupId>
        <artifactId>module-b</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

通过这一配置,Maven会在构建模块A时自动获取模块B生成的JAR文件,使得模块A可以使用模块B中的代码和功能。

根目录pom的配置

最后的最后,我们配置最后根目录pom的思路就是为了完成所有项目的统一编译:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>build</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>
    <name>build</name>

    <modules>
        <module>parent</module>
        <module>module-a</module>
        <module>module-b</module>
        <module>module-c</module>
    </modules>
</project>

这样,在根目录执行
mvn clean package
时,Maven根据根目录的
pom.xml
找到包括
parent
在内的共4个
<module>
,一次性全部编译。

这可能是最好的Spring教程!

感谢您看到这里
这可能是最好的Spring教程系列
更多的文章可以到这查看
这可能是最好的Spring教程!即便无基础也能看懂的入门Spring,仍在持续更新。
,我还在荔枝更新出最详细的Spring教程

大家好,我是vzn呀,又见面了。

前不久出了个有意思的事情:

某平台UP主发布了一段小米SU7碰撞的测评视频,表示碰撞后小米SU7的小电瓶出现故障导致车门打不开、紧急呼叫系统失灵等问题,引起不小轰动。就在大家都在吃瓜看小米如何应对时,小米官方抛出了一份内部调查报告,重点就一个:我还藏了个备用电源!我把过程上报到国家监控平台了!!你在黑我!!!。这剧情反转程度,像极了重生小说中的桥段,小米设计师似乎预料会有这么一出,提前藏了个小电瓶,就是为了等待这一刻的绝地反杀。

抛去吃瓜的爽文成分,深入其内核,我们不难发现,小米SU7备用电源的设计思路,在软件领域有着广泛的共鸣和深刻的意义。在软件开发与设计的广阔天地里,面对的是一个充满变数的世界,其中既包括
普通用户
的日常操作,也涵盖了
网络故障

硬件崩溃
乃至
恶意攻击
的种种挑战。按照
墨菲定律
的阐述,只要概率大于0%的事情就很容易会发生(说的通俗点,就是怕什么就会来什么)。做好异常场景的应对,是一个成熟程序员的进阶必修课,也是一个软件系统线上平稳运行的内在基石。

说到这里,就不得不提到系统架构设计的一个深层哲学,它强调的是
对未知风险的敬畏与思考
。一个优秀的系统架构设计,应当能够预见并应对未来可能出现的各种挑战,要容忍并接受局部错误存在客观性并努力将局部错误控制在一定范围内,同时当系统出现不可逆灾难时可以尽量最大程度的保障系统核心业务的持续可用。

本篇文章,我们就来聊聊软件开发中针对系统
容错能力
以及
灾难应对
能力的考量。

1. 容错设计,再给一次机会

我们开发的一款软件,上线运行后面临的情况是极其复杂的,说不出错,几乎是不可能的:

上游输入异常参数怎么办
外部接口挂了怎么办
依赖出现故障怎么办
网络抖动导致请求失败了怎么办?
硬盘坏了怎么办?

前面也说过,异常是必定存在的客观事实。如果我们一味的追求绝对的0偏差,其实是自己为难自己。所以呢,为了提升系统应对异常情况的能力,考虑增加一些容错设计,便是一个不错的思路。允许有限范围的异常、尝试去包容这些异常,尽量保证最终符合预期的目标达成即可。

容错能力的实现,有很多种方式,典型的就是
重试机制

补偿策略

1.1. 重试:浪子回头金不换

重试
是为了降低异常情况的出现给业务请求造成的影响,尽量保障请求按预期被处理的一种常用方式。那是否所有的失败场景都需要重试呢?显然不是,比如登录密码校验的时候,输入了一个错误的密码导致鉴权失败,这种不管重试多少次永远都依旧是失败。一般而言,只有受一些瞬时偶然因素干扰的失败场景,才需要考虑重试策略。比如:

  1. 某次对外网络请求的时候,因为
    网络抖动
    等原因导致的请求失败,或者是请求处理超时,可以通过
    有限次数
    的重试,来提升对外交互处理的成功率
  2. 分布式系统中,某个
    节点服务异常
    ,网关将请求重新分给另一个节点进行重新处理,提升整个集群的容错能力
  3. 抢夺分布式锁的排队处理场景中,某次没有获取到锁,等待一段时间后再次尝试获取锁

按照重试
触发时机
的不同,重试策略可以分为
立即重试

延时重试

触发时机 适用场景
立即重试 适用于一些因为偶然因素导致的失败,比如请求的时候如果因为网络抖动导致的链接失败,可以尝试立即重试。
延时重试 适用于因为资源受限引发的失败场景。比如对外请求的时候,由于下游接口流量过大触发限流导致的失败,如果立即重试,大概率重试依旧失败,这种场景就可以考虑等待一定时间后再重试,以提升成功率。

而根据重试操作的具体实现逻辑,还可以分为
原路重试

差异重试

重试类别 场景举例
原路重试 (1)调用三方系统HTTP接口,出现响应超时或者网络不通等异常情况,重新发出一次请求
(2)获取分布式锁失败的时候,尝试重新请求获取
差异重试 (1)分布式系统中,一个节点的请求处理失败后,网关将请求分发到另一个节点进行重试
(2)从Redis中获取数据失败,尝试从MySQL中捞取数据

在重试机制落地的时候,还有2个基础的原则不能忽略:

  1. 限定重试次数
    : 保证极端情况下,系统不会陷入无限循环重试。
  2. 重试次数要合理
    :避免过多的重试,浪费系统资源,不要为了重试而重试。

此外,在一个较长的处理链路上,如果涉及到重试的环节过多,还需要考虑引发
请求风暴
的风险。比如下图的场景,假定重试的最大次数限制为N:

所以重试手段并非是零成本的,它的使用也是有副作用的,尤其是在一些复杂链路场景中。为了规避连环重试可能导致的连环风暴隐患,还需要引入一些辅助手段来应对。

  • 打破链式重试

请求风暴的形成,是因为最末端的异常被无限制透传给了所有上游环节,然后触发了上游环节的反复重试,将请求数量指数级放大。但是实际上,仅仅是最后一个节点与DB之间的请求出现问题,其实只需要重试这个操作即可,上游节点并不需要重试。为了实现这一效果,需在请求交互层面进行规划,通过返回值、返回码等方式,告知上游节点是否需要重试,将重试的范围限定在故障发生位置,而非全链路的链式连锁反应。

  • 结合熔断策略

结合熔断机制,根据该路请求处理的失败率进行判断,达到一定阈值的时候,直接执行熔断操作。后续通过一定的探测机制,分配少量的试探性流量,如果成功率达到设定阈值,则恢复此链路的后续处理。

1.2. 补偿:亡羊补牢犹未晚

上面介绍的重试手段,主要目的是为了尽可能的提升当次操作的成功率。但是,总有一些异常场景不是即时重试就可以解决的。比如在一些大型的微服务分布式系统中,一个请求流程会跨越多个服务进行处理,且请求的处理往往是异步的,如果出现重试也无法解决的异常问题,就需要额外的补偿机制,对处理结果的最终一致性进行保障。

补偿机制经常被使用在分布式系统中,它的一个核心前提是,允许并接受过程中的暂时性数据问题,并通过补偿措施,保证最终的数据一致性。那么,如何知晓是否需要执行补偿操作、哪些数据需要执行补偿操作呢?这就需要“
对账
”了。

所谓“对账”,就是定期将此前一段时间内的业务处理数据进行盘点比对一下,找到数据层面不符合预期的数据。基于对账发现的异常记录,再执行对应的补偿修正处理。

举个例子:

一个电商平台系统,其订单系统的设计,买家的订单和卖家的订单是分库存储的。一个订单创建并付款完成之后,订单信息会流转到下游消费服务中被各自处理,并分别写入到买家订单库和卖家订单库中。

在微服务化场景下,虽然可以通过一些分布式事务等手段来加以防范,但依旧可能会因为一些极端情况,导致一个订单没有被同时成功写入到买家订单库和卖家订单库中,这样就可能会用户的使用造成影响。这种情况下,就可以考虑搞个定时任务,定期扫描下一段时间内的订单数据,校准下两边的差异,然后针对异常数据进行处理修正。如下所示:

这样,基于事后
对账+补偿
的双重手段,保障了系统的“最终一致性”目标达成。

2. 顾全大局,舍小义而谋大利

还有一些业务场景,它可能是牵扯到多个并列的依赖方,并最终诉求是将多个依赖方的结果混合在一起。这种情形下,某一个依赖方出现问题,对最终用户的使用体验而言影响很有限、甚至是无感的。一损俱损显然不是最优解,
弃卒保车
会更为合理些。

举个例子, 一款新闻资讯类的软件,首页的内容流列表由多路数据源汇总而成:

  1. 即时突发新闻
  2. 热门时政要文
  3. 关注账号发文
  4. 可能感兴趣内容
  5. 付费推广内容
  6. xxx

最终多个来源的数据,会被混合成一个列表内容流展示给用户。这个过程中,如果其中某一路(比如:即时突发新闻)出现异常未获取到数据,对用户而言其实是无感的,因为用户也不知道究竟是系统出问题了、还是确实没有即时突发新闻。但是因为某一路数据的获取失败,直接给用户报错异常、或者给用户一个白屏显示,反而是将用户给放大了。

在实际的项目中,当故障的出现已经不可避免且无法规避或者重试解决的时候,为了避免问题的进一步扩大,通过一定程度的“妥协”与“舍弃”,以尽量小的损失、避免故障影响面的放大,也是一种常规操作,实现手段有很多,主流的有降级、限流、熔断、放通、隔离等。

2.1. 降级

降级
作为一种兜底策略,通常是在故障场景下从业务层面作出的一种
妥协
策略。 一般是遇到局部功能障碍、或者资源负载层面问题的时候的一种应对方案。当出现某些突发情况,导致系统资源不足以支撑全量业务功能的正常开展时,为了将有限资源集中起来保障核心功能的可用,而主动将一些非核心的功能停用的思路。

降级的使用场景很多,比如:

  1. 电商每年的618或者双11等大促时节,为了保障抢购下单的正常推进,将订单评价、历史订单查询等非核心功能先降级停用,所有资源全力支撑商品的浏览、下单、付款等操作
  2. 互动社交平台,突发超级流量明星的大瓜新闻时,降低一些非核心功能(推广、关注流)的更新频率,将更多资源用以支撑爆炸性话题的访问与互动浏览操作
  3. 对于即时通信IM类场景,如果出现网络故障原因导致机房带宽承压有限,那就降级让视频和语音类服务不可用,尽力保障文字消息功能依旧可用

降级的本质,就是一个
取舍
的过程,舍弃不在乎的部分,保住最在乎的部分。舍弃谁、保全谁,需要根据自身业务的特征来判断。一般而言,有几个维度:

降级维度 场景举例
降低用户体验 界面刷新不及时、不展示动效、不展示高清图、不显示系统推送通知
舍弃部分功能 不允许查看历史订单、不允许数据导出操作、不允许上传文件操作
安全性让步 不做复杂二次校验、跳过风控判断、不记录操作日志
降低准确性 列表数据更新不及时、统计报表更新不及时
降低一致性 列表显示的评论数与点击进去正文显示的评论数不一致,已经删除的文章依旧出现在列表中
降低数据量 订单中心只显示最近100条记录,仅可以查询最近1年数据

实施降级操作的前提,需要系统业务规划层面进行配合,要做好系统业务功能的
SLA规划
,划分出核心功能与非核心功能。同时,在系统的架构层面要做好核心功能与非核心功能的解耦与隔离。

2.2. 限流

一般在春节、五一、或者国庆等节假日,一些热门的景区都会限制进入景区的客流量,以此保证游客的游览体验与人身安全。同样道理,软件系统受限于自身实现、业务规划以及硬件资源承载能力等诸多限制,其承压能力也是有上限的。如果请求流量突增且明显超出系统规划的可承受范围的时候,可能会引发系统宕机等事故。为了保障系统安全,避免突发流量对系统的正常运行造成冲击,就需要对进入系统的流量进行限制管控。

限流
一般可以依据两个维度进行实施:

限制维度 场景举例
限制并发数 比如限制连接池的连接数、线程池的线程数等。
限制QPS 限制每秒进入的请求量。

限流操作的实现,离不开限流算法,主流的有
漏桶算法

令牌桶算法

  1. 漏桶

漏桶算法
的原理很简单,它不限制流入的请求量,但是会以一个相对受限的速度从漏桶中获取请求进行消费处理,如果流出速度小于流入速度,请求就会在漏桶中积压暂存等待顺序被处理,一旦漏桶容量被积压的请求撑满,便会发生溢出,无法进入漏桶的请求将被丢弃。

正如其名字一般,漏桶的原理,像极了生活中使用的漏斗。这也是一个示例,再次印证了软件架构设计中的很多实现与处理策略,都是来源于最质朴的生活。

  1. 令牌桶

令牌桶
的逻辑与漏桶略有不同,它会有个令牌发放模块负责匀速生成令牌并放入到令牌桶中,然后每次请求处理前先尝试获取一个令牌,只有获取到令牌了才会去处理对应的请求。

值得注意的一点是,虽然令牌是设计成匀速生成并放入到令牌桶中的,但这并
没法保证
请求一定会被匀速处理。极端情况下,可能会出现短暂请求量突破限速值的情况(比如:大部分时候请求量小于令牌生成量,导致桶内蓄满令牌,突然来波大流量,会一口气消耗掉令牌桶中全部的存量令牌),所以需要根据系统设计的承压负载情况,合理设定限流的阈值。但这一设计也有其优势,偶尔短暂的脉冲波动可以尽量消化掉,同时又保证长期整体处理速率处于一个受控状态。

还有一种简陋的基于计数器的“
伪限速
”方案,这一思想很简单,每个计数周期维护一个计数器,然后来一个请求计数器就累加1次,计数满阈值后便拒绝后续请求,直到下一周计数器重新计数。这种本质上只能控制流量、无法控制过程流速,极端情况下的一些请求峰值,极有可能会击垮系统,要尽可能将流控计数周期设置的短一些,尽量避免在核心重要系统中使用此方案。

此外,对于一些集群化多节点部署的场景,规划限流的时候,还需要关注是
单机
的流量限制,还是
集群
整体的流量限制,选择适合自己业务的实现方案。

2.3. 熔断

熔断
在现实世界中最直观的应用,就是家里强电箱里空气开关中的保险丝了。当电流超载的时候,保险丝就会断开,以此来保护家里的整体电路不会烧掉,以及各种电器不受损坏。

同样道理,在软件实现中,也有类似电路保险丝一般的设计思路,通过在服务的对外请求调用处增加熔断器,当符合预设条件时,就会将对应依赖的服务从自身的请求链路中剔除,来避免自身节点耗费大量的资源在等待一个大概率错误的响应。

熔断,是自身的一种
自保手段
,目的是防止外部节点异常将自己耗死。熔断的策略一般有两种:

  1. 按照请求的失败率进行熔断

短期内发往某个目标服务的请求失败率高于某个阈值则执行熔断策略,不再继续调用此目标服务。然后通过定期的心跳探测机制,或者少量试探流量的方式,决定继续熔断还是恢复请求。

  1. 按照请求响应时长进行熔断

对于一些高并发量的处理场景,如果调用的目标服务的请求时延过大,势必会拖累整体系统的吞吐量。这种情况下,为了保障自身节点的处理性能,也可以按照请求响应时长,决定是否触发熔断操作。

此外,在集群部署环境下,网关节点也经常会将熔断作为基础功能进行提供,实现比服务熔断更细粒度的一种控制,当服务集群中某个节点出现故障时,直接将该节点剔出集群,待其恢复之后再加入到集群中。

具体应用的时候,可以直接使用一些成熟的开源方案,比如
Hystrix
或者
Sentinel
等。需要强调的一点是,熔断一般针对的目标是一些非核心、非必须的依赖服务,本质上,熔断也是降级的一种实现形式。

2.4. 隔离

隔离
作为一种故障控制手段, 其设计思想是通过将资源分隔开,互不干扰,这样系统出现故障的时候,就可以将故障限定在一定的传播范围内,避免出现滚雪球效应、波及全局。常见的隔离措施,有
数据隔离

机器隔离

线程池隔离
以及
信号量隔离
等等。

  • 数据隔离

最直观的表现,就是
分库分表
了。比如对系统的数据,按照业务维度进行分库存储,或者按照业务的重要度进行识别,将数据识别为重点数据/非重点数据,亦或是保密数据/非保密数据,然后按照细分后的结果实施差异化的数据存储保障策略。比如,对于非重点数据,简单的搞个一主一从双副本即可, 而重点数据,可能得考虑异地多副本可靠存储与备份。

  • 机器隔离

不同的业务使用不同的机器,从
硬件资源
层面进行隔离。通过将机器分组的方式,针对重点服务或者是高危服务实现专机专用,而对应一般普通服务,则可以多个业务混用同一套机器,从而实现了差异化的隔离处置。

  • 线程池隔离

隔离的思想,不仅仅是体现在数据层面或者是进程机器节点等宏观层面,该思想同样适用于对单个进程内部的实现。因为同一个进程内处理很多不同的逻辑,如果某个处理逻辑无限制的创建执行线程,占据了全部的系统CPU资源,则整个进程中其余的逻辑就会受到影响。

为了应对这种情况,可以基于
线程池
进行隔离设计,为主要业务处理方法指定对应的执行线程池,限定具体业务方法仅可以按照分配线程池提供的线程资源进行调度与使用,禁止业务方法自行无度占用系统的线程与CPU执行资源。这样一来,即使某个业务占用了自己全部线程池资源,依旧不会影响到其余线程池的正常处理,保障了其余业务的正常开展。

因为线程池的维护也会占用额外的资源,所以隔离的粒度的把控也要做到
适可而止
,遵循适度原则。

3. 硬件灾备:钞能力带来的超能力

软件的顺畅运行与硬件的稳健支撑密不可分。尽管软件层面通过巧妙的容错设计、灵活的降级策略以及精准的限流机制等手段,能够显著提升其自恢复能力和可用性,但在面对硬件故障这一硬性挑战时,单纯依赖软件手段就显得力不从心。所以,在设计规划建设一套可靠的软件服务的整体架构时,
硬件部署规划
时的可靠性设计,也是无法回避的话题。

相较于软件层面的各种容错策略,硬件层面的应对就显得简单且粗暴————
堆资源
!即通过资源的
冗余部署
来增强系统的容错能力。当然,这一策略的实施不可避免地会增加经济成本,所以具体实施与规划的时候,还需要结合预算情况,在成本许可范围内实现可靠性的最大化保障能力。

常见的硬件层冗备的实践,一个是保障业务应用高可用的
多活机制
,另一个是为了保障数据可靠存储的
多副本存储机制

3.1. 多活

随着越来越多的生活场景被搬到线上处理,互联网白热化时代,对与业务的
7*24
小时持续可用提出了严峻的挑战。但对于一个软件服务来说,不管架构多么完美、代码多么优雅,最终程序都得运行在硬件基础之上,而硬件层面的风险,是代码无法左右的。那么如何应对硬件的各种损坏或者不可用风险呢?很简单,
花钱消灾!
多花点钱,多搞点硬件资源,多部署几套服务就行咯。但是这个部署多套,实际也是有讲究的。

为了应对不同层级的风险,也引申出了多种不同的堆硬件的方式:

  • 集群化

为了应对单台服务器硬件的损坏、比如硬盘损坏、电源烧毁等, 单个机房内部署多个节点,由多个不同的机器,共同组成一个
集群
,这样其中一个节点故障,其余节点依旧可以正常处理业务,有效避免了单点故障的出现概率,提升了业务的可靠性。

  • 同城双活

上述在同一机房内利用多个节点组成集群的方式,虽然能应对单台机器的故障场景,但如果机房出现整体故障,比如停电、着火、光缆被挖断等情况,依旧会导致全军覆没。为了应对这一可能的风险,自然而然的解决方案就是再建一个机房,这样两个机房互为备份,风险就大大降低了。通常而言,两套机房之间会涉及到数据的同步,所以对机房之间的网络传输速度与时延有极高要求,这就要求两个机房不能离得太远,最好在同一个城市。 —— 这便形成了常说的
同城双活
架构。

  • 两地三中心

基于同城双活的模式,其可靠性已经可以满足大部分普通业务场景对于系统可靠性的诉求了。但若业务系统极其重要,尤其是一些金融、社交、基础服务提供商等牵扯到国计民生的领域,对系统的可靠性与数据的安全性有更加苛刻的要求。在同城双活架构中,为了控制机房间网络时延,两个机房的距离都不会太远,万一出现某些不可抗力的自然灾害(比如地震)造成两个机房全部损坏,依旧会导致业务或数据受损。所以如何应对?答案已经呼之欲出了,跨不同城市多建一些机房呗!于是乎,
两地三中心

三地五中心
等等解决方案应运而生。

看到没?系统的可靠程度,一定程度上,取决于堆的钞票的厚度。

3.2. 多副本

冗余备份
,又叫做
多副本
。本质上就是为了防止单点故障造成数据层面的丢失,而采取的将同一份数据分散在多个位置存储多份的一种方式。这种方式会造成额外的资源成本支出,但其所带来的数据可靠性与高可用性,是“孤本”无法比拟的。

多副本的策略,广泛的被应用在各种数据存储组件中。比如:

  1. 本地缓存多副本
  2. Redis多副本
  3. MySQL的多副本
  4. Kafka多副本

最常见且最简单的多副本策略,就是
Master-Slave
这种架构,类似MySQL的一主多从架构。在这种架构中,通常由Master节点负责数据的写操作,然后通过内在的数据同步机制,将数据变更同步更新到各个Slave节点进行数据的多副本存储。为了提升硬件的利用率,Slave节点除了用于数据内容的多副本可靠存储,还可以对外提供只读查询操作,在必要场景下支撑业务的读写分离诉求。

Master-Slave
这种主从架构的多副本策略有个致命的问题,就是每台节点存储的都是全量的数据文件,这使得数据总量
受限于单机存储
,存在瓶颈。对于超大数据量场景,还会需要更加复杂的多副本方案,对总体数据进行切片,对每个切片数据进行多副本支持,进而可以支持容量的水平扩展。像Redis集群或者是kafka所采用的便是这种策略。

  • 分片存储形态1:分散在不同机器上存储

  • 分片存储形态2: 多集群承载分片模式

这种对数据进行分片并分散在多台物理存储节点的方式,
打破了单机容量
的限制,但是也增加了数据读写与数据同步的复杂度。因为数据分散在多个节点上,所以在读写的时候,需要支持将请求路由分发到数据分片所在的节点上,比较常见的是使用
一致性Hash算法
来进行分片。此外,各节点上分片数据的同步与一致性保障也需要更加复杂的处理逻辑来支撑,比如Kafka就专门设计了
ISR算法
来处理多副本之间的数据同步。

4. 人工干预,保证对系统的控制权

我们按照业务场景与业务诉求进行功能实现的时候,会预先设想好各个场景的处理与应对,也会考虑一些可能异常场景的代码层面自动兼容与应对。但可能会有一些场景,它就是突破了我们预先对系统设定的一切合理规划,或者系统出现一些未曾预料到的场景无法自恢复的时候、亦或是自动恢复或回滚处理的影响面太大的时候,都可能需要
人工介入
处理。所以,系统在规划与实现的时候,很有必要构建一些人工干预手段与能力。

这种人工干预能力,有很多实际的应用场景,可以用来提升运维人员对系统的高度掌控力从而更好的应对各种突发场景,也可以作为运营人员的一种高权限后台处置权进行预留。

4.1. 人工介入应急处置能力

先看个例子:

背景:
某个业务需要从远端数据源获取数据并进行业务逻辑处理,由于业务本身属于特别核心且重要的服务,远端数据源数属于外部依赖,数据准确性与服务可用性不可控。

实现:

  1. 业务处理的时候,定期从远端进行数据源拉取更新,优先使用远端数据源的数据。
  2. 为了应对远端数据源不可控风险,定期更新的时候都会将远端获取到的数据写一份到本地磁盘中进行备份,本地磁盘保存最近N个的备份。
  3. 如果远端服务数据拉取失败,则业务自动尝试从本地读取最近的备份文件以支撑自身业务的继续运行。如果最近文件处理失败或数据异常,则自动加载前一个备份文件,以此类推,直到重试完本地所有备份文件后,如果依旧处理失败,则系统不可用,放弃挣扎。

结合上述背景,可以看出实现应对策略想的还算周到,做到了使用本地备份进行远端数据请求失败情况下的兜底处理,还考虑到了数据加载异常的情况增加了自动重试机制,自动往前加载直到尝试加载到一份可用的历史备份文件为止。但是考虑一种场景:假设远端服务接口正常,返回的数据响应格式也正确,但是由于远端数据源的服务开发人员昨天夜里升级了个版本,导致下发的数据内容本身存在严重的错误,这导致下游业务使用该数据的时候业务受损。这种情况下,前面规划的实现中的所有自恢复与自保手段都是失效的。

所以呢,如果在规划阶段,在上述实现的几点保障措施的基础上,再额外规划一条
人工指令干预通道
,在紧急情况下,可以人工下发指令,强制要求系统断开与服务端的实时更新逻辑,并强制加载第X份本地备份文件,便可以快速让自身服务摆脱远端数据源的故障影响,等到其故障修复后,再下发指令恢复对远端数据源的实时更新。改造后的示意图如下所示:

4.2. 非预期场景的人工处置权

人工干预能力,也可以算作是管理端系统的一个“
特权功能
”,为后台人员提供更高的操作权限,解决某些看似不合理、却极可能出现的业务层问题,比如处理某些难缠的客诉问题。

举个简单的例子:

一个证券公司开发了一款炒股APP,并提供了一个投顾付费功能,用户付费之后就可以使用对应的高级功能。业务规划的策略是用户购买之后不允许退订,并且界面以及用户协议里面也明确提示了购买之后不允许退订。

A用户购买之后就非要退订退款,然后不停地缠着客服并威胁不处理就去监管机构、证监会等地方去投诉。

理想情况下,我们预期是用户按照产品规划的策略进行购买,并且也已经尽到告知义务,不支持用户退订。但面对客户的胡搅蛮缠,本着维护公司形象的角度出发,为尽快平息争议,客服部门会私下同意后台操作为这个用户退单退款。如果系统设计与实现的时候,没有规划构建对应的后台人工退单退款能力,处理起来就会很被动了————正所谓:
可以不用、但不能没有

5. 监控预警,防患于未然

前面提及的容错设计以及一些灾备方案,其面向的是故障已经发生的情况下,如何去应对故障来保障系统业务的可用性。而更为稳妥的一种预期,是能够
在问题刚暴露一点苗头的时候,就能被发现并及时化解掉
,这里就需要在系统实现的时候进行一些必要的
数据埋点

指标采集
监测,及时将系统的预警信息告知具体维护人员,提醒维护人员及早介入处理。

并非“看不到的问题就是没问题、看不见的故障就是没故障”,作为系统的负责人员而言,应该是要知晓系统的整体运行状态以及系统的健康度,通过状态监控、指标监测等手段,让线上系统的运行状态从黑盒变为白盒。

5.1. 监控告警

一般而言,监控平台都是独立于业务进行构建,且提供
Push
或者
Pull
两种指标数据获取机制。在监控内容方面,可以涵盖
资源
使用情况、
系统
状态、
业务
运行数据等各维度。

监控告警是开发与运维人员知晓线上系统异常状态的一个重要手段,实施的时候需要注意不要滥用告警通道。告警消息的发送最好支持
分组聚合

消息抑制
等能力,避免出现无用告警消息的狂轰滥炸,麻痹接收人员的神经、淹没真正重要的“求救”信号。同时,在构建监控告警平台的时候,考虑尽量独立于业务,让告警相关的逻辑从业务中解耦,降低监控对业务逻辑的侵蚀性。

关于如何设计与规划构建监控告警平台,有兴趣的可以看下我此前的一篇文章
《搭建一个通用监控告警平台,架构上需要有哪些设计》

5.2. 实时Dashboard

既然要
防患于未然
,首先是要对系统整体的健康状态有个清晰的认知。这个时候,系统健康监测相关的能力的价值就会显现出来。这就像是一份系统的实施体检报告,基于这份体检报告,可以发现系统中潜在的压力点位、风险环节、可疑趋势,然后可以提前介入进行应对,将故障消灭在萌芽阶段。

5.3. 灾备演练

如前所述,系统在构建的时候规划了一系列高大上的异常应对与灾难恢复手段,但如何确保这些手段在异常出现的时候能达到预期效果呢?这就得通过灾备演练来检验了。如同和平年代的军事演习,灾备演练也是很多大型项目的定期“军演”。通过模拟一些可能的灾难故障场景,去验证系统的容错与异常保障手段的有效性,发现应急方案中存在的问题并及时修复。

对于一些大型系统而言,其整个业务流程的处理会牵扯到上下游以及周边等众多依赖,一些灾备预案的实施也是上下游联动触发的。所以定期灾备演练的另一个目的,也是锻炼开发运维人员的应急预案实施的默契度。

6. 意识养成,保持对风险的敏锐识别力

如前所述,在实现层面有很多种成熟且可落地的方案可以将系统的异常应对与灾难恢复等场景变为现实,但这些其实都是具体的“

”,是我们已知有这个风险或者诉求的前提下,为了应对这些已知可能场景而作出的具体应对之法。而身为一名IT从业者,一方面要经历将业务诉求变为现实的落地过程,同时也是与各种异常情况博弈的历险之旅。
对风险的敏锐洞察力
,应该是一个优秀程序员刻在骨子里的品质。这种品质,不仅体现在编码层面、亦非局限于架构设计,而是各个方面的,它是一种
思维模式
、是一种本能的
条件反射

保持对风险的敏锐识别力,才能让自己看见潜在风险,才能让各种风险防备之术得以落地。

举个简单的、非技术实现层面的例子。

线上系统临时有个问题,需要紧急手动换个包并重启下进程修复。
  • 头铁勇士的梭哈

多简单的一件事,进程停掉,删掉旧包,上传新包,然后进程已启动,完美解决。

也许,大部分情况,的确也没出现过问题。但对于有一定资历的人员而言,看到对线上环境的这一操作,往往会有点“心惊胆战”的感觉。比如:

万一上传的包有问题,启动失败了,这个时候线上包也删了,服务也没法启动了,线上服务直接就报废了

  • 吃过小亏之后的低头

结合上述操作可能存在的风险点,改良版的做法,自然就是旧包不删、改为重命名备份起来,这样新包万一有问题,可以直接用旧包回滚恢复线上服务即可。

这种改良之后的操作方法,从可靠性层面而言,的确有很大改进,给自己留了充足的回滚与回退的余地。但是仔细审视一下,依旧有改进的余地:

  1. 上传新包的操作,由于要走网络传输、受网络波动的影响较大,存在失败风险。
  2. 如果包的体积很大、或者上传的时候要走vpn、堡垒机等层层关卡,可能速度会比较慢,整个传输过程的耗时会很长。这种情况下,可能会导致线上进程停机时间太久。
  • 受尽毒打而幸存后的谨慎

进一步的优化上述操作的步骤,可以将整个动作分为前置准备环节和线上操作环节两部分,将一些比较耗时且风险比较大的操作,放在前置准备环节中预先完成。

这样一来,在正式线上操作的环节中,仅需要执行一些确定性较高的动作,这样既可以保证执行动作的快速结束,也可以降低执行动作的不确定性。通过风险前置操作,降低了整个操作过程中出现问题的概率。

说到这里,也许有的小朋友会反驳,觉得公司带宽很高、传输文件很快,不需要这么麻烦,直接梭哈干就行了。这其实就是一个意识层面的共识问题,也是一种对风险的应对策略问题。其实还是之前的那句话,能意识到的风险并非真正的风险,往往是那些看似不可能的风险才是真正的风险。所有行为的出发点其实就一条:这个动作
操作失败的后果,是不是你能够承担的
。如果可以,那可以直接梭哈,否则的话,就要三思。

7. 再论本心:扁鹊三兄弟的故事

最后,讲个故事吧。

传说扁鹊周游到魏国的时候,魏文王接待他并问他:你家兄弟三人都是学医的,那么你们三个人中谁的医术最高呢?扁鹊回答说:“我大哥医术最高,二哥次之,我医术最差”。魏文王很困惑:“为何世人皆尊你为神医、却不曾听闻你大哥二哥?”,扁鹊解释道:

  • 我大哥的医术最好,是因为他能够在你没有发病之前就能看出你是否有病。那个时候,病人是不会觉得自己患病了的,我大哥就在病人发现之前就将病给治好了。这是因为这个缘故,大哥的医术一直不被他人认可,也没有什么名气。

  • 二哥是家中医术第二好的,因为他能够在病人发病初期就看出来,然后将病人给治好,这样一来,病人们都认为我二哥只擅长治疗一些小病症。

  • 病人找我治病时,已经到了中晚期,病情已经十分的严重了。我将那些患了重病的病人给医治好后,我就更加出名了。但从根本上来讲,我的医术比不上我的两位哥哥。

放到当前日益内卷的IT行业,扁鹊大哥、扁鹊二哥这种人,也许是属于技术高超的一类人,他们默默守护自己的代码、不给异常爆发的机会。于是呢?始终稳定的线上服务,让人慢慢淡忘了相关开发人员的存在,使其反而成为被边缘化的透明人。真正可以有机会崭露头角、博得领导青睐的,往往都是团队里面的
救火队员
般存在的人,这些人,不停的在前线冲锋陷阵,去解决线上那些按起葫芦起了瓢的问题,久而久之便成为领导心中信赖的
柱石
,相关的机会与资源也向其倾斜。

技术之外的事情,虽然发人深省,却也似乎无解。正所谓
圣人治未病,不治已乱、治未乱
,反观我们自身,如何抉择,主动权在个人、遵从本心最重要。但是相信的是,时间会证明一切,一切的坚守与技术上的追求,最终一定会被看见(有点鸡汤的味道)。所以呢,技术上有点追求,永远是个正解。

8. 小结

好啦,关于软件开发与设计过程中的异常应对与灾备能力的探讨,暂且告一段落。这里所提到的
容错应对

灾难应对
能力,其重要性犹如生活购买的保险——平日里或许显得默默无闻,甚至让人有些“成本浪费”的错觉,但在关键时刻,它们却能成为抵御风险的坚固防线,其价值无可估量。

正如小米SU7配备的备用电池,这一设计在紧急情况下为用户多了一层求生保障。在软件开发与设计的世界里,是否需要构建类似的
备用方案

灾备系统
,取决于业务对潜在损失的容忍度。若灾难性后果是业务所无法承受的,那么增加一些额外的成本,构建一套完善的灾备兜底、异常保障以及监控告警机制,就显得尤为重要了。

亦如古语云:
未雨绸缪,有备无患

我是vzn呀,聊技术、又不仅仅聊技术~

如果觉得有用,请点个关注,也可以关注下我的公众号【是vzn呀】,获取更及时的更新。

期待与你一起探讨,一起成长为更好的自己。

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: SAM4MLLM: Enhance Multi-Modal Large Language Model for Referring Expression Segmentation

创新点


  • 提出了一种允许
    MLLM
    理解像素级细节的方法
    SAM4MLLM
    ,无需改变
    MLLM
    模型架构、引入新标记或使用额外损失,该方法简单但对引用表达分割(
    RES
    )非常有效。
  • 为了连接
    MLLM

    SAM
    ,引入了一种新颖的方法,通过主动询问语言系统来获取提示点线索。
  • 在各种
    RES
    基准上进行实验,包括
    RES
    数据集、
    GRES

    ReasonSeg
    ,验证了
    SAM4MLLM
    的有效性,并展示了其在处理复杂像素感知任务中的优良性能。

内容概述


SAM4MLLM
是一种创新的方法,集成
Segment Anything Model

SAM
)与多模态大型语言模型(
MLLMs
)以实现像素感知任务。

  1. 首先,在
    MLLM
    训练数据集中引入像素级信息,而不改变原有的
    MLLM
    架构,这使得
    MLLM
    能够使用与主流
    LLM
    相同的文本交叉熵损失来理解像素级信息。
  2. 其次,考虑到输入分辨率限制和模型架构未明确设计用于视觉任务,
    MLLM
    在像素表达方面可能存在的潜在限制。进一步利用
    SAM
    增强输出,通过后处理
    MLLM
    的输出以相对简单的方式获得更高精度的分割掩码。
  3. 最后,为了在
    SAM

    MLLM
    之间建立联系,一种简单的方法是使
    MLLM
    生成
    SAM
    的提示点。利用
    LLM
    的对话能力,主动要求
    MLLM
    获取
    SAM
    的有效提示点。

SAM4MLLM
解决了
RES
问题,使得
MLLMs
能够学习像素级的位置信息。将详细的视觉信息与大型语言模型强大的表达能力以统一的基于语言的方式结合起来,而在学习中没有额外的计算开销。

SAM4MLLM


编码分割掩码为
SAM
提示

现有的用于分割的
MLLMs
依赖于模型架构的专门设计、分割特定的
token
和异构损失函数来预测对象掩码。而
SAM4MLLM
利用了
SAM
的特点,将少量文本提示
token
(边界框加上几个指示它们是否位于对象区域的点)转换为高质量的连续分割掩码。

SAM4MLLM
使用在边界框内采样的点作为离散提示。具体而言,使用一个边界框
\(Prompt_B \in \mathbb{N}^4\)

\(\mathcal{K}\)
个点来编码任意形状的掩码。
\(\mathcal{K}\)
个点的提示,每个点包含三个值:
\(x\)
坐标、
\(y\)
坐标以及它是否在掩码上,编码为
\(Prompt_P \in \mathbb{N}^{\mathcal{K} \times 3}\)

通过将连续分割掩码编码为离散的
SAM
提示,避免了添加任何
token
或改变模型结构,同时仅使用文本自回归交叉熵损失进行训练。这种方法与语言模型的原始训练模式一致,使得
MLLMs
能够理解像素级信息,并促进未来的模型扩展变得更加容易。

使用
MLLM
提示
SAM

为了将
SAM
以统一的方式纳入
MLLM
,一个主要问题在于获取
SAM
的提示点,包括在物体掩码区域内的正点(
inside
)和在外部的负点(
outside
)。为此,提出了两种解决方案:提示点生成(
Prompt-Point Generation
,
PPG
)和主动查询提示点(
Proactive Query of Prompt-Points
,
PQPP
)。

PPG
直接采用
MLLM
来生成提示点和边界框,但同时生成多个点的学习将面临挑战,因此仅使用了少量提示点。
PQPP
则利用了
MLLM
的对话能力,首先询问一个粗略的边界框,然后通过问答的方式在边界框内探测多个感兴趣的点以提示
SAM

  • SAM4MLLM-PPG

PPG
采用了一种能够同时接受文本提示和图像输入的
MLLM
。为了使
MLLM
与分割任务对齐,使用了参数高效的微调技术
LoRA
,从而基于包含图像-文本对和真实掩码的
RES
数据集进行模型训练。
LoRA
输出位置提示,包括边界框
\(Prompt_B \in \mathbb{N}^4\)

\(k\)
组正点和负点
\(Prompt_P \in \mathbb{N}^{(n_1+n_2)k \times 3}\)
,如图 (
a
) 所示,其中一组包含
\(n_1\)
个正点和
\(n_2\)
个负点(
\(n_1=2, n_2=1\)
)。

为了向
LoRA
提供位置监督,在训练阶段根据物体掩码随机采样
\(K\)
组点(
\(K>k\)
),然后将这些提示发送给
SAM
。对于每一组,
SAM
输出分割结果。过滤掉与真实掩码相比
IoU
较低的提示,仅保留前
\(k\)
组(如图 (
c
) 所示)。在该实现中,仅需要文本损失(自回归交叉熵损失)。
\(K\)
通常为
64

\(k=1\)

在推理阶段,
LoRA
直接输出发送给
SAM
进行分割的点,如图 (
b
) 所示。

  • SAM4MLLM-PQPP

PQPP
利用
MLLM
的查询-响应能力,而不是直接生成提示。对提示点进行采样,并主动询问
MLLM
这些点是否在掩码内(或外)。在训练阶段,根据真实掩码随机采样一个边界框和
\(K\)
组点,并进行两轮对话。在对话的第一轮中,
LoRA
响应一个边界框。在第二轮中,对于每个
\((n_1+n_2)K\)
个点,
LoRA
在训练期间响应该点是否在掩码内(是或否)。

在推理阶段,
LoRA
在第一轮中为输入的文本查询和图像输出一个边界框。然后,在边界框内均匀采样点并在第二轮再次发送给
MLLM-LoRA
,并询问它们是否为正点(或负点),用于
SAM
进行分割。通常将网格大小设置为
\(5\times 5\)
。为了在发送到
SAM
之前提供高质量的提示点,低置信度的点将被移除。

RES训练

为了使基础
MLLM

RES
任务对齐,使用包含与
RES
相关示例的三个数据集来指导模型朝目标前进。其中两个(
RES
数据集和
gRefCOCO
数据集)包含具有真实掩码的
RES
数据,第三个(
VQA
)是一个没有掩码的视觉对话数据集,用于进一步增强联合视觉-语言理解的总体能力。

在训练期间,为了保持
MLLM
在图像上的泛化能力,冻结了大部分网络参数,只调整了
MLLM
的视觉重采样器和
LoRA
适配器。

对于上述提到的所有数据集,我们在训练过程中不使用数据增强,因为翻转和/或裁剪可能会改变图像中物体的相对位置或关系。

主要实验




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.

Harbor 介绍

  • Harbor是一个开源的企业级Docker Registry服务,它提供了一个安全、可信赖的仓库来存储和管理Docker镜像。Harbor翻译为中文名称为"庇护;居住;"。可以理解为是Docker镜像的"居住环境"或者是镜像的"庇护所"。Harbor最初由VMware公司开发,旨在解决企业级Docker镜像管理的安全和可信任性问题。VMware于2016年发布,在2017年,VMware将Harbor开源,这使得更广泛的社区和组织可以自由地使用和贡献代码。Harbor是一个成熟、功能丰富且安全可靠的企业级Docker Registry服务,为企业容器化应用的部署和管理提供了强大的支持。

  • 我们在日常 Docker 容器使用和管理过程中,渐渐发现部署企业私有仓库往往是很有必要的, 它可以帮助你管理企业的一些敏感镜像, 同时由于 Docker Hub 的下载速度和 GFW 的原因, 往往需要将一些无法直接下载的镜像导入本地私有仓库. 而 Harbor 就是部署企业私有仓库的一个不二之选。

Harbor 特性

  • 基于角色的访问控制
    :用户与 Docker 镜像仓库通过“项目”进行组织管理,一个用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。
  • 镜像复制
    :镜像可以在多个 Registry 实例中复制(同步)。尤其适合于负载均衡,高可用,混合云和多云的场景。
  • 图形化用户界面
    :用户可以通过浏览器来浏览,检索当前 Docker 镜像仓库,管理项目和命名空间。
  • AD/LDAP 支持
    :Harbor 可以集成企业内部已有的 AD/LDAP,用于鉴权认证管理。
  • 审计管理
    :所有针对镜像仓库的操作都可以被记录追溯,用于审计管理。
  • 国际化
    :已拥有英文、中文、德文、日文和俄文的本地化版本。更多的语言将会添加进来。
  • RESTful API
    :RESTful API 提供给管理员对于 Harbor 更多的操控, 使得与其它管理软件集成变得更容易。
  • 部署简单
    :提供在线和离线两种安装工具, 也可以安装到 vSphere 平台(OVA 方式)虚拟设备。

Harbor 和 Registry的比较

Harbor和Registry都是Docker的镜像仓库,但是Harbor作为更多企业的选择,是因为相比较于Regisrty来说,它具有很多的优势。

  • 提供基于Web界面的图形化管理界面,操作更友好。
  • 支持用户、项目和镜像的访问控制机制。
  • 可以对镜像进行扫描查找漏洞,提升安全性。
  • 完全支持LDAP/AD等标准化的企业用户管理。
  • 可以很好地集成到CI/CD流程中。
  • 提供API开放功能,便于第三方系统对接。

很适合团队和中小企业使用。

Harbor 架构

  • proxy
    :对应启动组件nginx。它是一个nginx反向代理,代理Notary client(镜像认证)、docker client(镜像上传下载)和浏览器的访问请求(Core Service)给后端的各服务器。
  • UI(Core Service)
    :对应启动组件harbor-ui。底层数据存储使用mysql数据库,主要提供了四个子功能。
    • UI
      :一个web管理页面ui
    • API
      :Harbor暴露的API服务。
    • Auth
      :用户认证服务,decode后的token中的用户信息在这里进行认证;auth后端可以接db、ldap、uaa三种认证实现。
    • Token服务
      :负责根据用户在每个project中的role来为每个docker push/pull 命令发布一个token,如果docker client发送给registry的请求没有带token,registry会重定向请求到token服务创建token。
  • Registry
    :对应启动组件registry。负责存储镜像文件和处理镜像的pull/push命令。Harbor对镜像进行强制的访问控制,Registry会将每个客户端的每个pull/push请求转发到token服务来获取有效的token。
  • Admin Service
    :对应启动组件harbor-admin server。是系统的配置管理中心附带检查存储用量,ui和jobserver启动时需要加载adminserver配置。
  • job server
    :对应启动组件harbor-jobservice。负责镜像复制工作,它和Registry通信。从一个Registry pull镜像然后push到另一个Registry,并记录job_log.
  • Log Collector
    :对应启动组件harbor-log。日志汇总组件,通过docker的log-driver把日志汇总到一起。
  • DB
    :对应启动组件harbor-db,负责存储project、user、role、replication、image_scan、access等的metadata数据。

Harbor 大概需要以下几个容器组成:

  • ui (Harbor的核心服务)。
  • log (运行着rsyslog的容器,进行日志收集)。
  • mysql (由官方mysql镜像构成的数据库容器)。
  • Nginx (使用Nginx做反向代理)。
  • registry (官方的Docker registry)。
  • adminserver (Harbor的配置数据管理器)。
  • jobservice (Harbor的任务管理服务)。
  • redis (用于存储session)。

本文harbor安装版本:harbor-offline-installer-v2.11.1.tgz
需要特别注意:由于Harbor是基于Docker Registry V2版本,所以Docker Engine必须大于20.10.10-ce+版本,docker-compose必须要大于v1.18.0+版本!

安装 Docker

详细安装步骤可参考

# 安装必要的依赖包
root@ubuntu2204:~# apt install apt-transport-https ca-certificates curl gnupg lsb-release -y

# 添加软件源的GPG密钥及docker源(阿里)
root@ubuntu2204:~# curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
root@ubuntu2204:~# add-apt-repository "deb [arch=$(dpkg --print-architecture)] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

# 选择安装指定稳定版, 例如 5:25.0.5-1~ubuntu.22.04~jammy
root@ubuntu2204:~# apt-cache madison docker-ce | awk '{ print $3 }'
5:27.3.1-1~ubuntu.22.04~jammy
5:27.3.0-1~ubuntu.22.04~jammy
......
5:26.0.2-1~ubuntu.22.04~jammy
5:26.0.1-1~ubuntu.22.04~jammy
5:26.0.0-1~ubuntu.22.04~jammy
5:25.0.5-1~ubuntu.22.04~jammy
5:25.0.4-1~ubuntu.22.04~jammy
......

root@ubuntu2204:~# VERSION_STRING=5:25.0.5-1~ubuntu.22.04~jammy
root@ubuntu2204:~# apt-get install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin docker-compose-plugin -y

# 查看docker信息
root@ubuntu2204:~# docker -v
Docker version 25.0.5, build 5dc9bcc
root@ubuntu2204:~#
root@ubuntu2204:~# docker version
Client: Docker Engine - Community
 Version:           25.0.5
 API version:       1.44
 Go version:        go1.21.8
 Git commit:        5dc9bcc
 Built:             Tue Mar 19 15:05:10 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          25.0.5
  API version:      1.44 (minimum version 1.24)
  Go version:       go1.21.8
  Git commit:       e63daec
  Built:            Tue Mar 19 15:05:10 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.22
  GitCommit:        7f7fdf5fed64eb6a7caf99b3e12efcf9d60e311c
 runc:
  Version:          1.1.14
  GitCommit:        v1.1.14-0-g2c9f560
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

root@ubuntu2204:~# ps -ef | grep docker
root       16048       1  0 14:56 ?        00:00:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root       16245   12531  0 14:57 pts/1    00:00:00 grep --color=auto docker

# 启动|停止|重启|查看|开机自启
root@ubuntu2204:~# systemctl start|stop|restart|status|enable docker
 
# 测试 docker 是否安装正确
root@ubuntu2204:~# docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:d211f485f2dd1dee407a80973c8f129f00d54604d2c90732e8e320e5038a0348
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

# 若能正常输出以上信息,则说明安装成功。

安装 Harbor 仓库

配置HTTPS证书

# 生成证书颁发机构证书
# 1.生成 CA 证书私钥
root@ubuntu2204:~# openssl genrsa -out ca.key 4096

# 2.生成 CA 证书
root@ubuntu2204:~# openssl req -x509 -new -nodes -sha512 -days 3650 -subj "/C=CN/ST=GD/L=GZ/O=DovOps/OU=IT/CN=zwc.harbor.com" -key ca.key -out ca.crt

# 参数说明:
#   C,Country,代表国家
#   ST,STate,代表省份
#   L,Location,代表城市
#   O,Organization,代表组织,公司
#   OU,Organization Unit,代表部门
#   CN,Common Name,代表服务器域名


# 生成服务器证书 
# 1.生成私钥
root@ubuntu2204:~# openssl genrsa -out zwc.harbor.com.key 4096

# 2.生成证书签名请求 (CSR)
root@ubuntu2204:~# openssl req -sha512 -new -subj "/C=CN/ST=GD/L=GZ/O=DovOps/OU=IT/CN=zwc.harbor.com" -key zwc.harbor.com.key -out zwc.harbor.com.csr

# 3.生成 x509 v3 扩展文件
root@ubuntu2204:~# cat > v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1=zwc.harbor.com
DNS.2=svr.harbor.com
EOF

# 4.使用 v3.ext 文件来为您的 Harbor 主机生成证书
root@ubuntu2204:~# openssl x509 -req -sha512 -days 3650 -extfile v3.ext -CA ca.crt -CAkey ca.key -CAcreateserial -in zwc.harbor.com.csr -out zwc.harbor.com.crt
Certificate request self-signature ok
subject=C = CN, ST = GD, L = GZ, O = DovOps, OU = IT, CN = zwc.harbor.com


# 向 Harbor 和 Docker 提供证书 
# 1.将服务器证书和密钥复制到 Harbor 主机上的 certficates 文件夹中(根据自己实际环境)
root@ubuntu2204:~# mkdir -p /data/app/harbor/certs
root@ubuntu2204:~# cp zwc.harbor.com.crt  /data/app/harbor/certs
root@ubuntu2204:~# cp zwc.harbor.com.key  /data/app/harbor/certs

# 2.转变 zwc.harbor.com.crt 到 docker.zwc.harbor.com.crt,供 Docker 使用
root@ubuntu2204:~# openssl x509 -inform PEM -in zwc.harbor.com.crt -out docker.zwc.harbor.com.cert

# 3.将服务器证书、密钥和 CA 文件复制到 Harbor 主机上的 Docker 证书文件夹中。您必须首先创建适当的文件夹。 
root@ubuntu2204:~# mkdir -p /etc/docker/certs.d/zwc.harbor.com
root@ubuntu2204:~# cp docker.zwc.harbor.com.cert /etc/docker/certs.d/zwc.harbor.com
root@ubuntu2204:~# cp zwc.harbor.com.key /etc/docker/certs.d/zwc.harbor.com
root@ubuntu2204:~# cp ca.crt /etc/docker/certs.d/zwc.harbor.com

# 4.重新启动 Docker 
root@ubuntu2204:~# systemctl restart docker

# 说明
root@ubuntu2204:~# tree /etc/docker/certs.d/
/etc/docker/certs.d/
└── zwc.harbor.com
    ├── ca.crt                      <-- Certificate authority that signed the registry certificate
    ├── docker.zwc.harbor.com.cert  <-- Server certificate signed by CA
    └── zwc.harbor.com.key          <-- Server key signed by CA

下载安装 Harbor

# 获取Harbor最新稳定版
root@ubuntu2204:~# curl -s https://api.github.com/repos/goharbor/harbor/releases/latest | grep browser_download_url | cut -d '"' -f4 | grep '.tgz'
https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-offline-installer-v2.11.1.tgz
https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-offline-installer-v2.11.1.tgz.asc
https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-online-installer-v2.11.1.tgz
https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-online-installer-v2.11.1.tgz.asc

# 下载离线Harbor
root@ubuntu2204:~# wget https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-offline-installer-v2.11.1.tgz

root@ubuntu2204:~# tar -xf harbor-offline-installer-v2.11.1.tgz -C /data/app
root@ubuntu2204:~# cd /data/app/harbor/
root@ubuntu2204:/data/app/harbor# ls
LICENSE  certs  common.sh  harbor.v2.11.1.tar.gz  harbor.yml.tmpl  install.sh  prepare


# 编辑harbor.yml配置文件
root@ubuntu2204:/data/app/harbor# cp harbor.yml.tmpl harbor.yml
root@ubuntu2204:/data/app/harbor# vim harbor.yml
......
  4 # DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
  5 hostname: 172.16.70.162  //设置访问地址,可以是ip、主机名,不可以设置为127.0.0.1或localhost
  6
  7 # http related config
  8 http:  //启用http
  9   # port for http, default is 80. If https enabled, this port will redirect to https port
 10   port: 80  //默认http端口
 11
 12 # https related config
 13 https:  //启用https(注释则为禁用)
 14   # https port for harbor, default is 443
 15   port: 443
 16   # The path of cert and key files for nginx
 17   certificate: /data/app/harbor/certs/zwc.harbor.com.crt  //启用时,证书路径(禁用则注释)
 18   private_key: /data/app/harbor/certs/zwc.harbor.com.key  //启用时,私钥路径(禁用则注释)
 19   # enable strong ssl ciphers (default: false)
 20   # strong_ssl_ciphers: false
......
 46 # Remember Change the admin password from UI after launching Harbor.
 47 harbor_admin_password: Harbor@54321  //修改harbor登录密码
......
 65 # The default data volume
 66 data_volume: /data/app/harbor/data  //修改harbor仓库数据目录(安装Harbor时会自动创建)
......
164     # The directory on your host that store log
165     location: /data/app/harbor/log/harbor  //修改日志路径(安装Harbor时会自动创建)
......


# 安装并启动trivy漏洞扫描工具
root@ubuntu2204:/data/app/harbor# ./install.sh --with-trivy
[Step 0]: checking if docker is installed ...

Note: docker version: 25.0.5

[Step 1]: checking docker-compose is installed ...

Note: Docker Compose version v2.29.7

[Step 2]: loading Harbor images ...
......

[Step 3]: preparing environment ...

[Step 4]: preparing harbor configs ...
prepare base dir is set to /data/app/harbor
......
[Step 5]: starting Harbor ...
......

✔ ----Harbor has been installed and started successfully.----  # 提示安装成功

root@ubuntu2204:/data/app/harbor# ls
LICENSE  certs  common  common.sh  data  docker-compose.yml  harbor.v2.11.1.tar.gz  harbor.yml  harbor.yml.tmpl  install.sh  log  prepare


# 查看运行中的harbor相关容器(应该是启动10个容器)
root@ubuntu2204:/data/app/harbor# docker ps -a
CONTAINER ID   IMAGE                                   COMMAND                  CREATED              STATUS                        PORTS                           NAMES
545f561eabfd   goharbor/harbor-jobservice:v2.11.1      "/harbor/entrypoint.…"   About a minute ago   Up About a minute (healthy)                                   harbor-jobservice
85e7e42c1ea4   goharbor/nginx-photon:v2.11.1           "nginx -g 'daemon of…"   About a minute ago   Up About a minute (healthy)   0.0.0.0:80->8080/tcp, \   	
																										:::80->8080/tcp, 0.0.0.0:443->8443/tcp, :::443->8443/tcp   nginx
938f3c7392ee   goharbor/trivy-adapter-photon:v2.11.1   "/home/scanner/entry…"   About a minute ago   Up About a minute (healthy)                                   trivy-adapter
8e12aab73943   goharbor/harbor-core:v2.11.1            "/harbor/entrypoint.…"   About a minute ago   Up About a minute (healthy)                                   harbor-core
c8d115b85841   goharbor/harbor-db:v2.11.1              "/docker-entrypoint.…"   About a minute ago   Up About a minute (healthy)                                   harbor-db
30c6ab0f77c6   goharbor/harbor-registryctl:v2.11.1     "/home/harbor/start.…"   About a minute ago   Up About a minute (healthy)                                   registryctl
ad0ec6ffdfb8   goharbor/redis-photon:v2.11.1           "redis-server /etc/r…"   About a minute ago   Up About a minute (healthy)                                   redis
b35f315c7932   goharbor/registry-photon:v2.11.1        "/home/harbor/entryp…"   About a minute ago   Up About a minute (healthy)                                   registry
2edff162d014   goharbor/harbor-portal:v2.11.1          "nginx -g 'daemon of…"   About a minute ago   Up About a minute (healthy)                                   harbor-portal
e6736ea4ca01   goharbor/harbor-log:v2.11.1             "/bin/sh -c /usr/loc…"   About a minute ago   Up About a minute (healthy)   127.0.0.1:1514->10514/tcp       harbor-log


# 查看docker相关端口
root@ubuntu2204:/data/app/harbor# netstat -ntpl | grep docker
tcp        0      0 127.0.0.1:1514          0.0.0.0:*               LISTEN      17754/docker-proxy
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      18387/docker-proxy
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      18427/docker-proxy
tcp6       0      0 :::443                  :::*                    LISTEN      18398/docker-proxy
tcp6       0      0 :::80                   :::*                    LISTEN      18436/docker-proxy

# 查看本机IP
root@ubuntu2204:/data/app/harbor# hostname -I
172.16.70.162 172.17.0.1 172.18.0.1

安装 docker-compose

root@ubuntu2204:~# VERSION_COMPOSE=v2.29.7
root@ubuntu2204:~# curl -L "https://github.com/docker/compose/releases/download/$VERSION_COMPOSE/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 
# 赋执行权限
root@ubuntu2204:~# chmod +x /usr/local/bin/docker-compose 
root@ubuntu2204:~# docker-compose -v
Docker Compose version v2.29.7

root@ubuntu2204:~# docker-compose -f /data/app/harbor/docker-compose.yml ps
WARN[0000] /data/app/harbor/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
NAME                IMAGE                                   COMMAND                  SERVICE         CREATED              STATUS                        PORTS
harbor-core         goharbor/harbor-core:v2.11.1            "/harbor/entrypoint.…"   core            About a minute ago   Up About a minute (healthy)
harbor-db           goharbor/harbor-db:v2.11.1              "/docker-entrypoint.…"   postgresql      About a minute ago   Up About a minute (healthy)
harbor-jobservice   goharbor/harbor-jobservice:v2.11.1      "/harbor/entrypoint.…"   jobservice      About a minute ago   Up About a minute (healthy)
harbor-log          goharbor/harbor-log:v2.11.1             "/bin/sh -c /usr/loc…"   log             About a minute ago   Up About a minute (healthy)   127.0.0.1:1514->10514/tcp
harbor-portal       goharbor/harbor-portal:v2.11.1          "nginx -g 'daemon of…"   portal          About a minute ago   Up About a minute (healthy)
nginx               goharbor/nginx-photon:v2.11.1           "nginx -g 'daemon of…"   proxy           About a minute ago   Up About a minute (healthy)   0.0.0.0:80->8080/tcp, \
[::]:80->8080/tcp, 0.0.0.0:443->8443/tcp, [::]:443->8443/tcp
redis               goharbor/redis-photon:v2.11.1           "redis-server /etc/r…"   redis           About a minute ago   Up About a minute (healthy)
registry            goharbor/registry-photon:v2.11.1        "/home/harbor/entryp…"   registry        About a minute ago   Up About a minute (healthy)
registryctl         goharbor/harbor-registryctl:v2.11.1     "/home/harbor/start.…"   registryctl     About a minute ago   Up About a minute (healthy)
trivy-adapter       goharbor/trivy-adapter-photon:v2.11.1   "/home/scanner/entry…"   trivy-adapter   18 minutes ago       Up 4 minutes (healthy)


# 注意:如果harbor.yml配置修改了,要先执行"./prepare"命令进行配置载入,然后再重启harbor服务。
# 停止|启动|重启
root@ubuntu2204:~# docker-compose stop|start|restart

# 另外:
#   docker-compose down -v	# 停止并删除容器(数据保留在文件系统中,因此不会丢失任何数据)
#   docker-compose up -d	# 创建并启动容器

修改admin密码

# 1.修改harbor的登陆用户密码,则最好在harbor web界面里直接修改。

# 2.忘记harbor的web密码,建议删除data源数据的database,重新部署。
# docker-compose down -v
# rm -rf /data/app/harbor/data/database
# vim harbor.yaml           #在这里重置或修改密码
# docker-compose up -d
  • 修改本地WIN电脑hosts文件,做好harbor主机的域名解析
    • 172.16.70.162 zwc.harbor.com
    • 172.16.70.162 svr.harbor.com
  • 将名为"ca.crt"的CA证书下载到本地WIN电脑,浏览器再导入该证书
    • 1.Web 域名访问:
      https://zwc.harbor.com
    • 2.Web IP 访问:
      https://172.16.70.162
  • 默认账号:admin , 密码:Harbor@54321 (对应harbor.yml中的配置)

使用 Harbot 仓库

首先在Harbor web界面里最好创建一个自己需要的"项目" (或者使用默认的"library"项目),项目公开和私有:

  • Public: 所有用户对于公开项目都有读权限。
  • Private: 私有项目只能被有特定用户权限的人去访问。

如创建一个公开项目"202411_public",点击进去可以看到推送命令的信息提示。

  • 镜像打标签的命令:
    docker tag 镜像名:标签 harbot仓库地址/仓库项目名/镜像名:标签


    • docker tag SOURCE_IMAGE[:TAG] 172.16.70.162/202411_public/REPOSITORY[:TAG]
  • 推送到harbot仓库的命令:
    docker push harbot仓库地址/仓库项目名/镜像名:标签


    • docker push 172.16.70.162/202411_public/REPOSITORY[:TAG]
  • 从harbot仓库拉取镜像的命令:
    docker pull harbot仓库地址/仓库项目名/镜像名:标签


    • docker pull 172.16.70.162/202411_public/REPOSITORY[:TAG]

harbor登录 镜像推/拉

# 登录报错
root@ubuntu2204:~# docker login 172.16.70.162
Username: admin
Password:
Error response from daemon: Get "http://172.16.70.162/v2/": dial tcp 172.16.70.162:80: connect: connection refused

# 解决办法
root@ubuntu2204:~# cat /etc/docker/daemon.json
{
    "registry-mirrors": [
        "https://dockerpull.com",
        "https://docker.anyhub.us.kg",
        "https://dockerhub.jobcher.com",
    ],
    "insecure-registries": ["172.16.70.162"]  # 添加此行
}

# 修改过后重启docker, 重启Harbor服务
root@ubuntu2204:~# systemctl daemon-reload && systemctl restart docker
root@ubuntu2204:~# docker-compose -f /data/app/harbor/docker-compose.yml restart

# 再次登录
root@ubuntu2204:~# docker login 172.16.70.162
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

# 登录账号信息保存位置,若不删除后续登录,则无需输入用户名和密码
root@ubuntu2204:~# cat /root/.docker/config.json
{
	"auths": {
		"172.16.70.162": {
			"auth": "YWRtaW46SGFyYm9yQDU0MzIx"
		}
	}

# 1.查看本地镜像
root@ubuntu2204:~# docker images
REPOSITORY                      TAG       IMAGE ID       CREATED        SIZE
goharbor/harbor-exporter        v2.11.1   cdf68efc001e   2 months ago   114MB
goharbor/redis-photon           v2.11.1   acf90a312d47   2 months ago   170MB
goharbor/trivy-adapter-photon   v2.11.1   24a8273e807a   2 months ago   339MB
goharbor/harbor-registryctl     v2.11.1   43fca2a06374   2 months ago   168MB
goharbor/registry-photon        v2.11.1   9da6663b36f2   2 months ago   90.3MB
goharbor/nginx-photon           v2.11.1   193a1b77b7d4   2 months ago   159MB
goharbor/harbor-log             v2.11.1   2752e033bfbb   2 months ago   169MB
goharbor/harbor-jobservice      v2.11.1   a8005a88b3dc   2 months ago   165MB
goharbor/harbor-core            v2.11.1   eaf65baad3f6   2 months ago   191MB
goharbor/harbor-portal          v2.11.1   f58813018a49   2 months ago   167MB
goharbor/harbor-db              v2.11.1   be56f8030c48   2 months ago   277MB
goharbor/prepare                v2.11.1   1d00ffdb2e67   2 months ago   216MB

# 2.本地镜像打标签并推送至Harbor
root@ubuntu2204:~# docker tag goharbor/nginx-photon:v2.11.1 172.16.70.162/202411_public/nginx-photon:v2
root@ubuntu2204:~# docker images | grep nginx
172.16.70.162/202411_public/nginx-photon   v2        193a1b77b7d4   2 months ago   159MB
goharbor/nginx-photon                      v2.11.1   193a1b77b7d4   2 months ago   159MB

root@ubuntu2204:~# docker push 172.16.70.162/202411_public/nginx-photon:v2
The push refers to repository [172.16.70.162/202411_public/nginx-photon]
7a130cf406bb: Pushed
fa65d0b345aa: Pushed
v2: digest: sha256:b7a54e6b04ffe19096cc5a788fa3364bc2dea742c26a990ea3270bf20eaa723d size: 741

# 3.拉取dockerhub镜像,打标签并推至Harbor
root@ubuntu2204:~# docker pull mysql:8.4.3
root@ubuntu2204:~# docker tag mysql:8.4.3 172.16.70.162/202411_public/mysql:8.4.3
root@ubuntu2204:~# docker images | grep mysql
172.16.70.162/202411_public/mysql          8.4.3     ed66f13824d5   4 weeks ago    592MB
mysql                                      8.4.3     ed66f13824d5   4 weeks ago    592MB

root@ubuntu2204:~# docker push 172.16.70.162/202411_public/mysql:8.4.3
The push refers to repository [172.16.70.162/202411_public/mysql]
488946e535dc: Pushed
c6a372379ade: Pushed
4baca2f64123: Pushed
e7f948391a9f: Pushed
3f0758c2bc58: Pushed
96bad7ffa575: Pushed
9a7be671c0ad: Pushed
4dae3171e4f9: Pushed
5cebbdcae534: Pushed
217e34a4f824: Pushed
8.4.3: digest: sha256:0b6d2de7d79984b386696b75aca8341fea4456775e2b22f806a463f2199d4624 size: 2411
  • 查看Harob仓库

在操作系统的世界里,Windows 系统一直占据着重要的地位。然而,你可能不知道的是,还有一个拥有 14.7K star 的开源替代版 ——
ReactOS

1、ReactOS 介绍

ReactOS 是一个自由开源的操作系统,其目标是实现与 Windows 系统在二进制级别上的兼容。这意味着,我们可以在这个系统上直接运行那些为Windows设计的软件和驱动程序,而无需担心兼容性问题。

2、ReactOS 的特点

1、开源免费

作为一个开源项目,ReactOS 允许任何人查看、修改和分发其源代码。这不仅为开发者提供了学习和创新的机会,也为用户提供了一个免费的操作系统选择。且项目源码大部分采用C和C++来实现的

2、兼容性强

ReactOS 致力于实现与 Windows 系统的高度兼容。它可以运行许多 Windows 应用程序,包括办公软件、游戏、多媒体播放器等。这使得用户在切换到 ReactOS 时,无需担心应用程序的兼容性问题。

3、轻量级

与一些庞大的操作系统相比,ReactOS 相对轻量级。它可以在较低配置的计算机上运行,为那些拥有老旧设备的用户提供了一个可行的选择。

4、多语言支持

开源的特性使得 ReactOS 的安全性可以得到更广泛的审查和改进。开发者和用户可以共同努力,发现和修复潜在的安全漏洞,提高系统的安全性。

:为了让全球用户都能轻松上手,ReactOS支持多种语言。无论你是中文爱好者,还是英文达人,都能找到适合自己的语言设置。

3、ReactOS 的应用场景

ReactOS不仅仅是一个替代品,更是一个让技术爱好者们可以尽情探索和实验的乐园。和Wine(Linux下的Windows兼容层)不同,ReactOS是一个完整的操作系统,而不是运行在其他系统上的模拟层,也不是基于Linux的,适合如下场景下使用。

1、老旧设备复活

对于那些拥有老旧计算机的用户,ReactOS 可以让他们的设备重新焕发生机。由于其轻量级的特点,它可以在较低配置的设备上流畅运行,延长设备的使用寿命。

2、软件开发和测试

开发者可以使用 ReactOS 进行软件开发和测试。由于它与 Windows 系统的兼容性,开发者可以在 ReactOS 上测试他们的应用程序,确保其在不同的操作系统环境下都能正常运行。

3、教育和学习

对于学习操作系统原理和软件开发的学生来说,ReactOS 是一个很好的学习资源。通过研究其源代码,学生可以深入了解操作系统的内部工作原理,提高他们的编程技能。

4、如何安装

如果想体验一下ReactOS,过程其实非常简单,首先去ReactOS官网:
https://reactos.org/
下载最新的ISO镜像文件。


镜像下载完成后,可以使用Vmware来安装它。

详细的安装步骤查参考WIKI:
https://reactos.org/wiki/Installing_ReactOS

如果你对操作系统底层开发感兴趣,或者手上有一些老旧的Windows应用想要复活,不妨试试ReactOS,它也许就是你的神奇解药。更多细节功能,感兴趣的可以到项目地址查看:

项目地址:
https://github.com/reactos/reactos