微服务设计模式 - 重试模式(Retry Pattern)

news2024/12/24 9:13:27

微服务设计模式 - 重试模式(Retry Pattern)

retry-in-microservice

定义

重试模式(Retry Pattern)是一种微服务中的设计模式,用于在临时性失败(如网络故障或暂时不可用的服务)发生时,自动重新尝试请求,而不是立即返回错误。通过重试,可以增加操作成功的概率,从而提高系统的可靠性。

结构

重试模式通常包括以下几个组件:

  • 调用者:发起请求的实体。
  • 操作:需要重试的操作,比如API调用或数据库操作。
  • 重试策略:定义重试次数、间隔时间和重试条件的策略。

工作原理

retry-pattern-flow

重试模式的工作原理如下:

  1. 调用者发起请求
  2. 执行操作,如果成功则返回结果,如果失败则进入重试策略。
  3. 重试策略检查是否满足重试条件,如最大重试次数未达到、错误类型允许重试等。
  4. 如果满足条件,则按照重试策略重新请求操作,否则返回最终失败结果。

优点

  1. 提高可靠性:在遇到暂时性故障时,通过重试机制增加操作成功的机会。

  2. 增强用户体验:避免频繁的错误提示,提高用户的满意度。

  3. 灵活性:通过配置不同的重试策略,适应不同的业务需求。

使用场景

重试模式(Retry Pattern)在很多场景中非常有用,尤其是在处理临时性故障(transient faults)的时候。下面列举了几种典型的使用场景:

  1. 网络通信问题
    • 网络抖动:在面临暂时性网络抖动或不稳定时,重试可以帮助确保请求成功。
    • 网络超时:一些网络请求可能超时,如果这些超时是临时的,那么可以通过重试来解决问题。
  2. 外部API调用
    • 第三方服务不稳定:在调用外部API或第三方服务时,如果这些服务偶尔不稳定,通过重试可以增加成功的概率。
    • API限流:外部API可能会对请求数量进行限流,导致部分请求被拒绝,重试可以在稍后的时间段重新发送请求。
  3. 数据库操作
    • 数据库连接中断:数据库连接可能偶尔中断,通过重试机制可以重新建立连接。
    • 锁定结果:在高并发情况下,某些数据库操作可能会因行锁或表锁被暂时阻塞,通过重试可以等待锁释放。
  4. 消息队列
    • 消息消费失败:在处理消息队列中的消息时,如果某些消息因临时性问题处理失败,可以通过重试机制重新处理这些消息。
  5. 分布式系统
    • 服务依赖:在分布式系统中,多个微服务之间相互依赖,如果某个服务临时不可用,通过重试可以确保请求最终成功。
  6. 其他临时性错误
    • 资源限制:某些临时性资源限制(如内存不足或CPU过载)可能导致操作失败,通过重试可以等待资源恢复。
    • 维护或升级:某些服务可能在维护或升级过程中短暂不可用,重试机制可以在服务恢复后继续尝试请求。

影响因素

在实现重试模式时,我们需要考虑多个重要因素,包括幂等性(Idempotency)、事务一致性(Transaction Consistency)、性能影响和异常类型,以确保系统的可靠性和有效性。

以下具体介绍每一个影响因素,并以SrpingBoot相关代码,以及resilience4j(用以实现重试模式)相关配置进行辅助说明。

幂等性(Idempotency)

定义:幂等性是指在相同条件下多次执行操作,结果应保持一致。换句话说,幂等操作在被执行一次或多次后对系统的状态产生相同的影响。

重要性:重试模式通常会多次执行相同操作,因此确保操作的幂等性是至关重要的。若操作不具有幂等性,可能会导致数据不一致或重复处理。

实现示例

  • 对于HTTP请求,可以使用HTTP动词来区分幂等操作。例如,PUT和DELETE通常为幂等操作,而POST可能不是。
  • 在数据库写操作时,添加唯一约束,或在应用层实现幂等逻辑。

示例代码 - 幂等性操作

@Service
public class IdempotentService {

    @Autowired
    private OrderRepository orderRepository;

    @Retry(name = "idempotentService", fallbackMethod = "fallback")
    public String createOrder(Order order) {
        // 检查订单是否已经存在(即幂等性检查)
        Optional<Order> existingOrder = orderRepository.findByOrderId(order.getOrderId());
        if (existingOrder.isPresent()) {
            return "Order already exists";
        }

        // 创建新订单
        orderRepository.save(order);
        return "Order created successfully";
    }

    private String fallback(Order order, Exception e) {
        return "Fallback response";
    }
}

事务一致性(Transaction Consistency)

定义:事务一致性确保在一组操作中,所有操作要么全部成功,要么全部失败,从而保证系统状态的一致性。

挑战:重试机制可能跨越多个事务,且每次重试都应当考虑事务的一致性问题。未能维护一致性可能导致数据混乱或部分提交的问题。

实现示例

  • 在Java中使用Spring的@Transactional注解来管理事务一致性。
  • 在分布式系统中,使用2PC(两阶段提交)或Saga模式等事务管理策略。

示例代码 - 事务一致性

@Service
public class TransactionalService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    @Retry(name = "transactionalService", fallbackMethod = "fallback")
    public String createOrderTransactional(Order order) {
        // 创建新订单
        orderRepository.save(order);

        // 下单后其他相关操作...

        return "Order created successfully with transaction";
    }

    private String fallback(Order order, Exception e) {
        return "Fallback response in transaction";
    }
}

性能影响(Impact on Performance)

定义:重试机制可能引入额外的延迟和资源消耗,因此需要谨慎管理以减少性能影响。

优化策略

  • 限次数:限制重试次数,避免无限重试。
  • 指数退避:每次重试时增加等待时间,减少系统负载。
  • 快速失败:在明显不可恢复的情况下,尽早返回错误而不是反复重试。

实现示例

  • 配置如maxAttemptswaitDuration等参数来控制重试策略。

示例配置 - 性能相关

resilience4j.retry:
  instances:
    myService:
      max-attempts: 3
      wait-duration: 500ms
      max-wait-duration: 2s
      exponential-backoff:
        multiplier: 2

异常类型(Exception Type)

定义:不同类型的异常可能需要不同的重试策略。有些异常是暂时性的,可以通过重试解决;另一些则是不可恢复的,不应重试。

实现示例

  • 使用防御性编程和异常分类来确定哪些异常应该触发重试。
  • 自定义重试规则来处理不同类型的异常。

示例代码 - 异常类型识别

@Service
public class ExceptionHandlingService {

    @Retry(name = "exceptionHandlingService", fallbackMethod = "fallback", retryExceptions = {
            TemporaryException.class }, ignoreExceptions = { PermanentException.class })
    public String handleService() {
        // 假设某操作可能抛出TemporaryException或PermanentException
        riskyOperation();
        return "Operation completed";
    }

    private void riskyOperation() throws TemporaryException, PermanentException {
        // 实现一些逻辑,可能抛出不同类型的异常
    }

    private String fallback(Exception e) {
        return "Fallback response for exceptions";
    }
}

重试策略

在实现重试模式时,选择合适的重试策略(Retry Strategy)是至关重要的。不同的重试策略会影响系统的可靠性、性能和响应时间。以下是常见的重试策略:

固定间隔重试(Fixed Interval Retry)

定义:固定间隔重试策略在每次重试之间使用相同的时间间隔。例如,重试每次间隔500毫秒。

优点:实现简单,适用于简单的重试场景。

缺点:在高负载或问题持续存在的情况下,可能会导致系统过载。

示例配置

resilience4j.retry:
  instances:
    myService:
      max-attempts: 3
      wait-duration: 500ms

指数退避重试(Exponential Backoff Retry)

定义:每次重试时,等待时间逐步增加。例如,第一次重试后等待500毫秒,第二次重试后等待1秒,第三次重试后等待2秒,以此类推。

优点:逐步增加的等待时间可以有效减少系统负载,适用于网络抖动或外部服务不稳定的情形。

缺点:实现稍微复杂,可能导致较长的重试时间。

示例配置

resilience4j.retry:
  instances:
    myService:
      max-attempts: 3
      wait-duration: 500ms
      exponential-backoff:
        multiplier: 2

抛出异常后退避重试(Backoff with Jitter Retry)

定义:在指数退避的基础上,加入随机时间间隔(称为“抖动”),以避免重试请求出现峰值。

优点:通过在重试间隔中加入随机性,进一步减少了系统因重试请求同时发出的风险,适用于高并发场景。

缺点:实现复杂度更高。

示例代码(Java示例):

RetryConfig config = RetryConfig.custom()
  .maxAttempts(3)
  .waitDuration(Duration.ofMillis(500))
  .retryOnException(throwable -> throwable instanceof TemporaryException)
  .intervalFunction(IntervalFunction.ofExponentialBackoff(500, 2).withRandomizedWait())
  .build();

增量退避重试(Incremental Backoff Retry)

定义:每次重试等待时间按照固定的增量增加。例如,第一次重试后等待500毫秒,第二次重试后等待1秒,第三次重试后等待1.5秒。

优点:控制每次重试的等待时间增加量,简单易理解。

缺点:在一定情况下,性能可能不如指数退避策略。

示例代码

resilience4j.retry:
  instances:
    myService:
      max-attempts: 3
      wait-duration: 500ms
      interval-function:
        increment-interval:
          interval: 500ms

固定次数重试(Retry with Max Attempts)

定义:限制重试的次数,当超过重试次数时停止重试。

优点:防止过多重试导致资源消耗,保护系统稳定。

缺点:可能导致在某些情况下无效重试。

示例代码

resilience4j.retry:
  instances:
    myService:
      max-attempts: 5
      wait-duration: 500ms

自定义重试策略(Custom Retry Strategy)

定义:根据特定的业务需求和场景,设计定制化的重试策略。

优点:灵活、满足特定需求。

缺点:需要更多的开发和测试工作。

示例代码(Java自定义实现):

RetryConfig config = RetryConfig.custom()
  .maxAttempts(5)
  .intervalFunction(IntervalFunction.of(Duration.ofMillis(500), IntervalFunction.of(Random::nextGaussian)))
  .retryOnException(throwable -> {
      // Define your custom retry condition here.
      return throwable instanceof TemporaryException;
  })
  .build();

综合以上,在选择重试策略时,建议如下:

  1. 分析场景:根据实际业务场景选择合适的重试策略。例如,网络波动适合使用指数退避重试。
  2. 测试不同策略:通过负载测试和性能测试,评估不同重试策略对系统的实际影响。
  3. 结合多种策略:可以组合多个重试策略,例如固定次数重试加上指数退避,满足更复杂的需求。
  4. 监控与调整:定期监控重试机制的效果,根据实际情况动态调整重试策略。

完整实例代码

这个示例展示了如何在Spring Boot应用中使用Resilience4j实现重试模式。配置文件中定义了重试策略,包括最大尝试次数、等待时间和指数退避参数。服务层通过重试注解@Retry实现重试逻辑,并在错误情况下调用回退方法。通过这一模式,系统可以有效应对各种临时性故障,提高整体的可靠性和稳定性。

项目结构

.
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── retry/
│   │   │               ├── RetryApplication.java
│   │   │               ├── controller/
│   │   │               │   └── RetryController.java
│   │   │               ├── service/
│   │   │               │   └── RetryService.java
│   │   │               └── exception/
│   │   │                   ├── TemporaryException.java
│   │   │                   └── PermanentException.java
│   │   ├── resources/
│   │   │   ├── application.yaml
└── pom.xml

Maven 依赖

首先,在Maven的pom.xml文件中添加Resilience4j依赖项:

<dependencies>
    <!-- Spring Boot dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Resilience4j dependencies -->
    <dependency>
        <groupId>io.github.resilience4j</groupId>
        <artifactId>resilience4j-spring-boot2</artifactId>
        <version>1.7.1</version>
    </dependency>
    <dependency>
        <groupId>io.github.resilience4j</groupId>
        <artifactId>resilience4j-retry</artifactId>
        <version>1.7.1</version>
    </dependency>
</dependencies>

Retry配置

在Spring Boot应用程序的配置文件application.yaml中配置Resilience4j的重试策略:

resilience4j.retry:
  instances:
    myService:
      max-attempts: 5
      wait-duration: 500ms
      exponential-backoff:
        multiplier: 2
      retry-exceptions:
        - com.example.retry.exception.TemporaryException
      ignore-exceptions:
        - com.example.retry.exception.PermanentException

代码说明

RetryApplication.java

主应用程序文件:

package com.example.retry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RetryApplication {
    public static void main(String[] args) {
        SpringApplication.run(RetryApplication.class, args);
    }
}
RetryController.java

控制器类:

package com.example.retry.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.retry.service.RetryService;

@RestController
public class RetryController {

    @Autowired
    private RetryService retryService;

    @GetMapping("/retry-test")
    public ResponseEntity<String> retryTest() {
        return ResponseEntity.ok(retryService.callExternalService());
    }
}
RetryService.java

服务层实现重试逻辑:

package com.example.retry.service;

import org.springframework.stereotype.Service;
import io.github.resilience4j.retry.annotation.Retry;
import com.example.retry.exception.TemporaryException;
import com.example.retry.exception.PermanentException;

@Service
public class RetryService {

    @Retry(name = "myService", fallbackMethod = "fallback")
    public String callExternalService() throws TemporaryException, PermanentException {
        // 模拟外部服务调用
        double random = Math.random();
        if (random < 0.5) {
            throw new TemporaryException("Temporary issue occurred");
        } else if (random < 0.8) {
            throw new PermanentException("Permanent issue occurred");
        }
        return "Success";
    }

    private String fallback(Exception e) {
        return "Fallback response: " + e.getMessage();
    }
}
TemporaryException.java

自定义临时异常类型:

package com.example.retry.exception;

public class TemporaryException extends Exception {
    public TemporaryException(String message) {
        super(message);
    }
}
PermanentException.java

自定义永久异常类型:

package com.example.retry.exception;

public class PermanentException extends Exception {
    public PermanentException(String message) {
        super(message);
    }
}

类序列图

retry-pattern-class

运行测试

运行Spring Boot应用程序后,访问 http://localhost:8080/retry-test 可以触发重试逻辑。根据随机数的不同,有时会成功,有时会触发临时异常进行重试,如果次数用尽则返回回退响应。

总结

Robert-C-Martin-Quote-Software-has-two-types-of-value-the-value-of

在云计算和微服务架构中,重试模式是一种重要的设计模式,通过处理暂时性故障来增强系统的可靠性。当实现重试模式时,必须考虑幂等性、事务一致性、性能影响和异常类型,以确保系统的整体稳定性和正确性。Resilience4j库提供了实现重试模式的便利方法,通过合理配置可以满足各种不同的业务需求。希望本文能帮助您更好地理解和选择合适的重试策略,为系统设计和实现提供参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2230228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

『 Linux 』网络传输层 - TCP(二)

文章目录 TCP六个标志位TCP的连接三次握手 四次挥手为什么是三次握手和四次挥手 重传机制 TCP六个标志位 在TCP协议报文的报头中存在一个用于标志TCP报文类型的标志位(不考虑保留标志位),这些标志位以比特位选项的方式存在,即对应标志位为0则表示为假,对应标志位为1则为真; SYN…

VLAN虚拟局域网,eNSP实验讲解

VLAN虚拟局域网&#xff0c;eNSP实验讲解 一、概念二、eNSP仿真实验1、实验一&#xff1a;vlan演示&#xff08;交换机端口access模式&#xff09;2、实验二&#xff1a;vlan演示&#xff08;交换机端口trunk模式&#xff09; 一、概念 VLAN&#xff08;Virtual Local Area Ne…

Ubuntu UFW防火墙规则与命令示例大全

在服务器安全领域&#xff0c;防火墙是守护网络安全的坚实盾牌。UFW&#xff08;Uncomplicated Firewall&#xff09;&#xff0c;即“不复杂的防火墙”&#xff0c;是一个运行在iptables之上的防火墙配置工具&#xff0c;它为Ubuntu系统默认提供了一个简洁的命令行界面&#x…

基于 Python 的 Django 框架开发的电影推荐系统

项目简介&#xff1a;本项目是基于 Python 的 Django 框架开发的电影推荐系统&#xff0c;主要功能包括&#xff1a; 电影信息爬取&#xff1a;获取并更新电影数据。数据展示&#xff1a;提供电影数据的列表展示。推荐系统&#xff1a;基于协同过滤算法实现个性化推荐。用户系…

ORB-SLAM2源码学习:ORBextractor.cc:IC_Angle 利用灰度质心法求解关键点方向角

ORB特征点&#xff1a; 特征点是由关键点和描述子两部分组成&#xff0c;关键点是指特征点在图像中的位置&#xff0c;描述子是用来描述关键点周围的像素信息。ORB关键点是在FAST关键点的基础上进行改进给像素增加了一个主方向&#xff0c;称为Oriented FAST。描述子在BRIEF的…

【设计模式】如何用C++实现依赖倒置

【设计模式】如何用C实现依赖倒置 一、什么是依赖倒置&#xff1f; 依赖倒置原则&#xff08;Dependency Inversion Principle&#xff0c;DIP&#xff09;是SOLID面向对象设计原则中的一项。它的核心思想是&#xff1a; 高层模块不应该依赖于低层模块&#xff0c;两者都应该…

‌植物神经紊乱患者,这些锻炼适合你!

植物神经紊乱是一种常见的心理疾病&#xff0c;其主要症状包括焦虑、抑郁、失眠等&#xff0c;严重时还可能出现心慌、气短、憋气、出汗异常等症状。然而&#xff0c;通过适量的锻炼&#xff0c;我们可以帮助调节自主神经系统&#xff0c;缓解这些症状。那么&#xff0c;植物神…

Golang | Leetcode Golang题解之第525题连续数组

题目&#xff1a; 题解&#xff1a; func findMaxLength(nums []int) (maxLength int) {mp : map[int]int{0: -1}counter : 0for i, num : range nums {if num 1 {counter} else {counter--}if prevIndex, has : mp[counter]; has {maxLength max(maxLength, i-prevIndex)} …

第十七届山东省职业院校技能大赛通知分享

近日&#xff0c;山东省教育厅联合相关部门发布了关于举办第十七届山东省职业院校技能大赛的通知&#xff0c;标志着这一旨在深化教育教学改革、推进产教融合与校企合作的重要赛事即将拉开帷幕。 据了解&#xff0c;本次大赛将设中等职业教育组和高等职业教育组&#xff0c;共包…

Angular实现gridview效果

说明&#xff1a;使用angular实现grid效果&#xff0c;支持文字图片多条数据展示 效果图: step1: <mat-grid-list cols"2" rowHeight"2:1"><mat-grid-tile *ngFor"let course of courses">{{ course }}</mat-grid-tile> &l…

web of sicence使用教程(研究生版)

学习视频链接 进入web of science 通过校园资料库进入进入&#xff0c;选择如下 核心检索规则 不区分字母大小写逻辑运算符 可以通过括号改变优先级 常用通配符 短语检索 检索界面 检索类型 选择数据库

2-8软件包管理

8.1 配置仓库 1.配置本地仓库 #进行代码安装前需要先进行仓库配置和挂载 [rootlocalhost ~]# cd /etc/yum.repos.d [rootlocalhost yum.repos.d]# vim base.repo [baseos] namebaseos baseurl/mnt/BaseOS gpgcheck0 [appstream] nameappstream baseurl/mnt/AppStream gp…

适配器模式:类适配器与对象适配器

适配器模式是一种结构性设计模式&#xff0c;旨在将一个接口转换成客户端所期望的另一种接口。它通常用于解决由于接口不兼容而导致的类之间的通信问题。适配器模式主要有两种实现方式&#xff1a;类适配器和对象适配器。下面&#xff0c;我们将详细探讨这两种方式的优缺点及适…

性能测试:性能测试流程与方法

性能测试流程是指在进行性能测试时所遵循的一系列步骤和阶段&#xff0c;以确保对系统的全面测试和评估。性能测试流程的具体步骤可能会因组织、项目和测试需求而有所不同。 性能测试流程 分析现状&#xff1a;首先需要对应用程序或系统进行详细的分析&#xff0c;了解其当前的…

使用WebStorm开发Vue3项目

记录一下使用WebStorm开发Vu3项目时的配置 现在WebStorm可以个人免费使用啦&#xff01;&#x1f929; 基本配置 打包工具&#xff1a;Vite 前端框架&#xff1a;ElementPlus 开发语言&#xff1a;Vue3、TypeScript、Sass 代码检查&#xff1a;ESLint、Prettier IDE&#xf…

Java已死,大模型才是未来?

作者&#xff1a;不惑_ 引言 在数字技术的浪潮中&#xff0c;编程语言始终扮演着至关重要的角色。Java&#xff0c;自1995年诞生以来&#xff0c;便以其跨平台的特性和丰富的生态系统&#xff0c;成为了全球范围内开发者们最为青睐的编程语言之一 然而&#xff0c;随着技术的…

Allegro: 开源的高级视频生成模型

我们很高兴地宣布 Allegro 的开源发布&#xff0c;这是 Rhymes AI 先进的文本到视频模型。Allegro 是一款功能强大的人工智能工具&#xff0c;能将简单的文字提示转化为高质量的视频短片&#xff0c;为人工智能生成视频领域的创作者、开发者和研究人员开辟了新的可能性。我们希…

【OD-支持在线评测】智能驾驶(200分)

📎 在线评测链接 https://app5938.acapp.acwing.com.cn/contest/11/problem/OD1073 🍓 OJ题目截图 🍿 最新机试E卷,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 94 分 🌍 评测功能需要 ⇒ 订阅专栏 ⇐ 后私信联系解锁~ 文章目录 📎…

SpringBoot【实用篇】- 配置高级

文章目录 目标&#xff1a;1.ConfigurationProperties2.宽松绑定/松散绑定3. 常用计量单位绑定4.数据校验 目标&#xff1a; ConfigurationProperties宽松绑定/松散绑定常用计量单位绑定数据校验 1.ConfigurationProperties ConfigurationProperties 在学习yml的时候我们了解…

构造小练习

一。 二。 构造函数的调用顺序&#xff1a; 先走全局&#xff0c;再main函数&#xff0c;而局部静态的函数是走到它那里才开始初始化。所以先构造C&#xff0c;再A,B&#xff0c;最后D。 析构函数的顺序&#xff1a; 先析构局部的&#xff0c;再析构全局的。后定义的先析构。…