美文网首页程序员
大厂都是怎么做Redis重试的?

大厂都是怎么做Redis重试的?

作者: JavaEdge | 来源:发表于2023-11-13 11:01 被阅读0次

    0 前言

    潜心打造国内一流,国际领先的技术干货。

    文章收录在我的 GitHub 仓库,欢迎Star/fork:

    JavaEdge-Interview

    受网络和运行环境影响,应用程序可能遇到暂时性故障,如瞬时网络抖动、服务暂时不可用、服务繁忙导致超时等。

    自动重试机制可大幅避免此类故障,保障操作成功执行。

    1 引发暂时性故障的原因

    1.1 故障触发了高可用机制

    云Redis支持节点健康状态监测,当监测到实例中的主节点不可用时,会自动触发主备切换,例如将主节点和从节点进行互换,保障实例的高可用性。此时,客户端可能会遇到下列暂时性故障:秒级的连接闪断。30秒内的只读状态(用于避免主备切换引起潜在的数据丢失风险和双写)。

    更多参见:主备切换

    1.2 慢查询引起了请求堵塞

    执行时间复杂度为O(N)的操作,引发慢查询和请求的堵塞,此时,客户端发起的其他请求可能出现暂时性失败。

    1.3 复杂的网络环境

    由于客户端与Redis服务器之间复杂网络环境引起,可能出现偶发的网络抖动、数据重传等问题,此时,客户端发起的请求可能会出现暂时性失败。

    2 推荐的重试准则

    2.1 仅重试幂等的操作

    由于超时可能发生在下述任一阶段:该命令由客户端发送成功,但尚未到达Redis。命令到达Redis,但执行超时。命令在Redis中执行结束,但结果返回给客户端时发生超时。如果执行重试可能导致某个操作在Redis中被重复执行,因此不是所有操作均适合设计重试机制。通常推荐仅重试幂等的操作,例如SET操作,即多次执行SET a b命令,那么a的值只可能是b或执行失败;如果执行LPUSH mylist a则不是幂等的,可能导致mylist中包含多个a元素。

    2.2 适当的重试次数与间隔

    根据业务需求和实际场景调整适当的重试次数与间隔,否则可能引发下述问题:如果重试次数不足或间隔太长,应用程序可能无法完成操作而导致失败。如果重试次数过大或间隔过短,应用程序可能会占用过多的系统资源,且可能因请求过多而堵塞在服务器上无法恢复。常见的重试间隔方式包括立即重试、固定时间重试、指数增加时间重试、随机时间重试等。

    2.3 避免重试嵌套

    避免重试嵌套,否则可能会导致重复的重试且无法停止。

    2.4 记录重试异常并打印失败报告

    在重试过程中,建议在WARN级别上打印重试错误日志,同时,仅在重试失败时打印异常信息。

    3 Jedis

    建议使用Jedis 4.0.0及以上版本,推荐使用最新的Jedis版本,以下代码为Jedis 5.0.0的重试示例。

    3.1 添加Jedis的Pom依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>5.0.0</version>
    </dependency>
    

    3.2 重试实战

    ① 标准架构实例或集群架构代理(Proxy)模式

    使用JedisPool模式。

    该示例会将SET命令自动重试5次,且总重试时间不超过10s,每次重试之间等待类指数间隔的时间,如果最终不成功,则抛出异常。

    PooledConnectionProvider provider = new PooledConnectionProvider(HostAndPort.from("127.0.0.1:6379"));
    int maxAttempts = 5; // 最大重试次数
    Duration maxTotalRetriesDuration = Duration.ofSeconds(10); // 最大的重试时间
    UnifiedJedis jedis = new UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration);
    try {
        System.out.println("set key: " + jedis.set("key", "value"));
    } catch (Exception e) {
        // 表示尝试maxAttempts次或到达了最大查询时间maxTotalRetriesDuration仍旧没有访问成功。
        e.printStackTrace();
    }
    

    ② 集群架构直连模式

    使用JedisCluster模式。

    可以通过配置maxAttempts参数来定义失败情况下的重试次数,默认值为5,如果最终不成功,则抛出异常。

    HostAndPort hostAndPort = HostAndPort.from("127.0.0.1:30001");
    int connectionTimeout = 5000;
    int soTimeout = 2000;
    int maxAttempts = 5;
    ConnectionPoolConfig config = new ConnectionPoolConfig();
    JedisCluster jedisCluster = new JedisCluster(hostAndPort, connectionTimeout, soTimeout, maxAttempts, config);
    try {
        System.out.println("set key: " + jedisCluster.set("key", "value"));
    } catch (Exception e) {
        // 表示尝试maxAttempts之后仍旧没有访问成功。
        e.printStackTrace();
    }
    

    4 Redisson

    Redisson客户端提供了两个参数来控制重试逻辑:

    • retryAttempts:重试次数,默认为3。
    • retryInterval:重试间隔,默认为1,500毫秒。

    重试示例如下:

    Config config = new Config();
    config.useSingleServer()
        .setTimeout(1000)
        .setRetryAttempts(3)
        .setRetryInterval(1500) //ms
        .setAddress("redis://127.0.0.1:6379");
    RedissonClient connect = Redisson.create(config);
    

    5 StackExchange.Redis

    StackExchang.Redis客户端目前仅支持重试时连接,重试示例如下:

    var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,connectRetry=3");
    

    说明

    如需实现API级别的重试策略,请参见Polly

    6 Lettuce

    Lettuce客户端未提供在命令超时后重试的参数,但是您可以通过下述参数来实现命令重试策略:

    • at-most-once execution:命令最多执行1次,即0次或1次,如果连接断开并重新连接,命令可能会丢失。
    • at-least-once execution(默认):最少成功执行1次,即可能会在执行时进行多次尝试,保障最少成功执行1次。使用此策略时,如果Tair实例发生了主备切换,此时客户端可能累积了较多的重试命令,主备切换完成后可能会引发Tair实例的CPU使用率激增。

    说明

    更多信息,请参见Client-OptionsCommand execution reliability

    重试示例:

    clientOptions.isAutoReconnect() ? Reliability.AT_LEAST_ONCE : Reliability.AT_MOST_ONCE;
    

    参考:

    本文由博客一文多发平台 OpenWrite 发布!

    相关文章

      网友评论

        本文标题:大厂都是怎么做Redis重试的?

        本文链接:https://www.haomeiwen.com/subject/roxdwdtx.html