[TOC] ### 8.7.1 名词解释 * Tablet:DorisDB 表的逻辑分片,一个表有多个分片。 * Replica:分片的副本,默认一个分片有3个副本。 * Healthy Replica:健康副本,副本所在 Backend 存活,且副本的版本完整。 * TabletChecker(TC):是一个常驻的后台线程,用于定期扫描所有的 Tablet,检查这些 Tablet 的状态,并根据检查结果,决定是否将 tablet 发送给 TabletScheduler。 * TabletScheduler(TS):是一个常驻的后台线程,用于处理由 TabletChecker 发来的需要修复的 Tablet。同时也会进行集群副本均衡的工作。 * TabletSchedCtx(TSC):是一个 tablet 的封装。当 TC 选择一个 tablet 后,会将其封装为一个 TSC,发送给 TS。 * Storage Medium:存储介质。DorisDB 支持对分区粒度指定不同的存储介质,包括 SSD 和 HDD。副本调度策略也是针对不同的存储介质分别调度的。 ### 8.7.2 副本状态 #### 8.7.2.1 Replica状态 一个 Replica 的健康状态有以下几种: * **BAD** 即副本损坏。包括但不限于磁盘故障、BUG等引起的副本不可恢复的损毁状态。 * **VERSION\_MISSING** 版本缺失。DorisDB 中每一批次导入都对应一个数据版本。而一个副本的数据由多个连续的版本组成。而由于导入错误、延迟等原因,可能导致某些副本的数据版本不完整。 * **HEALTHY** 健康副本。即数据正常的副本,并且副本所在的 BE 节点状态正常(心跳正常且不处于下线过程中)。 #### 8.7.2.2 Tablet状态 一个 Tablet 的健康状态由其所有副本的状态决定,有以下几种: * **REPLICA\_MISSING** 副本缺失。即存活副本数小于期望副本数。 * **VERSION\_INCOMPLETE** 存活副本数大于等于期望副本数,但其中健康副本数小于期望副本数。 * **REPLICA\_RELOCATING** 拥有等于 replication num 的版本完整的存活副本数,但是部分副本所在的 BE 节点处于 unavailable 状态(比如 decommission 中) * **REPLICA\_MISSING\_IN\_CLUSTER** 当使用多 cluster 方式时,健康副本数大于等于期望副本数,但在对应 cluster 内的副本数小于期望副本数。 * **REDUNDANT** 副本冗余。健康副本都在对应 cluster 内,但数量大于期望副本数。或者有多余的 unavailable 副本。 * **FORCE\_REDUNDANT** 这是一个特殊状态。只会出现在当期望副本数大于等于可用节点数时,并且 Tablet 处于副本缺失状态时出现。这种情况下,需要先删除一个副本,以保证有可用节点用于创建新副本。 * **COLOCATE\_MISMATCH** 针对 Colocation 属性的表的分片状态。表示分片副本与 Colocation Group 的指定的分布不一致。 * **COLOCATE\_REDUNDANT** 针对 Colocation 属性的表的分片状态。表示 Colocation 表的分片副本冗余。 * **HEALTHY** 健康分片,即条件\[1-8\]都不满足 ### 8.7.3 副本修复 TabletChecker 作为常驻的后台进程,会定期检查所有分片的状态。对于非健康状态的分片,将会交给 TabletScheduler 进行调度和修复。修复的实际操作,都由 BE 上的 clone 任务完成。FE 只负责生成这些 clone 任务。 > 注1:副本修复的主要思想是先通过创建或补齐使得分片的副本数达到期望值,然后再删除多余的副本。注2:一个 clone 任务就是完成从一个指定远端 BE 拷贝指定数据到指定目的端 BE 的过程。 针对不同的状态,我们采用不同的修复方式: 1. REPLICA\_MISSING/REPLICA\_RELOCATING 2. 选择一个低负载的,可用的 BE 节点作为目的端。选择一个健康副本作为源端。clone 任务会从源端拷贝一个完整的副本到目的端。对于副本补齐,我们会直接选择一个可用的 BE 节点,而不考虑存储介质。 3. VERSION\_INCOMPLETE 4. 选择一个相对完整的副本作为目的端。选择一个健康副本作为源端。clone 任务会从源端尝试拷贝缺失的版本到目的端的副本。 5. REPLICA\_MISSING\_IN\_CLUSTER 6. 这种状态处理方式和 REPLICA\_MISSING 相同。 7. REDUNDANT 8. 通常经过副本修复后,分片会有冗余的副本。我们选择一个冗余副本将其删除。冗余副本的选择遵从以下优先级: * 副本所在 BE 已经下线 * 副本已损坏 * 副本所在 BE 失联或在下线中 * 副本处于 CLONE 状态(该状态是 clone 任务执行过程中的一个中间状态) * 副本有版本缺失 * 副本所在 cluster 不正确 * 副本所在 BE 节点负载高 9. FORCE\_REDUNDANT 10. 不同于 REDUNDANT,因为此时虽然 Tablet 有副本缺失,但是因为已经没有额外的可用节点用于创建新的副本了。所以此时必须先删除一个副本,以腾出一个可用节点用于创建新的副本。 删除副本的顺序同 REDUNDANT。 11. COLOCATE\_MISMATCH 12. 从 Colocation Group 中指定的副本分布 BE 节点中选择一个作为目的节点进行副本补齐。 13. COLOCATE\_REDUNDANT 14. 删除一个非 Colocation Group 中指定的副本分布 BE 节点上的副本。 DorisDB 在选择副本节点时,不会将同一个 Tablet 的副本部署在同一个 host 的不同 BE 上。保证了即使同一个 host 上的所有 BE 都挂掉,也不会造成全部副本丢失。 ### 8.7.4 副本调度 #### 8.7.4.1 调度优先级 TabletScheduler 里等待被调度的分片会根据状态不同,赋予不同的优先级。优先级高的分片将会被优先调度。目前有以下几种优先级。 * VERY\_HIGH * REDUNDANT。对于有副本冗余的分片,我们优先处理。虽然逻辑上来讲,副本冗余的紧急程度最低,但是因为这种情况处理起来最快且可以快速释放资源(比如磁盘空间等),所以我们优先处理。 * FORCE\_REDUNDANT。同上。 * HIGH * REPLICA\_MISSING 且多数副本缺失(比如3副本丢失了2个) * VERSION\_INCOMPLETE 且多数副本的版本缺失 * COLOCATE\_MISMATCH 我们希望 Colocation 表相关的分片能够尽快修复完成。 * COLOCATE\_REDUNDANT * NORMAL * REPLICA\_MISSING 但多数存活(比如3副本丢失了1个) * VERSION\_INCOMPLETE 但多数副本的版本完整 * REPLICA\_RELOCATING 且多数副本需要 relocate(比如3副本有2个) * LOW * REPLICA\_MISSING\_IN\_CLUSTER * REPLICA\_RELOCATING 但多数副本 stable #### 8.7.4.2 手动优先级 系统会自动判断调度优先级。但是有些时候,用户希望某些表或分区的分片能够更快的被修复。因此我们提供一个命令,用户可以指定某个表或分区的分片被优先修复: ADMIN REPAIR TABLE tbl \[PARTITION (p1, p2, ...)\]; 此命令告诉 TC,在扫描 Tablet 时,对需要优先修复的表或分区中的有问题的 Tablet,给予 VERY\_HIGH 的优先级。 > 注:此命令只是一个 hint,并不能保证一定能修复成功,且优先级也会随 TS 的调度而发生变化。并且当 Master FE 切换或重启后,这些信息都会丢失。 可以通过以下命令取消优先级: ADMIN CANCEL REPAIR TABLE tbl \[PARTITION (p1, p2, ...)\]; #### 8.7.4.3 优先级调度 优先级保证了损坏严重的分片能够优先被修复,提高系统可用性。但是如果高优先级的修复任务一直失败,则会导致低优先级的任务一直得不到调度。因此,我们会根据任务的运行状态,动态的调整任务的优先级,保证所有任务都有机会被调度到。 * 连续5次调度失败(如无法获取资源,无法找到合适的源端或目的端等),则优先级会被下调。 * 持续 30 分钟未被调度,则上调优先级。 * 同一 tablet 任务的优先级至少间隔 5 分钟才会被调整一次。 同时为了保证初始优先级的权重,我们规定,初始优先级为 VERY\_HIGH 的,最低被下调到 NORMAL。而初始优先级为 LOW 的,最多被上调为 HIGH。这里的优先级调整,也会调整用户手动设置的优先级。 ### 8.7.5 副本均衡 DorisDB 会自动进行集群内的副本均衡。均衡的主要思想,是对某些分片,先在低负载的节点上创建一个副本,然后再删除这些分片在高负载节点上的副本。同时,因为不同存储介质的存在,在同一个集群内的不同 BE 节点上,可能存在一种或两种存储介质。我们要求存储介质为 A 的分片在均衡后,尽量依然存储在存储介质 A 中。所以我们根据存储介质,对集群的 BE 节点进行划分。然后针对不同的存储介质的 BE 节点集合,进行负载均衡调度。 同样,副本均衡会保证不会将同一个 Tablet 的副本部署在同一个 host 的 BE 上。 #### 8.7.5.1 BE 节点负载 我们用 ClusterLoadStatistics(CLS)表示一个 cluster 中各个 Backend 的负载均衡情况。TabletScheduler 根据这个统计值,来触发集群均衡。我们当前通过 **磁盘使用率** 和 **副本数量** 两个指标,为每个BE计算一个 loadScore,作为 BE 的负载分数。分数越高,表示该 BE 的负载越重。 磁盘使用率和副本数量各有一个权重系数,分别为 **capacityCoefficient** 和 **replicaNumCoefficient**,其 **和衡为1**。其中 capacityCoefficient 会根据实际磁盘使用率动态调整。当一个 BE 的总体磁盘使用率在 50% 以下,则 capacityCoefficient 值为 0.5,如果磁盘使用率在 75%(可通过 FE 配置项 `capacity_used_percent_high_water` 配置)以上,则值为 1。如果使用率介于 50% ~ 75% 之间,则该权重系数平滑增加,公式为: `capacityCoefficient= 2 * 磁盘使用率 - 0.5` 该权重系数保证当磁盘使用率过高时,该 Backend 的负载分数会更高,以保证尽快降低这个 BE 的负载。 TabletScheduler 会每隔 1 分钟更新一次 CLS。 #### 8.7.5.2 均衡策略 TabletScheduler 在每轮调度时,都会通过 LoadBalancer 来选择一定数目的健康分片作为 balance 的候选分片。在下一次调度时,会尝试根据这些候选分片,进行均衡调度。 ### 8.7.6 资源控制 无论是副本修复还是均衡,都是通过副本在各个 BE 之间拷贝完成的。如果同一台 BE 同一时间执行过多的任务,则会带来不小的 IO 压力。因此,DorisDB 在调度时控制了每个节点上能够执行的任务数目。最小的资源控制单位是磁盘(即在 be.conf 中指定的一个数据路径)。我们默认为每块磁盘配置两个 slot 用于副本修复。一个 clone 任务会占用源端和目的端各一个 slot。如果 slot 数目为零,则不会再对这块磁盘分配任务。该 slot 个数可以通过 FE 的 `schedule_slot_num_per_path` 参数配置。 另外,我们默认为每块磁盘提供 2 个单独的 slot 用于均衡任务。目的是防止高负载的节点因为 slot 被修复任务占用,而无法通过均衡释放空间。 ### 8.7.7 副本状态查看 副本状态查看主要是查看副本的状态,以及副本修复和均衡任务的运行状态。这些状态大部分都**仅存在于** Master FE 节点中。因此,以下命令需直连到 Master FE 执行。 #### 8.7.7.1 副本状态 1. 全局状态检查 通过 `SHOW PROC '/statistic';` 命令可以查看整个集群的副本状态。 ~~~ +----------+-----------------------------+----------+--------------+----------+-----------+------------+--------------------+-----------------------+ | DbId | DbName | TableNum | PartitionNum | IndexNum | TabletNum | ReplicaNum | UnhealthyTabletNum | InconsistentTabletNum | +----------+-----------------------------+----------+--------------+----------+-----------+------------+--------------------+-----------------------+ | 35153636 | default_cluster:DF_Newrisk | 3 | 3 | 3 | 96 | 288 | 0 | 0 | | 48297972 | default_cluster:PaperData | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | 5909381 | default_cluster:UM_TEST | 7 | 7 | 10 | 320 | 960 | 1 | 0 | | Total | 240 | 10 | 10 | 13 | 416 | 1248 | 1 | 0 | +----------+-----------------------------+----------+--------------+----------+-----------+------------+--------------------+-----------------------+ ~~~ 1. 其中 `UnhealthyTabletNum` 列显示了对应的 Database 中,有多少 Tablet 处于非健康状态。`InconsistentTabletNum` 列显示了对应的 Database 中,有多少 Tablet 处于副本不一致的状态。最后一行 `Total` 行对整个集群进行了统计。正常情况下 `UnhealthyTabletNum` 和 `InconsistentTabletNum` 应为0。如果不为零,可以进一步查看具体有哪些 Tablet。如上图中,UM\_TEST 数据库有 1 个 Tablet 状态不健康,则可以使用以下命令查看具体是哪一个 Tablet。 `SHOW PROC '/statistic/5909381';` 其中 `5909381` 为对应的 DbId。 ~~~ +------------------+---------------------+ | UnhealthyTablets | InconsistentTablets | +------------------+---------------------+ | [40467980] | [] | +------------------+---------------------+ ~~~ 1. 上图会显示具体的不健康的 Tablet ID(40467980)。后面我们会介绍如何查看一个具体的 Tablet 的各个副本的状态。 2. 表(分区)级别状态检查 用户可以通过以下命令查看指定表或分区的副本状态,并可以通过 WHERE 语句对状态进行过滤。如查看表 tbl1 中,分区 p1 和 p2 上状态为 NORMAL 的副本: `ADMIN SHOW REPLICA STATUS FROM tbl1 PARTITION (p1, p2) WHERE STATUS = "NORMAL";` ~~~ +----------+-----------+-----------+---------+-------------------+--------------------+------------------+------------+------------+-------+--------+--------+ | TabletId | ReplicaId | BackendId | Version | LastFailedVersion | LastSuccessVersion | CommittedVersion | SchemaHash | VersionNum | IsBad | State | Status | +----------+-----------+-----------+---------+-------------------+--------------------+------------------+------------+------------+-------+--------+--------+ | 29502429 | 29502432 | 10006 | 2 | -1 | 2 | 1 | -1 | 2 | false | NORMAL | OK | | 29502429 | 36885996 | 10002 | 2 | -1 | -1 | 1 | -1 | 2 | false | NORMAL | OK | | 29502429 | 48100551 | 10007 | 2 | -1 | -1 | 1 | -1 | 2 | false | NORMAL | OK | | 29502433 | 29502434 | 10001 | 2 | -1 | 2 | 1 | -1 | 2 | false | NORMAL | OK | | 29502433 | 44900737 | 10004 | 2 | -1 | -1 | 1 | -1 | 2 | false | NORMAL | OK | | 29502433 | 48369135 | 10006 | 2 | -1 | -1 | 1 | -1 | 2 | false | NORMAL | OK | +----------+-----------+-----------+---------+-------------------+--------------------+------------------+------------+------------+-------+--------+--------+ ~~~ 1. 这里会展示所有副本的状态。其中 `IsBad` 列为 `true` 则表示副本已经损坏。而 `Status` 列则会显示另外的其他状态。具体的状态说明,可以通过 `HELP ADMIN SHOW REPLICA STATUS;` 查看帮助。 `ADMIN SHOW REPLICA STATUS` 命令主要用于查看副本的健康状态。用户还可以通过以下命令查看指定表中副本的一些额外信息: `SHOW TABLET FROM tbl1;` ~~~ +----------+-----------+-----------+------------+---------+-------------+-------------------+-----------------------+------------------+----------------------+---------------+----------+----------+--------+-------------------------+--------------+----------------------+--------------+----------------------+----------------------+----------------------+ | TabletId | ReplicaId | BackendId | SchemaHash | Version | VersionHash | LstSuccessVersion | LstSuccessVersionHash | LstFailedVersion | LstFailedVersionHash | LstFailedTime | DataSize | RowCount | State | LstConsistencyCheckTime | CheckVersion | CheckVersionHash | VersionCount | PathHash | MetaUrl | CompactionStatus | +----------+-----------+-----------+------------+---------+-------------+-------------------+-----------------------+------------------+----------------------+---------------+----------+----------+--------+-------------------------+--------------+----------------------+--------------+----------------------+----------------------+----------------------+ | 29502429 | 29502432 | 10006 | 1421156361 | 2 | 0 | 2 | 0 | -1 | 0 | N/A | 784 | 0 | NORMAL | N/A | -1 | -1 | 2 | -5822326203532286804 | url | url | | 29502429 | 36885996 | 10002 | 1421156361 | 2 | 0 | -1 | 0 | -1 | 0 | N/A | 784 | 0 | NORMAL | N/A | -1 | -1 | 2 | -1441285706148429853 | url | url | | 29502429 | 48100551 | 10007 | 1421156361 | 2 | 0 | -1 | 0 | -1 | 0 | N/A | 784 | 0 | NORMAL | N/A | -1 | -1 | 2 | -4784691547051455525 | url | url | +----------+-----------+-----------+------------+---------+-------------+-------------------+-----------------------+------------------+----------------------+---------------+----------+----------+--------+-------------------------+--------------+----------------------+--------------+----------------------+----------------------+----------------------+ ~~~ 1. 上图展示了包括副本大小、行数、版本数量、所在数据路径等一些额外的信息。 > 注:这里显示的 `State` 列的内容不代表副本的健康状态,而是副本处于某种任务下的状态,比如 CLONE、SCHEMA\_CHANGE、ROLLUP 等。 1. 此外,用户也可以通过以下命令,查看指定表或分区的副本分布情况,来检查副本分布是否均匀。 `ADMIN SHOW REPLICA DISTRIBUTION FROM tbl1;` ~~~ +-----------+------------+-------+---------+ | BackendId | ReplicaNum | Graph | Percent | +-----------+------------+-------+---------+ | 10000 | 7 | | 7.29 % | | 10001 | 9 | | 9.38 % | | 10002 | 7 | | 7.29 % | | 10003 | 7 | | 7.29 % | | 10004 | 9 | | 9.38 % | | 10005 | 11 | > | 11.46 % | | 10006 | 18 | > | 18.75 % | | 10007 | 15 | > | 15.62 % | | 10008 | 13 | > | 13.54 % | +-----------+------------+-------+---------+ ~~~ 1. 这里分别展示了表 tbl1 的副本在各个 BE 节点上的个数、百分比,以及一个简单的图形化显示。 2. Tablet 级别状态检查 当我们要定位到某个具体的 Tablet 时,可以使用如下命令来查看一个具体的 Tablet 的状态。如查看 ID 为 29502553 的 tablet: `SHOW TABLET 29502553;` ~~~ +------------------------+-----------+---------------+-----------+----------+----------+-------------+----------+--------+---------------------------------------------------------------------------+ | DbName | TableName | PartitionName | IndexName | DbId | TableId | PartitionId | IndexId | IsSync | DetailCmd | +------------------------+-----------+---------------+-----------+----------+----------+-------------+----------+--------+---------------------------------------------------------------------------+ | default_cluster:test | test | test | test | 29502391 | 29502428 | 29502427 | 29502428 | true | SHOW PROC '/dbs/29502391/29502428/partitions/29502427/29502428/29502553'; | +------------------------+-----------+---------------+-----------+----------+----------+-------------+----------+--------+---------------------------------------------------------------------------+ ~~~ 1. 上图显示了这个 tablet 所对应的数据库、表、分区、上卷表等信息。用户可以复制 `DetailCmd` 命令中的命令继续执行: `SHOW PROC '/dbs/29502391/29502428/partitions/29502427/29502428/29502553';` ~~~ +-----------+-----------+---------+-------------+-------------------+-----------------------+------------------+----------------------+---------------+------------+----------+----------+--------+-------+--------------+----------------------+----------+------------------+ | ReplicaId | BackendId | Version | VersionHash | LstSuccessVersion | LstSuccessVersionHash | LstFailedVersion | LstFailedVersionHash | LstFailedTime | SchemaHash | DataSize | RowCount | State | IsBad | VersionCount | PathHash | MetaUrl | CompactionStatus | +-----------+-----------+---------+-------------+-------------------+-----------------------+------------------+----------------------+---------------+------------+----------+----------+--------+-------+--------------+----------------------+----------+------------------+ | 43734060 | 10004 | 2 | 0 | -1 | 0 | -1 | 0 | N/A | -1 | 784 | 0 | NORMAL | false | 2 | -8566523878520798656 | url | url | | 29502555 | 10002 | 2 | 0 | 2 | 0 | -1 | 0 | N/A | -1 | 784 | 0 | NORMAL | false | 2 | 1885826196444191611 | url | url | | 39279319 | 10007 | 2 | 0 | -1 | 0 | -1 | 0 | N/A | -1 | 784 | 0 | NORMAL | false | 2 | 1656508631294397870 | url | url | +-----------+-----------+---------+-------------+-------------------+-----------------------+------------------+----------------------+---------------+------------+----------+----------+--------+-------+--------------+----------------------+----------+------------------+ ~~~ 1. 上图显示了对应 Tablet 的所有副本情况。这里显示的内容和 `SHOW TABLET FROM tbl1;` 的内容相同。但这里可以清楚的知道,一个具体的 Tablet 的所有副本的状态。 #### 8.7.7.2 副本调度任务 1. 查看等待被调度的任务`SHOW PROC '/cluster_balance/pending_tablets';` ~~~ +----------+--------+-----------------+---------+----------+----------+-------+---------+--------+----------+---------+---------------------+---------------------+---------------------+----------+------+-------------+---------------+---------------------+------------+---------------------+--------+---------------------+-------------------------------+ | TabletId | Type | Status | State | OrigPrio | DynmPrio | SrcBe | SrcPath | DestBe | DestPath | Timeout | Create | LstSched | LstVisit | Finished | Rate | FailedSched | FailedRunning | LstAdjPrio | VisibleVer | VisibleVerHash | CmtVer | CmtVerHash | ErrMsg | +----------+--------+-----------------+---------+----------+----------+-------+---------+--------+----------+---------+---------------------+---------------------+---------------------+----------+------+-------------+---------------+---------------------+------------+---------------------+--------+---------------------+-------------------------------+ | 4203036 | REPAIR | REPLICA_MISSING | PENDING | HIGH | LOW | -1 | -1 | -1 | -1 | 0 | 2019-02-21 15:00:20 | 2019-02-24 11:18:41 | 2019-02-24 11:18:41 | N/A | N/A | 2 | 0 | 2019-02-21 15:00:43 | 1 | 0 | 2 | 0 | unable to find source replica | +----------+--------+-----------------+---------+----------+----------+-------+---------+--------+----------+---------+---------------------+---------------------+---------------------+----------+------+-------------+---------------+---------------------+------------+---------------------+--------+---------------------+-------------------------------+ ~~~ 1. 各列的具体含义如下: * TabletId:等待调度的 Tablet 的 ID。一个调度任务只针对一个 Tablet * Type:任务类型,可以是 REPAIR(修复) 或 BALANCE(均衡) * Status:该 Tablet 当前的状态,如 REPLICA\_MISSING(副本缺失) * State:该调度任务的状态,可能为 PENDING/RUNNING/FINISHED/CANCELLED/TIMEOUT/UNEXPECTED * OrigPrio:初始的优先级 * DynmPrio:当前动态调整后的优先级 * SrcBe:源端 BE 节点的 ID * SrcPath:源端 BE 节点的路径的 hash 值 * DestBe:目的端 BE 节点的 ID * DestPath:目的端 BE 节点的路径的 hash 值 * Timeout:当任务被调度成功后,这里会显示任务的超时时间,单位秒 * Create:任务被创建的时间 * LstSched:上一次任务被调度的时间 * LstVisit:上一次任务被访问的时间。这里“被访问”指包括被调度,任务执行汇报等和这个任务相关的被处理的时间点 * Finished:任务结束时间 * Rate:clone 任务的数据拷贝速率 * FailedSched:任务调度失败的次数 * FailedRunning:任务执行失败的次数 * LstAdjPrio:上一次优先级调整的时间 * CmtVer/CmtVerHash/VisibleVer/VisibleVerHash:用于执行 clone 任务的 version 信息 * ErrMsg:任务被调度和运行过程中,出现的错误信息 2. 查看正在运行的任务 `SHOW PROC '/cluster_balance/running_tablets';` 其结果中各列的含义和 `pending_tablets` 相同。 3. 查看已结束任务 `SHOW PROC '/cluster_balance/history_tablets';` 我们默认只保留最近 1000 个完成的任务。其结果中各列的含义和 `pending_tablets` 相同。如果 `State` 列为 `FINISHED`,则说明任务正常完成。如果为其他,则可以根据 `ErrMsg` 列的错误信息查看具体原因。