`
yangzb
  • 浏览: 3470070 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

GlassFish 版本 2 中的群集功能

阅读更多

GlassFish Java EE Application Server 版本 2 包含很多新功能,其中包括增强的群集功能。新的群集功能通过内存中会话状态复制增强了部署体系结构的可用性和可伸缩性。群集服务器实例通过使用内存中状态 复制,在一个环形拓扑结构中复制会话状态,并将复制的信息存储在内存中。

本文介绍了 GlassFish 版本 2 的群集功能,并帮助您着手将应用程序部署到 GlassFish 群集中。

Sun Java System Application Server 9.1 是 Sun 支持的开源 GlassFish 版本 2 应用服务器的分发版。本文使用名称“GlassFish 版本 2”来表示这两者。

目录

基本概念

应用服务器中的群集功能可增强可伸缩性和可用性,而这两者是彼此相关的概念。

为了提供高可用性的服务,软件系统必须具备以下功能:

  • 系统必须能够创建和运行服务提供实体的多个实例。对于应用服务器而言,服务提供实体是指配置为在群集中运行的 Java EE 应用服务器实例,服务是指部署的 Java EE 应用程序。
     
  • 系统必须能够在群集中添加应用服务器实例,从而扩大部署规模,以接受日益增加的服务负载。
     
  • 群集中的一个应用服务器实例发生故障时,必须能够故障转移到另一个服务器实例,从而使服务不会中断。尽管服务器实例或物理计算机故障可能会使总体服务质量有所下降,但在高可用性环境中服务完全中断是不可接受的。
     
  • 如果某个进程更改了用户会话状态,则在进程重新启动后必须保留该会话状态。最简单直接的方法是保留一个可靠的会话状态副本;如果进程异常中止,可以在进程重新启动后恢复会话状态。这一原理与高可靠性 RAID 存储系统中采用的原理类似。

总之,这些要求必然会导致系统牺牲高效率来换取高可用性。

为了实现可伸缩性和高可用性目标,GlassFish 应用服务器提供了以下服务器端实体:

可以在安装 GlassFish 时创建节点代理、服务器实例和群集,如本文结尾部分 所述。群集和实例组成了管理域 ,而域管理服务器 (DAS) 是这些域的主要特征,如下所述。

域管理体系结构

GlassFish 群集体系结构的核心是管理域的概念。管理域表示管理员或管理员组的访问权限。下图简要说明了单一域环境下的域管理体系结构。

字典的类图
图 1. 域管理体系结构

 

管理域是具有双重特性的实体:

  • 在由开发者使用时,它提供一个功能完善的 Java EE 进程,可以在其中运行应用程序和服务。

  • 在实际企业部署中使用时,它提供专用于配置和管理其他进程的进程。在这种情况下,管理域采用域管理服务器 (Domain Administration Server, DAS) 的形式,只能用于管理目的。

在文件系统中,管理域由一组配置文件组成。在运行时,它是一个对本身、独立服务器实例、群集、应用程序和资源进行管理的进程。

总的来说,高可用性安装需要的是群集,而不是独立服务器实例。GlassFish 应用服务器提供同构群集,使您能够将每个群集作为单一实体进行管理和修改。

如图所示,每个域具有一个域管理服务器 (Domain Administration Server, DAS),它用于管理域中的 Java EE 服务器实例。图中央的管理节点支持 DAS。应用程序、资源和配置信息存储在离 DAS 很近的地方。由 DAS 管理的配置信息称为配置中央系统信息库

每个域进程必须在物理主机上运行。在运行时,域本身表现为 DAS。同样,每个服务器实例必须在物理主机上运行,并且需要 Java 虚拟机。必须在每台运行服务器实例的计算机上安装 GlassFish 应用服务器。

管理域
 
不要将管理域网络域 这两个概念混为一谈,这两个概念毫无关系。在 Java EE 环境中, 是指管理域:由管理员控制的计算机和服务器实例。
 

图右侧显示了两个节点(节点 1 和节点 2),它们分别承载两个 GlassFish 服务器实例。

每个节点代理控制在其计算机(位于给定域中)上配置的实例的生命周期。通常,每个生命周期是由 DAS 根据管理员要求进行管理的。 DAS 将每个实例的实际生命周期管理委派给其相应的节点代理。节点代理是一个轻量进程,它本身并不运行 Java EE 应用程序。

除了控制实例生命周期以外,节点代理还监视它所负责的服务器实例。如果服务器实例发生故障,其节点代理便会将其重新启动,而不需要管理员或 DAS 进行干预。

图 1 的左侧显示了几个管理客户机。 DAS 中的管理基础结构基于 Java 管理扩展 (Java Management Extensions, JMX) 技术。DAS 中的基础结构遵循 JAX 规范的设备级别规定,并以受管 Bean (MBean)(MBean 是表示要管理的资源的 Java 对象)的形式使用管理信息。

由于 MBean 符合 JMX 标准,因此,您可以使用任何远程标准 JMX 客户机(如随 Java SE 5.0 及更高版本分发的 JConsole)浏览它们。图 1 中所示的内置客户机使用 JMX API 来管理域。这些客户机需要具有管理员权限才能管理域。以下是几种备受关注的管理客户机:

  • 管理控制台 – 管理控制台是一个基于浏览器的界面,用于管理中央系统信息库。中央系统信息库提供 DAS 级别的配置。
     
  • 命令行界面asadmin 命令提供与管理控制台相同的功能。另外,某些操作只能通过 asadmin 来执行,如创建域或节点代理。除非具有 DAS ,否则无法运行管理控制台,而 DAS 又是以域和节点代理作为先决条件的。asadmin 命令提供了一种引导体系结构的方法。
     
  • IDE – 图中显示了 JSP (JavaServer Page) 编辑器的快照,它是 NetBeans IDE 的一部分。在开发期间,NetBeans IDE 等工具可以使用 DAS 连接到应用程序并对其进行管理。NetBeans IDE 还支持群集模式部署。大多数开发者在单一域中使用一台计算机进行工作,这称为开发者配置文件 。在开发者配置文件中, DAS 本身充当所有应用程序的主机。
     
  • Sun Provisioning Server – Sun Provisioning Server 用于在进行了基本配置的计算机上安装和置备 (provisioning) DAS 。例如,假设要在一个大型数据中心中添加一台新计算机。在这种情况下,需要在计算机上安装操作系统对其进行初始化,然后安装所需的所有软件产品。此后,您可以创建一个节点代理;根据具体要求,可能还需要创建 DAS 。最后,应启动节点代理,以将该计算机合并到现有域中。Sun Provisioning Server 可以完成所有这些操作,您无需在新计算机上执行手动安装。

群集体系结构

图 2 通过以运行时为中心的视角展示了 GlassFish 群集体系结构。此视图强调了该体系结构的高可用性方面。图 2 中没有显示 DAS,节点及其应用服务器实例组合在一起,形成群集的实例。

群集体系结构概况
图 2. 群集体系结构概况

 

图 2 顶部显示了各种传输协议( HTTPJMSRMI-IIOP ),它们通过负载平衡层与群集实例进行通信。自定义资源(如企业信息系统)通过 Java 连接器体系结构中的资源适配器连接到负载平衡器。可以跨群集在所有传输协议之间实现负载平衡,这既可以提高可伸缩性,又可以通过在发生单点故障时提供冗余设备来实现容错策略。

图的底部是高可用性应用程序状态系统信息库(一个会话状态存储概念)。系统信息库存储会话状态,包括 HTTP 会话状态、有状态 EJB 会话状态以及单点登录信息。可通过内存复制或数据库的方式存储这种状态信息。

高可用性数据库的替代技术

Sun Microsystems 以前曾为应用服务器提供一种可靠的高可用性解决方案,这种解决方案基于高可用性数据库 (High-Availability Database, HADB) 技术。HADB 为维护会话状态信息提供 99.999%(“5 个 9”)的可用性。不过,它的实现和维护成本相对较高;虽然可以免费获得该技术,但没有提供开源版本。

为了给开源 GlassFish 应用服务器提供一种轻型的开源替代技术,我们在 GlassFish 版本 2 中提供了内存复制功能。内存复制依靠群集中的实例,将其他实例的状态信息存储在内存中,而不是存储在数据库中。但仍可以使用 HADB 解决方案,它在很多安装中可能还是首选方案。

群集中的内存复制

在内存中保存状态信息且与 GlassFish 兼容的容错系统必须具备一些功能。系统必须提供高可用性的 HTTP 会话状态、单点登录状态和 EJB 会话状态。另外,它必须与基于 HADB 的现有体系结构相兼容。

内存复制功能利用 GlassFish 群集功能来提供 HADB 策略的大多数优点,而安装和管理开销却小得多。

在 GlassFish 应用服务器中,群集实例组成一个环形拓扑结构。环中的每个成员将内存状态数据发送到环中的下一个成员(其复制伙伴),并从上一个成员接收状态数据。在任何成员中更新状态数据后,将会在整个环形拓扑中复制该数据。图 3 显示了简化形式的拓扑结构。

字典的类图
图 3. 群集拓扑结构

 

拓扑结构形成环形的方式是由为实例指定的名称的字母数字顺序决定的。因此,如果按图 3 所示对实例进行命名,则环形拓扑中的实例 1 将复制到实例 2,实例 2 将复制到实例 3,依此类推。

典型的群集拓扑结构如图 4 所示。在该图中,显示的实例位于不同的物理计算机上。通过将实例 1 和实例 3 放在一台计算机上,而将实例 2 和实例 4 放在另一台计算机上,可以最大限度地提高可用性。如果其中一台计算机发生灾难性故障,另一台计算机上将会保留所有数据(以原始形式保存或以故障计算机上实 例的副本形式保存)。

典型的群集拓扑结构
图 4. 典型的群集拓扑结构

 

典型的故障转移方案

GlassFish 应用服务器进行了专门设计,从而在发生故障时,负载平衡器层不需要特殊信息即可有效地执行负载平衡。例如,负载平衡器已将一个会话传送到实例 1,它并不需要知道应该在实例 1 发生故障时将该会话传送到实例 2。负载平衡器可以向群集中的任何实例发出故障转移请求,这种情况通常称为位置透明性。在群集中对故障进行响应。当负载平衡器将一个会话重新传送到正常工 作的实例时,如果需要的话,该实例会从另一个实例获取所需的存储会话信息。

来自负载平衡器的故障转移请求分为以下两种情况:

图 5 更详细地展示了群集结构。左侧是负载平衡层,可能位于 Web 服务器上。每个服务器实例中有一个用于存储 HTTP 会话信息的本地高速缓存,该高速缓存将复制到下一个实例中的副本高速缓存。

具有负载平衡器的典型群集拓扑结构
图 5. 具有负载平衡器的典型群集拓扑结构
单击此处 可查看大图

 

图 6 说明了第一种故障转移情况:重新传送的服务器实例可以直接访问会话状态数据。在该图中,实例 1 发生故障,负载平衡器的服务请求恰巧被传送到实例 2,而实例 2 具有所需会话状态信息的副本。

第一种故障转移情况
图 6. 第一种故障转移情况
单击此处 可查看大图

 

图 7 说明了第二种故障转移情况:负载平衡器层将会话重新传送到一个服务器实例,但该实例无法直接访问会话状态数据。在该图中,实例 4 发现自己没有所需的会话状态数据,并向群集中的其他实例广播 SASE 以请求数据。该请求用黄色箭头表示。

第二种故障转移情况
图 7. 第二种故障转移情况
单击此处 可查看大图

 

其中一个实例(图 7 中的实例 2)发现其副本包含所需的数据,并对 SASE 请求进行回复。实例 2 将会话数据传输到实例 4,实例 4 随后将对会话进行处理。

每当实例使用副本数据处理会话时(无论是第一种情况,还是第二种情况),都会先对副本数据进行测试以确保它是最新版本。

群集动态形状变化

如果群集中的某个实例发生故障,或者管理员有意使其脱机,群集的拓扑结构必然会发生变化。

在此示例中,由于实例 1 发生故障,群集拓扑结构必须发生变化以维持会话高速缓存复制机制。在图 8 中,实例 2 和实例 4 发现实例 1 消失了。由于实例 1 发生故障,因此,与其进行的通信尝试将会由于 I/O 异常而失败。如果有意将某个实例关闭,JXTA 技术将会发送消息,指出实例 1 的管道已关闭。

群集发现出现故障的实例
图 8. 群集发现出现故障的实例
单击此处 可查看大图

 

在发现实例 1 消失后,实例 4 将选择一个新的复制伙伴(如图 9 所示)。实例 4 清除其旧连接,并与实例 2 建立连接。群集现在从 4 个服务器实例缩减到 3 个服务器实例。

群集发现出现故障的实例
图 9. 群集动态形状变化
单击此处 可查看大图

 

注意,在总体会话活动数量相同的情况下,较小群集中的每个实例现在要承担更多的工作。在进行资源规划时,要考虑到内存中复制使用堆内存这一情况。为了提供高可用性,应确保为每个实例预留出足够多的内存,以便应付群集收缩的情况。

当某个实例加入(或重新加入)群集时,会发生基本相反的过程。当群集中的新实例从负载平衡器层接收请求时,该实例广播一个寻找复制伙伴请求,然后选择复制伙伴,而拓扑结构将自动进行调整以接纳该新实例。

组管理服务

组管理服务 (Group Management Service, GMS) 提供有关群集及其成员实例的动态成员身份信息。其设计借鉴了 Shoal 项目 的许多设计理念,Shoal 是一种基于 Java 技术的群集框架。GMS 的核心部分还基于 JXTA 技术。

GMS 管理 GlassFish 中的群集形状变化事件,并根据成员加入、成员正常关闭或成员发生故障等事件进行相应的调整。内存复制通过 GMS 执行必要的操作以响应这些事件,并提供连续的服务可用性。

在 GlassFish 应用服务器中使用 GMS 来监视群集的运行状况并支持内存复制模块。

简而言之,GMS 提供以下支持:

  • 群集成员身份变化通知和群集状态
     
  • 整个群集范围内或成员之间的消息传送
     
  • 面向恢复的计算,其中包括恢复成员选择、故障防护以及针对多个故障的恢复链
     
  • 分布式高速缓存,这是一种适于交换有关群集成员身份消息的轻型实现
     
  • 用于接入组通信提供者的服务提供者接口 (Service-provider Interface, SPI);缺省提供者基于 JXTA 技术
     
  • 计时器迁移 – 如果需要的话,GMS 会选择一个实例来接收故障实例的计时器

内存复制配置

要配置群集内存复制,您必须执行以下三个步骤:

  1. 创建一个管理域。在创建该域并在承载群集的计算机上创建其节点代理后,创建一个群集管理配置文件。此配置文件设定缺省复制设置、启用 GMS 并将 persistence-type 属性设置为 replicated
     
  2. 创建一个群集及其实例(如本文后面所述)。
     
  3. 部署 Web 应用程序并将 availability-enabled 属性设置为 true

可以使用 GUICLI 来完成这些步骤。

可能还需要进行一些其他的调整。例如,群集管理配置文件的缺省堆大小为 512 MB。对于企业部署,该值应该增加到 1 GB 或更大。可以通过域管理服务器轻松完成此操作,即使用以下标记设置 JVM 选项:

<jvm-options>-Xmx1024m</jvm-options>
<jvm-options>-Xms1024m</jvm-options>

 

还需确保在 Web 应用程序的 web.xml 文件中添加 <distributable /> 标记。此标记将应用程序标识为支持群集功能。

要求插入 <distributable /> 标记是为了提醒您,先在群集环境中测试应用程序,然后再将其部署到群集中。某些应用程序在部署到单一实例时可以正常工作,但在部署到群集时将发生故障。例 如,属于应用程序 HTTP 会话的任何对象(如有状态会话 Bean)必须能够进行序列化,以便在网络中保留其状态,然后才能在群集中成功部署应用程序。无法序列化的对象在部署到单一服务器实例时可以正常工作,但 在群集环境中将发生故障。应检查会话数据的内容,以确保它可以在分布式环境中正常工作。

内存复制实现

在 GlassFish 版本 2 应用服务器中,内存复制功能基于 JXTA 技术的传输和消息传送功能。

许多人都把 JXTA 技术视为对等技术。它被定义为一组基于 XML 的协议,连接到网络上的设备可通过这些协议交换消息并协同工作,而不会受到网络拓扑的限制。在开发 GlassFish 版本 2 应用服务器时,改进了 JXTA 技术以满足内存复制的高容量和吞吐量需求。为了提高可伸缩性和性能,内存复制功能开发者还与 Grizzly 项目 进行了有益的协作,此项目旨在帮助开发者使用 Java New I/O API (NIO) 构建可伸缩的可靠服务器。

JXTA 技术中的组成员关系概念可以很好地映射到 GlassFish 应用服务器群集和实例模型:JXTA 组映射到 GlassFish 群集,JXTA 对等项映射到 GlassFish 服务器实例。GMS 充分利用这些组成员关系概念,提供了一些消耗性组件,如内存复制(一种用于处理群集中的运行时事件的通知事件模型)。

在开发 GlassFish 版本 2 应用服务器时,群集拓扑结构被限制在单一子网中。未来的计划包括利用 JXTA 来支持地域分散的群集拓扑结构。

最后,利用 JXTA 技术的简单 API,可以简化 GlassFish 群集的配置要求。

应用服务器安装

要安装 GlassFish 应用服务器,请执行以下步骤:

  1. 键入以下命令:
     
    <!---->
    java -jar filename.jar
    
    
     
    <!----> 例如:
     
    <!---->
    java -jar glassfish-installer-v2-b58g.jar
    

     
    <!---->
  2. 接受许可协议。在接受许可协议后,文件将解压缩到 GlassFish 安装目录(缺省名称为 glassfish )中。

现在,您需要配置 GlassFish 应用服务器。

群集配置

安装目录包含两个 ant 生成脚本,可以使用它们来创建缺省域。这两个脚本是 setup.xmlsetup-cluster.xml

setup.xml 脚本用于创建开发者配置文件;setup-cluster.xml 脚本用于创建群集配置文件。可通过 Sun Java System Application Server 管理控制台将开发者配置文件转换为群集配置文件,如下所述。

要使用群集配置文件创建缺省域,请执行以下步骤:

  • 在 GlassFish 安装目录中键入以下命令:
     
    <!---->
    lib/ant/bin/ant -f setup-cluster.xml 
    
     
    <!----> 配置脚本将解压缩归档,并创建 domains 子目录和支持群集的名为 domain1 的域。

现在,GlassFish 配置已经完成。

域检查

您可以通过 CLIasadmin 命令)或 GUI (Sun Java System Application Server 管理控制台)了解和管理域。

 

从命令行界面中检查域

 

配置步骤在安装目录中创建了 domains 子目录。此目录用于存储所有 GlassFish 域。

您可以从 CLI 中使用 asadmin 命令与域进行交互,该命令位于安装目录下的 bin 子目录中。可以采用批处理模式或交互模式来使用 asadmin 命令。

例如,可以使用以下命令列出所有域及其状态:

bin/asadmin list-domains 

 

如果尚未启动 domain1,上述命令将生成以下输出:

domain1 not running

 

要启动 domain1,请键入以下命令:

bin/asadmin start-domain domain1

 

如果只有一个域,则 domain1 参数是可选的。此命令启动 domain1 并提供以下内容的相关信息:日志文件位置、服务器版本、域名、可用 Web 上下文、部署的应用程序和使用的端口等等。

 

使用 Sun Java System Application Server 管理控制台检查域

 

作为 asadmin 命令的一种替代方法,可以使用 Sun Java System Application Server 管理控制台来控制应用服务器。下一节说明了如何启动该控制台。

此管理控制台简化了通过 .war.ear 文件甚至通过 JBI(Java Business Integration,Java 业务集成)服务组件部署应用程序的过程。从此控制台中,您可以监视资源使用情况、搜索日志文件、启动和停止服务器、访问联机帮助以及执行很多其他管理功能和服务器管理功能。

现有域的群集支持

您可以在现有域中添加群集支持。除非修改了配置,否则,具有开发者配置文件的域并不支持群集。在 GlassFish 安装目录中,可以使用以下命令创建开发者配置文件域:

lib/ant/bin/ant -f setup.xml

 

要在开发者配置文件域中启用群集功能,请执行以下步骤:

  1. 在 GlassFish 安装目录中键入以下命令,以便启动要重新配置以启用群集功能的域:
     
    <!---->
    bin/asadmin start-domain domain_name
    
    
     
    <!---->

    例如:

    <!---->
    bin/asadmin start-domain domain1
    
     
    <!---->

    此命令在域中启动 GlassFish 应用服务器,并在命令 shell 窗口中提供相应信息。最后一行信息说明了域的功能;在本例中为:

    <!---->
    Domain does not support application server clusters and other standalone instances.
    
     
    <!---->
  2. 将 Web 浏览器指向以下 URL 以启动管理控制台:
     
    <!---->
    http://hostname:port
    
    
     
    <!---->

    缺省端口为 4848。例如:

    <!---->
    http://kindness.sun.com:4848
    
     
    <!---->

    如果在安装了应用服务器的计算机上运行此管理控制台,请将主机名指定为 localhost 。在 Windows 上,从“开始”菜单中启动 Application Server 管理控制台。

    缺省登录信息为

    用户名:admin
    口令:adminadmin

  3. 在窗口左侧的“常规任务”树中,选择“应用服务器”。在窗口右侧,选择“常规”选项卡。
     
  4. 单击“添加群集支持”按钮,如下图所示。
     
    <!---->
    添加群集支持
    图 10. 添加群集支持
    单击此处 可查看大图
     
    <!---->
  5. 此时,将显示一个确认页面,提醒您更改群集支持会产生什么后果。下面是一些要考虑的事项:
     
    • 更改域配置以支持群集。更改包括添加一些系统属性和模板配置。
       
    • 支持群集的服务器同时支持群集和单独的服务器实例。
       
    • 由于群集通常会增加资源需求,因此,您可能需要修改管理服务器的 JVM 设置,如堆大小。
       
    • 当前在支持群集的域中的服务器上部署的所有应用程序可以继续正常工作。
       
    • 在重新启动域服务器和 asadmin CLI 后,群集支持更改才会生效。
       
    • 在继续操作之前,您可能需要备份域的 domain.xml 文件,以便在回滚群集支持时使用。
       
  6. 单击“确定”为域启用群集支持。此时,将打开一个页面,提醒您重新启动这个域的服务器实例。
     
  7. 单击“停止实例”按钮。
     
  8. 如果在命令 shell 中运行 asadmin 命令,可以在 asadmin> 提示符下键入 quit 以退出该命令。
     
  9. 键入以下命令从 CLI 重新启动域:
     
    <!---->
    asadmin start-domain domain_name
    
    
     
    <!---->

    例如,

    <!---->
    asadmin start-domain domain1
    
     
    <!---->

    如果为该域成功启用了群集支持,则命令 shell 中的最后一行输出如下所示:
     

    <!---->
    Domain supports application server clusters and other standalone instances
    
     
    <!---->

HTTP 负载平衡器插件

负载平衡器在多个应用服务器实例中分发工作负载,从而提高了系统的总体吞吐量。尽管在将会话请求传送给服务器实例时,负载平衡器层不需要任何特殊信息,但是它需要维护可用节点的列表。如果某个节点没有对请求做出预期的回复,负载平衡器将选取另一个节点。

可以使用软件或硬件来实现负载平衡器。有关实现硬件供应商设备的详细信息,请参阅供应商提供的信息。

GlassFish 版本 2 应用服务器可以使用 HTTP 负载平衡器插件。该插件可以与 Sun Java System Application Server 9.1 以及 Apache Web 服务器和 Microsoft IIS 结合使用。此负载平衡器还可以将请求从一个服务器实例故障转移到另一个实例,这有助于实现高可用性安装。

有关如何安装负载平衡器插件的详细信息,请参阅 Sun Java System Application Server 9.1 管理控制台中的联机帮助。有关详细信息,请参见《Sun Java System Application Server 9.1 High Availability Administration Guide》(Sun Java System Application Server 9.1 高可用性管理指南)中的第 5 章 "Configuring HTTP Load Balancing"(配置 HTTP 负载平衡)

结束语

GlassFish 版本 2 应用服务器提供了一种灵活的群集体系结构,它由管理域、域管理服务器、服务器实例和物理计算机组成。这种体系结构简便易用且具有很强的管理控制能力,可以提高可用性和水平可伸缩性。

  • 高可用性 - 多个服务器实例可以共享状态,最大限度地减少了单点故障,尤其是在与负载平衡方案结合使用的情况下。服务器会话数据的内存中复制可以最大限度地减少因服务器实例故障造成的用户服务中断。
     
  • 水平可伸缩性 - 随着用户负载的增加,可以添加更多的计算机、服务器实例和群集,并轻松对其进行配置以处理增加的负载。GMS 减轻了维护高可用性群集的管理负担。
评论

相关推荐

Global site tag (gtag.js) - Google Analytics