为缓解热点Key带来大量读请求的影响,您可以启用读写分离功能并增加只读节点,系统能够将读请求分散至各个节点。这将降低主节点的压力,并提升系统的整体吞吐量和稳定性。
功能概述
读写分离架构的所有只读节点通过异步复制主节点的数据(最终一致性),整体数据同步延迟低。这意味着在部分写入量较大的情况下,可能会发生数据同步延迟,此时应用程序能够容忍稍旧的数据。例如下述场景:
缓存数据:网址首页的HTML缓存等,更新频率低。
游戏排行榜:小时级别排行榜,其轻微的更新延迟对用户影响并不显著。
天气预报数据:用户频繁查询天气,数据则按照预定时间进行更新。
增加只读节点的直接作用
分担主节点负载:主节点仅负责100%写请求、1/N的读请求(N为总节点数,例如1个主节点、3个只读节点,即N为4)。这降低了主节点的CPU压力、网络压力,以及连接开销。
提高系统吞吐量:每个只读节点可独立处理读请求,整体读性能呈线性扩展。在理想情况下,增加N个只读节点可提升N倍读性能。
降低响应延迟:将读请求分散到多个节点后,可减少单个请求的排队等待时间。
不仅支持为标准(主从)架构开启读写分离,也支持为集群架构开启读写分离,可为集群的每个数据分片节点增加对应的只读节点,具体架构信息请参见读写分离功能。
如何开启
前提条件:
部署模式为云原生。
实例为Redis开源版或Tair(企业版)内存型、持久内存型。
实例规格为1 GB及以上。
实例类型为高可用。
开启方法:在实例详情页中,单击左侧导航栏的节点管理,并打开读写分离开关。具体操作请参见开启读写分离。
客户端连接
单可用区实例
通常情况下,读写分离架构实例仍提供一个连接地址,实例将通过Proxy组件实现路由分发。您无需修改代码,开箱即用,更多信息请参见客户端程序连接教程。
双可用区实例
在双可用区、读写分离架构实例,实例将分别提供主、备可用区连接地址。其中备可用区连接地址仅用于读请求,可实现就近访问,缩短读请求延迟。
例如实例为杭州I(主可用区)、杭州J(备可用区)。
位于杭州I的客户端(ECS实例)可以连接实例的主可用区地址,并执行读写操作。代码如下:
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class MasterReadWrite { public static void main(String[] args) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(200); config.setMaxTotal(300); config.setTestOnBorrow(false); config.setTestOnReturn(false); // 配置主可用区连接地址、端口、账号密码信息。 String host = "r-bp1vtq8tnrquy****pd.redis.rds.aliyuncs.com"; int port = 6379; String password = "default:Passw***2"; JedisPool pool = new JedisPool(config, host, port, 3000, password); Jedis jedis = null; try { jedis = pool.getResource(); // 执行相关操作,示例如下。 jedis.set("foo", "bar"); System.out.println(jedis.get("foo")); } catch (Exception e) { // 超时或其他异常处理。 e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } pool.destroy(); // 当应用退出,需销毁资源时,调用此方法。此方法会断开连接、释放资源。 } }
位于杭州J的客户端(ECS实例)可以连接实例的备可用区地址,并仅执行读操作(如需执行写操作,仍需连接主可用区地址)。代码如下:
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class ReplicaRead { public static void main(String[] args) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(200); config.setMaxTotal(300); config.setTestOnBorrow(false); config.setTestOnReturn(false); // 配置备可用区连接地址、端口、账号密码信息。 String host = "r-bp1vtq8tnrquy****pd.redis.rds.aliyuncs.com"; int port = 6379; String password = "default:Passw***2"; JedisPool pool = new JedisPool(config, host, port, 3000, password); Jedis jedis = null; try { jedis = pool.getResource(); // 执行相关操作,示例如下。 System.out.println(jedis.get("foo")); } catch (JedisDataException e) { // 捕获写操作异常(如果误操作执行了写操作)。 e.getMessage(); } catch (Exception e) { // 超时或其他异常处理。 e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } pool.destroy(); // 当应用退出,需销毁资源时,调用此方法。此方法会断开连接、释放资源。 } }