区域 ID
REGION_ID
是 Google 根据您在创建应用时选择的区域分配的缩写代码。此代码不对应于国家/地区或省,尽管某些区域 ID 可能类似于常用国家/地区代码和省代码。对于 2020 年 2 月以后创建的应用,REGION_ID.r
包含在 App Engine 网址中。对于在此日期之前创建的现有应用,网址中的区域 ID 是可选的。
详细了解区域 ID。
软件开发需要权衡利弊,微服务也不例外。 在代码部署和操作方面实现独立性,性能开销就需要有所牺牲。本部分提供了一些建议,帮助您采取相关措施,最大限度地减轻这一影响。
将 CRUD 操作转换为微服务
微服务特别适合使用创建、检索、更新、删除 (CRUD) 模式访问的实体。使用此类实体时,您通常一次只使用一个实体(例如用户),并且通常一次只执行一项 CRUD 操作。因此,您只需调用一个微服务即可完成操作。查找具有 CRUD 操作的实体以及可在应用的许多部分中使用的一组业务方法。这些实体可作为微服务的理想候选对象。
提供批处理 API
除了 CRUD 形式的 API 之外,您还可以通过提供批量 API 来为实体组提供良好的微服务性能。例如,您可以提供一个 API 来接受一组用户 ID 并返回一个包含相应用户的字典,而不是仅公开用于检索单个用户的 GET API 方法:
请求:
/user-service/v1/?userId=ABC123&userId=DEF456&userId=GHI789
响应:
{
"ABC123": {
"userId": "ABC123",
"firstName": "Jake",
… },
"DEF456": {
"userId": "DEF456",
"firstName": "Sue",
… },
"GHI789": {
"userId": "GHI789",
"firstName": "Ted",
… }
}
App Engine SDK 支持许多批处理 API(例如通过单个 RPC 从 Cloud Datastore 提取许多实体的功能),因此提供这类批处理 API 服务的效率很高。
使用异步请求
通常,您需要与许多微服务交互才能编写响应。
例如,您可能需要提取已登录用户的偏好设置以及用户的公司详细信息。通常,这些信息并不相互依赖,而且可以并行提取。App Engine SDK 中的 Urlfetch
库支持异步请求,允许您并行调用微服务。
from google.appengine.api import urlfetch
preferences_rpc = urlfetch.create_rpc()
urlfetch.make_fetch_call(preferences_rpc,
'https://preferences-service-dot-my-app.uc.r.appspot.com/preferences-service/v1/?userId=ABC123')
company_rpc = urlfetch.create_rpc()
urlfetch.make_fetch_call(company_rpc,
'https://company-service-dot-my-app.uc.r.appspot.com/company-service/v3/?companyId=ACME')
### microservice requests are now occurring in parallel
try:
preferences_response = preferences_rpc.get_result() # blocks until response
if preferences_response.status_code == 200:
# deserialize JSON, or whatever is appropriate
else:
# handle error
except urlfetch.DownloadError:
# timeout, or other transient error
try:
company_response = company_rpc.get_result() # blocks until response
if company_response.status_code == 200:
# deserialize JSON, or whatever is appropriate
else:
# handle error
except urlfetch.DownloadError:
# timeout, or other transient error
并行操作往往与良好的代码结构背道而驰,这是因为在现实环境中,您通常使用一个类来封装偏好设置方法,并使用另一个类封装公司方法。很难既利用异步 Urlfetch
调用,而又不破坏此封装。App Engine Python SDK 的 NDB 包提供了一个很好的解决方案:Tasklet。Tasklet 使您能够在代码中保持良好的封装,同时仍提供实现并行微服务调用的机制。请注意,Tasklet 使用 Future 而不是 RPC,但想法是类似的。
使用最短的路由
根据调用 Urlfetch
的方式,您可以让系统使用不同的基础架构和路由。为了使用性能最佳的路由,请考虑以下建议:
- 使用
REGION_ID.r.appspot.com
,而不是自定义网域 - 在通过 Google 基础架构路由时,自定义网域会导致使用不同的路由。由于微服务调用在内部进行,因此使用
https://PROJECT_ID.REGION_ID.r.appspot.com
可以轻松执行操作而且性能更佳。 - 将
follow_redirects
设置为False
- 调用
Urlfetch
时明确设置follow_redirects=False
,以避免使用旨在遵循重定向且权重较高的服务。您的 API 端点应该不需要重定向客户端,因为它们是您自己的微服务,并且端点应该只返回 HTTP 200、400 和 500 系列响应。 - 最好使用单个项目中的多项服务,而不是使用多个项目
- 在构建基于微服务的应用时,您有充分的理由使用多个项目,但如果性能是您的主要目标,则请使用单个项目中的多项服务。一个项目中的所有服务均托管在同一数据中心,即使 Google 数据中心之间的互联网络具有极高的吞吐量,也是本地调用的速度更快。
避免在强制执行安全机制期间使用 Chatter
如果使用涉及大量来回通信的安全机制对发起调用的 API 进行身份验证,则会对性能产生不利的影响。例如,如果您的微服务需要通过回调应用来验证来自应用的票证,那么您需要进行多次往返才能获取数据。
OAuth2 实现可以通过在 Urlfetch
调用之间使用刷新令牌以及缓存访问令牌来分摊此费用。不过,如果缓存的访问令牌存储在 Memcache 中,则提取该令牌时会产生 Memcache 开销。为了避免这种开销,您可以将访问令牌缓存在实例内存中,但是您仍会频繁遇到 OAuth2 活动,因为每个新实例都会协商访问令牌;请记住,App Engine 实例会经常启动和关闭。混合使用 Memcache 和实例缓存有助于缓解此问题,但您的解决方案会开始变得更加复杂。
另一种效果出色的方法是在微服务之间共享密钥令牌,例如,将密钥令牌作为自定义 HTTP 标头传送。在此方法中,每项微服务都可以为每个调用者提供唯一的令牌。 通常,对于安全性的实施而言,共享密钥并不是一种可靠的方案,但由于所有微服务都位于同一应用中,再考虑到可以实现的性能提升,因此这个问题并不重要。借助共享密钥,微服务只需要对传入密钥与推测的内存中字典执行字符串比较,安全方面的工作量很少。
如果您的所有微服务都在 App Engine 上,您还可以检查传入的 X-Appengine-Inbound-Appid
标头。
在向另一个 App Engine 项目发出请求时,Urlfetch
基础架构会添加此标头,并且外部方无法设置此标头。根据您的安全要求,您的微服务可以检查此传入标头,以强制执行安全政策。
跟踪微服务请求
在您构建基于微服务的应用时,会因为连续的 Urlfetch
调用而开始积累开销。发生这种情况时,您可以使用 Cloud Trace 了解正在进行的调用以及开销来源。重要的是,Cloud Trace 还可以帮助识别以串行方式调用独立微服务的位置,这样您便可以重构代码以并行执行这些提取。
当您在单个项目中使用多项服务时,Cloud Trace 的一项实用功能就会派上用场。当某个项目中的各项微服务之间发生调用时,Cloud Trace 会将所有这些调用合并到一个调用图中,以单一跟踪的形式直观呈现整个的端到端请求。
请注意,在上面的示例中,由于是使用异步 Urlfetch
并行执行 pref-service
和 user-service
调用,因此图表中显示的 RPC 有些杂乱。
不过,这仍然是一种诊断延迟的有用工具。
后续步骤
- 大致了解 App Engine 的微服务架构。
- 了解如何为 App Engine 中的微服务创建开发、测试、质检、预演和生产环境并为其命名。
- 了解可以采用哪些最佳做法来设计用于在微服务之间进行通信的 API。
- 了解如何将现有的单体式应用迁移到一个具有多项微服务的应用。