docs: minor changes

This commit is contained in:
Simon Guan 2025-03-04 22:55:25 +08:00
parent 0175488136
commit 10ca97b6cf
9 changed files with 129 additions and 87 deletions

View File

@ -17,7 +17,7 @@ import TabItem from "@theme/TabItem";
## 无模式写入行协议
TDengine 的无模式写入行协议兼容 InfluxDB 的行协议、OpenTSDB 的 telnet 行协议和 OpenTSDB 的 JSON 格式协议。InfluxDB、OpenTSDB 的标准写入协议请参考各自的官方文档。
TDengine 的无模式写入行协议兼容 InfluxDB 的行协议、OpenTSDB 的 TELNET 行协议和 OpenTSDB 的 JSON 格式协议。InfluxDB、OpenTSDB 的标准写入协议请参考各自的官方文档。
下面首先以 InfluxDB 的行协议为基础,介绍 TDengine 扩展的协议内容。该协议允许用户采用更加精细的方式控制(超级表)模式。采用一个字符串来表达一个数据行,可以向写入 API 中一次传入多行字符串来实现多个数据行的批量写入,其格式约定如下。
@ -70,10 +70,9 @@ tag_set 中的所有的数据自动转化为 nchar 数据类型,并不需要
| 5 | i32/u32 | Int/UInt | 4 |
| 6 | i64/i/u64/u | BigInt/BigInt/UBigInt/UBigInt | 8 |
- t, T, true, True, TRUE, f, F, false, False 将直接作为 BOOL 型来处理。
- t、T、true、True、TRUE、f、F、false、False 将直接作为 BOOL 型来处理。
例如如下数据行表示:向名为 st 的超级表下的 t1 标签为 "3"NCHAR、t2 标签为 "4"NCHAR、t3
标签为 "t3"NCHAR的数据子表写入 c1 列为 3BIGINT、c2 列为 falseBOOL、c3
例如如下数据行表示:向名为 st 的超级表下的 t1 标签为 "3"NCHAR、t2 标签为 "4"NCHAR、t3 标签为 "t3"NCHAR的数据子表写入 c1 列为 3BIGINT、c2 列为 falseBOOL、c3
列为 "passit"BINARY、c4 列为 4DOUBLE、主键时间戳为 1626006833639000000 的一行数据。
```json
@ -94,24 +93,22 @@ TDengine 提供数据写入的幂等性保证,即您可以反复调用 API 进
"measurement,tag_key1=tag_value1,tag_key2=tag_value2"
```
- 需要注意的是,这里的 tag_key1, tag_key2 并不是用户输入的标签的原始顺序而是使用了标签名称按照字符串升序排列后的结果。所以tag_key1 并不是在行协议中输入的第一个标签。
排列完成以后计算该字符串的 MD5 散列值 "md5_val"。然后将计算的结果与字符串组合生成表名:“t_md5_val”。其中的 “t_” 是固定的前缀,每个通过该映射关系自动生成的表都具有该前缀。
- 需要注意的是,这里的 tag_key1、tag_key2 并不是用户输入的标签的原始顺序而是使用了标签名称按照字符串升序排列后的结果。所以tag_key1 并不是在行协议中输入的第一个标签。
排列完成以后计算该字符串的 MD5 散列值 "md5_val"。然后将计算的结果与字符串组合生成表名:"t_md5_val"。其中的 "t_" 是固定的前缀,每个通过该映射关系自动生成的表都具有该前缀。
- 如果不想用自动生成的表名,有两种指定子表名的方式(第一种优先级更高)。
1. 通过在taos.cfg里配置 smlAutoChildTableNameDelimiter 参数来指定(`@ # 空格 回车 换行 制表符`除外)。
1. 举例如下:配置 smlAutoChildTableNameDelimiter=- 插入数据为 st,t0=cpu1,t1=4 c1=3 1626006833639000000 则创建的表名为 cpu1-4。
2. 通过在taos.cfg里配置 smlChildTableName 参数来指定。
1. 举例如下:配置 smlChildTableName=tname 插入数据为 st,tname=cpu1,t1=4 c1=3 1626006833639000000 则创建的表名为 cpu1注意如果多行数据 tname 相同,但是后面的 tag_set 不同,则使用第一行自动建表时指定的 tag_set其他的行会忽略。
- 通过在 taos.cfg 里配置 smlAutoChildTableNameDelimiter 参数来指定(`@ # 空格 回车 换行 制表符` 除外),例如配置 smlAutoChildTableNameDelimiter=- 插入数据为 st,t0=cpu1,t1=4 c1=3 1626006833639000000 则创建的表名为 cpu1-4。
- 通过在 taos.cfg 里配置 smlChildTableName 参数来指定,例如配置 smlChildTableName=tname 插入数据为 st,tname=cpu1,t1=4 c1=3 1626006833639000000 则创建的表名为 cpu1注意如果多行数据 tname 相同,但是后面的 tag_set 不同,则使用第一行自动建表时指定的 tag_set其他的行会忽略。
2. 如果解析行协议获得的超级表不存在,则会创建这个超级表(不建议手动创建超级表,不然插入数据可能异常)。
3. 如果解析行协议获得子表不存在,则 Schemaless 会按照步骤 1 或 2 确定的子表名来创建子表。
3. 如果解析行协议获得子表不存在,则 schemaless 会按照步骤 1 或 2 确定的子表名来创建子表。
4. 如果数据行中指定的标签列或普通列不存在,则在超级表中增加对应的标签列或普通列(只增不减)。
5. 如果超级表中存在一些标签列或普通列未在一个数据行中被指定取值,那么这些列的值在这一行中会被置为 NULL。
6. 对 BINARY 或 NCHAR 列,如果数据行中所提供值的长度超出了列类型的限制,自动增加该列允许存储的字符长度上限(只增不减),以保证数据的完整保存。
7. 整个处理过程中遇到的错误会中断写入过程,并返回错误代码。
8. 为了提高写入的效率,默认假设同一个超级表中 field_set 的顺序是一样的(第一条数据包含所有的 field后面的数据按照这个顺序如果顺序不一样需要配置参数 smlDataFormat 为 false否则数据写入按照相同顺序写入库中数据会异常从 3.0.3.0 开始,自动检测顺序是否一致,该配置废弃。
9. 由于 sql 建表表名不支持点号(.),所以 schemaless 也对点号(.)做了处理,如果 schemaless 自动建表的表名如果有点号(.),会自动替换为下划线(\_。如果手动指定子表名的话子表名里有点号.),同样转化为下划线(\_
10. taos.cfg 增加 smlTsDefaultName 配置(值为字符串只在client端起作用配置后schemaless自动建表的时间列名字可以通过该配置设置。不配置的话默认为 _ts。
10. taos.cfg 增加 smlTsDefaultName 配置(字符串类型),只在 client 端起作用配置后schemaless 自动建表的时间列名字可以通过该配置设置。不配置的话,默认为 _ts。
11. 无模式写入的数据超级表或子表名区分大小写。
12. 无模式写入仍然遵循 TDengine 对数据结构的底层限制,例如每行数据的总长度不能超过 48KB从 3.0.5.0 版本开始为 64KB标签值的总长度不超过 16KB。
@ -147,7 +144,7 @@ InfluxDB行协议的数据将被映射成具有模式的数据其中measu
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000
```
该行数据映射生成一个超级表: st 其包含了 3 个类型为 nchar 的标签分别是t1, t2, t3。五个数据列分别是 tstimestampc1 (bigintc3(binary)c2 (bool), c4 (bigint。映射成为如下 SQL 语句:
该行数据映射生成一个超级表st 其包含了 3 个类型为 nchar 的标签分别是t1、t2、t3。五个数据列分别是 tstimestamp、c1 (bigint、c3(binary)、c2 (bool)、c4 (bigint。映射成为如下 SQL 语句:
```json
create stable st (_ts timestamp, c1 bigint, c2 bool, c3 binary(6), c4 bigint) tags(t1 nchar(1), t2 nchar(1), t3 nchar(2))
@ -164,7 +161,7 @@ st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4i 1626006833640000000
```
第一行的数据类型映射将 c4 列定义为 Double 但是第二行的数据又通过数值后缀方式声明该列为 BigInt 由此会触发无模式写入的解析错误。
第一行的数据类型映射将 c4 列定义为 Double 但是第二行的数据又通过数值后缀方式声明该列为 bigInt 由此会触发无模式写入的解析错误。
如果列前面的行协议将数据列声明为了 binary 后续的要求长度更长的 binary 长度,此时会触发超级表模式的变更。
@ -299,7 +296,7 @@ writer.write(lineDemo, SchemalessProtocolType.LINE, SchemalessTimestampType.NANO
## 查询写入的数据
运行上节的样例代码,会在 power 数据库l中自动建表,我们可以通过 TDengine CLI 或者应用程序来查询数据。下面给出用 TDengine CLI 查询超级表和 meters 表数据的样例。
运行上节的样例代码,会在 power 数据库中自动建表,我们可以通过 TDengine CLI 或者应用程序来查询数据。下面给出用 TDengine CLI 查询超级表和 meters 表数据的样例。
```shell
taos> show power.stables;

View File

@ -19,9 +19,9 @@ import TabItem from "@theme/TabItem";
我们只推荐使用下面两种形式的 SQL 进行参数绑定写入:
```sql
一、确定子表存在
一、确定子表存在
1. INSERT INTO meters (tbname, ts, current, voltage, phase) VALUES(?, ?, ?, ?, ?)
二、自动建表
二、自动建表
1. INSERT INTO meters (tbname, ts, current, voltage, phase, location, group_id) VALUES(?, ?, ?, ?, ?, ?, ?)
2. INSERT INTO ? USING meters TAGS (?, ?) VALUES (?, ?, ?, ?)
```

View File

@ -16,7 +16,7 @@ TDengine 提供了类似于消息队列产品的数据订阅和消费接口。
**注意**
在 TDengine 连接器实现中,对于订阅查询,有以下限制。
- 查询语句限制:订阅查询只能使用 select 语句,并不支持其他类型的SQL如订阅库订阅超级表非 select 方式),insert、update 或 delete 等。
- 查询语句限制:订阅查询只能使用 select 语句,并不支持其他类型的 SQL如订阅库、订阅超级表非 select 方式)、insert、update 或 delete 等。
- 原始始数据查询:订阅查询只能查询原始数据,而不能查询聚合或计算结果。
- 时间顺序限制:订阅查询只能按照时间正序查询数据。
@ -28,22 +28,67 @@ TDengine 消费者的概念跟 Kafka 类似,消费者通过订阅主题来接
### 创建参数
创建消费者的参数较多,非常灵活的支持了各种连接类型、 Offset 提交方式、压缩、重连、反序列化等特性。各语言连接器都适用的通用基础配置项如下表所示:
| 参数名称 | 类型 | 参数说明 | 备注 |
| :-----------------------: | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `td.connect.ip` | string | 服务端的 FQDN | 可以是ip或者host name |
| `td.connect.user` | string | 用户名 | |
| `td.connect.pass` | string | 密码 | |
| `td.connect.port` | integer | 服务端的端口号 | |
| `group.id` | string | 消费组 ID同一消费组共享消费进度 | <br />**必填项**。最大长度192超长将截断。<br />每个topic最多可建立 100 个 consumer group |
| `client.id` | string | 客户端 ID | 最大长度255超长将截断。 |
| `auto.offset.reset` | enum | 消费组订阅的初始位置 | <br />`earliest`: default(version < 3.2.0.0);从头开始订阅; <br/>`latest`: default(version >= 3.2.0.0);仅从最新数据开始订阅; <br/>`none`: 没有提交的 offset 无法订阅 |
| `enable.auto.commit` | boolean | 是否启用消费位点自动提交true: 自动提交客户端应用无需commitfalse客户端应用需要自行commit | 默认值为 true |
| `auto.commit.interval.ms` | integer | 消费记录自动提交消费位点时间间隔,单位为毫秒 | 默认值为 5000 |
| `msg.with.table.name` | boolean | 是否允许从消息中解析表名, 不适用于列订阅(列订阅时可将 tbname 作为列写入 subquery 语句从3.2.0.0版本该参数废弃恒为true | 默认关闭 |
| `enable.replay` | boolean | 是否开启数据回放功能 | 默认关闭 |
| `session.timeout.ms` | integer | consumer 心跳丢失后超时时间,超时后会触发 rebalance 逻辑,成功后该 consumer 会被删除从3.3.3.0版本开始支持) | 默认值为 12000取值范围 [6000 1800000] |
| `max.poll.interval.ms` | integer | consumer poll 拉取数据间隔的最长时间,超过该时间,会认为该 consumer 离线触发rebalance 逻辑,成功后该 consumer 会被删除从3.3.3.0版本开始支持) | 默认值为 300000[1000INT32_MAX] |
#### td.connect.ip
- 说明:服务端的 FQDN
- 类型string
- 备注:可以是 ip 或者 host name
#### td.connect.user
- 说明:用户名
- 类型string
#### td.connect.pass
- 说明:密码
- 类型string
#### td.connect.port
- 说明:服务端的端口号
- 类型integer
#### group.id
- 说明:消费组 ID同一消费组共享消费进度
- 类型string
- 备注:**必填项**。最大长度192超长将截断。<br />每个topic最多可建立 100 个 consumer group
#### client.id
- 说明:客户端 ID
- 类型string
- 备注:最大长度 255超长将截断
#### auto.offset.reset
- 说明:消费组订阅的初始位置
- 类型enum
- 备注:<br />`earliest`default(version < 3.2.0.0)从头开始订阅<br/>`latest`default(version >= 3.2.0.0),仅从最新数据开始订阅;<br/>`none`:没有提交的 offset 无法订阅。
#### enable.auto.commit
- 说明:是否启用消费位点自动提交
- 类型boolean
- 备注true自动提交客户端应用无需 commitfalse客户端应用需要自行 commit默认值为 true。
#### auto.commit.interval.ms
- 说明:消费记录自动提交消费位点时间间隔
- 类型integer
- 备注:单位为毫秒,默认值为 5000
#### msg.with.table.name
- 说明:是否允许从消息中解析表名
- 类型boolean
- 备注:不适用于列订阅(列订阅时可将 tbname 作为列写入 subquery 语句),默认关闭。从 3.2.0.0 版本该参数废弃。
#### enable.replay
- 说明:是否开启数据回放功能
- 类型boolean
- 备注:默认关闭
#### session.timeout.ms
- 说明consumer 心跳丢失后超时时间
- 类型integer
- 备注:超时后会触发 rebalance 逻辑,成功后该 consumer 会被删除(从 3.3.3.0 版本开始支持)。默认值为 12000取值范围 [60001800000]。
#### max.poll.interval.ms
- 说明consumer poll 拉取数据间隔的最长时间
- 类型integer
- 备注:超过该时间,会认为该 consumer 离线,触发 rebalance 逻辑,成功后该 consumer 会被删除(从 3.3.3.0 版本开始支持)。默认值为 300000[1000INT32_MAX] 。
下面是各语言连接器创建参数:
<Tabs defaultValue="java" groupId="lang">

View File

@ -323,7 +323,7 @@ def process(input: datablock) -> tuple[output_type]:
```
主要参数说明如下:
- input:datablock 类似二维矩阵,通过成员方法 data(row, col) 读取位于 row 行、col 列的 python 对象
- input:datablock 类似二维矩阵,通过成员方法 data(row, col) 读取位于 row 行、col 列的 Python 对象
- 返回值是一个 Python 对象元组,每个元素类型为输出类型。
#### 聚合函数接口
@ -405,7 +405,7 @@ def finish(buf: bytes) -> output_type:
本文内容由浅入深包括 5 个示例程序,同时也包含大量实用的 debug 技巧。
注意:**UDF 内无法通过 print 函数输出日志,需要自己写文件或用 python 内置的 logging 库写文件**。
注意:**UDF 内无法通过 print 函数输出日志,需要自己写文件或用 Python 内置的 logging 库写文件**。
#### 示例一
@ -652,7 +652,7 @@ tail -20 taospyudf.log
2023-05-25 11:42:34.541 ERROR [1679419] [PyUdf::PyUdf@217] py udf load module failure. error ModuleNotFoundError: No module named 'moment'
```
这是因为 “moment” 所在位置不在 python udf 插件默认的库搜索路径中。怎么确认这一点呢?通过以下命令搜索 taospyudf.log。
这是因为 “moment” 所在位置不在 Python udf 插件默认的库搜索路径中。怎么确认这一点呢?通过以下命令搜索 taospyudf.log。
```shell
grep 'sys path' taospyudf.log | tail -1
@ -664,7 +664,7 @@ grep 'sys path' taospyudf.log | tail -1
2023-05-25 10:58:48.554 INFO [1679419] [doPyOpen@592] python sys path: ['', '/lib/python38.zip', '/lib/python3.8', '/lib/python3.8/lib-dynload', '/lib/python3/dist-packages', '/var/lib/taos//.udf']
```
发现 python udf 插件默认搜索的第三方库安装路径是: /lib/python3/dist-packages而 moment 默认安装到了 /usr/local/lib/python3.8/dist-packages。下面我们修改 python udf 插件默认的库搜索路径。
发现 Python udf 插件默认搜索的第三方库安装路径是: /lib/python3/dist-packages而 moment 默认安装到了 /usr/local/lib/python3.8/dist-packages。下面我们修改 Python udf 插件默认的库搜索路径。
先打开 python3 命令行,查看当前的 sys.path。
```python
@ -754,7 +754,7 @@ create or replace aggregate function myspread as '/root/udf/myspread.py' outputt
这个 SQL 语句与创建标量函数的 SQL 语句有两个重要区别。
1. 增加了 aggregate 关键字
2. 增加了 bufsize 关键字,用来指定存储中间结果的内存大小,这个数值可以大于实际使用的数值。本例中间结果是两个浮点数组成的 tuple序列化后实际占用大小只有 32 个字节,但指定的 bufsize 是128可以用 python 命令行打印实际占用的字节数
2. 增加了 bufsize 关键字,用来指定存储中间结果的内存大小,这个数值可以大于实际使用的数值。本例中间结果是两个浮点数组成的 tuple序列化后实际占用大小只有 32 个字节,但指定的 bufsize 是128可以用 Python 命令行打印实际占用的字节数
```python
>>> len(pickle.dumps((12345.6789, 23456789.9877)))

View File

@ -374,7 +374,7 @@ SQLWriter 类封装了拼 SQL 和写数据的逻辑。所有的表都没有提
- 已安装 Python3 推荐版本 >= 3.8
- 已安装 taospy
2. 安装 faster-fifo 代替 python 内置的 multiprocessing.Queue
2. 安装 faster-fifo 代替 Python 内置的 multiprocessing.Queue
```
pip3 install faster-fifo

View File

@ -36,7 +36,7 @@ taosAdapter 提供了以下功能:
- RESTful 接口;
- WebSocket 连接;
- 兼容 InfluxDB v1 格式写入;
- 兼容 OpenTSDB JSON 和 Telnet 格式写入;
- 兼容 OpenTSDB JSON 和 TELNET 格式写入;
- 无缝连接到 Telegraf
- 无缝连接到 collectd
- 无缝连接到 StatsD

View File

@ -8,7 +8,7 @@ toc_max_heading_level: 4
在这些进程中taoskeeper、taos-explorer、taosadapter 和 taosx 的资源占用相对较少,通常不需要特别关注。此外,这些进程对存储空间的需求也较低,其占用的 CPU 和内存资源一般为 taosd 进程的十分之一到几分之一(特殊场景除外,如数据同步和历史数据迁移。在这些情况下,涛思数据的技术支持团队将提供一对一的服务)。系统管理员应定期监控这些进程的资源消耗,并及时进行相应处理。
在本节中,我们将重点讨论 TDengine 数据库引擎的核心进程taosd 的资源规划。合理的资源规划将确保 taosd 进程的高效运行,从而提高整个时序数据平台的性能和稳定性。
在本节中,我们将重点讨论 TDengine 数据库引擎的核心进程 taosd 的资源规划。合理的资源规划将确保 taosd 进程的高效运行,从而提高整个时序数据平台的性能和稳定性。
## 服务器内存需求
@ -154,9 +154,9 @@ TDengine 的多级存储功能在使用上还具备以下优点。
| taosKeeper | 6043 | TCP |
| statsd 格式写入接口 | 6044 | TCP/UDP |
| collectd 格式写入接口 | 6045 | TCP/UDP |
| openTSDB Telnet 格式写入接口 | 6046 | TCP |
| collectd 使用 openTSDB Telnet 格式写入接口 | 6047 | TCP |
| icinga2 使用 openTSDB Telnet 格式写入接口 | 6048 | TCP |
| tcollector 使用 openTSDB Telnet 格式写入接口 | 6049 | TCP |
| openTSDB TELNET 格式写入接口 | 6046 | TCP |
| collectd 使用 openTSDB TELNET 格式写入接口 | 6047 | TCP |
| icinga2 使用 openTSDB TELNET 格式写入接口 | 6048 | TCP |
| tcollector 使用 openTSDB TELNET 格式写入接口 | 6049 | TCP |
| taosX | 6050, 6055 | TCP |
| taosExplorer | 6060 | TCP |

View File

@ -21,7 +21,7 @@ taosd 是 TDengine 集群中最主要的服务组件,本节介绍手动部署
在进行 TDengine 集群部署之前,全面检查所有 dnode 以及应用程序所在物理节点的网络设置至关重要。以下是检查步骤:
- 第 1 步,在每个物理节点上执行 hostname -f 命令以查看并确认所有节点的hostname 是唯一的。对于应用程序驱动所在的节点,这一步骤可以省略。
- 第 2 步,在每个物理节点上执行 ping host 命令,其中 host 是其他物理节点的 hostname。这一步骤旨在检测当前节点与其他物理节点之间的网络连通性。如果发现无法 ping 通,请立即检查网络和 DNS 设置。对于 Linux 操作系统,请检查 /etc/hosts 文件;对于 Windows 操作系统请检查C:\Windows\system32\drivers\etc\hosts 文件。网络不通畅将导致无法组建集群,请务必解决此问题。
- 第 2 步,在每个物理节点上执行 ping host 命令,其中 host 是其他物理节点的 hostname。这一步骤旨在检测当前节点与其他物理节点之间的网络连通性。如果发现无法 ping 通,请立即检查网络和 DNS 设置。对于 Linux 操作系统,请检查 /etc/hosts 文件;对于 Windows 操作系统,请检查 `C:\Windows\system32\drivers\etc\hosts` 文件。网络不通畅将导致无法组建集群,请务必解决此问题。
- 第 3 步,在应用程序运行的物理节点上重复上述网络检测步骤。如果发现网络不通畅,应用程序将无法连接到 taosd 服务。此时,请仔细检查应用程序所在物理节点的 DNS 设置或 hosts 文件,确保其配置正确无误。
- 第 4 步,检查端口,确保集群中所有主机在端口 6030 上的 TCP 能够互通。
@ -210,7 +210,7 @@ http {
### 部署 taosX-Agent
有些数据源如 Pi, OPC 等因为网络条件和数据源访问的限制taosX 无法直接访问数据源,这种情况下需要部署一个代理服务 taosX-Agent关于它的详细说明和部署请参考企业版参考手册。
有些数据源如 PI、OPC 等因为网络条件和数据源访问的限制taosX 无法直接访问数据源,这种情况下需要部署一个代理服务 taosX-Agent关于它的详细说明和部署请参考企业版参考手册。
### 部署 taos-Explorer
@ -342,7 +342,7 @@ spec:
### 有状态服务 StatefulSet
根据 Kubernetes 对各类部署的说明,我们将使用 StatefulSet 作为 TDengine 的部署资源类型。 创建文件 tdengine.yaml其中 replicas 定义集群节点的数量为 3。节点时区为中国Asia/Shanghai每个节点分配 5G 标准standard存储你也可以根据实际情况进行相应修改。
根据 Kubernetes 对各类部署的说明,我们将使用 StatefulSet 作为 TDengine 的部署资源类型。 创建文件 tdengine.yaml其中 replicas 定义集群节点的数量为 3。节点时区为中国Asia/Shanghai每个节点分配 5GB 标准standard存储你也可以根据实际情况进行相应修改。
请特别注意 startupProbe 的配置,在 dnode 的 Pod 掉线一段时间后,再重新启动,这个时候新上线的 dnode 会短暂不可用。如果 startupProbe 配置过小Kubernetes 会认为该 Pod 处于不正常的状态,并尝试重启该 Pod该 dnode 的 Pod 会频繁重启,始终无法恢复到正常状态。