陈友行 陈友行的专栏
浏览 956
文章 5
订阅 41
Teledb表分析及碎片处理 陈友行 发表于 : 2020-08-11 14:33

Teledb表分析: analyze table Teledb碎片处理: 1、drop table table_name 立刻释放磁盘空间 ,不管是 Innodb和MyISAM ; 2、truncate table table_name 立刻释放磁盘空间 ,不管是 Innodb和MyISAM 。truncate table其实有点类似于drop table 然后create,只不过这个create table 的过程做了优化,比如表结构文件之前已经有了等等。所以速度上应该是接近drop table的速度; 3、delete from table_name删除表的全部数据,对于MyISAM 会立刻释放磁盘空间 (应该是做了特别处理,也比较合理),InnoDB 不会释放磁盘空间; 4、对于delete from table_name where xxx带条件的删除, 不管是innodb还是MyISAM都不会释放磁盘空间; 5、delete操作以后使用optimize table table_name 会立刻释放磁盘空间。不管是innodb还是myisam 。所以要想达到释放磁盘空间的目的,delete以后执行optimize table 操作。 查看表占用硬盘空间大小的SQL语句如下:(默认用M做展示单位) SELECT TABLE_NAME, (DATA_LENGTH+INDEX_LENGTH)/1048576 as size_Mb, TABLE_ROWS FROM information_schema.tables WHERE TABLE_SCHEMA=’wow_tencent_1’ AND TABLE_NAME=’user’ 然后执行空间优化语句: optimize table user 6、delete from表以后虽然未释放磁盘空间,但是下次插入数据的时候,仍然可以使用这部分空间。

点赞 3
0 条评论
Http2服务调用排坑记 陈友行 发表于 : 2020-06-03 22:45

   1.   了解HTTP2时代在发展,使用http协议的人越来越多。http1.1的弊端慢慢都被显现出来。1)   浏览器方式一些网站频繁发送请求,造成一家独大其他网站无法使用。或者所有网站都频发发送请求造成用户体验差等等问题。限制每个url同时并发数量。2)   提高请求的响应速度。只有一个连接,只有一次tcp三次握手或者tls的7次握手。一个http1.1请求所用的时间,http2.0可以处理三到四个请求。3)   提高服务端与客服端的性能(尤其是大型互联网公司流量很大,如果使用http2.0,可以减少一半的http服务器)。http客服端不知道http服务端是否支持http2.0。反过来 http服务端也不知道http客服端是否支持http2.0。为什么出现这种现象,让所有的http服务端与http客服端直接从http1.1过度到http2.0是不可能的事情。甚至在大点的公司内部直接从http1.1直接过度到http2.0也是一件不现实的事情,那么出现一件麻烦的事情有http1客服端,也有http2客服端。有http2服务端,也有http1服务端。这种两个维度,四种情况的共存现象。有人会问,只支持http1.1不好吗? 已经支持http2,.0的client肯定不会放弃http2.0优秀的性能与特性,能使用使用http2.0,就要使用。那么http2.0的设计者为了解决这种麻烦的东西。推出了解决方案:协商。https1.1与https2.0的协商是基于ALPN机制。ALPNS是基于TLS实现。在建立TLS链接的时候,客服端会 在TLS协议里面加入自己支持的协议,服务端在客服端支持的协议里面选中一个自己最合适的。然后把选中的协议通知给客服端。如果客户端没有发送支持的http协议,服务端会默认使用http1.1。http没有TLS协议,无法基于TLS传递协议。协议制定者使用了Upgrade机制。客户端发送一个空请求,请求里面包含该Upgrade,Connection,HTTP2-Settings请求头。服务端从Upgrade取出支持的协议然后响应请求,在响应的请求头里面包含Upgrade,Connection。这样协商就成功。下面是http1.1与http2.0的协商流程,请求头示例:GET / HTTP/1.1 Host: example.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: 如果服务端不支持 HTTP/2,它会忽略 Upgrade 字段,直接返回 HTTP/1.1 响应,例如:HTTP/1.1 200 OK Content-Length: 243 Content-Type: text/html如果服务端支持 HTTP/2,那就可以回应 101 状态码及对应头部:HTTP/1.1 100 Switching Protocols Connection: Upgrade Upgrade: h2c小结:1)https 1.1 与https.2.0的协商 与 http1.1与http2.0的协商是两套设计方案。https 1.1 与https.2.0 TLS自动实现。http1.1与http2.0的协商需要自己实现。2)现在的趋势,客服端与服务端都需要同时支持http1.1,http2.0,https1.1,https2.0,技术实现比较复杂。2.   问题初现1、项目的问题出在于通过JAVA端去调用华为提供的H2服务时,一直返回PROTOCOL_ERROR错误;但是同样的写法调用,自己写的H2服务是可以的,调用baidu,taobao,small都可以;也就是说明程序是可以用的,但为什么华为就是返回错误呢? 以下是华为侧回复的内容:意思是说客户端调用时使用的协议不对,服务端解析不了,要求我们在报文头加上协调参数。客户端按要求修改,但问题依旧;发现服务端并不支持协商的方式去调用,抓如下:2、其它工具验证:1)     浏览器调用不成功。2)     POSTMAN调用不成功。3)     CURL调用不成功。 3、Jdk版本支持H2说明:原文链接:https://blog.csdn.net/taiyangdao/article/details/808831891)     Java 8初始即支持TLS 1.2,并且客户端默认即使用TLS 1.2。但是为了支持HTTP2,还要借助于Jetty的ALPN模块alpn-boot。下载alpn-boot的jar,并且在启动JVM时加参数java -Xbootclasspath/p: ...2)     Java 9初始即支持TLS 1.2,并且客户端默认即使用TLS 1.2。通过JEP 244, TLS ALPN Extension支持ALPN,完整支持了HTTP2。3)      Java 10同Java 9。4)     Java 11同Java 9,并进而支持TLS 1.3。3.   排查过程到此时发现,根据第二部分所讲,JAVA端调用HTTP2的以下两种方式都走不通:1)       调https方式,因为5G核心网为了提高效率,而且都是内部使用,规范中并不提供加密调用的方式,走不通。2)       走http1.1协商的方式,需要服务端根据协调参数,自动选择;但现在也不支持。3.1.  换jar包在此过程上反复的尝试了多种方法:1)升级到jdk11,通过原生的httpClient去调用,问题依旧:HttpClient client = HttpClient.newBuilder()       .version(HttpClient.Version.HTTP_2)       .priority(0)       .sslContext(SSLContext.getInstance("TLSv1.2"))       .build();2)  通过okHttpClient3.2去调用,问题依旧:3)  通过netty去调用,问题依旧。     4)   协调了客户侧的网络专家帮忙分析,通过curl --http2调用,问题依旧。3.2. 分析C++成功案例:后台C++开发客户端是可以正常调用的,通过了解,C++指定了强制使用H2协议去调用,将包抓出来比对,发现在客户端发送请求时,确实存在差异:问题定位:java端是否有也类型的参数,可以指定强制使用H2方式去调用呢;3.3. 尝试curl命令首先通过curl进行反复的尝试:Curl 只设置了--http2 参数时调用不成功 。在https://curl.haxx.se/docs/http2.html 找到了参数--http2-prior-knowledge说明:Curl 通过 --http2-prior-knowledge 强制使用H2协议去调用,验证成功。3.4. 查找类prior-knowledge参数,解决问题在https://www.jianshu.com/p/9530d58250c7 中找到解决办法:动手尝试验证:第一步,升级okHttpClient包到4.1.0:第二步,编写测试程序:第三步,抓包验证,成功:4.   总结:java调用H2的方法4.1.  加密调用https1.1与https2.0的协商是基于ALPN机制。ALPNS是基于TLS实现。在建立TLS链接的时候,客服端会 在TLS协议里面加入自己支持的协议,服务端在客服端支持的协议里面选中一个自己最合适的。然后把选中的协议通知给客服端。如果客户端没有发送支持的http协议,服务端会默认使用http1.1 4.2. 协商调用  开源地址:https://http2.github.io/http2-spec/#discover-http:4.3. 强制指定H2方式调用

点赞 4
1 条评论
Ngnix性能数据采集造成服务耗时波动 陈友行 发表于 : 2020-05-31 16:29

1、现象:    某些服务请求响应时间有规律性波动,每分钟存在波动,在响应图上体现为明显的柱状。2、分析过程:    部署架构为两层nignx,nginx(接入层)->nginx(路由层),然后是服务层各应用集群:·       1)如下图所示,curl在服务器上调用也存在同样的问题,如下图所示,正常情况下3ms,但波动时>5s,因此调用存在nginx及后端。·       2)Curl单独调用服务层接口没有问题,因此确定问题出现在nginx。·       3)绕过接入层nginx,直接调用路由层nginx地址仍然出现,因此定位故障在路由层。·       4)路由层nginx,会在服务注册时候,定期进行relaod,从而影响性能,但通过检查linux的状态,没有发生过定期规律性的restart/reload。·       5)发现路由层nginx有每分钟的定时任务。·       6)关闭监控采集程序后问题解决。3、根源分析:    PaaS监控组件Prometheus,会定时调用nginx监控模块,采集性能指标,用于性能分析、展现、告警。nginx指标提供模块,采用的是 prometheus-lua,在采集程序访问时候,该字典会被加锁,造成nginx的线程等待。因此在指标量越大,访问量越多,越受影响,PaaS监控组件Prometheus每分钟调用linux的这个监控模块,会阻塞linux的服务,导致响应规律性的波动,并且由于该省访问量大,集群规模大,因此受到的影响更高。通过将prometheus-lua切换为nginx的vts模块问题解决。 Please keep in mind that all metrics stored by this library are kept in a single shared dictionary (lua_shared_dict). While exposing metrics the module has to list all dictionary keys, which has serious performance implications for dictionaries with large number of keys (in this case this means large number of metrics OR metrics with high label cardinality). Listing the keys has to lock the dictionary, which blocks all threads that try to access it (i.e. potentially all nginx worker threads).There is no elegant solution to this issue (besides keeping metrics in a separate storage system external to nginx), so for latency-critical servers you might want to keep the number of metrics (and distinct metric label values) to a minimum.

点赞 5
1 条评论
max_fails和fail_timeout配置不合理,造成服务故障 陈友行 发表于 : 2020-05-28 10:30

1、现象:    1)通过NGINX访问ppm服务开始超时。    2)不久大量服务开始报no live upstream,造成部分服务不可用,导致了业务影响。2、解决过程:    1) 查看路由控制台管理界面,发现NGINX中有ppm的服务被超时下架。    2)重启超时被下架的ppm服务,重启nginx解决。3、根源分析:生产环境中,应用启动后,需要动态注册到路由nginx中。请求到达接入层nginx后,通过负载把请求发给路由层nginx,再根据路由规则,分发到docker资源池的不同服务实例。1) 在本次故障中,因为路由层nginx调用后端ppm多次服务超时,因此路由把ppm的服务下架。2) 下架后接入层nginx把访问ppm的请求转发给路由层nginx,导致请求失败。3) 多次失败后接入层nginx把路由层nginx下架。4) 在路由层所有nginx都下架后,访问其他服务也就没有可以访问的live upstream,从而导致了到其他服务实例访问都失败。接入层nginx 访问后端的upstream通过两个参数来进行控制:max_fails, fail_timeout, 比如:max_fails=10, fail_timeout=5s,则认为5秒超时为1个失败,10次失败后则不再访问后端的该upstream。因此为避免某个服务超时导致路由nginx被下架,需要对接入层nginx的这两个参数进行调整:1) fail_timeout需要设置为 > 服务端应用的最大执行时间。     2) 增大max_fails, 并对no live upstream进行监控告警。

点赞 7
1 条评论
容器cpu使用率持续100%的问题定位方法 陈友行 发表于 : 2020-05-28 09:53

1、问题现象:在容器管理上监控到受理中心容器cpu使用率持续大于100% 。2、定位步骤:1)ssh到对应主机,执行命令 docker ps -a |grep 端口,可以获取到容器id。2) 进入容器docker exec -it 容器id bash。3)执行top命令,获取耗cpu的线程ID。top -p 1,持续观察进程cpu使用率,按shift + h 切换成线程cpu使用情况,持续观察可以获取到cpu使用率高的线程pid。4)用计算器或者printf "%x\n" 十进制的pid 例如 printf "%x\n" 640以上命令可以获取十六进制的javacore中的nid,注意十六进制用计算器获得的结果大写字母要转换成小写。5) 获取javacore: jstack -l 1 >a1.log。6)用vi命令在a1.log中查找第四步获取到的十六进制nid ,即可定位耗cpu的线程堆栈。例如:7)根据线程堆栈中指示的业务代码进一步分析。3、根据业务代码发现产生了while死循环的可能,进一步分析和修改业务代码。

点赞 6
1 条评论
TA的热门
1、现象:    1)通过NGINX访问ppm服务开始超时。    2)不久大量服务开始报no live upstream,造成部分服务不可用,导致了业务影响。2、解决过程:    1) 查看路由控制台管理界面,发现NGINX中有ppm的服务被超时下架。    2)重启超时被下架的ppm服务,重启nginx解决。3、根源分析:生产环境中,应用启动后,需要动态注册到路由nginx中。请求到达接入层nginx后,通过负载把请求发给路由层nginx,再根据路由规则,分发到docker资源池的不同服务实例。1) 在本次故障中,因为路由层nginx调用后端ppm多次服务超时,因此路由把ppm的服务下架。2) 下架后接入层nginx把访问ppm的请求转发给路由层nginx,导致请求失败。3) 多次失败后接入层nginx把路由层nginx下架。4) 在路由层所有nginx都下架后,访问其他服务也就没有可以访问的live upstream,从而导致了到其他服务实例访问都失败。接入层nginx 访问后端的upstream通过两个参数来进行控制:max_fails, fail_timeout, 比如:max_fails=10, fail_timeout=5s,则认为5秒超时为1个失败,10次失败后则不再访问后端的该upstream。因此为避免某个服务超时导致路由nginx被下架,需要对接入层nginx的这两个参数进行调整:1) fail_timeout需要设置为 > 服务端应用的最大执行时间。     2) 增大max_fails, 并对no live upstream进行监控告警。
1、问题现象:在容器管理上监控到受理中心容器cpu使用率持续大于100% 。2、定位步骤:1)ssh到对应主机,执行命令 docker ps -a |grep 端口,可以获取到容器id。2) 进入容器docker exec -it 容器id bash。3)执行top命令,获取耗cpu的线程ID。top -p 1,持续观察进程cpu使用率,按shift + h 切换成线程cpu使用情况,持续观察可以获取到cpu使用率高的线程pid。4)用计算器或者printf "%x\n" 十进制的pid 例如 printf "%x\n" 640以上命令可以获取十六进制的javacore中的nid,注意十六进制用计算器获得的结果大写字母要转换成小写。5) 获取javacore: jstack -l 1 >a1.log。6)用vi命令在a1.log中查找第四步获取到的十六进制nid ,即可定位耗cpu的线程堆栈。例如:7)根据线程堆栈中指示的业务代码进一步分析。3、根据业务代码发现产生了while死循环的可能,进一步分析和修改业务代码。
1、现象:    某些服务请求响应时间有规律性波动,每分钟存在波动,在响应图上体现为明显的柱状。2、分析过程:    部署架构为两层nignx,nginx(接入层)->nginx(路由层),然后是服务层各应用集群:·       1)如下图所示,curl在服务器上调用也存在同样的问题,如下图所示,正常情况下3ms,但波动时>5s,因此调用存在nginx及后端。·       2)Curl单独调用服务层接口没有问题,因此确定问题出现在nginx。·       3)绕过接入层nginx,直接调用路由层nginx地址仍然出现,因此定位故障在路由层。·       4)路由层nginx,会在服务注册时候,定期进行relaod,从而影响性能,但通过检查linux的状态,没有发生过定期规律性的restart/reload。·       5)发现路由层nginx有每分钟的定时任务。·       6)关闭监控采集程序后问题解决。3、根源分析:    PaaS监控组件Prometheus,会定时调用nginx监控模块,采集性能指标,用于性能分析、展现、告警。nginx指标提供模块,采用的是 prometheus-lua,在采集程序访问时候,该字典会被加锁,造成nginx的线程等待。因此在指标量越大,访问量越多,越受影响,PaaS监控组件Prometheus每分钟调用linux的这个监控模块,会阻塞linux的服务,导致响应规律性的波动,并且由于该省访问量大,集群规模大,因此受到的影响更高。通过将prometheus-lua切换为nginx的vts模块问题解决。 Please keep in mind that all metrics stored by this library are kept in a single shared dictionary (lua_shared_dict). While exposing metrics the module has to list all dictionary keys, which has serious performance implications for dictionaries with large number of keys (in this case this means large number of metrics OR metrics with high label cardinality). Listing the keys has to lock the dictionary, which blocks all threads that try to access it (i.e. potentially all nginx worker threads).There is no elegant solution to this issue (besides keeping metrics in a separate storage system external to nginx), so for latency-critical servers you might want to keep the number of metrics (and distinct metric label values) to a minimum.
   1.   了解HTTP2时代在发展,使用http协议的人越来越多。http1.1的弊端慢慢都被显现出来。1)   浏览器方式一些网站频繁发送请求,造成一家独大其他网站无法使用。或者所有网站都频发发送请求造成用户体验差等等问题。限制每个url同时并发数量。2)   提高请求的响应速度。只有一个连接,只有一次tcp三次握手或者tls的7次握手。一个http1.1请求所用的时间,http2.0可以处理三到四个请求。3)   提高服务端与客服端的性能(尤其是大型互联网公司流量很大,如果使用http2.0,可以减少一半的http服务器)。http客服端不知道http服务端是否支持http2.0。反过来 http服务端也不知道http客服端是否支持http2.0。为什么出现这种现象,让所有的http服务端与http客服端直接从http1.1过度到http2.0是不可能的事情。甚至在大点的公司内部直接从http1.1直接过度到http2.0也是一件不现实的事情,那么出现一件麻烦的事情有http1客服端,也有http2客服端。有http2服务端,也有http1服务端。这种两个维度,四种情况的共存现象。有人会问,只支持http1.1不好吗? 已经支持http2,.0的client肯定不会放弃http2.0优秀的性能与特性,能使用使用http2.0,就要使用。那么http2.0的设计者为了解决这种麻烦的东西。推出了解决方案:协商。https1.1与https2.0的协商是基于ALPN机制。ALPNS是基于TLS实现。在建立TLS链接的时候,客服端会 在TLS协议里面加入自己支持的协议,服务端在客服端支持的协议里面选中一个自己最合适的。然后把选中的协议通知给客服端。如果客户端没有发送支持的http协议,服务端会默认使用http1.1。http没有TLS协议,无法基于TLS传递协议。协议制定者使用了Upgrade机制。客户端发送一个空请求,请求里面包含该Upgrade,Connection,HTTP2-Settings请求头。服务端从Upgrade取出支持的协议然后响应请求,在响应的请求头里面包含Upgrade,Connection。这样协商就成功。下面是http1.1与http2.0的协商流程,请求头示例:GET / HTTP/1.1 Host: example.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>如果服务端不支持 HTTP/2,它会忽略 Upgrade 字段,直接返回 HTTP/1.1 响应,例如:HTTP/1.1 200 OK Content-Length: 243 Content-Type: text/html如果服务端支持 HTTP/2,那就可以回应 101 状态码及对应头部:HTTP/1.1 100 Switching Protocols Connection: Upgrade Upgrade: h2c小结:1)https 1.1 与https.2.0的协商 与 http1.1与http2.0的协商是两套设计方案。https 1.1 与https.2.0 TLS自动实现。http1.1与http2.0的协商需要自己实现。2)现在的趋势,客服端与服务端都需要同时支持http1.1,http2.0,https1.1,https2.0,技术实现比较复杂。2.   问题初现1、项目的问题出在于通过JAVA端去调用华为提供的H2服务时,一直返回PROTOCOL_ERROR错误;但是同样的写法调用,自己写的H2服务是可以的,调用baidu,taobao,small都可以;也就是说明程序是可以用的,但为什么华为就是返回错误呢? 以下是华为侧回复的内容:意思是说客户端调用时使用的协议不对,服务端解析不了,要求我们在报文头加上协调参数。客户端按要求修改,但问题依旧;发现服务端并不支持协商的方式去调用,抓如下:2、其它工具验证:1)     浏览器调用不成功。2)     POSTMAN调用不成功。3)     CURL调用不成功。 3、Jdk版本支持H2说明:原文链接:https://blog.csdn.net/taiyangdao/article/details/808831891)     Java 8初始即支持TLS 1.2,并且客户端默认即使用TLS 1.2。但是为了支持HTTP2,还要借助于Jetty的ALPN模块alpn-boot。下载alpn-boot的jar,并且在启动JVM时加参数java -Xbootclasspath/p:<path_to_alpn_boot_jar> ...2)     Java 9初始即支持TLS 1.2,并且客户端默认即使用TLS 1.2。通过JEP 244, TLS ALPN Extension支持ALPN,完整支持了HTTP2。3)      Java 10同Java 9。4)     Java 11同Java 9,并进而支持TLS 1.3。3.   排查过程到此时发现,根据第二部分所讲,JAVA端调用HTTP2的以下两种方式都走不通:1)       调https方式,因为5G核心网为了提高效率,而且都是内部使用,规范中并不提供加密调用的方式,走不通。2)       走http1.1协商的方式,需要服务端根据协调参数,自动选择;但现在也不支持。3.1.  换jar包在此过程上反复的尝试了多种方法:1)升级到jdk11,通过原生的httpClient去调用,问题依旧:HttpClient client = HttpClient.newBuilder()       .version(HttpClient.Version.HTTP_2)       .priority(0)       .sslContext(SSLContext.getInstance("TLSv1.2"))       .build();2)  通过okHttpClient3.2去调用,问题依旧:3)  通过netty去调用,问题依旧。     4)   协调了客户侧的网络专家帮忙分析,通过curl --http2调用,问题依旧。3.2. 分析C++成功案例:后台C++开发客户端是可以正常调用的,通过了解,C++指定了强制使用H2协议去调用,将包抓出来比对,发现在客户端发送请求时,确实存在差异:问题定位:java端是否有也类型的参数,可以指定强制使用H2方式去调用呢;3.3. 尝试curl命令首先通过curl进行反复的尝试:Curl 只设置了--http2 参数时调用不成功 。在https://curl.haxx.se/docs/http2.html 找到了参数--http2-prior-knowledge说明:Curl 通过 --http2-prior-knowledge 强制使用H2协议去调用,验证成功。3.4. 查找类prior-knowledge参数,解决问题在https://www.jianshu.com/p/9530d58250c7 中找到解决办法:动手尝试验证:第一步,升级okHttpClient包到4.1.0:第二步,编写测试程序:第三步,抓包验证,成功:4.   总结:java调用H2的方法4.1.  加密调用https1.1与https2.0的协商是基于ALPN机制。ALPNS是基于TLS实现。在建立TLS链接的时候,客服端会 在TLS协议里面加入自己支持的协议,服务端在客服端支持的协议里面选中一个自己最合适的。然后把选中的协议通知给客服端。如果客户端没有发送支持的http协议,服务端会默认使用http1.1 4.2. 协商调用  开源地址:https://http2.github.io/http2-spec/#discover-http:4.3. 强制指定H2方式调用
Teledb表分析: analyze table Teledb碎片处理: 1、drop table table_name 立刻释放磁盘空间 ,不管是 Innodb和MyISAM ; 2、truncate table table_name 立刻释放磁盘空间 ,不管是 Innodb和MyISAM 。truncate table其实有点类似于drop table 然后create,只不过这个create table 的过程做了优化,比如表结构文件之前已经有了等等。所以速度上应该是接近drop table的速度; 3、delete from table_name删除表的全部数据,对于MyISAM 会立刻释放磁盘空间 (应该是做了特别处理,也比较合理),InnoDB 不会释放磁盘空间; 4、对于delete from table_name where xxx带条件的删除, 不管是innodb还是MyISAM都不会释放磁盘空间; 5、delete操作以后使用optimize table table_name 会立刻释放磁盘空间。不管是innodb还是myisam 。所以要想达到释放磁盘空间的目的,delete以后执行optimize table 操作。 查看表占用硬盘空间大小的SQL语句如下:(默认用M做展示单位) SELECT TABLE_NAME, (DATA_LENGTH+INDEX_LENGTH)/1048576 as size_Mb, TABLE_ROWS FROM information_schema.tables WHERE TABLE_SCHEMA=’wow_tencent_1’ AND TABLE_NAME=’user’ 然后执行空间优化语句: optimize table user 6、delete from表以后虽然未释放磁盘空间,但是下次插入数据的时候,仍然可以使用这部分空间。