

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 持续连接问题
<a name="TroubleshootingConnections"></a>

在对持续连接问题进行故障排除时，必须验证以下各项 ElastiCache：

**Topics**
+ [安全组](#Security_groups)
+ [网络 ACL](#Network_ACLs)
+ [路由表](#Route_tables)
+ [DNS 解析](#DNS_Resolution)
+ [通过服务器端诊断识别问题](#Diagnostics)
+ [网络连接验证](#Connectivity)
+ [Network-related 限制](#Network-limits)
+ [CPU 使用率](#CPU-Usage)
+ [从服务器端终止的连接](#Connections-server)
+ [Client-side 针对 Amazon EC2 实例进行故障排除](#Connections-client)
+ [解剖完成单个请求所花费的时间](#Dissecting-time)

## 安全组
<a name="Security_groups"></a>

安全组是保护您的 ElastiCache 客户端（EC2 实例、 AWS Lambda 函数、Amazon ECS 容器等）和 ElastiCache 缓存的虚拟防火墙。安全组是有状态的，也就是说在允许传入或传出流量后，对该流量所做的响应将在该特定安全组的上下文中自动获得授权。

有状态功能要求安全组跟踪所有已授权的连接，而且对跟踪的连接有限制。如果达到该限制，新连接将会失败。有关如何识别客户端或 ElastiCache 端是否已达到限制的帮助，请参阅故障排除部分。

您可以同时为客户机和 ElastiCache 群集分配单个安全组，也可以为每个安全组分配单独的安全组。

在这两种情况下，您都需要允许来自源 ElastiCache 端口的 TCP 出站流量和来自同一端口的入站流量到 ElastiCache。Memcached 的默认端口为 11211，Valkey 或 Redis OSS 的默认端口为 6379。默认情况下，安全组允许所有出站流量。在这种情况下，只需要目标安全组中的入站规则。

有关更多信息，请参阅[访问 Amazon VPC 中 ElastiCache 集群的访问模式](elasticache-vpc-accessing.md)。

## 网络 ACL
<a name="Network_ACLs"></a>

网络访问控制列表 (ACL) 是无状态规则。必须在入站和出站两个方向上都允许流量，才能成功。网络 ACL 将分配给子网，而不是特定资源。可以为客户端资源分配相同的 ACL，尤其是当它们位于同一个子网中时。 ElastiCache 

默认情况下，网络 ACL 允许所有流量。但可对它们自定义，以拒绝或允许流量。此外，ACL 规则的评估是按顺序进行的，也就是说，匹配流量的编号最小的规则将允许或拒绝该流量。允许 Valkey 或 Redis OSS 流量的最低配置为：

客户端网络 ACL：
+ **入站规则：**
+ 规则编号：最好低于所有拒绝规则；
+ 类型：自定义 TCP 规则；
+ 协议：TCP
+ 端口范围：1024 – 65535
+ 来源：0.0.0。 0/0 （或者为 ElastiCache 集群子网创建单独的规则）
+ Allow/Deny: 允许
+ **出站规则：**
+ 规则编号：最好低于所有拒绝规则；
+ 类型：自定义 TCP 规则；
+ 协议：TCP
+ 端口范围：6379
+ 来源：0.0.0。 0/0 （或集 ElastiCache 群子网。 请记住，使用特定 IP 可能会在故障转移或扩展集群时产生问题）
+ Allow/Deny: 允许

ElastiCache 网络 ACL：
+ **入站规则：**
+ 规则编号：最好低于所有拒绝规则；
+ 类型：自定义 TCP 规则；
+ 协议：TCP
+ 端口范围：6379
+ 来源：0.0.0。 0/0 （或者为 ElastiCache 集群子网创建单独的规则）
+ Allow/Deny: 允许
+ **出站规则：**
+ 规则编号：最好低于所有拒绝规则；
+ 类型：自定义 TCP 规则；
+ 协议：TCP
+ 端口范围：1024 – 65535
+ 来源：0.0.0。 0/0 （或集 ElastiCache 群子网。 请记住，使用特定 IP 可能会在故障转移或扩展集群时产生问题）
+ Allow/Deny: 允许

有关更多信息，请参阅[网络 ACL](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html)。

## 路由表
<a name="Route_tables"></a>

与网络 ACL 类似，每个子网可以具有不同的路由表。如果客户端和 ElastiCache 集群位于不同的子网中，请确保它们的路由表允许它们相互访问。

环境越复杂（涉及多个 VPC、动态路由或网络防火墙），排除问题可能会变得越难。请参阅 [网络连接验证](#Connectivity) 以确认您的网络设置是否合适。

## DNS 解析
<a name="DNS_Resolution"></a>

ElastiCache 根据 DNS 名称提供服务端点。可用的端点包括 `Configuration`、`Primary`、`Reader` 和 `Node` 端点。有关更多信息，请参阅[查找连接终端节点](Endpoints.md)。

在故障转移或集群修改的情况下，与端点名称关联的地址可能会发生变化，并将自动更新。

自定义 DNS 设置（即不使用 VPC DNS 服务）可能不知道 ElastiCache-provided DNS 名称。确保您的系统能够使用诸如`dig`（如下所示）或之类的系统工具成功解析 ElastiCache 端点`nslookup`。

```
$ dig +short example.xxxxxx.ng.0001.use1.cache.amazonaws.com
example-001.xxxxxx.0001.use1.cache.amazonaws.com.
1.2.3.4
```

您还可以通过 VPC DNS 服务强制进行名称解析：

```
$ dig +short example.xxxxxx.ng.0001.use1.cache.amazonaws.com @169.254.169.253
example-001.tihewd.0001.use1.cache.amazonaws.com.
1.2.3.4
```

## 通过服务器端诊断识别问题
<a name="Diagnostics"></a>

CloudWatch 来自 ElastiCache 引擎的指标和运行时信息是识别连接问题潜在来源的常用来源或信息。良好的分析通常从以下项目开始：
+ CPU 使用率：Valkey 和 Redis OSS 是多线程应用程序。但是，每个命令的执行都发生在一个（主）线程中。因此， ElastiCache 提供了指标`CPUUtilization`和`EngineCPUUtilization`。 `EngineCPUUtilization`提供专用 Valkey 或 Redis OSS 进程的 CPU 使用率，以及所有 vCPU `CPUUtilization` 的使用率。具有多个 vCPU 的节点通常具有不同的 `CPUUtilization` 和 `EngineCPUUtilization` 值，第二个值通常更高。高 `EngineCPUUtilization` 可能是由于请求数量增加或需要大量 CPU 时间才能完成的复杂操作引起的。您可以通过以下方式标识两者：
  + 增加的请求数：检查与 `EngineCPUUtilization` 模式匹配的其他指标的增加。有用的指标包括：
    + `CacheHits` 和 `CacheMisses`：成功的请求数量或在缓存中找不到有效项目的请求的数量。如果未命中与命中之比较高，则应用程序会因无效的请求浪费时间和资源。
    + `SetTypeCmds` 和 `GetTypeCmds`：这些与 `EngineCPUUtilization` 相关的指标可以帮助理解写入请求（由 `SetTypeCmds` 衡量）还是读取请求（由 `GetTypeCmds` 衡量）的负载明显更高。如果负载主要是读取，则使用多个只读副本可以在多个节点之间平衡请求，并将主节点留出用于写入。在禁用集群模式的集群中，可通过使用读取器终端节点在应用程序中创建其他连接配置来使用只读副本。 ElastiCache 有关更多信息，请参阅[查找连接终端节点](Endpoints.md)。读取操作必须提交到此额外连接。写入操作将通过常规主端点完成。在已启用集群模式的情况下，建议使用支持本机只读副本的库。使用正确的标记，库将能够自动发现集群拓扑、副本节点，通过 [READONLY](https://valkey.io/commands/readonly) Valkey 或 Redis OSS 命令启用读取操作，然后将读取请求提交到副本。
  + 增加的连接数：
    + `CurrConnections` 和 `NewConnections`：`CurrConnection` 是数据点集合时已建立的连接数，而 `NewConnections` 显示的是在期间内创建的连接数。

      创建和处理连接意味着大量的 CPU 开销。此外，创建新连接所需的 TCP 三向握手会对整体响应时间产生负面影响。

      `NewConnections`每分钟数千个的 ElastiCache 节点表示仅通过几个命令即可创建和使用连接，这不是最佳选择。最佳做法是保持已建立连接并将其重复用于新操作。当客户端应用程序支持并正确实现连接池或持久连接时，可采用此最佳做法。使用连接池时，`currConnections` 数量没有很大的变化，`NewConnections` 应该尽可能的低。Valkey 和 Redis OSS 通过少量的当前连接提供最佳性能。将当前连接保持为数十或数百个的顺序，可最大限度地减少支持单独连接（如客户端缓冲区和 CPU 周期）的资源使用，以便为连接提供服务。
  + 网络吞吐量：
    + 确定带宽： ElastiCache 节点的网络带宽与节点大小成正比。由于应用程序具有不同的特征，因此结果可能会因工作负载而异。例如，小请求比率较高的应用程序对 CPU 使用率的影响往往大于网络吞吐量，而较大的密钥则会导致更高的网络利用率。因此，建议使用实际工作负载测试节点，以便更好地了解限制。

      模拟应用程序的负载可以提供更准确的结果。但是，通过基准工具可以很好地了解限制。
    + 对于主要是读取请求的情况，使用副本进行读取操作将减轻主节点上的负载。如果使用场景主要是写入，则使用许多副本将增加网络使用率。对于写入主节点的所有字节，有 N 个字节将被发送到副本，N 为副本数。写入密集型工作负载的最佳实践是使用 ElastiCache 启用集群模式的 Redis OSS，这样写入操作就可以在多个分片之间进行平衡，或者扩展到具有更多网络功能的节点类型。
    +  CloudWatchmetrics `NetworkBytesIn`和分别`NetworkBytesOut`提供进入或离开节点的数据量。 `ReplicationBytes`是专用于数据复制的流量。

    有关更多信息，请参阅 [Network-related 限制](#Network-limits)。
  + 复杂命令：Redis OSS 命令在单个线程上提供，这意味着按顺序处理请求。单个慢速命令可能会影响其他请求和连接，最终导致超时。对多个值、密钥或数据类型进行操作的命令的使用必须仔细完成。根据参数数量或其输入或输出值的大小，可以阻止或终止连接。

    一个显著例子就是 `KEYS` 命令。此命令扫描整个密钥空间来搜索给定模式，并在其执行过程中阻止其他命令的执行。Redis OSS 使用“Big O”符号来描述其命令的复杂性。

    密钥命令具有 O (N) 时间复杂性，N 表示数据库中的密钥数。因此，密钥数越大，命令的速度就越慢。`KEYS` 可能会以不同的方式制造麻烦：如果未使用搜索模式，则该命令会返回所有可用的密钥名称。在含有数千或百万个项目的数据库中，这将会创建大量输出并充满网络缓冲区。

    如果使用了搜索模式，则只有匹配该模式的密钥才会返回到客户端。但是，引擎仍然会扫描整个密钥空间来搜索它，并且完成命令的时间将是相同的。

    `KEYS` 命令的一个替代选择是 `SCAN` 命令。此命令会遍历密钥空间并限制特定数量项目中的迭代，避免引擎上的长时间阻塞。

    扫描具有 `COUNT` 参数，用于设置迭代块的大小。默认值为 10（每次迭代 10 个项目）。

    取决于数据库中的项目数，较小的 `COUNT` 值数据块将需要更多的迭代才能完成全面扫描，而且较大的值将使引擎在每次迭代中处于繁忙状态。虽然小计数值将使 `SCAN` 在大数据库变慢，较大的值可能会导致出现 `KEYS` 中提及的相同问题。

    例如，运行 `SCAN` 命令（计数值为 10）将需要在具有 100 万个密钥的数据库上进行 10 万次重复操作。如果平均网络往返时间为 0.5 毫秒，则传输请求将耗时大约 5 万毫秒（50 秒）。

    另一方面，如果计数值为 100,0000，则需要一次迭代，并且传输它只需要 0.5 毫秒。但是，在命令完成扫描所有密钥空间之前，该引擎将完全阻止其他操作。

    除了 `KEYS` 之外，其他几个命令如果使用不当也可能会有害。要查看所有命令及其各自的时间复杂度的列表，请转到 [Valkey 和 Redis OSS 命令](https://valkey.io/commands)。

    潜在问题的示例：
    + Lua 脚本：Valkey 和 Redis OSS 提供了嵌入式 Lua 解释器，允许在服务器端执行脚本。Valkey 和 Redis OSS 上的 Lua 脚本在引擎级别执行，而且根据定义，其具有原子性，这意味着在脚本执行过程中不允许运行其他命令或脚本。Lua 脚本提供了直接在引擎上运行多个命令、决策算法、数据解析和其他操作的可能性。虽然脚本的原子性和分载应用程序的可能性很诱人，但必须小心使用脚本，并且仅用于小型操作。开 ElastiCache启后，Lua 脚本的执行时间限制为 5 秒。未写入密钥空间的脚本将在 5 秒后自动终止。为了避免数据损坏和不一致，如果脚本执行在 5 秒内未完成并且在执行过程中有任何写入，则节点将进行故障转移。[事务](https://valkey.io/topics/transactions)是保证 Redis OSS 中多个相关密钥修改的一致性的替代方案。事务允许执行一个命令块，监视现有密钥以进行修改。如果任何受监视的密钥在事务完成之前发生了更改，则会放弃所有修改。
    + 批量删除项目：`DEL` 命令接受多个参数，这些参数是要删除的密钥名称。删除操作是同步的，如果参数列表很大，或者包含大列表、集合、排序集或哈希（包含多个子项的数据结构），则需要大量 CPU 时间。换句话说，如果具有许多元素，那么即使删除单个密钥也可能需要相当长的时间。`DEL` 的替代项选择为 `UNLINK`，这是自 Redis OSS 4 以来可用的异步命令。`UNLINK` 必须尽可能优先于 `DEL`。从 Red ElastiCache is OSS 6x 开始，该`lazyfree-lazy-user-del`参数使`DEL`命令的行为与启用`UNLINK`时相同。有关更多信息，请参阅 [Redis OSS 6.0 参数更改](ParameterGroups.Engine.md#ParameterGroups.Redis.6-x)。
    + 对多个密钥进行操作的命令：`DEL` 在先前是作为接受多个实际参数的命令提起，其执行时间直接与之成正比。但是，Redis OSS 提供了更多工作原理类似的命令。例如，`MSET` 和 `MGET` 允许一次插入或检索多个字符串键。使用它们可能有助于降低多个单独的 `SET` 或 `GET` 命令固有的网络延迟。但是，参数列表过大会影响 CPU 使用率。

       虽然仅仅 CPU 使用率并不是导致连接问题的原因，但是通过多个密钥花费过多时间处理单个或少量命令可能会导致其他请求失败，并增加总体 CPU 使用率。

      密钥的数量及其大小将影响命令的复杂性，从而影响完成时间。

      其他可对多个密钥进行操作的命令示例：`HMGET`、`HMSET`、`MSETNX`、`PFCOUNT`、`PFMERGE`、`SDIFF`、`SDIFFSTORE`、`SINTER`、`SINTERSTORE`、`SUNION`、`SUNIONSTORE`、`TOUCH`、`ZDIFF`、`ZDIFFSTORE`、`ZINTER` 或 `ZINTERSTORE`。
    + 作用于多种数据类型的命令：Redis OSS 还提供对一个或多个密钥执行操作的命令，无论其数据类型如何。 ElastiCache 对于 Redis，OSS 提供了监控此类命令`KeyBasedCmds`的指标。此指标汇总了以下命令在所选时间段内的执行情况：
      + O(N) 复杂性：
        + `KEYS`
      + O(1)
        + `EXISTS`
        + `OBJECT`
        + `PTTL`
        + `RANDOMKEY`
        + `TTL`
        + `TYPE`
        + `EXPIRE`
        + `EXPIREAT`
        + `MOVE`
        + `PERSIST`
        + `PEXPIRE`
        + `PEXPIREAT`
        + `UNLINK (O(N)` 来回收内存。但是，内存回收任务发生在一个单独的线程中，并且不会阻塞引擎
      + 根据数据类型不同的复杂性时间：
        + `DEL`
        + `DUMP`
        + `RENAME` 被认为是一个具有 O(1) 复杂性的命令，但在内部执行 `DEL`。执行时间将根据重命名密钥的大小而变。
        + `RENAMENX`
        + `RESTORE`
        + `SORT`
      + 大哈希：哈希是一种数据类型，允许单个密钥和多个键值子项目。每个哈希可以存储 4,294,967,295 个项目，并且对大哈希执行的操作可能会变得昂贵。类似于 `KEYS`，哈希具有 `HKEYS` 命令，该命令具有 O(N) 时间复杂度，N 表示哈希中的项目数。`HSCAN` 必须优先于 `HKEYS` 来避免长时间运行的命令。`HDEL`、`HGETALL`、`HMGET`、`HMSET` 和 `HVALS` 是应谨慎用于大哈希的命令。
    + 其他大数据结构：除了哈希之外，其他数据结构可能是 CPU 密集型的。集、列表、排序集和 Hyperloglog 也可能需要相当长的时间来处理，具体取决于它们的大小和使用的命令。有关这些命令的更多信息，请参阅 [Valkey 和 Redis OSS 命令](https://valkey.io/commands)。

## 网络连接验证
<a name="Connectivity"></a>

查看与 DNS 解析、安全组、网络 ACL 和路由表相关的网络配置后，可以使用 VPC Reachability Analyzer 和系统工具验证连接性。

Reachability Analyzer 将测试网络连接并确认是否满足所有要求和权限。对于以下测试，您将需要您的 VPC 中可用 ElastiCache 节点之一的 ENI ID（弹性网络接口识别）。您可以执行以下操作来查找：

1. 去 [https://console.aws.amazon.com/ec2/v2/home？ \#NIC:](https://console.aws.amazon.com/ec2/v2/home?#NIC)

1. 根据您的 ElastiCache 集群名称或之前从 DNS 验证中获得的 IP 地址筛选接口列表。

1. 记下或以其他方式保存 ENI ID。如果显示多个接口，请查看描述以确认它们属于正确的 ElastiCache 集群，然后从中选择一个。

1. 继续执行下一步骤。

1. 在以下位置创建分析路径 [https://console.aws.amazon.com/vpc/home？ ](https://console.aws.amazon.com/vpc/home?#ReachabilityAnalyzer)\# ReachabilityAnalyzer 并选择以下选项：
   + **源类型****：如果您的 ElastiCache 客户端在 Amazon EC2 实例上运行，则选择实例；如果您的客户端使用其他服务（例如带有 awsvpc 网络的 A AWS Fargate mazon ECS 等） AWS Lambda，则选择网络**接口**，以及相应的资源 ID（EC2 实例或 ENI ID）；**
   + **Destination Type（目的地类型）**：选择 **Network Interface（网络接口）**，然后在列表中选择 **ElastiCache ENI**。
   + **目标端口**：为 Redis OSS 指定 6379 ElastiCache ，为 Memcached 指定 11211。 ElastiCache 这些端口是使用默认配置定义的端口，本示例假定它们未更改。
   + **协议**：TCP

创建分析路径并等待结果。如果状态为无法访问，请打开分析详细信息并查看 **Analysis Explorer**，了解请求被阻止的详细信息。

如果可到达性测试通过，请继续进行系统级别的验证操作。

要验证 ElastiCache 服务端口上的 TCP 连接，`Nping`请执行以下操作：在 Amazon Linux 上，包`nmap`中提供，可以测试 ElastiCache 端口上的 TCP 连接，还可以提供网络往返时间来建立连接。使用它来验证 ElastiCache 集群的网络连接和当前延迟，如下所示：

```
$ sudo nping --tcp -p 6379 example.xxxxxx.ng.0001.use1.cache.amazonaws.com

Starting Nping 0.6.40 ( http://nmap.org/nping ) at 2020-12-30 16:48 UTC
SENT (0.0495s) TCP ...
(Output suppressed )

Max rtt: 0.937ms | Min rtt: 0.318ms | Avg rtt: 0.449ms
Raw packets sent: 5 (200B) | Rcvd: 5 (220B) | Lost: 0 (0.00%)
Nping done: 1 IP address pinged in 4.08 seconds
```

默认情况下，`nping` 会发送 5 个探测器，探测器之间的延迟为 1 秒。您可以使用选项“-c”来增加探测器的数量，并使用“--delay”来更改发送新测试的时间。

如果测试`nping`失败且 *VPC Reachability Analyzer* 测试通过，请您的系统管理员查看可能的 Host-based 防火墙规则、非对称路由规则或操作系统级别的任何其他可能限制。

在 ElastiCache 控制台上，检查 ElastiCache 集群详细信息中是否启用了**传输中加密**。如果传输中加密已启用，请使用以下命令确认是否可以建立 TLS 会话：

```
openssl s_client -connect {{example.xxxxxx.use1.cache.amazonaws.com:6379}}
```

如果连接和 TLS 协商成功，则预计会有大量输出。检查最后一行中可用的返回代码，该值必须为 `0 (ok)`。如果 openssl 返回的内容有所不同，请在 [https://www.openssl.org/docs/man1.0.2/man1/verify.html\#DIAGNOSTICS](https://www.openssl.org/docs/man1.0.2/man1/verify.html#DIAGNOSTICS) 中查看错误的原因。

如果所有基础架构和操作系统测试均已通过，但您的应用程序仍无法连接 ElastiCache，请检查应用程序配置是否符合 ElastiCache 设置。常见的错误有：
+ 您的应用程序不支持 ElastiCache 集群模式，并且 ElastiCache 已启用集群模式；
+ 您的应用程序不支持 TLS/SSL，并且 ElastiCache 已启用传输中加密；
+ 应用程序支持 TLS/SSL 但没有正确的配置标志或可信的证书颁发机构；

## Network-related 限制
<a name="Network-limits"></a>
+ 最大连接数：同时连接的数量有硬限制。每个 ElastiCache 节点允许在所有客户端之间同时连接多达 65,000 个。可以通过上的`CurrConnections`指标来监控此限制 CloudWatch。但是，客户端也有出站连接限制。在 Linux 上，使用以下命令检查允许的临时端口范围：

  ```
  # sysctl net.ipv4.ip_local_port_range
  net.ipv4.ip_local_port_range = 32768 60999
  ```

  在前面的示例中，将允许从同一源到相同目标 IP（ElastiCache 节点）和端口的 28231 个连接。以下命令显示特定 ElastiCache 节点 (IP 1.2.3.4) 有多少连接：

  ```
  ss --numeric --tcp state connected "dst 1.2.3.4 and dport == 6379" | grep -vE '^State' | wc -l
  ```

  如果数量过高，您的系统可能会因尝试处理连接请求而变得过载。建议考虑实施连接池或持久连接等技术，以更好地处理连接。尽可能配置连接池以将最大连接数限制为几百个。此外，建议采用退避逻辑来处理超时或其他连接异常，以避免在出现问题时出现连接损失。
+ 网络流量限制：检查 [Redis OSS 的以下CloudWatch 指标](CacheMetrics.Redis.md)，以确定 ElastiCache 节点上可能达到的网络限制：
  + `NetworkBandwidthInAllowanceExceeded`/`NetworkBandwidthOutAllowanceExceeded`：由于吞吐量超过了聚合带宽限制而形成的网络数据包。

    请注意，写入主节点的每个字节都将被复制到 N 个副本，N 代表副本的数量。具有小节点类型、多个副本和密集型写入请求的集群可能无法应对复制积压。对于这种情况，最佳做法是纵向扩展（更改节点类型）、横向扩展（在已启用集群模式的集群中添加分区）、减少副本数量或最大程度减少写入次数。
  + `NetworkConntrackAllowanceExceeded`：由于超过了分配给节点的、跨所有安全组跟踪的连接最大数量而形成的数据包。在此期间，新连接可能会失败。
  + `NetworkPackets PerSecondAllowanceExceeded`：超过每秒最大数据包数。基于高比率小请求的工作负载可能会在达到最大带宽之前达到此限制。

  以上指标是确认节点达到网络限制的理想方法。但是，通过网络指标的高原也可以确认限制。

  如果平稳状态持续了很长时间，则它们之后可能会出现复制滞后、用于缓存的字节增加、可用内存减少、高交换和 CPU 使用率。Amazon EC2 实例同样具有网络限制，这些限制可通过 [ENA 驱动程序指标](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html)跟踪。具有增强联网支持和 ENA 驱动程序 2.2.10 或更高版本的 Linux 实例可以使用以下命令查看限制计数器：

  ```
  # ethtool -S eth0 | grep "allowance_exceeded"
  ```

## CPU 使用率
<a name="CPU-Usage"></a>

CPU 使用率指标是调查的起点，以下项目可以帮助缩小可能存在 ElastiCache 的问题：
+ Redis OSS SlowLogs： ElastiCache 默认配置保留了花费超过 10 毫秒才完成的最后 128 条命令。慢速命令的历史记录会在引擎运行时保留，如果发生故障或重启，则会丢失。如果列表达到 128 个条目，旧事件将被删除来为新事件预留空间。慢速事件列表的大小和被视为慢的执行时间可以通过[自定义参数组](ParameterGroups.md)中的参数 `slowlog-max-len` 和 `slowlog-log-slower-than` 进行修改。慢速日志列表可以通过在引擎上运行 `SLOWLOG GET 128` 来进行检索，128 代表报告的最近 128 个慢速命令。每个条目都包含以下字段：

  ```
  1) 1) (integer) 1 -----------> Sequential ID
     2) (integer) 1609010767 --> Timestamp (Unix epoch time)of the Event
     3) (integer) 4823378 -----> Time in microseconds to complete the command.
     4) 1) "keys" -------------> Command
        2) "*" ----------------> Arguments 
     5) "1.2.3.4:57004"-> Source
  ```

  上述事件发生在 12 月 26 日，UTC 19:26:07，耗时 4.8 秒（4823 毫秒）完成，该事件由从客户端 1.2.3.4 请求的 `KEYS` 命令导致。

  在 Linux 上，时间戳可以使用命令日期进行转换：

  ```
  $ date --date='@1609010767'
  Sat Dec 26 19:26:07 UTC 2020
  ```

  使用 Python：

  ```
  >>> from datetime import datetime
  >>> datetime.fromtimestamp(1609010767)
  datetime.datetime(2020, 12, 26, 19, 26, 7)
  ```

  或者在 Windows 上使用 PowerShell：

  ```
  PS D:\Users\user> [datetimeoffset]::FromUnixTimeSeconds('1609010767')
  DateTime      : 12/26/2020 7:26:07 PM
  UtcDateTime  
                  : 12/26/2020 7:26:07 PM
  LocalDateTime : 12/26/2020 2:26:07 PM
  Date          : 12/26/2020 12:00:00 AM
  Day           : 26
  DayOfWeek    
                  : Saturday
  DayOfYear     : 361
  Hour          : 19
  Millisecond   : 0
  Minute        : 26
  Month        
                  : 12
  Offset        : 00:00:00Ticks         : 637446075670000000
  UtcTicks     
                  : 637446075670000000
  TimeOfDay     : 19:26:07
  Year          : 2020
  ```

  如果短时间内（一分钟内或更短时间）有许多慢速命令，则需要引起关注。查看命令的性质以及如何对其进行优化（请参阅前面的示例）。如果经常报告 O(1) 时间复杂度的命令，请检查前面提到的 CPU 使用率高的其他因素。
+ 延迟指标： ElastiCache 对于 Redis，OSS 提供了用于监控不同类别命令的平均延迟的 CloudWatch 指标。数据点的计算方法是将类别中命令的执行总数除以期间内的总执行时间。了解延迟指标结果是多个命令的聚合，这一点非常重要。单个命令可能会导致意外结果（如超时），不会对指标产生重大影响。对于这种情况，慢日志事件将是一个更准确的信息来源。以下列表包含可用的延迟指标以及影响它们的相应命令。
  + EvalBasedCmdsLatency: 与 Lua 脚本命令有关，`eval`，; `evalsha` 
  + GeoSpatialBasedCmdsLatency: `geodist`, `geohash`, `geopos`, `georadius`, `georadiusbymember`, `geoadd`;
  + GetTypeCmdsLatency: 读取命令，无论数据类型如何；
  + HashBasedCmdsLatency: `hexists`, `hget`, `hgetall`, `hkeys`, `hlen`, `hmget`, `hvals`, `hstrlen`, `hdel`, `hincrby`, `hincrbyfloat`, `hmset`, `hset`, `hsetnx`;
  + HyperLogLogBasedCmdsLatency: `pfselftest`, `pfcount`, `pfdebug`, `pfadd`, `pfmerge`;
  + KeyBasedCmdsLatency: 可以对不同数据类型进行操作的命令：`dump`、、、、、、、、、、、、`exists`、、`keys`、、`object`、、`pttl`、、`randomkey`、、`ttl`、、`type`、、`del`、、`expire`、、`expireat`、、`move`、、`persist`、、`pexpire`、、`pexpireat`、、`rename`、、`renamenx`、、`restoreK`、、`sort`、、、`unlink`；
  + ListBasedCmdsLatency: lindex、llen、lange、blop、brpop、brpop、broppush、linsert、lpop、lpush、lpush、lpush、ltrim、rprop、roplpush、rpush、rpush、rpush、rpush、rpush、rpush；
  + PubSubBasedCmdsLatency: 取消订阅、发布、发布订阅、取消订阅、订阅、取消订阅；
  + SetBasedCmdsLatency: `scard`, `sdiff`, `sinter`, `sismember`, `smembers`, `srandmember`, `sunion`, `sadd`, `sdiffstore`, `sinterstore`, `smove`, `spop`, `srem`, `sunionstore`; 
  + SetTypeCmdsLatency: 无论数据类型如何，都要编写命令；
  + SortedSetBasedCmdsLatency: `zcard`, `zcount`, `zrange`, `zrangebyscore`, `zrank`, `zrevrange`, `zrevrangebyscore`, `zrevrank`, `zscore`, `zrangebylex`, `zrevrangebylex`, `zlexcount`, `zadd`. `zincrby`, `zinterstore`, `zrem`, `zremrangebyrank`, `zremrangebyscore`, `zunionstore`, `zremrangebylex`, `zpopmax`, `zpopmin`, `bzpopmin`, `bzpopmax`; 
  + StringBasedCmdsLatency: `bitcount`, `get`, `getbit`, `getrange`, `mget`, `strlen`, `substr`, `bitpos`, `append`, `bitop`, `bitfield`, `decr`, `decrby`, `getset`, `incr`, `incrby`, `incrbyfloat`, `mset`, `msetnx`, `psetex`, `set`, `setbit`, `setex`, `setnx`, `setrange`; 
  + StreamBasedCmdsLatency: `xrange`, `xrevrange`, `xlen`, `xread`, `xpending`, `xinfo`, `xadd`, `xgroup`, `readgroup`, `xack`, `xclaim`, `xdel`, `xtrim`, `xsetid`; 
+ Redis OSS 运行时命令：
  + info commandstats：提供自引擎启动以来执行的命令列表、其累积执行数、总执行时间以及每个命令的平均执行时间；
  + 客户端列表：提供当前已连接的客户端列表以及相关信息，如缓冲区使用情况、最后执行的命令等；
+ 备份和复制： ElastiCache 对于 2.8.22 之前的 Redis OSS 版本，使用分叉进程来创建备份并处理与副本的完全同步。对于写入密集型使用案例，此方法可能会产生大量内存开销。

  从 ElastiCache Redis OSS 2.8.22 开始， AWS 引入了一种无分支备份和复制方法。新方法可能会延迟写入，以防止出现故障。这两种方法都可能产生较高的 CPU 使用率，导致更长的响应时间，从而导致客户端在执行过程中出现超时。始终检查客户端故障是否发生在备份窗口或在该期间内 `SaveInProgress` 指标是否为 1。建议将备份窗口安排在使用率低的时间段，以最大限度地减少客户端出现问题或备份失败的可能性。

## 从服务器端终止的连接
<a name="Connections-server"></a>

Redis OSS ElastiCache 的默认配置可以无限期地保持客户端连接的建立。但是，在某些情况下，可能需要终止连接。例如：
+ 客户端应用程序中的漏洞可能会导致连接被遗忘并在空闲状态仍保持为已建立状态。这被称为“连接泄漏”，其结果是在 `CurrConnections` 指标上观察到的已建立连接数量的稳步上升。这种行为可能会导致客户端或 ElastiCache 端出现饱和。当客户端无法立即修复时，一些管理员会在其 ElastiCache 参数组中设置 “超时” 值。超时是允许空闲连接保留的时间（以秒为单位）。如果客户端在此期间内未提交任何请求，则引擎将在连接达到超时值后立即终止连接。较小的超时值可能会导致不必要的断开连接，客户端需要正确处理它们并重新连接，因此会导致延迟。
+ 用于存储密钥的内存与客户端缓冲区共享。具有较大请求或响应的速度较慢的客户端可能需要大量内存来处理其缓冲区。Redi ElastiCache s OSS 配置的默认配置不限制常规客户端输出缓冲区的大小。如果达到 `maxmemory` 限制，引擎将尝试移出项目以满足缓冲区使用情况。在内存极低的情况下， ElastiCache 对于 Redis，OSS 可能会选择断开消耗大量客户端输出缓冲区的客户端，以释放内存并保持集群的运行状况。

  可以通过自定义配置来限制客户端缓冲区的大小，达到限制的客户端将断开连接。但客户端应能够处理意外断开的连接。用于处理常规客户端缓冲区大小的参数如下：
  + client-query-buffer-limit：单个输入请求的最大大小；
  + client-output-buffer-limit-normal-soft-limit：针对客户端连接的软限制。如果保持在软限制以上的时间超过 client-output-buffer-limit-normal-soft-seconds 定义的时间（以秒为单位），或者如果达到硬限制，则连接将终止；
  + client-output-buffer-limit-normal-soft-seconds：超出 client-output-buffer-limit-normal-soft-limit 的连接允许的时间；
  + client-output-buffer-limit-normal-hard-limit：达到此限制的连接将立即终止。

  除了常规的客户端缓冲区外，以下选项还控制副本节点和 Pub/Sub (Publish/Subscribe) 客户端的缓冲区：
  + client-output-buffer-limit-replica-hard-limit；
  + client-output-buffer-limit-replica-soft-seconds；
  + client-output-buffer-limit-replica-hard-limit；
  + client-output-buffer-limit-pubsub-soft-limit；
  + client-output-buffer-limit-pubsub-soft-seconds；
  + client-output-buffer-limit-pubsub-hard-limit；

## Client-side 针对 Amazon EC2 实例进行故障排除
<a name="Connections-client"></a>

客户端的负载和响应能力也会影响对的请求 ElastiCache。在排除间歇性连接或超时问题时，需要仔细检查 EC2 实例和操作系统限制。需要注意的一些关键点：
+ CPU：
  + EC2 实例 CPU 使用率：确保 CPU 未饱和或接近 100%。历史分析可以通过以下方式完成 CloudWatch，但请记住，数据点的粒度要么是 1 分钟（启用详细监控），要么是 5 分钟；
  + 使用[可突增 EC2 实例](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html)时，请确保他们的 CPU 积分余额没有被耗尽。此信息可在`CPUCreditBalance` CloudWatch 指标上找到。
  + 短时间的高CPU使用率可能会导致超时，而不会反映100％的利用率。 CloudWatch这种情况需要使用操作系统工具（如 `top`、`ps` 和 `mpstat`）进行实时监控。
+ Network
  + 根据实例功能，检查网络吞吐量是否在可接受的值之内。有关更多信息，请参阅 [Amazon EC2 实例类型](https://aws.amazon.com/ec2/instance-types/)。
  + 在具有 `ena` 增强型网络驱动程序的实例上，请检查 [ENA 统计数据](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/troubleshooting-ena.html#statistics-ena)查看超时或超出限制情况。以下统计数据对于确认网络限制饱和度很有用：
    + `bw_in_allowance_exceeded`/`bw_out_allowance_exceeded`：由于入站或出站吞吐量过高而形成的数据包数量；
    + `conntrack_allowance_exceeded`：由于安全组[连接跟踪限制](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html#connection-tracking-throttling)而丢弃的数据包数。当此限制饱和时，新连接将失败；
    + `linklocal_allowance_exceeded`：由于通过 VPC DNS 对实例元数据的请求过多而丢弃的数据包数。对所有服务的限制是每秒 1024 个数据包；
    + `pps_allowance_exceeded`：由于每秒数据包过多而丢弃的数据包数。当网络流量包含每秒成千上万个非常小的请求时，就会达到 PPS 限制。 ElastiCache 可以优化流量，以便通过管道或命令更好地利用网络数据包，这些管道或命令可以同时执行多个操作，比如，`MGET`而不是`GET`。

## 解剖完成单个请求所花费的时间
<a name="Dissecting-time"></a>
+ 在网络上：`Tcpdump`和`Wireshark`（命令行上的 tshark）是便捷的工具，用于了解请求在网络上传输、命中 ElastiCache 引擎并获得返回所花费的时间。以下示例突出显示使用以下命令创建的单个请求：

  ```
  $ echo ping | nc example.xxxxxx.ng.0001.use1.cache.amazonaws.com 6379
  +PONG
  ```

  与上面的命令并行，tcpdump 经过执行并返回：

  ```
  $ sudo tcpdump -i any -nn port 6379 -tt
  tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
  1609428918.917869 IP 172.31.11.142.40966
      > 172.31.11.247.6379: Flags [S], seq 177032944, win 26883, options [mss 8961,sackOK,TS val 27819440 ecr 0,nop,wscale 7], length 0
  1609428918.918071 IP 172.31.11.247.6379 > 172.31.11.142.40966: Flags [S.], seq 53962565, ack 177032945, win
      28960, options [mss 1460,sackOK,TS val 3788576332 ecr 27819440,nop,wscale 7], length 0
  1609428918.918091 IP 172.31.11.142.40966 > 172.31.11.247.6379: Flags [.], ack 1, win 211, options [nop,nop,TS val 27819440 ecr 3788576332], length 0
  1609428918.918122
      IP 172.31.11.142.40966 > 172.31.11.247.6379: Flags [P.], seq 1:6, ack 1, win 211, options [nop,nop,TS val 27819440 ecr 3788576332], length 5: RESP "ping"
  1609428918.918132 IP 172.31.11.142.40966 > 172.31.11.247.6379: Flags [F.], seq 6, ack
      1, win 211, options [nop,nop,TS val 27819440 ecr 3788576332], length 0
  1609428918.918240 IP 172.31.11.247.6379 > 172.31.11.142.40966: Flags [.], ack 6, win 227, options [nop,nop,TS val 3788576332 ecr 27819440], length 0
  1609428918.918295
      IP 172.31.11.247.6379 > 172.31.11.142.40966: Flags [P.], seq 1:8, ack 7, win 227, options [nop,nop,TS val 3788576332 ecr 27819440], length 7: RESP "PONG"
  1609428918.918300 IP 172.31.11.142.40966 > 172.31.11.247.6379: Flags [.], ack 8, win
      211, options [nop,nop,TS val 27819441 ecr 3788576332], length 0
  1609428918.918302 IP 172.31.11.247.6379 > 172.31.11.142.40966: Flags [F.], seq 8, ack 7, win 227, options [nop,nop,TS val 3788576332 ecr 27819440], length 0
  1609428918.918307
      IP 172.31.11.142.40966 > 172.31.11.247.6379: Flags [.], ack 9, win 211, options [nop,nop,TS val 27819441 ecr 3788576332], length 0
  ^C
  10 packets captured
  10 packets received by filter
  0 packets dropped by kernel
  ```

  从上面的输出中，我们可以确认 TCP 三向握手是在 222 微秒（918091 – 917869）内完成的，并且在 173 微秒（918295 – 918122）内提交并返回了 ping 命令。

   请求关闭连接耗费了 438 微秒（918307 – 917869）。这些结果将确认网络和引擎响应时间良好，调查可以集中在其他组件上。
+ 在操作系统上：`Strace` 可以帮助识别操作系统级别的时间差。对实际应用程序的分析将更加广泛，建议使用专门的应用程序分析器或调试器。以下示例仅显示操作系统基本组件是否按预期工作，其他内容可能需要进一步调查。通过 `strace` 使用相同的 Redis OSS `PING` 命令，我们得到：

  ```
  $ echo ping | strace -f -tttt -r -e trace=execve,socket,open,recvfrom,sendto nc example.xxxxxx.ng.0001.use1.cache.amazonaws.com (http://example.xxxxxx.ng.0001.use1.cache.amazonaws.com/)
      6379
  1609430221.697712 (+ 0.000000) execve("/usr/bin/nc", ["nc", "example.xxxxxx.ng.0001.use"..., "6379"], 0x7fffede7cc38 /* 22 vars */) = 0
  1609430221.708955 (+ 0.011231) socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
  1609430221.709084
      (+ 0.000124) socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
  1609430221.709258 (+ 0.000173) open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
  1609430221.709637 (+ 0.000378) open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
  1609430221.709923
      (+ 0.000286) open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
  1609430221.711365 (+ 0.001443) open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
  1609430221.713293 (+ 0.001928) socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
  1609430221.717419
      (+ 0.004126) recvfrom(3, "\362|\201\200\0\1\0\2\0\0\0\0\rnotls20201224\6tihew"..., 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("172.31.0.2")}, [28->16]) = 155
  1609430221.717890 (+ 0.000469) recvfrom(3, "\204\207\201\200\0\1\0\1\0\0\0\0\rnotls20201224\6tihew"...,
      65536, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("172.31.0.2")}, [28->16]) = 139
  1609430221.745659 (+ 0.027772) socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
  1609430221.747548 (+ 0.001887) recvfrom(0, 0x7ffcf2f2ca50, 8192,
      0, 0x7ffcf2f2c9d0, [128]) = -1 ENOTSOCK (Socket operation on non-socket)
  1609430221.747858 (+ 0.000308) sendto(3, "ping\n", 5, 0, NULL, 0) = 5
  1609430221.748048 (+ 0.000188) recvfrom(0, 0x7ffcf2f2ca50, 8192, 0, 0x7ffcf2f2c9d0, [128]) = -1 ENOTSOCK
      (Socket operation on non-socket)
  1609430221.748330 (+ 0.000282) recvfrom(3, "+PONG\r\n", 8192, 0, 0x7ffcf2f2c9d0, [128->0]) = 7
  +PONG
  1609430221.748543 (+ 0.000213) recvfrom(3, "", 8192, 0, 0x7ffcf2f2c9d0, [128->0]) = 0
  1609430221.752110
      (+ 0.003569) +++ exited with 0 +++
  ```

   在上面的示例中，完成该命令所耗费的时间稍多于 54 毫秒（752110 – 697712 = 54398 微秒）。

   实例化 NC 并执行名称解析耗费了大量的时间（从 697712 到 717890，大约 20 毫秒），之后创建 TCP 套接字需要 2 毫秒（745659 到 747858），提交和接收请求的响应需要 0.4 毫秒（747858 到 748330）。