我们主要从以下几个方面,逐步做了架构演进:
技术选型:
我们基于开源 Victoriametrics (以下简称 Vms ) 作为整体架构基础,针对小红书内部的业务场景和痛点进行定制开发,以适配公司内部组件。全链路选择 Vms 的原因主要是:
演进后,当前架构图如下所示:
包括以下核心组件:
接下来我们将从采集端、全链路高可用方案、查询优化、高基数处理和跨云多活等方面来详细介绍。
1、采集我们主要从以下几个方面,逐步做了架构演进:
技术选型:
我们基于开源 Victoriametrics (以下简称 Vms ) 作为整体架构基础,针对小红书内部的业务场景和痛点进行定制开发,以适配公司内部组件。全链路选择 Vms 的原因主要是:
演进后,当前架构图如下所示:
包括以下核心组件:
接下来我们将从采集端、全链路高可用方案、查询优化、高基数处理和跨云多活等方面来详细介绍。
1、采集在最初的采集端部署方案中,我们按照每个 K8s 集群进行 1:1 的部署。例如,推荐集群在多达 15 个 K8s 集群中进行部署,整个公司的 Prometheus 部署组超过两百个。在资源使用方面,普遍采用的是 1:8 的大内存机型,其中某些 Prometheus 实例的内存配置接近于 512GB;即便如此,几乎每周都会出现多次内存超过 85% 的告警。扩容时由于大内存资源无法及时释放,导致故障时间延长。采集变更通过上线变更实现,当业务需求发生变化时,需要对十几组 Prometheus 分别进行上线,集群运维复杂烦琐。为了快速迭代上线,不同部署组的 Prometheus 配置逐渐分裂,增加了配置管理的负担。
1)整体架构
针对以上这些问题,我们重新设计实现了采集的架构,整体架构如下:
基于 vmagent 的开发方案:我们选择使用 vmagent 替代 Prometheus,并通过全链路的双活机制(后文介绍)替换和下线 Thanos。放弃使用 Prometheus agent 的原因是,在原型验证阶段发现了该代理存在的必现 Bug,且社区无相应的解决方案。经过测试,我们发现 vmagent 在性能方面明显优于 Prometheus agent,且其代码结构更加简洁直观,可扩展性更好。
指标采集配置动态生效:采集配置通过配置中心下发,vmagent 接收并保存到本地,周期性地检查配置变化,并通过重新加载(Reload)生效。这样,日常业务采集需求的变更可以通过产品化修改配置下发,避免了 Prometheus 变更每次需重新发布或者黑屏手动 Reload 的复杂操作,大幅度提高了运维效率。
分片&平滑扩缩容:考虑到实际生产环境中集群规模庞大,无法通过单个实例采集所有指标的情况,我们提供了简单配置能力以支持采集分片和快速平滑扩缩容。我们在采集配置中新增全局的 Shard_count 配置,用于表示实际的采集分片数。通过配置中心下发 Shard_count 参数后,每个采集分片会 Reload config 并更新采集对象 (Scrape targets) 列表。在更新 targets 列表时,我们使用 Pod_index 和 Shard_count 进行哈希计算,以保留相应的 targets。
对于扩容场景,先扩容实例,再修改并下发 Shard_count 参数:
对于缩容场景,先修改并下发 Shard_count 参数,等采集流量迁移完成后,再缩容实例:
采集过程保护:在业务生产环境中,经常会出现由于 Metrics 异常使用造成时间序列膨胀问题,进而导致采集进程资源占用不断飙升、甚至崩溃的情况。因此,采集过程的限制和保护是非常必要的。我们目前在单次采集的 Sample 设置上限、Sample 内容长度两个层面进行校验。
Sample limit 增强:支持在一个采集 Job 内单独配置 Sample limit,为重点应用额外设置 Sample limit,并优先保留核心 Metric 指标和重点集群的 Metric 白名单。
Metric、Label 长度校验:开源的 vmagent 并未对 Metric 和 Label 的长度做校验。然而,在实际生产环境中,经常出现业务不规范使用造成的 Metric 和 Label 内容过长问题,导致 CPU 突然飙升,需要紧急扩容大量资源来应对。因此,在处理采集到的指标内容时,我们增加了对 Metric 和 Label 内容长度的校验,并对超长的指标进行告警,同时做截断处理。
性能优化:在采集性能方面,我们发现当 vmagent 面对大量采集对象时存在一些问题,对此我们进行了针对性的优化,包括:
大量采集对象的延迟启动:当 vmagent 在进程启动或运行时新增大量对象采集时,会出现 CPU 和内存的抖动,相对日常运行时的 CPU 内存使用率明显增加,甚至可能导致 OOM。通过延迟初始化的方式,将大量对象分批启动,启动资源消耗降低到日常运行的消耗水平,使整体消耗降低一半。
删除大量采集对象时的 OOM:在删除大量对象时,通过使用对象池和并发限速的方式,解决资源占用过多的问题。
资源使用率不均衡:在实际生产环境中采集大量对象时,vmagent 的 CPU 使用率通常较高,而内存使用率很低(有时甚至低于 10%)。通过性能分析,我们发现 CPU 的大部分消耗来自垃圾回收(GC)。从 Golang 1.19 版本开始,提供了 SetMemoryLimit 功能,可以提高 GC 的阈值,降低 GC 的频率。通过调整内存限制,内存使用率得到提升,通过内存换 CPU 的方式,CPU 使用率普遍降低近一半,从而降低整体采集过程的资源消耗。
我们完成几十个 K8s 集群、数百个 Prometheus 虚拟机采集实例的治理,并成功将整体资源统一迁移到了新 Metrics 采集架构上。迁移后,采集模块的变更统一到小红书内部的发布平台和配置中心进行管理,使得采集变更更加高效便捷,同时实现可灰度、可回滚和可追溯,大幅降低了日常运维的复杂性,解放了运维人力。在资源消耗方面,新的 Metrics 采集架构相对于原有架构,采集性能提高近十倍,降低成本折合数万核 CPU、数百 TB SSD 。此外,在机器资源规格方面,我们不再依赖于大内存的机器,使得扩缩容更便捷,同时显著降低内存等资源告警量。
2)多种指标的标准化采集
K8s 基础指标的采集:
当前我们使用 cAdvisor、Node_exporter、Kube-state-metrics 等 K8s 组件来采集每个 K8s 集群的基础指标。最初 Kube-state-metrics 部署方式为单机,但由于部分 K8s 集群的节点数量达到近 5000个,Kube-state-metrics 暴露的指标量超过 500 万,单次采集响应的数据量超过 700 MB。每个K8s集群都部署了一个独占的、高规格的 Prometheus 来确保采集不会超时。然而,这种部署方式浪费了大量资源,且存在稳定性差难维护、升级扩容困难、单机扩容存在上限隐患等问题。
为了解决这些问题,我们将 Kube-state-metrics 调整为 Statefulset 的分片部署方式。每个分片只上报自身指标,并自动感知总分片数,从而降低单个分片暴露的指标量。在多分片进行扩缩容时,我们通过在采集配置中配置 Relabel 规则,将 Instance label 覆盖为固定值,以解决由于 Instance label 变化导致的指标抖动问题。此外,我们在变更时增加一个额外副本,并结合 Vmstorage 的去重能力,来实现用户无感的 kube-state-metrics 升级、扩缩容等运维操作
在实现分片部署后,我们使用现有的 vmagent 集群进行 Kube-state-metrics 的抓取,并下线了之前独占部署的 Prometheus。
虚机监控采集:
小红书的主机监控依赖 Consul 作为服务发现,由于公司逐步下线 Consul,且主机监控面临多种问题:Consul 的性能衰减导致注册超时,导致大量新机器的监控缺失;退机时 Consul 解注册接口失败,导致已下线的主机仍然尝试采集指标,触发误告警,日均引发 2 个以上的相关客服工单。此外,除了 Consul 之外,部分服务通过静态 IP 配置抓取,而地址信息的更新需要通过 Metrics 研发团队同学发布才能生效,缺乏白屏化管理。
针对上述问题,我们上线了新的主机监控系统:首先,我们定制了新的注册和服务发现方式。在主机信息维护机制上,使用服务树权威数据来源同步,替换了原有的基于变更维护的方式,以解决监控数据不一致的问题。此外,我们使用“局部同步+兜底全量同步“的方式提高同步性能。
同时,我们上线配置平台,提供了标准化产品化的接入方式,基本的配置变更无须研发团队介入。目前,已有 50+ 个主机监控采集任务由用户自主接入,包括网关、数据库、etcd、zookeeper 等,共抓取 10 多万个监控对象。此外,开关机注册的监控成功率也大幅提升,客服工单数量从日均 2 个以上,降低到两个月1 个,释放出运维人力。
业务数据推送通道:
针对各个数据源普遍存在少量的推送需求,例如 Python 类服务,我们提供统一的业务 Push 数据链路,通过推送服务网关,接收各个业务方的推送数据,并根据租户信息将数据分发到实际的业务数据源。
多 K8s 集群管理:
为简化采集端部署方式,并支持同时采集多个 K8s 集群的应用指标,我们采用了集中式抓取方式。由于公司内部常常新增 K8s 集群,因此采集端支持实时新增 Kube-config 配置,以便快速生效新集群的监控采集。
3)埋点 SDK 增强
小红书内部的 Java 服务使用基于 Micrometer 的 Prometheus 埋点 SDK,Micrometer 提供了丰富的度量类型、自动化配置以及默认的指标采集,并与 SpringBoot 等流行框架有良好的集成。然而,面对小红书核心服务的高复杂度,以及 Metrics 埋点的高 QPS 和时间序列基数,Micrometer 的使用逐渐暴露出一些问题:
针对核心业务 Prometheus 埋点性能和稳定性问题,我们对 SDK 功能进行了增强:
此外,为了降低用户 PromQL 的使用成本,我们在埋点 SDK 侧收集了 Metrics 元数据,包含 Metric 名称、Metric 类型、Label 列表等信息。产品层根据 Metrics 元数据,默认生成业务基本的 PQL 语句,以实现用户开箱即用的体验。这一举措不仅提升了用户体验,同时也减少了产品配置工作。总体结构如下图所示:
2、高可用改造1)背景
在之前的架构中存在以下问题:
全链路单副本:系统只有单个实例,导致在单实例故障、多实例故障和变更等场景下缺乏基本的高可用性,造成监控数据丢失、大量误告警,严重影响用户体验。当集群不可用时,恢复时间较长。
集群雪崩:多次出现单个数据源雪崩现象,特别是当集群负载较高时(如内存使用率超过40%)。
静态 IP 不变依赖/弹性扩缩容:系统在服务发现方面依赖静态 IP 不变的特性进行写入和查询操作,导致扩缩容、集群切换等变更非常困难。云原生化后受到静态 IP 保持特性的限制,只能选择提供静态 IP 保持的云产品。此外,由于 Pass 平台提供的静态 IP 不变特性存在 bug,也会导致故障。
实例数据安全:大多数实例数据存储在本地盘上,部分实例出现数据文件被整体误删无法恢复的情况。
基于以上问题,我们的目标如下:
故障容忍:系统需要容忍一个集群内的部分实例故障和一个集群整体故障,降低集群雪崩的概率。
快速扩缩容:在资源可用的情况下,能在几分钟内实现平滑的水平扩容,无须通过应用发布上线。
数据安全:在数据误删等场景下,能够进行数据恢复。
快速迁移能力:加快机房迁移速度,对于单节点存储量低于 1TB 的实例,在一天内完成迁移。
2)整体架构
高可用的整体架构如下:
针对上述问题,我们依次提出解决方案:
为确保高可用性和数据备份,我们采用全链路双活部署。在采集侧,我们部署两套完全相同的采集集群,并将数据同时写入两个远端存储集群。在存储侧,我们同样部署两套存储集群。每个存储集群正常情况下会收到两份采集数据,为避免数据重复,存储集群在数据合并时进行去重操作,确保在同一时间窗口内相同的指标数据只保留一份。通过这种部署方式,系统可以容忍单个集群完全故障的情况。
在单副本模式下,线上会遇到存储集群雪崩故障的情况。经过分析,我们发现这主要发生在指标量快速增长、集群整体负载较高的情况下,同时伴随着存储节点实例故障或者少量扩容。在此情况下,Vms 使用 Reroute 重定向机制,通过一致性 Hash 将原本写入问题节点的流量,分摊到其他可用节点上。虽然这种方式确保数据的持续写入,但是剩余存储节点在短时间内收到大量新序列数据后,需要创建索引和缓存,致使 CPU 和内存的快速上涨。如果剩余存储节点本身负载较高,无法及时响应写入请求,将进一步触发新的 Reroute,形成雪崩效应,导致整个集群无法进行写入和查询操作。
因此,考虑到 Reroute 机制作为实例级别的高可用机制,在某些情况下存在一定危险性。在已经对存储构建了双活集群的情况下,我们考虑替换 Reroute 机制,以避免整个集群发生雪崩现象。为了确保同一份数据写入固定的下游存储节点,我们引入了本地队列机制。具体而言,当下游存储不可用时,Vminsert 将数据写入本地队列, 等存储恢复正常后,在消费队列中的数据将写入下游存储。两种机制对比如下:
本地队列方案与存储双活结合后,当主存储集群中的实例发生故障时,Vminsert 会产生时序数据积压,并向 meta service 上报数据情况。一旦积压超过阈值,meta service 通知查询集群自动切换至备用存储集群。当主存储集群中的故障实例恢复后,Vminsert 逐步消费积压的快照数据。积压恢复后,Vminsert 会再次上报给 meta service,随后 meta service 通知查询集群切换回主集群。通过这种自动化的故障容忍和处理机制,我们实现了对集群故障的自动化处理。
静态 IP 不变依赖/弹性扩缩容
Vms 仅在商业版中提供了服务发现功能,而开源版本需要手动更新 -storageNode 启动参数来调整 Vmstorage 地址,运维成本较高。在云原生场景中,实例迁移时 IP 会发生改变。最初的 Vms 存储集群部署依赖底层云服务商提供的静态 IP 特性,但由于各种原因,IP 特性失效导致 IP 被释放的问题多次出现。此外,依赖云厂商的特性也限制了部署环境的选择。因此,我们开发了支持 Vmstorage IP 变化和服务发现的解决方案,具体如下:
新增 meta service 支持服务发现机制,Vminsert 和 Vmselect 定期向 meta service 轮询,以获取最新的 Storage 地址列表
调整节点分片 Hash 算法
灰度扩缩容
实例数据安全
使用云盘代替本地盘
初始阶段,存储节点部署在虚拟机上,并将数据写入本地盘。然而,在实例故障时,存在数据丢失无法恢复的风险。为了确保数据在实例故障和迁移等场景下不会丢失,我们选择将存储迁移到云盘上,以实现容器化部署和迁移。
Backup/Restore 机制
在极端情况下,底层存储 PV 可能会受到不良影响,例如由于错误的 PV 配置导致误删除,写入异常数据或误操作删除文件而导致 Vmstorage 无法启动等问题。为了解决这些问题,我们需要对存储进行备份。通过利用 Vmstorage 提供的快照能力,对 Vmstorage 的全量数据进行增量备份与版本管理,每小时、每天、每月将备份数据按需保存多份至对象存储,即可在发生故障时,回滚至正常状态。示意图如下:
此外,集群迁移也是一种重建方法。基于 Backup/Restore 机制,结合 Vminsert 实现的本地队列能力,我们可以无损将存储集群快速迁移到新的部署环境。近期,我们将 Vms 在云产品之间做迁移部署时,便利用了这一机制加快迁移过程,将迁移时间从以前的至少需要 30 天的双写缩短到半天,避免长时间双写带来的额外集群成本。总体流程如下:
全量数据备份与初始化同步:假设时间为 time 1,对原始集群的 Vmstorage 数据进行全量备份,上传到对象存储中;在完成数据备份后,迁移后的新集群从对象存储中获取到截止至 time 1 的全量数据。此时,Vmagent 的采集流量将不会写入迁移后的新集群。
增量数据备份与同步:Vmagent 开始将采集流量写入新集群,此时新集群的 Vmstorage 设置为 not ready 状态,即 Vminsert 无法连接 Vmstorage。Vminsert 将快照数据写入队列(queue)中。一旦 Vmagent 的采集数据稳定地写入新集群(假设时间为 time 2),原始集群开始增量备份数据(time 2 - time 1 的数据)。完成增量数据备份后,新集群再次从对象存储中获取增量备份数据,使得新集群具有截至 time 2 的数据。
数据回放与去重:新集群中的 Vmstorage 消费备份数据完成后,将其设置为 ready 状态(假设时间为 time 3)。Vminsert 开始回放 queue 中的数据(time 3 - time 2 的数据)并写入 Vmstorage 中。在 queue 的快照数据回放完成后,新的 Vmstorage 集群具有完整的全量数据。在整个过程中,时间范围上重叠的数据在 Vmstorage 中被 dedup 去重,以保证数据的一致性和准确性。
3、查询优化在指标查询方面,我们之前面临的主要问题是业务方反馈一些 PQL 查询速度较慢,并且存在一些高基数、高密度数据无法正常查询的情况。此外,从查询模块本身来看,多次出现大量数据的查询导致内存溢出(OOM)的问题。我们之前暂时通过扩展资源来部分缓解这些问题,但是随着业务指标量的不断增加,简单地堆积资源的方式已经无法持续发展,也不能从根本上解决问题。因此,我们决定从架构层面寻求更有效的解决方案。
1)计算下推
以一个常见的查询 sum(rate(http_request{code=200}[1m])) 为例,开源查询流程如下:
查询节点解析 PQL 后,提取出 Metric + Tag,并向存储节点发送请求。存储节点根据 Metric + Tag + time range,查询原始的 Timeseries 数据,并将 Timeseries 数据返回给查询节点。查询节点在接收到所有的存储节点返回的 Timeseries 数据,依次进行 rate、sum 等操作。在时间线特别多的情况下,单个存储节点返回的时间线原始数据量较大,导致查询节点接收到所有的时间线量更大,进而导致后续的 rate、sum 等操作需要更多的计算资源,增加了处理延迟。此外,为了防止查询节点因大数据量查询的影响而崩溃,查询节点会对时间线数量和单个查询的内存上限限制,从而使得部分 PQL 查询因触发上限而无法正常执行。
为了解决上述问题,我们考虑将聚合操作(如 sum、count、avg、max、min等)下推至存储节点执行,这样存储结点进行聚合后,可以极大地减少返回给查询节点的原始数据量,并且能够显著提高查询量的上限。
Vms 本身不支持计算下推功能。这是因为在存储节点对时间线进行聚合之前,要求一个时间线在查询的时间范围内只能存在于单个存储节点中,然而由于写入时的 Reroute 机制,一个时间线在存储节点中的分布是未知的。
为了解决这个问题,我们在高可用改造中去除了对 Reroute 机制的依赖。在非扩缩容的情况下,单个时间线是稳定地保存在一个存储节点中。此外,Metric 的特点是大多数查询都集中在最近的时间段内,而扩缩容操作相对低频且持续时间很短,因此在大多数情况下,我们可以跳过扩容的少数时间,依然能将聚合操作下推到存储节点。这一点改进对系统尤为重要,线上绝大多数的查询逻辑都包含聚合类的计算,非常适合进行下推操作。
计算下推后,新的流程如下:
在存储节点收到查询请求后,根据 Metric + Tag + Time range 查询到原始的 Timeseries 数据。参考查询节点中的类似处理逻辑,进行 rate、sum 操作,得到聚合后的 Timeseries 数据,返回给查询节点。在绝大多数查询 Case 中,聚合后返回的 timeseries 数据量,比原始的 Timeseries 数据量大幅下降。查询节点在收到各个存储节点的响应后,不再进行 rate 操作,而只对 Timeseries 数据进行 sum 操作。
在各个聚合类操作中,avg 操作需要进一步特殊处理。在各个存储节点中直接计算出 avg 是不准确的。对于 avg 操作,需要转换成 sum/count 并在查询节点中执行,即在查询节点中 avg = sum/count。以 avg(rate(http_request{code=200}[1m])) 为例,整体流程如下:
Vmstorage 节点在首次收到查询请求时,通过查询获得 Timeseries 原始数据,rate 后同时计算出 sum 和 count 数据。其中,sum 得到的数据返回给查询节点;同时存储 count 数据到 cache 中。查询节点在接收到第一次返回的 sum 数据后,会发送第二次查询请求以获取 count 数据。当查询节点收集到所有存储节点的 sum 和 count 数据,通过 sum/count 操作计算出最终的 avg 结果。
当前计算下推优化已经在推荐、社区业务监控落地。这一优化措施对于处理大数据量的慢查询,实现了几倍甚至几十倍的查询速度提升。同时,查询量上限也得到了显著提升,例如在推荐的排序服务中,一个查询支持的查询范围提升了 70 倍。
2)查询数据量限制
在 Vms 当前的查询逻辑中,查询节点将 Metric + Tag 传递给存储节点,存储查询到 Timeseries 后返回给查询节点;查询节点再将各个Vmstorage 返回的 Timeseries 进行加载处理。在存储节点数量多,且单次返回的 Timeseries 数量较大的情况下,可能会导致查询节点出现 OOM 的问题。根据我们对实际线上 case 的观察,一些大数据量查询的场景中,查询进程的单次内存使用量超过了 50GB。
因此,我们需要从多个角度对查询内存进行保护,以防止由于一些不合理的查询条件,导致查询节点出现 OOM。首先,针对单个 Query 的多个子查询,在加载处理 Timeseries 时,需要预估内存使用量,并对其超限的查询进行限制。对于单个查询内的多个并发执行的子查询,应进行统一计算和限制内存使用量。
此外,在计算下推的场景下,存储节点本身会在查询数据后,对查询结果进行计算。因此,存储节点本身也需要限制内部数据量的上限,以确保其正常运行。
4、高基数治理高基数问题(Cardinality)一直是 Metrics 领域避不开的话题。在云原生生态中,例如 Kubernetes 自带标签和服务自定义标签设计的不合理、 Label 取值宽泛等问题常常导致高基数问题的出现,时间线爆炸影响存储和查询集群的稳定性。
在小红书内部,高基数问题尤为明显,经常出现由于用户使用不当、参数出现随机值等异常情况造成 Timeseries 数量激增的情况,进而导致数据采集和存储资源水位不断上涨。历史上出现的存储单节点崩溃现象,90% 以上的 Root Casue 均是由高基数问题引起的。
为了解决高基数问题,一般有以下几种思路:
通过产品化+自动化的方式,实现低成本的 Label 基数控制,满足用户差异化的合理基数需求,有效保障数据采集和存储的稳定性。
独立高基数指标的数据处理通道,提供更高能力的数据存储方式,不影响其他指标的稳定性运行
用户使用门槛增加,需要其理解基数管理的概念,并合理评估取值上限
采用独立高基数指标采集和存储方式后,很难无缝兼容原有的查询使用习惯
高基数问题没有一个完美的解决方案,需要在性能、稳定性和成本之间进行平衡。不同用户有差异化 Label 基数需求,例如基础组件、复杂业务场景的 Label 基数需求更高,不应该采取一刀切策略。
因此,我们的策略是在时序存储写入的容忍范围内,尽可能满足用户合理的指标基数需求,并通过上述计算下推的能力提升用户高基数的查询体验。
对于不合理的使用场景,严格抑制由随机取值造成的时序数据急剧膨胀问题。针对类似用户行为、商品订单和算法实验等真实高基数场景,建议采用日志或者独立 Push 通道进行采集和处理。综上所述,我们最终选择”差异化动态基数管理 + 高基数隔离“的解决方案,整体架构如下:
其中,Label_manage 是 Vminsert 中可插拔的核心扩展模块,它主要负责完成以下工作:
Label_manage 提供了分级的高基数检测策略,全局 Label 白名单检测优先,Label 基数超限后会触发指标级别的 Label 高基数检测,并支持不同服务指标的 Label 隔离。每个 Label 都会绑定一个 Bloomfilter,用户申请的 Metric 和 Label 配置会动态生成指标级别 Bloomfilter。超出基数限制的 Label Value 将被统一归类为特定的值,并对数据进行重组。具体检测逻辑如下所示:
5、跨云多活小红书业务部署在多个云上,Metrics 最初的跨云部署情况如下:
在每个云环境的 K8s 集群内,我们部署了 Prometheus 来负责采集集群内的 Pod 指标和基础指标,并将所有采集到的指标统一写入到对应云环境的存储中。然而,在混合云部署中,这种设计带来了两个主要问题:
跨云传输问题:由于监控数据需要跨云传输,导致跨云带宽成本高。
单 Region 故障风险:所有数据都存储在一个云环境中,一旦该环境出现问题,整个监控系统将面临整体不可用的风险。此外,如果跨云专线发生故障,可能导致其他 Region 的监控数据无法访问。
为了应对业务监控数据在跨多云环境中的挑战,我们采取了一系列措施。
本文地址:http://syank.xrbh.cn/quote/842.html 迅博思语资讯 http://syank.xrbh.cn/ , 查看更多