游戏服务全链路、真全栈无盲点可观测实践
01
序言
本文整理自2023年12月16日于北京清华大学举办的 以《网络为中心的零侵扰可观测性》的技术论坛, 来自蓝鲸观测平台团队的 刘文平 做了题为 《腾讯游戏真全栈观测实践》的演讲。 介绍了腾讯 IEG 蓝鲸观测平台如何运用前沿的 DeepFlow 的 eBPF 技术,结合传统的 APM 体系,实现了对游戏服务全链路、真全栈,无盲点观测。这一跨越系统、网络、应用、基础组件、服务到业务的监控能力,不仅提升了问题诊断的效率,还优化了应用性能,确保了游戏玩家能获得最佳的体验。
关键词:
视图、协议、应用层、开发者、蓝鲸、集成、架构、组件、插件、调用视图、系统调用、管理平台、容器管理、接入成本、数据服务、应用层数据、节点管理、服务模块
02
分享纪要
本次分享纪要总结了 刘文平 在《网络为中心的零侵扰可观测性》技术论坛上的演讲内容。演讲围绕腾讯游戏的真全栈观测实践,介绍了蓝鲸观测平台的功能、架构、以及与 OTel 和 eBPF 技术的结合使用。
以下是分享的主要内容:
1
蓝鲸观测平台介绍
- 提供了全面的架构图,涵盖从底层基础设施到上层 SaaS 应用的各种平台服务
- 观测平台位于架构中心,支持上层的持续运营场景
- 功能分层,左侧是监控,右侧包括数据服务和 AIOPS
2
基于OTel+eBPF的实践
- OTel 提供了观测领域的标准协议框架,降低了开发者接入成本
- eBPF 是 Linux 内核技术,用于网络、安全、观测领域,提供无侵入式的观测能力
- 将 OTel 和 eBPF 的数据融合起来,取长补短,提供了全面的观测视角
3
数据集成与优化
- 通过远程读取(Remote Read)集成无 traceid的数据,优化链路资源消耗
- 扩展支持私有协议,如 trpc,通过 WASM 插件形式扩展应用层数据解析
- 通过 Prometheus Remote Write 协议将指标数据导出,实现数据集成和告警
4
游戏场景全栈观测实践
- 介绍了腾讯游戏的组成,包括自研和发行两大类,以及它们的独特架构和部署模式
- 通过 eBPF 无侵入式解决全观测问题,提高了问题排查效率
- 形成了以 trace 为中心的数据全关联,方便查看和分析问题
5
未来展望
- 希望实现自动生成 WASM 插件的方法,让开发者和 SRE 更容易参与
- 考虑使用 eBPF 采集模式进行无侵入式Profiling
分享最后,刘文平对未来的发展进行了展望,期望通过技术的不断进步,进一步简化观测平台的使用,提升性能和效率。
演讲视频:
消灭盲点!腾讯游戏真·全栈观测实践-可观测性技术论坛·北京站
03
正文内容
很高兴参加本次关于可观测性的Meetup。我是刘文平,2017年加入腾讯,一直致力于监控和可观测性领域。本次分享也是近几年我们做的一些尝试和努力,所积累的一些经验,希望能分享出来,贡献给社区的小伙伴。
本次分享主要围绕四个议题:首先,我将介绍蓝鲸及其可观测平台;其次,我会深入讲解我们如何将OTel与eBPF结合,应用于应用层的实践;然后,我将分享我们如何在游戏场景中实施全栈可观测性;最后,我会对未来的发展进行展望。
蓝鲸观测平台介绍
首先是蓝鲸及其可观测平台的介绍,蓝鲸提供了一个全面的架构图,从底层基础设施,到上层 SaaS 应用,中间提供了各种平台应用,包含 aPaaS(开发者中心,前后台开发框架等)和 iPaaS(持续集成、CMDB、作业平台、容器管理、AI 等原子平台)等模块,可以帮助企业技术人员快速的构建自己基础运营 PaaS。
蓝鲸观测平台位于架构的中心,主要支持上层的持续运营场景。
蓝鲸观测平台功能分层的左侧是监控部分,右侧则包括数据服务和 AIOPS。
用户若需使用监控功能,只需遵循监控协议进行接入即可享有全部功能。若用户需体验更高级的功能,可通过开关选项将数据流向数据服务和 AIOPS。这对用户和 AIOPS 平台都极为有利,因为数据在经过监控平台处理后流入数据服务时,已是标准化且质量有保证的。
我们已构建的系统类似一座房子,以蓝鲸体系为基础,五种数据类型为支柱,顶层为业务提供即开即用的观测场景。为适应游戏业务的复杂架构,我们提供了多样的接入方法,图中所示仅为一部分,实际上我们提供了超过 20 种接入方式。最近,我们还将 eBPF 集成进来,进一步丰富了我们对业务的无侵入性支持。接下来,我会详细介绍 eBPF 与追踪数据的整合情况。
基于 OTel+eBPF 的实践
1:OTel 和 eBPF 分别是什么
OTel,即 OpenTelemetry。从 OTel 官方的介绍来看,可以看到 OTel 是专注于观测领域,提出来了一套标准的协议框架,也相应的为各种语言提供了自己的一些实现 SDK 和接收服务 OTel Collector,极大的降低了开发者的接入使用成本。
从 eBPF 官方的介绍来看,可以看到 eBPF 其实是一项 Linux 内核技术,从 bcc 提供的各种基于 BPF 的工具图来看,可以看到无论是应用层,系统调用、还是内核,或者驱动,都能很好的提供钩子能力,帮助我们观测到想要的数据。目前 eBPF 主要是用在网络、安全、观测这三大领域。
2:OTel+eBPF数据融合提供全面观测视角
从两者的官方介绍来看,两者都能用于观测领域 ,那我们是不是用其中一种就可以了呢?
从最终效果图来看,两者确实好像都能实现同样的效果,都能帮助业务拿到了单次请求的所有调用关系,业务可以很方便的从图中找到自己最慢的请求在哪个服务,哪个接口中。
但是细细看,其实是有所区别:
OTel 更多的是从应用的视角出发,获取到的是应用层面的调用关系,当然也更符合开发者的视角。但是用户需要加载 SDK,未接入的服务则获取不到调用数据,存在盲区,接入成本高。
eBPF 更多的是从系统和网络的视角出发,获取到的是系统调用和网络流的对应关系。不需要加载 SDK,提供了无侵入式的方式,可以覆盖几乎所有组件服务,对用户来说几乎是零成本。但是数据更符合SRE视角,如果要让开发者也使用这份数据,那么我们需要往里面注入更多业务相关标签,才能使得这部分数据对开发者更有意义。
既然谁也不能替代谁,那是不是可以将两者数据融合起来,取长补短。这样两者的数据都更有意义,可以覆盖组件的盲区,同时也可以将开发者和 SRE 的视角统一起来。
3:OTel+eBPF效果展示
下面这个是我们尝试统一后的一个效果展示。
左边这个是只有 OTel 数据的调用链视图,右边这个是开启了上面这个eBPF开关后的效果。
可以看到,开启 eBPF 后,很好的将系统的调用和网络的调用,都关联进了同一个视图下,同时也顺利的补充上了未插码的一些服务,像截图里接入层的 nginx,以及下面 web 到 api 之间的未插码的网关服务。
有了这样的效果后,问题排查就会变得便捷很多,比如,在没有 eBPF 的情况,当问题出现在基础组件的时候,问题排查往往就会陷入了盲区,拉人处理也基本上是在天级别,如果涉及跨部门,可能到一周也是有可能的。当有了 eBPF 后,基本上只需要开启上边的开关,就可以快速定位到是哪个基础服务问题。做到秒级明确责任。
4:技术上的效果如何实现
从技术上我们怎么实现这样的一个效果呢?这个也是半年前一次 Meetup 我的主要分享,这里我也简单说一下:
01 首先是数据打通
可以有两种方式,要么观测平台把 OTel 数据上报到 DeepFlow,或者 DeepFlow 把数据导出到我们的接收端。为了和观测平台后续数据的联动,以及减少组件的维护,我们选择了后者,将 eBPF 数据通过Server 导出,同时将数据转换成 OTel 格式,实现数据的统一。
02 其次是数据怎么关联
这里主要是先用traceid 查询,再使用syscalltraceid 以及 tcp 序列号的方式做关联一层层查询
03 另外就是数据怎么展示排序
假如我们按开始时间排,感觉好像没问题,但是当有时针差异的时候,这种方式就会出现错乱,另外也不能按耗时长短来排,像系统和网络之间的耗时差异其实是很细微的,假如系统的时间精度只能去到毫秒,那这种方式也会出现错乱。最后其实我们按系统调用顺序,来自定义了一个排序规则。
04 关于数据隔离问题
比如当同一个集群部署了不同业务的时候,数据就会糅在一起。 所以我们需要做数据的隔离。这时我们可以通过 k8s 的 labels 标签来区分不同 pod 的数据。
解决了上面的几个主要的难点后,我们顺利的得到了前面展示的效果。
5:推广过程中遇到的一些挑战
有了前面的效果后,接下来的话,我们就开始给业务去推广,下面主要是介绍下我们在推广过程中的一些实践,主要集中在这三点。
01 不同环境的一键部署
首先,对于腾讯游戏业务,服务器数量,以及环境的复杂性,多云环境,我们不可能手动部署所有的机器或容器集群。所以推广面临的第一个要解决的点,就是怎么做到不同环境的一键部署。
02 进一步的资源优化
其次,由于服务是要部署到业务集群内,对于资源的占用,我们需要小心又谨慎。最大层度的做到对业务服务无感知,无影响,所以我们需要进一步的优化资源。
03 数据量级带来的存储成本问题
最后,可能也是大家比较关心的,eBPF采集了这么多数据,把系统和网络的每一个数据都采集了,如果都存储的话,大概率可能会遇到存储成本问题,我的测试环境没有跑什么服务,采集的量级都基本上达到了万级别的 qps。
挑战一:如何一键部署
首先我们看下第一个问题,如何做到不同环境的一键部署。
这里前面蓝鲸体系的图里也可以看到,我们是有很多的平台原子层,可以帮忙我们做很多事情,所以这里的问题,其实很好解决,我可以借助蓝鲸容器管理平台和蓝鲸节点管理两个服务来完成这个愿景。
对于 K8S 环境,我们将 DeepFlow 进行标准化改造,去适配蓝鲸的标准化 charts 组件包,同时经过我们的内部安全扫描后,再内置到容器管理平台的公共仓库下,作为一个公共组件,用户可在平台上一键部署到集群内,如右侧的架构图。
对于物理环境,我们也将 Agent 改造为按蓝鲸的采集插件规范,输出蓝鲸的采集插件包,这样既可以集成到我们的节点管理平台上。这里的节点管理可以类似 k8s 对资源的管理一样,也是声明式的,用户只需要声明要部署的集群或者模块,当集群或者模块下有新增机器或者缩减机器时,节点管理都会自动的安装或卸载对应的 eBPF 采集插件。用户几乎都不用关心怎么部署。
挑战二:资源如何进一步缩减
其次,对于资源如何进一步优化,这里我们主要思考了两个点:
第一点:从前面的结构可以看到,我们主要关心的是入口处包含有 traceid 所关联出来的数据,其他的数据暂时不太关心,所以思考这里是否可以增加导出开关,只导出和 OTel 可以关联的数据,这样既可减少server 对格式转换所带来的资源消耗。
第二点,由于前面场景可以看出,既然在这种模式下,我们所关心的数据都已经导出了,那这里的存储是不是也可以作为可选项,由用户来决定是否保留。
我们将这些想法提出来,和社区一起沟通,也得到了社区的进一步认同。
在这里要感谢下 DeepFlow 同学,这个功能在我们提出后,也是比较快的就支持到了,给出了如图相应的配置项。
所以这里我们将存储 disabled 了之后,这样 Server 的资源就得到了进一步缩减,Server 仅仅承担数据转换能力,几乎不占用什么资源。
挑战三:数据量级
最后,对于数据量级问题,我们是这么思考的,从架构图来看,我们主要从两个点入手:
01 从源头处Agent
首先从源头 Agent 端,通过过滤的方式,比如端口的黑白名单模式,或者写 bpf 语句来过滤部分不需要采集的数据。其实当时我们主要遇到的问题是这个,也就是旁边这个图画出来的,Agent 会采集所有组件的数据,发给 Server,Server 会导出数据给到我们的组件 bk-collector,当这个服务部署到同一个集群的时候,其实就会产生循环问题。所以我们想过滤掉bk-collector 的数据采集,于是就有了如图的配置方式。
02 从中间链路部分
其次是链路侧,我们将原有的尾部采样模式适配上 eBPF 的采集数据源,做到更灵活的个性化配置方式,交由用户来决定哪些是他所想要保留的数据。比如这个场景,当用户想保留出错的、或耗时长的,对于不满足条件的也需要保留 50% 数据,那么可以在页面这么配置。
这里需要提的是,这里的满足条件,我们是会保留和这个 span 相关的所有 trace,而不是单独一条数据。所以这里是一个完整的尾部连贯采样。
数据集成与优化
仅仅做了前面的部分是不是就能达到全观测了,貌似还不够。从前面我们可以看到,其实我们只关注了和OTel 有关的部分,也就是只包含了有 traceid 的那部分请求数据。
对于无 traceid 的请求数据我们是没有集成的。其实就会缺失部分组件数据,那么我们怎么去集成它;集成后又怎么让这部分数据也和有 traceid 的数据一样变得有价值,给它赋予应用层含义;以及对于私有协议我们怎么去补充这部分数据。
对于指标数据我们也是没有集成,指标数据在观测领域内,也是不可缺失的一环,不仅要做到可查看,同时也要将告警及时的触达给用户。
下面我对这两部分数据一一讲解。
1. Remote Read 方式集成
首先是无traceid的数据
我们是这么思考的,基于前面的导出思路,我们其实也可以将这部分数据也按前面的方式导出,这样也能集成进来。但是实践发现,这样其实会浪费很大一部分链路资源。
eBPF采集的数据非常多,要让所有的这些数据穿透我们整个链路,对链路的每一个组件资源消耗都会很大。 那倒不如让这部分数据留在原地,我们要使用的时候,直接去拿。于是就有这个Remote Read的集成方式。
我们将 DeepFlow 整体作为我们的一个Remote Storage,数据查询直接走 server 或者clickhouse,所以我们实现了一个查询适配层,实现了和 deepflow-app 一样的关联查询逻辑,同时对协议也做了适配,将查询出来的数据直接转换成 OTel 的格式,这样观测平台就能很好的利用这部分数据。
2. Agent 协议扩展
其次的话,是私有协议问题,腾讯游戏有很多基于TCP 或者 UDP 的私有应用层协议。这里我先大致讲一下Agent的一个解析过程,就能知道我们为什么要做这个了。
数据通过 eBPF 抓取到之后,会经过如图所示的一个大致解析过程,先判断是否曾经解析标记过,如果是则直接进入对应协议的解析过程,然后聚合到一个 session 中,最后发给 server。
如果是未标记的 payload,则会经过一个协议 check的过程,去识别这份数据,按协议顺序HTTP、DNS、MYSQL 等等,这里是一个简化版,实际这个列表里有很多的协议,顺序也不一定是按这个来排列
如果所有的协议都无法识别,那么就会进到这个分支,生成一个 ProtocolUnknown,代表不可识别。这部分的数据最终只能被丢弃,无法聚合。所以我们需要对游戏的私有协议做扩展。
这里我以内部的trpc协议为例说明一下具体的实现方案和效果:
对于 trpc,也是开源的,是一个基于 tcp 的远程调用框架。
右上角这个是 trpc 的协议格式。固定的 16 字节帧头,再加上底下的可变长的协议部分。从旁边这个 wireshark 的截图我们也可以看出来,固定头前面16 位,是以 0x930 为标识,所以可以使用这个来作为解析的依据。同时对于变长的协议部分,我们也可以参照 trpc.proto 的定义来进一步解析。这样我们就可以进一步的获取到里面的调用方,被调方,接口,请求ID 等数据。
从左边的这个 tcpdump 抓出来的包解析,我们也可以看到这些信息。
那了解了协议之后,就可以动手扩展了。基于 Rust 语言去改写 eBPF 采集器,支持trpc私有协议解析方式,其实改动的文件也不多。
最后的效果如图,可以看到列表里已经能看到 TRPC协议相关的请求了。完美的支持了 TRPC 私有协议的无侵入式采集方式。
3. 基于 WASM 应用层数据解析
其次的话,是怎么让这部分数据更好的关联,同时让这部分数据也变得更有意义。
那么我们不仅需要给他补充类似 traceid 一样的请求 ID 信息外,同时也需要给它补充上应用层的数据信息,比如用户信息。这里我们是通过 WASM 插件的形式来扩展。
这里我举一个我们自己业务的例子,这个是蓝鲸服务的 API 接口规范,请求的响应中,都是以这四个字段返回,HTTP 协议部分都是 200,具体是否错误,需要进一步从 body 中获取。
这个时候就需要编写逻辑去解析应用层的这部分 body 数据,然后再将这部分逻辑编译成 wasm 模块,动态加载到 Agent 中。
如图所示的效果,可以看到 result 和 code 字段都补充到了这个网络或者系统的请求下。这样这个调用如果 result 字段不是 true,即可以判断为错误。 同时用户也可以基于这些应用层的信息去检索,可以更快速的排查问题。
关于 Agent 和 wasm 两种协议方式方式扩展,这里我也分享下我们的一些使用心得。
对于 Agent 协议扩展,我们一般用在一些通用的标准的协议框架,比如前面提到的 tRPC 框架,因为它被许多的业务使用。这种方式的扩展成本高,需要使用 Rust 修改 Agent,同时也需要重新发布 Agent 才能生效。不过这种方式扩展性会更强,资源消耗相对会低一些。
对于 wasm 的方式,我们一般用在一些业务特定的逻辑,比如从 http 头部或者 body 中获取信息,将这些信息补充到 span 下。这种方式非常灵活,可热更新。但是由于 wasm 语言相对还不够成熟,限制会比较多,同时也需要在 Rust 和 wasm 之间来回拷贝数据,导致内存资源消耗会更大。
4. Prometheus Remote Write 指标导出
最后的话,是关于指标数据,不仅要做到可查看,同时也要将告警及时的触达给用户。指标的数据量其实比较小,因为是聚合后的数据,同时我们也为了和观测平台告警,以及和其他数据的联动。所以我们还是选择了和最原先的导出方案。
这里我们使用 Prometheus Remote Write 的协议方式,给 Server 增加了一种指标数据导出的能力,将eBPF 采集聚合后的指标数据,通过 Server 导出到我们的平台。这样则实现数据的集成和联动。 这里选择 Prometheus 的协议格式,也是考虑为了结合 Prometheus 的生态,同时让社区的小伙伴都能使用。
所以,我们将这部分能力回馈给了社区,已经合入到了最新的版本。现在社区小伙伴可以直接使用这个,只要是 Prometheus 协议生态的后端存储,基本上都可以接收这个数据,这里我在官方文档上也提供了一个文档指引,详细可以看这个文档使用方式。
到这里的话,基本上上面的能力,已经能满足我们想要的全观测能力。
下面我再以游戏的全栈观测场景,来展示下我们是怎么去实践的。
游戏场景全栈观测实践
1:传统 APM 方案痛点
腾讯游戏组成,一般是由自研和发行两大类。
自研类,就是自己研发的游戏,发行类,简单讲就是第三方公司开发的游戏,在公司内托管运营。对于这类业务,我们也需要感知其内部运行状态,做到全观测。
每个游戏都有其自有的独特架构,开发语言也是各种多变,服务器分布也是在全球各地域,有容器化部署,也有物理机部署模式。
在没有 eBPF 的情况下,我们很直观的想到一种方式,就是将服务都引入 APM,将指标、日志、Trace这些数据都上报上来,这样就能实现业务的全观测。
其实这是一件很难的事情,首先要将所有服务都接入 SDK,第一步就很难实现。对于自研的,还处在研发周期的游戏,勉强还能接受这个方案,但也不是所有服务都能加载 SDK。
对于发行类的游戏就更不用说了,接受度更低,基本上很难要求代理方给你加上这个插码逻辑。
所以这是传统 APM 方案在实践过程中一些比较痛的点,接入成本高,数据无法全覆盖,所以我们尝试引入 eBPF 的解决方案。
2:无侵入式实现解决方案
那么无侵入式怎么解决全观测呢?这个是一个简化的架构图。
玩家在通过手机体验游戏时,请求会通过网络进到我们的后端游戏服务器,根据功能会进到不同的微服务模块,比如登录、支付、商城等服务,去完成相应动作后返回到页面。整个这个链路不是特别长,但是后端的场景服务会特别的多。
在以前,当用户反馈一个问题后,比如卡顿或者支付失败,那么一般的做法会是通过用户 ID,来检索日志。找到时间相关的日志,不同服务需要排查不同日志。这个过程会耗时很长。
引入 eBPF 后,我们在后端所有的服务上都会部署上相应的 eBPF 采集模块。同时在入口服务处,我们通过 wasm 插件的方式去解析到应用层的用户 ID 信息。
有了这些之后,用户就可以通过用户 ID 直接检索,得到相关的入口所有请求,通过请求,可以关联出后面所有服务的整体调用链路。从而快速的帮助用户排查问题。
3:数据全关联方案
同时我们也形成了以 trace 为中心的数据全关联,将其他领域的数据关联到同一个页面,方面快速的查看。这里的关联方式我也大致讲一下。
trace 和 metric 之间是通过同标签的形式,比如同一个主机,或者同一个 pod 的指标,反过来的就是通过 exemplars 打点 traceid 来精确定位到 trace 请求
trace 和 log 之间逻辑类型,通过相同服务的概念,来做到相互关联,比如通过服务产生的日志。反过来就是通过日志里的 traceid 做到精确定位到 trace 请求。
trace 和 profiling 之间可以通过 span_id 来相互跳转
profiling 和 metrics、logs 之间也是通过服务的相关性来关联。
metrcs 和 logs 之间,我们通过同标签的形式,比如同一个主机的数据来关联。
4:具体效果展示
下面展示下,具体的一些效果。
比如当用户反馈一个问题后,我们可以带着这个用户相关信息,在这个 trace 检索页面搜索,即可搜到和这个用户有关的所有请求,同时通过点击这个请求,我们也可以看到这个请求所经过的所有链路服务,同时当开启了 eBPF 开关时,我们还可以看到所有关联出来的所有网络和系统请求,以及未插码的服务,做到 360° 无死角,全观测。
同时,当定位到问题慢之后,也可以通过点击某个具体的 span,看到这个服务当时的一个 profiling 整体的性能请求,具体定位到是哪一个函数,哪一行代码比较执行比较慢。
在 trace 视图下,我们也提供了这种单个 trace 的拓扑视图视角,可以看到整个请求所经过的所有服务调用。同时也可以通过和历史的 trace 请求进行拓扑比对,发现当前 trace 请求的一些问题,比如少调用了某些服务,或者调用某个服务本次耗时异常增加等情况。
也可以切换到时序图的视角下,从时间上看整体请求的调用关系;也可以在火焰图的视角下,更关注的看耗时的情况,火焰图我们也提供的对比的能力,可以看到哪些接口耗时和基线的一个变化情况。
可以看到上面,我们通过以trace为中心的数据全关联的形式,对于玩家反馈的问题,让开发者可以在这个视角下做到数据的全观测。
对于玩家未反馈问题的情况下,我们也通过数据统计分析的方式,提供了这种基于应用的总体观测视角,做到架构的巡检,包括这里的总览视图,全景拓扑图等全局视角,可以很便捷的看到当前应用的一个总体情况,从拓扑也可以进一步下钻分析各个服务底下的情况。
比如服务接口分析,可以看到当前服务下接口的所有请求情况,黄金指标等数据。同时也可以关联到前面的trace请求视图下,看这个服务所产生的所有trace请求情况。
比如错误分析视图,可以看到的错误类别统计,以及每一种错误的堆栈情况。
同时也提供了这种数据库请求的视角,可以看到一些 sql 语句的慢查询等请求,从而进一步的优化自己的程序逻辑。
前面的这些数据也都是可以配置告警的,这样当发现问题的时候,可以做到及时的触达到开发者。
这个是一个产生了一个告警的视图,从告警视图下,我们也基于这个告警的相关信息,去关联其他五大类数据,可以更加便捷的辅助用户更快速的定位问题。
最后再讲一下未来的展望,这里主要是对前面部分实践的一些愿景。
未来展望
首先是,对于应用层数据解析部分,希望做到自动生成 wasm 插件的形式,用户通过在页面定义好数据的字段提取规则,而不是手动的去编写插件代码,从而更多的让开发者还有 SRE 同学都能参与进来。
其次的话,是对于 Profiling 的部分,前面我们主要展示的是应用层的 Profiling,后续也会考虑这种无侵入式的通过 eBPF 采集的模式。
OK,以上就是我要分享的全部内容了。谢谢。