剑客
关注科技互联网

无服务器架构 1/3

  • 举几个例子
    • UI驱动的应用程序
    • 消息驱动的应用程序
  • 理解“函数即服务”
    • 状态
    • 执行时间
    • 启动延迟
    • API网关
    • 工具
    • 开源
  • 无服务器不是什么?
    • 与PaaS相比
    • 与容器相比
    • #NoOps
    • 存储过程即服务

无服务器架构是指大量依赖第三方服务(也叫做后端即服务,即“BaaS”)或暂存容器中运行的自定义代码(函数即服务,即“FaaS”)的应用程序,AWS Lambda是目前最著名的供应商。通过采用这样的方式并将大部分行为移至前端,这种架构使得应用程序内部不再需要具备传统的“始终运行”的服务器系统。取决于具体情况,尽管对供应商依赖增加了,并且(目前)配套服务还不成熟,但此类系统依然可以大幅降低运维成本和复杂度。

无服务器是软件架构世界中的热门话题。在这一领域我们已经 看到 很多 书籍开源框架大量 不同 类型供应商产品 ,甚至专门的 会议活动 。但无服务器到底是什么,为什么值得(或不值得)考虑该技术?通过 出版物的进化 这篇文章,希望能针对这些问题向你提供一些启发。

首先我们要介绍无服务器到底是“什么”,在介绍这种技术的优势和劣势时我会尽量保持中立 – 下文将分别探讨这些问题。

无服务器是什么?

与软件行业很多其他趋势类似,关于“无服务器”到底是什么并没有一个清晰直观的看法,而涉及到两个截然不同但又相互重叠的领域后,问题就更突出了:

  1. “无服务器”这个称呼最早被用于描述大量或完全依赖(“位于云中的”)第三方应用程序/服务管理服务器端逻辑和状态的应用程序。这些程序通常是使用可通过云访问的数据库(例如Parse、Firebase)、身份验证服务(例如Auth0、AWS Cognito)等庞大生态系统的“富客户端”应用程序(例如只包含一个页面的Web应用或移动应用)。这类服务以往也被叫做“ (移动)后端即服务 ”,下文会将其简称为“ BaaS ”。
  2. “无服务器”也可以指部分服务器端逻辑依然由应用程序开发者来编写的应用程序,但与传统架构的不同之处在于,这些逻辑运行在完全由第三方管理,由事件触发的无状态(Stateless)暂存(可能只存在于一次调用的过程中)计算容器内。(感谢ThoughtWorks在他们 最近的Tech Radar 中对这种情况的定义。)这种做法可以看作“ 函数即服务 /FaaS”。 AWS Lambda 是目前最流行的FaaS实现,但除此之外也有其他实现。下文将使用“ FaaS ”作为无服务器这种运用方式的简称。

“无服务器”的起源

“无服务器”这个词并不好理解,因为有很多应用程序的服务器硬件与进程运行在不同位置,但与传统方式的差异之处在于,构建和支持“无服务器”应用程序的组织并不需要考虑服务器或进程,这些东西可以外包给供应商。

这个词首次出现大概是在2012年,包含在 Ken Fromm 撰写的 这篇文章 中。 Badri Janakiraman 说他也在这段时间前后在 持续集成 和托管服务式的源代码控制系统领域听到过这个词,但并不是用来代表某个公司自己的服务器。这种用法侧重的是基础结构的开发,并未将其纳入具体产品。

当亚马逊于2014年发布AWS Lambda后,2015年这个词开始变得广为人知,亚马逊在2015年7月发布API网关后这个词也变得愈加热门。这里有 一个例子 ,API网关公布后, Ant Stanley 写了一篇有关无服务器的帖子。2015年10月,亚马逊的re:Invent大会中有一场名为“ 使用AWS Lambda搭建的无服务器公司 ”的讲话,其中介绍了 PlayOn! Sports 这家公司。2015年底,“Javascript Amazon Web Services(JAWS)”开源项目 更名无服务器框架(Serverless Framework) ,使得这一势头愈加热烈。

快进到今天(2016年中期),我们可以看到更多范例,例如最近举办的 无服务器大会 以及各种无服务器供应商,从产品描述到工作岗位介绍,都在广泛使用这个词。无论怎样,“无服务器”这个词成了“网红”。

本文将主要介绍上文提到的第二个领域,因为这个领域是最新出现的,与我们以往对技术架构的看法有更大不同,并且围绕无服务器这个词在这个领域有更多宣传炒作。

然而这些概念其实是相互关联,实际上甚至会逐渐会聚的。例如 Auth0 就是个很好的例子 – 这个技术最开始属于BaaS形式的“身份验证即服务”,但随着 Auth0 Webtask 的发布开始步入FaaS领域。

在很多情况下,当开发一款“BaaS形式”的应用程序时,尤其是在开发“富”Web应用而非移动应用时,可能依然需要一定数量的自定义服务器端功能。此时FaaS函数是一种很好的解决方案,尤其是需要在某种程度上与正在使用的某些BaaS服务进行集成时。这类功能的例子包括数据验证(防范仿冒客户端)以及计算密集型处理(例如图片或视频操作)等。

举几个例子

UI驱动的应用程序

想象一个包含服务器端逻辑,面向客户端的传统三层系统。例如典型的电商应用(可以说在线宠物店吗?)

传统架构看起来类似下图,假设服务器端使用Java实现,并使用HTML/Javascript组件作为客户端:

无服务器架构 1/3

这种架构下的客户端会显得相当笨重,系统中身份验证、页面导航、搜索、事务等大部分逻辑都是由服务器应用程序实现的。

如果使用无服务器架构,看起来将会是这样:

无服务器架构 1/3

这是个大幅简化的视图,尽管如此其中也包含大量显著改动。请注意这并非为了向你推荐迁移时所要使用的架构,此处使用的这种架构仅仅为了介绍无服务器相关概念!

  1. 我们删掉了原始应用程序中的身份验证逻辑,并使用第三方BaaS服务代替。
  2. 作为BaaS的另一个例子,我们让客户端直接访问数据库子集(用于列出产品),而该数据库完全由第三方承载(例如使用AWS Dynamo)。我们还为访问数据库的客户端提供了不同的安全配置文件,这样即可从任何服务器资源访问数据库。
  3. 上两点引出了非常重要的第三点:一些原本位于宠物店服务器上的逻辑,例如追踪用户会话,理解应用程序的UX结构(例如页面导航),从数据库读取并转换为可用视图等,现在被放入到客户端中。实际上客户端现在已经变成了一个 单页应用程序(Single Page Application)
  4. 我们希望将一些与UX有关的功能继续保留在服务器上,例如计算密集型功能或需要访问大量数据的功能。“搜索”就是个很好的例子。搜索功能无需使用持续运行的服务器,而是可以用一个能通过API网关(下文将详细介绍)响应Http请求的FaaS函数来实现。可以通过这样的客户端或服务器函数从同一个数据库中读取产品数据。
    由于原本的服务器是通过Java实现的,而AWS Lambda(本例中使用的FaaS供应商)支持通过Java实现的函数,无须彻底重写即可将搜索功能的代码从宠物店服务器直接移至宠物店搜索函数。
  5. 最后还可以使用另一个FaaS函数取代原本的“购买”功能,但出于安全考虑依然将其保留在服务器端,而非在客户端重新实现。这也是一种API网关的前端。

消息驱动的应用程序

另一个例子是后端数据处理服务。假设编写的某个以用户为中心的应用程序需要快速响应UI请求,但你希望随后能记录发生的所有类型的操作。例如在线广告系统,用户点击广告后你希望非常快速地将用户重定向至目标广告,但为了向广告主收费,与此同时你还希望记录已发生点击这件事。(这并不是虚构的例子,我以前在 Intent Media 所属的团队就因为这个原因重新设计了自己的系统。)

传统方式下这种系统的架构可能是类似这样的:“广告服务器”会以同步的方式响应用户(由于只是例子,我们并不需要关心具体的交互),同时需要向渠道发布一条消息并由负责更新数据库的“点击处理器”应用程序以异步的方式处理,例如扣掉广告主的部分预算。

无服务器架构 1/3

在无服务器的世界中,这个系统应该是这样的:

无服务器架构 1/3

和上一个例子相比,本例中两种架构的差异很小。我们将需要长期运行的消费者应用程序替换为一个在供应商提供的事件驱动的上下文中运行的FaaS*函数*。请注意该供应商同时提供了消息代理(Message Broker)和FaaS环境,这两个系统非常紧密地相互结合在一起。

通过实例化(Instantiating)函数代码的多个副本,FaaS环境可以并行处理多个点击,这主要取决于最初流程的编写方式,同时这也是一个需要考虑的新概念。

理解“函数即服务”

上文已多次提到FaaS这种想法,但还需要深入考虑一下这到底是什么意思。为此我们一起看看亚马逊Lambda产品的 公开描述 。我将这些描述划分成几点,下文将分别展开介绍。

AWS Lambda可供你在无须供应或管理服务器的情况下运行自己的代码。 (1) … 借助Lambda,你可以为几乎任何类型的应用程序或后端服务运行所需代码 (2) – 所有环境完全无须管理,只需要上传自己的代码,Lambda将自动完成运行代码所需的一切条件 (3) 并可进行伸缩 (4) 你的代码将实现高可用。你可以将代码设置为通过其他AWS服务自动触发运行 (5) 或直接从任何Web或移动应用内部调用 (6)

  1. 从本质上来说,FaaS意在无须自行管理服务器系统或自己的服务器应用程序,即可直接运行后端代码 。其中所指的 – 服务器应用程序 – 是该技术与容器和PaaS(平台即服务)等其他现代化架构最大的差异。
    回想上文列举的点击处理系统那个例子,可以用FaaS取代点击处理服务器(可能是物理计算机,但绝对需要运行某种应用程序),这样不仅不需要自行供应服务器,也不需要全时运行应用程序。
  2. FaaS产品不要求必须使用特定框架或库进行开发。在语言和环境方面,FaaS函数就是常规的应用程序。例如AWS Lambda的函数可以通过‘一等公民’Javascript、Python以及任何JVM语言(Java、Clojure、Scala)等实现。然而Lambda函数也可以执行任何捆绑有所需部署构件的进程,因此可以使用任何语言,只要能编译为Unix进程即可(参阅下文的Apex)。FaaS函数在架构方面确实存在一定的局限,尤其是在状态和执行时间方面,这些会在下文详细介绍。
    再次回想上文提到的点击处理范例,在迁往FaaS的过程中,唯一需要修改的代码是“主方法/启动”代码,其中可能需要删除顶级消息处理程序的相关代码(“消息监听器接口”的实现),但这可能只需要更改方法签名即可。在FaaS的世界中,代码的其余所有部分(例如向数据库执行写入的代码)无须任何变化。
  3. 由于无须运行任何服务器应用程序,相比传统系统,部署方法会有较大变化 – 将代码上传至FaaS供应商,其他事情均可由供应商完成。目前这种方式通常意味着需要上传代码的全新定义(例如上传zip或JAR文件),随后调用一个专有API发起更新过程。
  4. 横向伸缩过程是完全自动化且有弹性的,由供应商负责管理。如果系统需要并行处理100个请求,你自己无须任何额外配置,供应商可全部搞定。负责执行你的函数的‘计算容器’只会短暂存在,FaaS供应商将完全根据运行时需求自动供应和撤销。
    继续回到点击处理器那个例子。假设某天运气好,客户的广告点击量是平时的10倍。点击处理应用程序能否顺利处理?例如我们的代码能否同时处理多条消息?就算能处理,只使用一个应用程序实例是否足以应对激增的负载?如果可以运行多个进程,是否要自动伸缩,或者需要手工修改配置?如果使用FaaS,需要在编写函数的过程中就考虑到这些问题,但随后将由FaaS供应商自动完成伸缩相关的任务。
  5. FaaS中的函数可以通过供应商定义的事件类型触发。对于亚马逊AWS,此类触发事件可以包括S3(文件)更新、时间(计划任务),以及加入消息总线的消息(例如 Kinesis )。通常你的函数需要通过参数指定自己需要绑定到的事件源。对于点击处理程序,我们假设已经使用了可被FaaS支持的消息代理。如果不支持,则需要换为使用可支持的,并且也可能需要更改消息的创建方。
  6. 大部分供应商还允许函数作为对传入Http请求的响应来触发,通常这类请求来自某种该类型的API网关(例如 AWS API网关Webtask )。我们在宠物店例子中为“搜索”和“下单”函数使用了这种触发方式。

状态

在本地(机器/实例范围内)状态方面FaaS函数会遇到很多局限。简而言之你需要假设对于函数的任何调用,你所创建的任何进程内或主机状态均无法被任何后续调用所使用。包括内存(RAM)中的状态以及写入本地磁盘的状态。换句话说,从部署单位的角度来看,FaaS函数是无状态的。

虽然影响因素还有很多,但这会对应用程序架构产生巨大影响。“十二要素应用(Twelve-Factor App)”这一概念也存在 完全相同的局限

考虑到这种局限,备选方案有哪些?通常这意味着FaaS函数可能是天然无状态的,例如只是单纯对输入内容提供功能的转换,或者可以使用数据库、跨应用程序缓存(例如Redis)或网络文件存储(例如S3)跨越不同请求存储状态,以进一步将其作为其他请求的输入。

执行时间

FaaS函数通常会对每次调用可以执行的时间长度有所限制。目前AWS Lambda函数只能运行不超过5分钟,超过这一时间将被终止。

这意味着如果不进行重构,某些类型的“长寿”任务不适合使用FaaS函数,例如可能需要创建多个相互协调的FaaS函数,而在传统环境中只需要用一个“长寿”的任务同时负责协调和执行。

启动延迟

目前来说,FaaS函数需要等待多长时间才能响应请求,这取决于多种因素,具体时间可能介于10毫秒到2分钟之间。听起来很糟糕,但我们一起用AWS Lambda作为例子更深入地看看这个问题。

如果函数使用Javascript或Python实现并且不大(例如不到五百行代码),那么运行该函数的开销通常绝对不会超过10-100毫秒。更大的函数偶尔可能需要更长时间。

如果你的Lambda函数是通过JVM实现的,由于JVM需要逐渐启动,响应时间偶尔可能会显得较长(例如超过10秒)。然而只有在下列情况下才会存在这样的问题:

  • 不同调用之间偶尔需要以超过10分钟的间隔处理函数处理事件。
  • 流量突然激增,例如通常每秒只需处理10个请求,但突然在不到10秒内需要每秒处理100个请求。

通过用没什么技术含量的手段每5分钟Ping一下函数,使其保活,前一种问题在某些情况下可以避免。

是否需要担心这些问题,主要取决于应用程序的类型和流量模式。我以前加入的一个团队使用Java实现了异步消息处理Lambda应用,该应用每天需要处理数以百亿计的消息,他们就不需要担心启动延迟的问题。话虽如此,如果你要编写一个低延迟的交易应用程序,无论使用什么语言实现,现阶段可能都不会想使用FaaS系统。

无论是否觉得自己的应用会遇到这样的问题,你都需要使用类似生产环境的负载进行测试,看看能获得怎样的性能。如果你的用例目前还无法支持,也许可以过几个月再试试,因为目前FaaS供应商都在努力对这一领域进行完善。

API网关

无服务器架构 1/3

上文还提到过FaaS一个比较重要的内容:“API网关”。API网关是一种HTTP服务器,通过在配置中定义路由/端点,可将每个路由与某一FaaS函数关联在一起。当API网关收到请求后,会查找与该请求匹配的路由配置,随后调用相应的FaaS函数。通常来说API网关会将Http请求的参数与FaaS函数的输入参数映射在一起。API网关会将FaaS函数的调用结果转换为Http响应,并将其返回给原始调用方。

Amazon Web Services有自己的API网关,其他供应商也提供了类似的能力。

除了最基本的请求路由,API网关还可以执行身份验证、输入验证、响应代码映射等功能。你可能会从直觉上纳闷这样做是否真是个好办法,先把这个想法暂时放一放,下文还将进一步介绍。

API网关+FaaS的用例之一是在无服务器系统中创建以Http为前端的微服务,并通过FaaS获得伸缩、管理,以及其他各类收益。

目前与API网关有关的工具还非常不成熟,因此使用API网关定义应用程序这种做法很可能需要你足够大胆。

工具

上文有关API网关工具还不成熟的观点实际上也适用于整个无服务器FaaS大环境。不过凡事总有例外,例如 Auth0 Webtask 对于工具开发的重视远远超过开发者UX的完善。 Tomasz Janczuk 在最近举办的无服务器大会上也很好地阐述了自己对相关工具的重视。

总的来说,无服务器应用的调试和监控都很麻烦,我们也会在下文详细介绍这个问题。

开源

无服务器FaaS应用程序的主要收益之一在于可以透明地实现生产运行时供应,因此目前开源对这一方式的意义并不像对于Docker和容器等技术的意义那么大。未来我们可能会看到一些主流的FaaS/API网关平台可以支持在“本地”,或开发者的工作站上运行。 IBM OpenWhisk 就是此类实现的一个例子,我们很期待看到类似这样的实现最终能获得更大范围的采用。

尽管除了运行时实现,已经有开源的工具和框架可以为定义、部署、运行提供帮助。例如 无服务器框架 使得API网关+Lambda的使用过程变得比AWS更简单,虽然这种方式大量依赖Javascript,但如果你要编写JS API网关应用,绝对值得一试。

另一个例子是 Apex ,这个项目可以“更轻松地构建、部署和管理AWS Lambda函数”。Apex尤其有趣的一点在于,该技术可供你用亚马逊不直接支持的语言开发Lambda函数,例如Go语言。

无服务器不是什么?

至此本文已经介绍了“无服务器”是好几个不同概念的结合,例如“后端即服务”和“函数即服务”,并且还对第二个概念进行了详细的介绍。

在开始介绍这一技术最重要的收益和不足之处前,我还想进一步谈谈这一概念的定义,至少要讨论一下无服务器“不是”什么。我曾发现很多人(包括我自己以前)对这些概念感到困惑,因此有必要进行澄清。

与PaaS相比

考虑到无服务器FaaS函数其实非常类似于12要素应用,那么无服务器是否像 Heroku 一样仅仅是另一种类型的“平台即服务”(PaaS)?引用Adrian Cockcroft的观点作为答案吧。

无服务器架构 1/3

“如果你的PaaS可以非常高效地在20毫秒内启动实例,并将该实例运行0.5秒,那就将其称之为‘无服务器’吧。 —— @adrianco”

换句话说,大部分PaaS应用程序并不适合针对每个请求让整个应用程序上线并下线,而FaaS平台就是为实现这一做法而生的。

但那又怎样,如果我精通 12要素应用 的开发,具体如何写代码其实也没什么不同?没错,但应用的运维方式会有翻天覆地的变化。我们都是精通DevOps的工程师,对运维和开发的重视程度是相同的,对吧!

FaaS和PaaS在运维方面的最大差异在于伸缩。大部分PaaS依然需要考虑伸缩,例如对于Heroku,到底需要运行几个Dyno?但FaaS应用程序完全无须考虑这些问题。就算配置PaaS应用程序进行自动伸缩,但也无法在特定请求层面上做到这一点(除非具备非常有针对性的流量塑形配置文件),因此在成本方面FaaS应用程序效率更高。

尽管能获得这样的收益,又为什么还要继续使用PaaS?原因有很多,工具和API网关的成熟度可能是其中最重要的。另外12要素应用在PaaS中的实现为了进行优化可能需要使用应用内只读缓存,FaaS函数还无法支持这种技术。

与容器相比

使用无服务器FaaS的另一个原因在于避免在操作系统层面或更底层的层面管理计算过程。平台即服务(例如Heroku)也能实现这一点,但上文已经介绍过PaaS与无服务器FaaS的差异。容器是另一种流行的过程抽象方式,例如 Docker 已成为此类技术最明显的例子。我们还发现诸如 MesosKubernetes 等容器承载系统正在开始普及,这些技术可以将特定应用程序从操作系统级别的部署中抽象出来。另外还有云端承载的容器平台,例如 Amazon ECSGoogle Container Engine ,这些技术与无服务器FaaS类似,可以让用户完全无须管理自己的服务器系统。那么容器技术的发展势头这么迅猛,还需要考虑无服务器FaaS吗?

我对于PaaS的主要观点依然适用于容器技术,对于无服务器FaaS来说, 伸缩 是自动实现的,是透明的,是非常细化的。容器平台目前还无法满足这些特征。

另外我想说的是,虽然过去几年来容器技术的流行度与日俱增,但这个技术依然尚未成熟。这不是说无服务器FaaS更成熟,只不过无论如何你只能在各种都不完善的技术中做出选择。

不过也要承认,随着时间流逝,这些争议终将消失。虽然容器平台目前还做不到无服务器FaaS那样程度的无需管理、自动伸缩等特性,但我们也注意到诸如Kubernetes的“ Horizontal Pod Autoscaling ”等技术正在继续向着这个目标努力。我认为这些功能会逐渐包含某些非常智能的流量模式分析,以及与负载更为密切相关的衡量指标。并且Kubernetes的快速演化也许很快就能为我们提供一个极为简单稳定的平台。

考虑到无服务器FaaS和托管式容器在管理和伸缩方面的差距,选择的余地也将缩小,可能只需要考虑应用程序的风格和类型。例如对于事件驱动的应用,并且每个应用程序组件只需要处理少数类型的事件,此时FaaS将是最佳选择;对于包含很多入口点,由同步请求驱动的组件,则更适合使用容器。我预计5年内很多应用程序和团队将同时使用这两种架构,很期待尽快看到这样的使用模式日益涌现。

#NoOps

无服务器并不是指‘无须运维’,取决于你在无服务器的道路上能走多远,可能是指“无须内部系统管理员”。这方面需要考虑两个重要问题。

首先,“运维”不光是服务器本身的管理,还意味着监控、安全、网络,以及大部分情况下必不可少的生产调试和系统伸缩。无服务器应用中依然存在这些问题,需要制定相应的策略。某些情况下无服务器世界中的运维可能更难,主要因为这还是一种比较新的技术。

其次,就算系统本身的管理工作也是存在的,只不过将其外包给了无服务器技术供应商。这不是什么坏事,很多东西都被我们外包了。但取决于你希望实现的精确程度,这种做法是好是坏还有待商榷,无论哪种方式,在某种程度上这样的抽象都有可能渗漏,你必须意识到依然是由人类系统管理员在为你的应用程序提供支持。

Charity Majors 在最近举办的无服务器大会上针对这个话题做了一次非常棒的演讲,建议当本次演讲发布到网上后大家都能去看看。在这之前你可以阅读她写的 这篇 以及 这篇 文章。

存储过程即服务

我发现无服务器FaaS还有着“存储过程即服务”的特征。我觉得这一特征主要源自很多FaaS函数(包括我在本文中用到的那些)都是围绕数据库访问编写的小规模代码片段。如果我们对FaaS的全部应用仅限于此,那么这名字还是很贴切的,但由于这只是FaaS各种能力中的一部分,对FaaS的这种看法实际上是一种无效的约束。

无服务器架构 1/3

“我担心如果无服务器服务终将变成类似存储过程的技术,一个原本挺好的想法可能很快欠下一大堆技术债。 —— @skamille”

话虽如此,依然有必要考虑FaaS是否会遇到一些与存储过程相同的问题,例如在上述引用的推文中Camille提到的有关技术债的担忧。使用存储过程的过程中我们学到了很多经验,有必要在FaaS的世界中仔细反省一下这些问题,看看是否会出现类似的情况。存储过程所面临的一些问题包括:

  1. 通常需要供应商明确指定的语言,或至少需要供应商针对某一语言指定的框架/扩展。
  2. 由于需要在数据库的上下文情境中执行因此难以进行测试。
  3. 很难像对待“一等公民应用程序”那样实现版本控制/处理。

并非所有存储过程的实现都会遇到上述问题,但我以前的工作中都遇到过。FaaS是否也存在此类问题:

目前我所接触过的FaaS实现都无须担心(1),这一条可以划掉了。

对于(2),由于需要面对的“仅仅是代码”,单元测试过程将与其他任何代码一样易行。集成测试则是另一个截然不同(并且非常合理)的问题,下文将详细介绍。

对于(3),需要再次重申,由于FaaS函数面对的“仅仅是代码”,因此版本控制很好实现。但在应用程序打包方面目前还没有出现较为成熟的模式。上文提到的无服务器框架提供了自己的打包模式,AWS也于2016年5月在无服务器大会上宣布自己也会开发一种打包技术(“Flourish”),但目前这依然是一个值得我们关注的问题。

本文作者: Mike Roberts阅读英文原文Serverless Architectures

感谢陈兴璐对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址