运维开发网

「微服务设计之禅」重试模式

运维开发网 https://www.qedev.com 2021-03-06 12:39 出处:51CTO 作者:轮子工厂
前言微服务本质上分布式架构,当我们使用分布式系统时任何不可预知的问题都会发生(例如网络可用性问题、服务可用性问题、中间件可用性问题)。一个系统的问题可能会直接影响另外一个系统的使用或性能。所以在系统设计过程既要保证自身运行的弹性需求,也要避免对下游服务级联故障。重试模式在微服务技术架构中,当有多个服务(A,B,C ,D)时,一个服务(A)可能依赖于另一服务(B),而另一服务(B)又可能依赖于 C,

前言

微服务本质上分布式架构,当我们使用分布式系统时任何不可预知的问题都会发生(例如网络可用性问题、服务可用性问题、中间件可用性问题)。一个系统的问题可能会直接影响另外一个系统的使用或性能。所以在系统设计过程既要保证自身运行的弹性需求,也要避免对下游服务级联故障。

重试模式

在微服务技术架构中,当有多个服务(A,B,C ,D)时,一个服务(A)可能依赖于另一服务(B),而另一服务(B)又可能依赖于 C,依此类推。有时由于某些问题,服务 D 可能无法按预期响应。服务 D 可能引发了某些异常,例如 OutOfMemory Error 或 Internal Server Error。此类异常会影响下游服务,这可能导致用户体验较差,如下所示。

「微服务设计之禅」重试模式

1607306376

类似于网络通信异常等,大部分情况通过刷新再次请求服务接口即可解决。在微服务架构中,生产环境针对服务 D 部署了多个实例,并通过负载均衡等实现服务 D 高可用。如果其中一个实例网络请求有问题,无法响应期望结果,我们可以通过重试该请求,通过负载均衡器将请求发送至运行状态良好的实例获取正确结果。因此通过“重试”模式,可以使得应用可用性得到保证。

「微服务设计之禅」重试模式

1607306392

示例程序

架构图

「微服务设计之禅」重试模式

1607306406

如上图所示,简单模拟电商下单逻辑

  • 用户登录浏览商品 (商品库存模块)
  • 扣减商品库存 (商品库存模块)
  • 创建商品订单 (订单模块)

product-service 通过调用 order-service 服务下单

代码实现

├── retry-demo

   ├── order-service        #订单服务  (8070)

   └── product-service      #商品库存服务  (8050)

  • 依赖说明。由于 hystrix 年久失修,这里使用 resilience4j 断路保护器做演示
<dependency>

    <groupId>io.github.resilience4j</groupId>

    <artifactId>resilience4j-spring-boot2</artifactId>

    <version>1.6.1</version>

</dependency>

  • 针对接口定义重试策略
resilience4j.retry:

  instances:

    ratingService:

      maxRetryAttempts: 1 #重试策略

      retryExceptions:   #针对哪些异常进行重试

        - org.springframework.web.client.HttpServerErrorException

  • 消费方接口。product-service 8050
/**

 * 用户点击购买

 */

@SneakyThrows

@GetMapping("/order")

public String buy() {

    // 模拟调用 订单服务下单

    orderService.createOrder().get();

    return "success";

}

  • 定义远程调用类使用 Retry 包装
/**

 * 创建订单

 * name: 指定接口重试配置名称

 * fallbackMethod: 重试后降级方法

 */

@Bulkhead(name = "createOrder", fallbackMethod = "getError")

public CompletableFuture<String> createOrder() {

    return CompletableFuture.supplyAsync(() -> restTemplate.getForEntity("http://localhost:8070/createOrder"

            , String.class).getBody());

}

public CompletableFuture<String> getError(Throwable error) {

    log.warn("创建订单失败了 {}", error.getMessage());

    return CompletableFuture.completedFuture("");

}

    • 服务提供方。order-service 8070
@RestController

public class PayController {

    private static int num = 0;

    @SneakyThrows

    @GetMapping("/createOrder")

    public String createOrder() {

        log.info("开始请求创建订单接口 {}", ++num);

        // 请求次数奇数模拟创建订单异常

        if (num % 2 != 0) {

            throw new RuntimeException();

        }

        return "创建订单服务";

    }

}

开始测试

  • 请求商品服务,返回成功
curl http://localhost:8050/order

success⏎

  • 订单服务日志,由于是第一次请求触发异常,然后服务调用方自动重试产生第二次调用。
2020-12-06 15:50:07.664  INFO 25846 --- [nio-8070-exec-1] c.e.o.controller.OrderController         : 开始请求创建订单接口 1

2020-12-06 15:50:07.686 ERROR 25846 --- [nio-8070-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException] with root cause

... 异常日志 ...

2020-12-06 15:50:08.271  INFO 25846 --- [nio-8070-exec-2] c.e.o.controller.OrderController         : 开始请求创建订单接口 2

总结

重试模式可以通过编码的形式自动发起重试,避免终端用户手动刷新体验问题。但重试模式的缺点是会造成整体的响应时间,部分业务逻辑不能适用(比如幂等接口[1]等)

扫码领视频副本.gif

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号