Fast-Retry:一个支持百万级多任务异步重试框架

前言

假设你的系统里有100万个用户,然后你要轮询重试的获取每个用户的身份信息, 如果你还在使用SpringRetry和GuavaRetry 之类的这种单任务的同步重试框架,那你可能到猴年马月也处理不完,即使加再多的机器和线程也是杯水车薪,而Fast-Retry正是为这种场景而生。

Fast-Retry

一个高性能的多任务重试框架,支持百万级任务的异步重试、以及支持编程式和注解声明式等多种使用方式、 也支持自定义结果重试逻辑。

What is this?

与主流的Spring-Retry、Guava-Retry等单任务同步重试框架不同,Fast-Retry是一个支持异步重试框架,支持异步任务的重试、超时等待、回调。

Spring-Retry、Guava-Retry均无法支持大批量任务的重试,即使加入线程池也无法解决,因为实际每个重试任务都是单独的同步逻辑,然后会会占用过多线程资源导致大量任务在等待处理,随着任务数的增加,系统吞吐量大大降低,性能指数级降低,而Fast-Retry在异步重试下的性能是前者的指数倍。

下图是三者的性能对比

  • 测试线程池: 8个固定线程

  • 单个任务逻辑: 轮询5次,隔2秒重试一次,总耗时10秒

  • 未测预计公式:当我们使用线程池的时候, 一般线程池中 总任务处理耗时 = 任务数/并发度 x 单个任务重试耗时

图片

图片

可以看到即使是处理100万个任务,Fast-Retry的性能也比Spring-Retry和Guava-Retry处理在50个任务时的性能还要快的多的多属实降维打击,这么快的秘密在于除了是异步,重要的是当别人在重试间隔里休息的时候,Fast-Retry还在不停忙命的工作着。

即使抛开性能不谈,SpringRetry使用繁琐,不支持根据结果的进行重试,GuavaRetry虽然支持,但是又没有提供注解声明式的使用。

快速开始

引入依赖

<dependency>
    <groupId>io.github.burukeyou</groupId>
    <artifactId>fast-retry-all</artifactId>
    <version>0.2.0</version>
</dependency>

有以下三种方式去构建我们的重试任务

使用重试队列

RetryTask就是可以配置我们重试任务的一些逻辑,比如怎么重试,怎么获取重试结果,隔多久后重试,在什么情况下重试。它可以帮助我们更加自由的去构建重试任务的逻辑。但如果只是简单使用,强烈建议使用FastRetryBuilder 或者 @FastRetry注解

RetryQueue就是一个执行和调度我们重试任务的核心角色,其在使用上与线程池的API方法基本一致

ExecutorService executorService = Executors.newFixedThreadPool(8);
RetryQueue queue = new FastRetryQueue(executorService);
RetryTask<String> task = new RetryTask<String>() {
    int result = 0 ;
    
    // 下一次重试的间隔
    @Override
    public long waitRetryTime() {
        return 2000;
    }

    // 执行重试,每次重试回调此方法
    @Override
    public boolean retry() {
        return ++result < 5;
    }

     // 获取重试结果
    @Override
    public String getResult() {
        return  result + "";
    }
};
CompletableFuture<String> future = queue.submit(task);
log.info("任务结束 结果:{}",future.get());

使用FastRetryBuilder

底层还是使用的RetryQueue去处理, 只是帮我们简化了构建RetryTask的逻辑

RetryResultPolicy<String> resultPolicy = result -> result.equals("444");
FastRetryer<String> retryer = FastRetryBuilder.<String>builder()
        .attemptMaxTimes(3)
        .waitRetryTime(3, TimeUnit.SECONDS)
        .retryIfException(true)
        .retryIfExceptionOfType(TimeoutException.class)
        .exceptionRecover(true)
        .resultPolicy(resultPolicy)
        .build();

CompletableFuture<String> future = retryer.submit(() -> {
    log.info("重试");
    //throw new Exception("test");
    //int i = 1/0;
    if (0 < 10){
        throw new TimeoutException("test");
    }
    return "444";
});

String o = future.get();
log.info("结果{}", o);

使用@FastRetry注解

底层还是使用的RetryQueue去处理, 只是帮我们简化了构建RetryTask的逻辑,并且与Spring进行整合能对Spring的bean标记了FastRetry注解的方法进行代理, 提供了重试任务注解声明式的使用方式

  • 依赖Spring环境,所以需要在Spring配置类加上@EnableFastRetry注解启用配置,这个@FastRetry注解的使用才会生效

  • 如果将结果类型使用CompletableFuture包装,自动进行异步轮询返回,否则同步阻塞等待重试结果。(推荐)

下面定义等价于RetryQueue.execute方法

// 如果发生异常,每隔两秒重试一次
 @FastRetry(retryWait = @RetryWait(delay = 2))
 public String retryTask(){
     return "success";
 }

下面定义等价于 RetryQueue.submit方法,支持异步轮询

@FastRetry(retryWait = @RetryWait(delay = 2))
public CompletableFuture<String> retryTask(){
    return CompletableFuture.completedFuture("success");
}

自定义重试注解

如果不喜欢或者需要更加通用化的贴近业务的重试注解,提供一些默认的参数和处理逻辑,可以自行定义一个重试注解并标记上@FastRetry并指定factory,然后实现AnnotationRetryTaskFactory接口实现自己的构建重试任务的逻辑即可。@FastRetry默认实现就是:FastRetryAnnotationRetryTaskFactory

使用建议

无论是使用以上哪种方式去构建你的重试任务,都建议使用异步重试的方法,即返回结果是CompletableFuture的方法, 然后使用CompletableFuturewhenComplete方法去等待异步重试任务的执行结果。

其他

github项目地址

https://github.com/burukeYou/fast-retry

maven仓库地址

https://central.sonatype.com/artifact/io.github.burukeyou/fast-retry-all

<think> 我们正在讨论如何设计一个RPC框架。参考提供的引用内容,特别是引用[5]中提到的RPC框架设计要点,我们可以系统地构建设计方法。以下是设计RPC框架的关键步骤和方法: ### 一、RPC框架核心目标 - **透明性**:使远程调用像本地调用一样简单,调用者无需感知远程调用的细节[^5]。 - **高效性**:低延迟、高吞吐量,支持大规模并发。 - **可扩展性**支持服务动态扩缩容,适应业务增长。 ### 二、核心模块设计 #### 1. 客户端(Client) - **动态代理**:通过Java动态代理技术生成服务接口的代理对象,拦截方法调用并封装为RPC请求[^1][^2]。 - **服务发现**:从注册中心获取服务提供者地址列表,支持负载均衡策略选择实例。 - **序列化**:将方法参数和调用信息序列化为字节流,支持多种协议(如JSON、Protobuf)。 - **网络传输**:基于Netty实现异步非阻塞IO(AIO),提高并发性能[^5]。 #### 2. 服务端(Server) - **服务注册**:启动时将服务实例注册到注册中心(如Zookeeper)[^4]。 - **请求处理**:采用线程池处理请求,避免频繁创建线程开销[^5]。 ```java ExecutorService threadPool = Executors.newFixedThreadPool(100); // 固定大小线程池 ``` - **反序列化与反射调用**:解析请求数据,通过反射机制调用目标方法并返回结果。 #### 3. 注册中心(Registry) - **服务注册/发现**:维护服务名与实例地址的映射关系,支持心跳检测剔除故障节点。 - **高可用设计**:采用集群部署(如Zookeeper的ZAB协议),避免单点故障。 #### 4. 序列化模块 - **协议设计**: ```protobuf message RpcRequest { string request_id = 1; // 请求ID string service_name = 2; // 服务名 string method_name = 3; // 方法名 bytes params = 4; // 序列化后的参数 } ``` - **性能优化**- 二进制协议(如Protobuf)比文本协议(JSON)体积小30%~50%[^1]。 - 零拷贝技术减少内存复制。 ### 三、关键流程 #### 1. 远程调用过程 ```mermaid sequenceDiagram participant C as Client participant S as Server participant R as Registry C->>R: 查询服务A的实例列表 R-->>C: 返回实例IP:Port C->>S: 发送RPC请求(序列化后) S->>S: 线程池处理请求 S->>S: 反射调用目标方法 S-->>C: 返回结果(序列化) ``` #### 2. 服务注册流程 ```mermaid graph TD A[服务启动] --> B[向注册中心注册] B --> C[定期发送心跳] C --> D{心跳超时?} D -->|是| E[从注册中心剔除] D -->|否| C ``` ### 四、高级特性实现 #### 1. 负载均衡 - **策略**- 随机选择(Random) - 加权轮询(Weighted Round Robin) - 最少活跃调用(Least Active) #### 2. 容错机制 - **策略**- 失败重试Retry- 快速失败(Failfast- 熔断降级(Circuit Breaker) #### 3. 异步调用 - **CompletableFuture实现**: ```java CompletableFuture<Result> future = rpcClient.invokeAsync(request); future.thenAccept(result -> System.out.println(result)); ``` ### 五、性能优化 #### 1. 网络层 - **连接池**:复用TCP连接,减少握手开销。 - **压缩传输**:对大于1KB的数据启用Snappy压缩。 #### 2. 服务端 - **异步处理**:Netty的IO线程与业务线程分离[^3]。 - **背压控制**:通过令牌桶限流保护服务端。 #### 3. 客户端 - **本地缓存**:缓存服务实例列表,减少注册中心查询。 - **批量调用**:合并多个请求一次性发送(如gRPC Streaming)。 ### 六、容灾设计 1. **注册中心故障**:客户端缓存服务列表,支持降级本地配置。 2. **服务端宕机**:注册中心心跳检测自动摘除故障节点。 3. **网络分区**:通过超时重试和熔断机制避免雪崩。 ### 七、典型部署架构 ```mermaid graph LR C1[Client1] --> R[Registry] C2[Client2] --> R R --> S1[Server Cluster] R --> S2[Server Cluster] S1 --> DB[(Database)] S2 --> DB ``` ### 容量估算示例 假设: - 日均调用量:1亿次 - 平均请求大小:1KB 则日网络流量: $$ 10^8 \times 1\text{KB} = 100\text{TB} $$ 单机QPS目标:5000,需部署服务器: $$ \frac{10^8 / 86400}{5000} \approx 23 \text{ 台} $$ --- ### §§相关问题§§ 1. 如何选择适合的序列化协议(Protobuf vs JSON vs Hessian)? 2. 注册中心(Zookeeper vs Nacos vs etcd)的设计差异与选型建议? 3. RPC框架如何实现跨语言兼容性? 4. 如何通过链路追踪定位RPC调用性能瓶颈? 5. 在微服务架构中如何设计RPC框架的灰度发布方案? </think> ### 设计RPC框架的核心方法 基于引用内容,设计RPC框架的核心方法论可分为以下六个关键模块: --- #### 一、核心目标设计 1. **透明性** - 实现远程调用与本地调用一致的体验 - 通过**动态代理**技术隐藏网络通信细节[^2] ```java // Java动态代理示例 public Object createProxy(Class<?> interfaceClass) { return Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, (proxy, method, args) -> rpcInvoke(method, args)); } ``` 2. **跨语言支持** - 定义语言中立的接口描述语言(IDL) - 通过代码生成工具实现多语言适配(如Protobuf/Thrift) --- #### 二、通信架构设计 ```mermaid graph LR A[客户端] -->|序列化| B[网络传输] B -->|反序列化| C[服务端] C --> D[线程池处理] D --> E[服务实现] ``` 1. **网络传输层** - **协议设计**: ```protobuf // Protobuf协议示例 message RpcRequest { string request_id = 1; string method_name = 2; bytes params = 3; // 序列化后的参数 } ``` - 传输方式:TCP长连接(可靠性)或UDP(高性能场景)[^5] 2. **IO模型优化** | 模型 | 适用场景 | 性能关键 | |------|----------|----------| | BIO | 低并发 | 线程阻塞 | | NIO | 中等并发 | 多路复用 | | AIO | 高并发 | 异步回调[^5] | --- #### 三、核心模块实现 1. **序列化模块** - 设计要点: - 支持跨语言(JSON/Protobuf/MessagePack) - 性能优化:二进制协议 > 文本协议 - 性能对比: $$ \text{Protobuf体积} \approx \text{JSON体积} \times 60\% $$ 2. **服务注册与发现** ```mermaid sequenceDiagram 服务提供者->>注册中心: 注册服务(service_name, IP:Port) 服务消费者->>注册中心: 查询可用实例 注册中心-->>服务消费者: 返回实例列表 服务消费者->>负载均衡: 选择实例 ``` - 实现方案:Zookeeper/etcd/Nacos[^4][^5] - 缓存机制:客户端本地缓存服务列表[^5] 3. **线程池优化** - 服务端工作线程池配置: ```java ExecutorService threadPool = new ThreadPoolExecutor( 50, // corePoolSize 200, // maximumPoolSize 60, // keepAliveTime TimeUnit.SECONDS, new LinkedBlockingQueue(1000) // 任务队列 ); ``` - 拒绝策略:降级处理或快速失败[^3] --- #### 四、高级特性设计 1. **负载均衡** | 策略 | 描述 | 适用场景 | |------|------|----------| | 随机 | 随机选择节点 | 节点性能均匀 | | 轮询 | 依次分配请求 | 简单场景 | | 加权 | 按性能分配权重 | 异构集群 | | 最少连接 | 选择最空闲节点 | 长连接服务 | 2. **容错机制** - **熔断模式**:失败率阈值触发熔断 ```python if error_rate > 0.5: # 超过50%错误率 circuit_breaker.open() ``` - **重试策略**:指数退避重试(2ⁿ毫秒延迟) 3. **异步调用** - 基于CompletableFuture的异步调用链: ```java CompletableFuture.supplyAsync(() -> rpc.callAsync()) .thenAccept(result -> handleResult(result)); ``` --- #### 五、性能优化策略 1. **连接池技术** - 复用TCP连接避免三次握手 - 连接健康检查机制 2. **压缩传输** - 对>1KB的数据启用Snappy压缩 $$ \text{压缩率} = \frac{\text{压缩后大小}}{\text{原始大小}} \times 100\% \approx 30-40\% $$ 3. **批处理机制** - 合并多个请求一次性发送 - 服务端批量执行减少IO次数 --- #### 六、部署架构设计 ```mermaid graph TD A[客户端] --> B[注册中心] C[服务端集群] --> B A -->|直连| C D[监控系统] --> B D --> C ``` 1. **注册中心集群**:避免单点故障 2. **服务分组**:按业务划分服务域 3. **监控体系**- 调用链路追踪(TraceID透传) - QPS/时延/错误率监控 --- ### 典型RPC框架类图 ```plaintext +----------------+ +-------------------+ +-----------------+ | RpcClient |<>---->| Serializer |<>---->| Transport | +----------------+ +-------------------+ +-----------------+ | +invoke() | | +serialize() | | +sendRequest() | | +createProxy() | | +deserialize() | +-----------------+ +----------------+ +-------------------+ ↑ ↑ | | +----------------+ +-------------------+ | RpcServer | | Registry | +----------------+ +-------------------+ | +register() | | +register() | | +start() | | +discover() | +----------------+ +-------------------+ ``` [^2] --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值