「微服务设计之禅」隔离模式

JAVA架构日记

共 2586字,需浏览 6分钟

 · 2020-12-17

前言

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

隔离模式

9dad4a8f788e945c6b5ad18a04c55d21.webp

如上图,如果我们仔细观察船的结构时会发现,使用了多个隔板将船分为了多个小的隔间。隔板用于密封船体的各个部分,避免由于某个部位泄漏导致整个船淹没。当我们进行软件设计时,应该将整个应用拆分为多个组件,并且一个组件的故障不会影响到全局,这就所谓的隔离模式。

例如:假设有两个服务 A 和 B,服务 A 只能同时处理 5 个并发请求。服务 A 里面有两个接口

  • /a/b 依赖于服务 B (耗时比较多)

  • /a      本地服务

dee63e6581ace714cc03d7888901e71b.webp

当有 10 个并发请求 A 服务接口, 其中 5 个请求 /a/b接口 ,5 个请求 /a由于/a/b 需要调用服务 B 耗时比较多,可能会造成 5 个线程阻塞应用无法处理 /a 虽然只是本地处理。由于一个接口耗时占用了整个应用的资源导致其他接口无法使用,导致用户体验很差。

43cec0b9851b6efdc38ee378f4645186.webp

通过隔离模式分配特定接口的资源限制,避免资源瓶颈。如上图,将a/b 接口最大处理线程设置为 2,这样应用始终会有剩余线程处理其他接口请求。

示例程序

架构图

bfe79e3060058bb82c5764e5a320776a.webp

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

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

product-service 提供两个接口


  1. /products 查询全部商品展示

  1. /order 调用 order 服务下单

代码实现

├── bulkhead-demo
   ├── order-service        #订单服务  (8070)
   └── product-service      #商品库存服务  (8050)
  • 依赖说明。由于 hystrix 年久失修,这里使用 resilience4j 断路保护器做演示
<dependency>
    <groupId>io.github.resilience4jgroupId>
    <artifactId>resilience4j-spring-boot2artifactId>
    <version>1.6.1version>
dependency>
  • 消费方定义隔离策略
server:
  tomcat:
    threads:
      max: 5 #限制应用只能处理5 个并发,方便测试

resilience4j.bulkhead:
  instances:
    createOrder:  # 限制此接口只能使用两个
      maxConcurrentCalls: 2
  • 消费方接口。product-service 8050
  /**
   * 用户点击购买
   */

  @SneakyThrows
  @GetMapping("/order")
  public String buy() {
      // 模拟调用 订单服务下单
      orderService.createOrder().get();
      return "success";
  }

  /***
   * 获取全部商品接口
   */

  @SneakyThrows
  @GetMapping("/products")
  public String products() {
      // 模拟查询DB 耗时
      Thread.sleep(100);
      return "products";
  }
  • 定义远程调用类使用 Bulkhead 包装
/**
 * 创建订单
 * name: 指定接口隔离配置名称
 * fallbackMethod: 超出隔离限制降级方法
 */
@Bulkhead(name = "createOrder", fallbackMethod = "getError")
public CompletableFuture createOrder() {
    return CompletableFuture.supplyAsync(() -> restTemplate.getForEntity("http://localhost:8070/createOrder"
            , String.class).getBody());
}

public CompletableFuture getError(Throwable error) {
    log.warn("创建订单失败了 {}", error.getMessage());
    return CompletableFuture.completedFuture("");
}
  • 服务提供方。order-service 8070
@RestController
public class PayController {
    @SneakyThrows
    @GetMapping("/pay")
    public String pay(){
        // 模拟调用支付渠道耗时 10s
        Thread.sleep(10000);
        return "支付成功";
    }
}

测试

  • Jmeter 10 个线程请求 /order 接口, 10 个线程请求 /products ,30S 请求服务性能如下

测试使用资源隔离

资源隔离下,30S 共计执行样本 1750 次,通过率 43.5/每秒

f8dc5efd1e3f410e7c20f704eab1c67f.webp1607306553

测试不使用资源隔离(去掉@Bulkhead 逻辑)

由于未做资源隔离,30S 共计执行样本 50 次,通过率 49.8/每分钟

2c9e5a959d95709b850f1bf29789af0b.webp

总结

在应用资源紧张的前提下,通过设置资源隔离策略能大大提升整个应用的性能。所谓资源紧张即请求并发大于容器的处理前最大值,如上边压测 tomcat 限制只能处理 5 个并发,但是我们测试是 20 个并发。

源码:https://github.com/lltx/microservices-pattern

参考资料和部分图片来源 https://www.vinsguru.com

往期推荐

「微服务设计之禅」超时模式

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

「2020封箱」Spring Boot 2.4.1 发布

Spring Boot 2.4.0 正式发布,全面拥抱云原生




浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报