工程

Elastic RUM(真实用户监测)浅谈

如果我让您误以为是要品一杯朗姆酒调制的美妙鸡尾酒,却发现我说的是此 RUM 而非彼朗姆时,那就抱歉了!但请相信,Elastic RUM 也绝非徒有其名!让我们一起来“呷一口”吧!不过我要提醒您的是,本篇博客介绍了大量细节,看完这些内容是需要花点时间的。

什么是 RUM?

Elastic 真实用户监测 (RUM) 可捕获用户与 Web 浏览器的交互,并能够从性能角度让您详细了解 Web 应用程序的“真实用户体验”。Elastic RUM 代理是 JavaScript 代理,这意味着它支持任何基于 JavaScript 的应用程序。RUM 可对您的应用程序提供有价值的见解。RUM 的一些常见优势包括:

  • RUM 性能数据可帮助您确定瓶颈,并发现站点性能问题如何影响访问者的体验
  • 通过 RUM 捕获的用户代理信息,您可以确定客户最常用的浏览器、设备和平台,以便能够明智地对您的应用程序进行优化
  • 结合位置信息和 RUM 提供的个体用户性能数据,可帮助您了解网站在全球不同区域的性能
  • RUM 可针对应用程序的服务级别协议 (SLA) 提供见解和测量数据
  • RUM 会收集一段时间内客户访问和点击行为的信息,这些信息对于开发团队确定新功能的影响非常有用

通过 Elastic APM 开始使用 RUM

在本篇博客中,我将系统地逐步介绍如何为一个具有 React 前端和 Spring Boot 后端的简单 Web 应用程序装载测量工具。您会看到使用 RUM 代理有多么简单。另外,您还会看到 Elastic APM 如何通过一个全面的分布式跟踪视图将前端和后端性能信息结合在一起。如果您有兴趣获取更多详细信息,请参阅先前发表的这篇博客,以大体了解 Elastic APM 和分布式跟踪

要使用 Elastic APM 真实用户监测,您必须先安装 Elastic Stack 和 APM 服务器。当然,您可以下载最新的 Elastic Stack 和 APM 服务器,并在计算机上进行本地安装。但是,最简单的方法是创建一个 Elastic Cloud 试用帐户,在几分钟之内便可准备好您的集群。在默认 I/O 优化模板中,APM 已处于启用状态。从现在开始,我假设您已经准备好了一个集群。

样例应用程序

我们要打算为其装载测量工具的这个应用程序是一个简单的汽车数据库应用程序,由一个 React 前端和一个对内存中汽车数据库提供 API 访问的 Spring Boot 后端组成。这个应用程序特意编写得非常简单。主要是想从零开始为您演示详细的测量工具装载步骤,以便您可以按照相同的步骤为自己的应用程序装载测量工具。

A simple application with a React frontend and Spring backend

在您的笔记本电脑上任意找一个位置,创建一个名为 CarApp 的目录。然后,将前端和后端应用程序都克隆到这个目录下。

git clone https://github.com/carlyrichmond/carfront
git clone https://github.com/carlyrichmond/cardatabase

正如您看到的,这个应用程序非常简单。React 前端中只有几个组件,后端 Spring Boot 应用程序中也只有几个类。按照 GitHub 中的说明,为前端和后端构建并运行应用程序。您应该看到类似下面的内容。您可以从中浏览、筛选汽车以及执行 CRUD 选项。

The simple React user interface

现在,应用程序开始运行后,接下来我们就可以使用 RUM 代理来完成测量工具装载了。

RUM 开箱即用的各种测量工具装载功能

首先,我们需要 Elastic APM 服务器。您需要启用 RUM 来从您的 RUM 代理捕获事件。要安装 RUM 代理,有两种操作方法:

  1. 您可以通过包管理器(如 npm)以项目依赖项的形式来安装 RUM 代理:
    npm install @elastic/apm-rum --save
  2. 通过 HTML 脚本标记嵌入 RUM 代理。请注意,根据文档内容,可以作为阻塞或非阻塞操作进行本安装。
    <script 
    src="https://unpkg.com/@elastic/apm-rum@5.12.0/dist/bundles/elastic-apm-rum.umd.min.js">
    </script>
    <script>
    elasticApm.init({
    serviceName: 'carfront',
    serverUrl: 'http://localhost:8200',
    serviceVersion: '0.90'
    })
    </script>

由于我们的前端是 React 应用程序,所以我们准备使用第一种方法。在项目中安装了 @elastic/apm-rum 之后,请检查 rum.js 中的初始化代码。该代码与 index.js 位于同一目录中,内容如下所示(不过其中的 serviceUrl 会被替换为您自己的 APM 服务器终端):

import { init as initApm } from '@elastic/apm-rum'
var apm = initApm({
//Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
serviceName: 'carfront',
// Set the version of your application
// Used on the APM Server to find the right sourcemap
serviceVersion:'0.90',
// Set custom APM Server URL (default: http://localhost:8200)
serverUrl:'APM_URL',
// distributedTracingOrigins: ['http://localhost:8080'],
})
export default apm;

这就是初始化 RUM 代理的全部操作!如果您正在使用特定于框架的功能,如 React、Angular 或 Vue 中的路由,您可能还需要安装和配置特定于框架的集成,这些集成在文档中有介绍。在本例中,由于这是一个单独的页面,不需要特定于 React 的插桩,所以我们没有安装额外的依赖项。

我们稍后再讨论 distributedTracingOrigins。下面是关于一些其他配置的简短说明:

  1. 服务名称:必须设置服务名称。它在 APM UI 中表示您的应用程序,因此,请用有意义的名称来命名。
  2. 服务版本:这是您应用程序的版本。APM 服务器也会使用这个版本号来查找正确的源映射。我们稍后将详细讨论源映射。
  3. 服务器 URL:这是 APM 服务器的 URL。请注意,APM 服务器的 URL 通常可从公共互联网进行访问,因为 RUM 代理会从互联网上的最终用户浏览器向其报告数据。

熟悉 Elastic APM 后端代理的用户可能会感到奇怪,为什么不在这里传递 APM 令牌。这是因为 RUM 代理实际上并不使用加密 APM 令牌。令牌仅用于后端代理。由于前端代码是公开的,因此,加密令牌并不能提供额外的安全性。

我们将在加载应用程序时加载这个 JavaScript 文件,并将其放在我们要定制装载测量工具的位置。现在,让我们先看看在未对测量工具进行任何定制的情况下,有哪些开箱即用的功能。为此,我们只需在 index.js 中包含 rum.js 即可。index.js 文件导入 rum.js 并设置一个页面加载名称。如果不设置页面加载名称,您会在 APM UI 中看到该页面加载显示为“/”,这样不是很直观。index.js 类似如下所示。

import apm from './rum'
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
apm.setInitialPageLoadName("Car List")
ReactDOM.render(, document.getElementById('root'));
serviceWorker.unregister();

通过访问页面和添加或删除汽车,生成一些流向您应用程序的流量。然后登录 Kibana 并单击“Observability”(可观测性)图块。在这里,从“APM”子菜单中选择“Services”(服务)选项,如下所示:

您应看到列出一个名为“carfront”的服务。单击该服务名称会将您转到事务页面。您应该看到默认时间范围为“Last 15 minutes”(最近 15 分钟)的延迟和吞吐量等指标的概述。 如果看不到,请将时间选择器更改为该范围。

在“Transactions”(事务)部分,您应该看到“Car List”事务。单击“Car List”链接,您将移动到“Transactions”(事务)选项卡,其中包含此事务示例的统计信息。滚动到页面底部,您将看到类似如下图所示的浏览器交互瀑布视图:

是否对 RUM 代理默认捕获的信息量感到惊奇?请特别关注一下顶部的 timeToFirstByte、domInteractive、domComplete 和 firstContentfulPaint 等标记。将光标移到黑点上即可看到名称。它们提供了有关内容检索和呈现这些内容的浏览器的详细信息。另外,还要关注一下有关从浏览器加载资源的所有性能数据。只需初始化 RUM 代理,无需对测量工具进行任何定制,便可获得所有这些开箱即用的详细性能指标!当出现性能问题时,这些指标可帮助您轻松确定问题是因为后端服务慢、网络慢所致,还是仅仅因为客户端浏览器慢所致。这非常吸引人!

针对需要温习知识的用户,下面来简要解释一下 Web 性能指标。请记住,对于像 React 这样的现代 Web 应用程序框架,由于 React 的异步特性,这些指标可能只表示网页的“静态”部分。例如,如您稍后会看到的,在 domInteractive 后可能仍然会加载动态内容。

  • timeToFirstByte 是浏览器在请求后等待从 Web 服务器接收第一条信息的时间。它表示网络和服务器端这二者结合的处理速度。
  • domInteractive 是用户代理将当前文档就绪状态设置为“交互式”(这意味着浏览器已完成对所有 HTML 的解析并且 DOM 构造已完成)前一刻的时间。
  • domComplete 是用户代理将当前文档就绪状态设置为“完成”(这意味着页面及图像等所有子资源已完成下载并准备就绪)前一刻的时间。下载旋转图标已停止旋转。
  • firstContentfulPaint 是浏览器呈现来自 DOM 的内容第一个位的时间。这对用户来说是一个重要的里程碑,因为它提供了页面实际加载的反馈。

灵活的定制测量工具

正如您刚才所看到的,RUM 代理为您的浏览器交互提供了详细且开箱即用的测量工具。您也可以根据需要对测量工具进行定制。例如,因为 React 应用程序是单页面应用程序,删除汽车不会触发“页面加载”,因此,RUM 不会默认捕获删除汽车的性能数据。针对此类情况,我们就可以使用定制事务。

在我们的当前版本(APM 真实用户监测 JavaScript 代理 5.x)中,AJAX 调用和单击事件会由代理捕获并发送到 APM 服务器。您可以使用 disableInstrumentation 设置来配置交互类型

也可以添加自己的定制插桩,以提供更有意义的跟踪。这对于跟踪新功能尤其有用。在我们的示例应用程序中,通过前端应用程序中的“New Car”(添加新汽车)按钮,您可以向数据库添加新汽车。我们将为代码装载测量工具,以捕获添加新汽车的性能。打开组件目录中的文件 Carlist.js。您将看到以下代码:

//Add new car
addCar(car) {
// Add car metadata as labels to the RUM click transaction
var transaction = apm.startTransaction("Add Car", "Car");
transaction.addLabels(car);
fetch(SERVER_URL + 'api/cars',
{
method:'POST',
headers: {
'Content-Type': 'application/json',
},
body:JSON.stringify(car)
})
.then(res => this.fetchCars())
.catch(err => console.error(err))
}
fetchCars = () => {
fetch(SERVER_URL + 'api/cars')
.then((response) => response.json())
.then((responseData) => {
this.setState({
cars: responseData._embedded.cars,
});
})
.catch(err => console.error(err));
// End the current transaction at the end of the response call back
var transaction = apm.getCurrentTransaction()
if (transaction) transaction.end()
}

这段代码基本上创建了一个名为“Add Car”的类型为“Car”的新事务。然后,它使用 car 标记了该事务以提供上下文信息。然后,我们在方法的末尾显式结束事务。

从应用程序的 Web UI 中添加新汽车。在 Kibana 中单击 APM UI。您应看到列出的一个“Add Car”事务。请确保在“Filter by type”(按类型筛选)下拉列表中选择“Car”。默认情况下,它显示“page-load”事务。

单击“Add Car”事务链接。您应看到定制事务“Add Car”的性能信息:

单击“Metadata”(元数据)选项卡。您将看到我们添加的标签以及代理捕获的默认标签。标签和日志可向您的 APM 跟踪添加有价值的上下文信息。

这就是执行定制测量工具装载所需要的一切 — 简单而强大!如需了解更多详细信息,请参阅 API 文档

用户体验仪表板

Elastic APM 提供了精心设计的 APM UI 和内置的 APM 仪表板,可以将代理捕获的所有 APM 数据直接进行可视化。

您还可以在 Elastic 中使用采集节点管道创建自己的定制可视化,以此扩充和转换 APM 数据。例如,RUM 代理捕获的用户 IP 和用户代理数据相当于客户非常详细的信息。有了用户 IP 和用户代理的所有信息,就可以创建如下图所示的可视化视图,在地图上显示 Web 流量的来源,以及客户使用的操作系统和浏览器。

然而,许多相关的用户数据可能显示在 Elastic 可观测性中可见的用户体验仪表板中。可视化示例如下所示:

通过分布式跟踪查看大局

另外,我们还会为后端 Spring Boot 应用程序插桩,以便您在一个视图中就能完整地查看从 Web 浏览器到后端数据库的整个事务。Elastic APM 分布式跟踪可支持您实现这一点。

在 RUM 代理中配置分布式跟踪

默认情况下,RUM 代理中已启用分布式跟踪。但是,它只包含对同域的请求。若要包含跨域请求,您必须设置 distributedTracingOrigins 配置选项。此外,您还必须在后端应用程序中设置 CORS 策略,这点将在下一节中讨论。

对于我们的应用程序,从 http://localhost:3000 为前端提供服务。若要包含对 http://localhost:8080 发出的请求,我们需要向 React 应用程序添加 distributedTracingOrigins 配置。这是在 rum.js 内完成的。代码已经在这个文件中了,只需取消注释该行即可。

var apm = initApm({
...
distributedTracingOrigins: ['http://localhost:8080']
})

新的代理版本向针对 http://localhost:8080 发出的请求实施 W3C 跟踪上下文规范和 traceparent 标头。但是,请注意,以前这是通过向这些请求添加定制标头 elastic-apm-traceparent 来实现的。

根据最新版本的文档,可以通过三种方式配置服务器端插桩:

  1. 使用 apm-agent-attach-cli.ja 自动附加到运行中的 JVM
  2. 使用 apm-agent-attach 进行编程设置,这需要对 Java 应用程序进行代码更改
  3. 使用 -javaagent 标记进行手动设置,我们将在下面的示例中执行此操作

要在服务器端使用手动插桩方法,您需要下载 Java 代理,并用它启动您的应用程序。在您最喜欢的 IDE 中,您需要将以下 vmArg 添加到启动配置中。

-javaagent:apm/wrapper/elastic-apm-agent-1.33.0.jar 
-Delastic.apm.service_name=cardatabase
-Delastic.apm.application_packages=com.packt.cardatabase
-Delastic.apm.server_urls=
-Delastic.apm.secret_token=

如果您使用的是 Elastic Cloud,可以在部署的 APM 集成中找到 RUM 和 APM 代理的完整配置,示例如下。

代理的配置位置取决于您所选择的 IDE。下面是 Spring Boot 应用程序的 VSCode 启动配置的截图:

现在,从浏览器刷新您的汽车列表以生成另一个请求。转到 Kibana APM UI,查看最后一个“car list”页面加载。您应该看到一个完整的跟踪,包括 Java 方法调用,类似于下面的截图:

如您所看到的,来自浏览器的客户端性能数据和服务器端性能数据(包括 JDBC 访问)都很好地显示在了一个分布式跟踪中。注意,分布式跟踪的不同部分有不同的颜色。请记住,这是您获得的默认跟踪,无需在服务器端对测量工具执行任何定制,只需使用代理启动应用程序即可。感受一下 Elastic APM 和分布式跟踪的强大功能!

对于真正关注上述时间线可视化的读者,您可能会想知道为什么“Car List”页面加载事务在 193 毫秒(即 domInteractive 时间)结束之后,仍会从后端提供数据。很好的问题!这是因为提取调用在默认情况下是异步的。浏览器“认为”它完成了对所有 HTML 的解析,DOM 构造在 193 毫秒完成,因为它加载了从 Web 服务器提供的所有“静态”HTML 内容。另一方面,React 仍然异步从后端服务器加载数据。

跨域资源共享 (CORS)

RUM 代理只是分布式跟踪中的一小部分。为了使用分布式跟踪,我们还需要正确配置其他组件。通常情况下,您必须配置的一个组件是跨域资源共享(即“臭名昭著”的 CORS)!这是因为前端和后端服务通常是分开部署的。对于同域策略,如果未正确配置 CORS,则从另一个域到后端的前端请求将会失败。从根本上说,CORS 是服务器端检查是否允许来自不同域请求的一种方法。若要详细了解跨域请求和为什么此过程是必需的,请参阅有关跨域资源共享的 MDN 页面。

它对我们有什么意义?它包含两层意义:

  1. 就像我们所做的那样,我们必须设置 distributedTracingOrigins 配置选项。
  2. 通过这项配置,RUM 代理还会在真正的 HTTP 请求之前发送一个 HTTP OPTIONS 请求,以确保支持所有的标头和 HTTP 方法,并且允许该域的请求。具体而言,http://localhost:8080 将接收具有以下标头的 OPTIONS 请求:
    Access-Control-Request-Headers: traceparent, tracestate
    Access-Control-Request-Method: [request-method]
    Origin: [request-origin]
    APM 服务器应使用下面这些标头和 200 状态代码来做出响应:
    Access-Control-Allow-Headers: traceparent, tracestate
    Access-Control-Allow-Methods: [allowed-methods]
    Access-Control-Allow-Origin: [request-origin]

Spring Boot 应用程序中的 MyCorsConfiguration 类正是这样做的。有多种方式可配置 Spring Boot 来完成此操作,但我们在这里将使用基于筛选器的方法。它会配置我们的服务器端 Spring Boot 应用程序,以允许来自任何域的具有任何 HTTP 标头和任何 HTTP 方法的请求。对于您的生产应用程序,您不需要启用此配置。

@Configuration
public class MyCorsConfiguration {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}

总结

希望这篇博客可以让您清晰地了解:使用 Elastic RUM 为应用程序插桩不仅简单易行,而且功能极其强大。结合后端服务的其他 APM 代理,通过分布式跟踪,RUM 可让您从最终用户角度整体查看应用程序性能。

再强调一下,若要开始使用 Elastic APM,您可以下载 Elastic APM 服务器,在本地运行,也可以创建一个 Elastic Cloud 试用帐户,在几分钟内便可准备好集群。

一如既往地,如果您想展开讨论或有任何问题,请在 Elastic APM 论坛上留言。快乐执行 RUM!

这篇博文最初于 2019 年 4 月 1 日发布。于 2022 年 10 月 20 日更新。