使用只读副本的最佳实践
许多应用程序(例如会话存储、排行榜和推荐引擎)都需要高可用性,且处理的读取操作要远多于写入操作。这类应用程序通常可以容忍稍微陈旧的数据(最终一致性),意味着如果不同用户在短时间内看到相同数据的不同版本,这是可以接受的。例如:
缓存的查询结果通常可以容忍稍微陈旧的数据,特别是对于可信来源位于外部的旁路缓存模式。
在游戏排行榜中,分数更新延迟几秒钟通常不会对用户体验产生重大影响。
对于会话存储,跨副本传播会话数据时出现一些轻微延迟通常不会影响应用程序的功能。
推荐引擎通常使用历史数据分析,因此对实时一致性要求较低。
最终一致性意味着在复制过程完成后,所有副本节点最终都将返回相同的数据,且通常在毫秒内完成。对于此类使用案例,实施只读副本是减少从 ElastiCache 实例读取时出现延迟的有效策略。
在 Amazon ElastiCache 中使用只读副本可通过以下方式显著提升性能:
增强的读取可扩展性
将读取操作分配到多个副本节点
从主节点卸载读取流量
通过处理来自地理位置较近的副本的请求来降低读取延迟
优化的主节点性能
将主节点资源专门用于写入操作
减少主节点连接开销
提高写入性能,并在流量高峰期保持较短的响应时间
在 ElastiCache 无服务器中使用“从副本读取”功能
ElastiCache 无服务器提供两个不同的端点,以满足不同的一致性要求。这两个端点使用相同的 DNS 名称,但采用不同的端口。要使用 read-from-replica 端口,您必须配置 VPC 的安全组和网络访问控制列表,授权您的客户端应用程序访问这两个端口。
主端点(端口 6379)
用于需要即时一致性的操作
保证读取最新数据
最适合关键事务和写入操作
对于写入操作是必需的
示例:
test-12345.serverless.use1.cache.amazonaws.com:6379
针对延迟进行了优化的端点(端口 6380)
针对可容忍最终一致性的读取操作进行了优化
在可能的情况下,ElastiCache 无服务器会自动将读取请求路由到客户端本地可用区中的副本节点。这种优化避免了从不同可用区的节点检索数据时产生的额外网络延迟,从而降低了整体延迟。
如果本地节点不可用,ElastiCache 无服务器会自动选择其他区域中的可用节点
示例:
test-12345.serverless.use1.cache.amazonaws.com:6380如果您提供“从副本读取”配置,Glide 和 Lettuce 等客户端将自动检测读取操作,并将其路由至针对延迟进行了优化的端点。如果您的客户端不支持路由配置(例如 valkey-java 和旧版 jedis),则必须定义正确的端口和客户端配置才能从副本读取。
在 ElastiCache 无服务器中连接只读副本 - Valkey 和 Glide
以下代码片段展示了如何在 Valkey 和 Glide 库中为 ElastiCache 无服务器配置“从副本读取”功能。您无需为“从副本读取”指定端口,但需要配置路由配置 ReadFrom.PREFER_REPLICA。
package glide.examples; import glide.api.GlideClusterClient; import glide.api.logging.Logger; import glide.api.models.configuration.GlideClusterClientConfiguration; import glide.api.models.configuration.NodeAddress; import glide.api.models.exceptions.ClosingException; import glide.api.models.exceptions.ConnectionException; import glide.api.models.exceptions.TimeoutException; import glide.api.models.configuration.ReadFrom; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class ClusterExample { public static void main(String[] args) { // Set logger configuration Logger.setLoggerConfig(Logger.Level.INFO); GlideClusterClient client = null; try { System.out.println("Connecting to Valkey Glide..."); // Configure the Glide Client GlideClusterClientConfiguration config = GlideClusterClientConfiguration.builder() .address(NodeAddress.builder() .host("your-endpoint") .port(6379) .build()) .useTLS(true) .readFrom(ReadFrom.PREFER_REPLICA) .build(); // Create the GlideClusterClient client = GlideClusterClient.createClient(config).get(); System.out.println("Connected successfully."); // Perform SET operation CompletableFuture<String> setResponse = client.set("key", "value"); System.out.println("Set key 'key' to 'value': " + setResponse.get()); // Perform GET operation CompletableFuture<String> getResponse = client.get("key"); System.out.println("Get response for 'key': " + getResponse.get()); // Perform PING operation CompletableFuture<String> pingResponse = client.ping(); System.out.println("PING response: " + pingResponse.get()); } catch (ClosingException | ConnectionException | TimeoutException | ExecutionException e) { System.err.println("An exception occurred: "); e.printStackTrace(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // Close the client connection if (client != null) { try { client.close(); System.out.println("Client connection closed."); } catch (ClosingException | ExecutionException e) { System.err.println("Error closing client: " + e.getMessage()); } } } } }