写点什么

Spring Cloud Hystrix 项目展望:为云原生 Java 项目提供持续支持

2020 年 1 月 28 日

Spring Cloud Hystrix项目展望:为云原生 Java 项目提供持续支持

本文要点

  • Spring Cloud Hystrix 项目已弃用,因此,新应用程序不应该再使用该项目了。

  • 对于 Spring 开发人员来说, Resilience4j 是实现断路器模式的一个新选择。

  • 除了断路器模式之外, Resilience4j 还提供了限流器、重试、舱壁隔离等特性。

  • Resilience4j 可以很好地与 Spring Boot 配合使用,并且还可以使用 micrometer 库来发送监控度量指标。

  • 由于 Spring 没有为 Hystrix Dashboard 引入替代品,所以用户需要使用 Prometheus 或 NewRelic 来搭建监控。


Spring Cloud Hystrix 项目是由 Netflix Hystrix 库包装而成的。自从那时起,许多企业和开发人员就开始采用它来实现断路器(CircuitBreaker )模式了。


2018 年 11 月,Netflix 宣布将这个项目置于维护模式,它也促使 Spring Cloud 宣布了同样的消息。从那时起,就再没有对这个 Netflix 库进行进一步的增强了。在 2019 年的 SpringOne 上,Spring 宣布将从 Spring Cloud 3.1 版本中移除 Hystrix Dashboard,这促成了它的官方死亡


由于已经大肆宣传过断路器模式了,许多开发人员不是已经使用它了,就是正想使用它,因此现在需要一个替代品。Resilience4j 的引入填补这一空白,并且它也为 Hystrix 用户提供迁移路径。


Resilience4j

Resilience4j 的灵感来自 Netflix Hystrix,但它是专为 Java 8 和函数式编程而设计的。与 Hystrix 相比,它是轻量级的,因为它仅需依赖 Vavr 库。相比之下,Netflix Hystrix 依赖于 Archaius,而 Archaius 还需依赖其他几个外部库,比如 Guava 和 ApacheCommons。


与老库相比,新库始终有一个优势,即它可以从以前的错误中吸取教训。Resilience4j 还提供了许多新特性:


断路器(CircuitBreaker)

当一个服务调用另一个服务时,另一个服务总有可能停机或具有高延迟。由于服务可能正在等待其他请求完成,所以这可能会导致线程耗尽。断路器模式的功能与电路的熔断器类似:


  • 当多个连续失败超过规定阈值时,断路器跳闸。

  • 在超时期间,所有调用远程服务的请求都将立即失败。

  • 超时到期后,断路器允许有限数量的测试请求通过。

  • 如果测试请求成功,断路器将恢复正常工作状态。

  • 否则,如果还是失败,超时时间将再次开启。


限流器(RateLimiter)

限流模式可以确保服务在窗口期间只能接收设定的最大请求数。这样重点资源可以限量使用,并且能保证其不会被耗尽。


重试(Retry)

重试模式可以使应用程序在调用外部服务时处理瞬态失败。它可以确保对外部资源进行特定次数的重试操作。如果所有重试都尝试之后仍没有成功,那么它才应失败,并且应用程序应该能优雅地处理响应。


舱壁隔离(Bulkhead )

舱壁隔离可以确保发生在系统中某一部分的故障不会导致整个系统瘫痪。它能控制并发调用组件的数量。这样,等待来自该组件的响应资源的数量将会受到限制。舱壁隔离的实现方式有两种:


  • 信号量隔离方式限制了对服务的并发请求数。一旦达到限制,它会立即拒绝请求。

  • 线程池隔离方式使用线程池将服务与调用方分离,并将其包含到系统资源的子集中。


线程池方式还提供了一个等待队列,仅当线程池和队列都满时才会拒绝请求。管理线程池增加了一些开销,且与信号量方式相比,它会稍微降低性能,但它允许挂起的线程超时。


使用 Resilience4j 构建 Spring Boot 应用程序

在本文中,我们将构建 2 个服务:“图书管理”和“图书馆管理”。


在这个系统中,“图书馆管理”调用“图书管理”。我们需要操作“图书管理”服务的上线和下线,以模拟断路器、限流、重试和舱壁隔离等特性的不同场景。


先决条件

  • JDK 8

  • Spring Boot 2.1.x

  • resilience4j 1.1.x ( resilience4j 最新版本是 1.3,但是 resilience4j-spring-boot2 仅有 1.1.x 的最新版本 )

  • 诸如 Eclipse、VSC、 intelliJ 之类的 IDE(最好使用 VSC,因为它非常轻量,与 Eclipse 和 intelliJ 相比,我更喜欢它)

  • Gradle

  • NewRelic APM 工具(也可以使用带有 Grafana 的 Prometheus)


“图书管理”服务

  1. Gradle 依赖


该服务是一个简单的基于 REST 的 API,并且需要依赖一些 Web 和测试相关的标准 spring-boot starter jar 包。我们还会使用 Swagger 来测试该 API:


dependencies {    //REST    implementation 'org.springframework.boot:spring-boot-starter-web'    //swagger    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'    implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'    testImplementation 'org.springframework.boot:spring-boot-starter-test'}
复制代码


  1. 配置


只需详细配置一个端口即可:


server:    port: 8083
复制代码


  1. 服务实现


服务中包含两个方法 addBook 和 retrieveBookList 。由于它仅是一个演示示例,所以我们使用了一个 ArrayList 对象来存储图书信息:


@Servicepublic class BookServiceImpl implements BookService {
List<Book> bookList = new ArrayList<>();
@Override public String addBook(Book book) { String message = ""; boolean status = bookList.add(book); if(status){ message= "Book is added successfully to the library."; } else{ message= "Book could not be added in library due to some technical issue. Please try later!"; } return message; }
@Override public List<Book> retrieveBookList() { return bookList; }}
复制代码


  1. 控制器


Rest Controller 发布了两个 API,一个是用于添加图书的 POST API,另一个是用于检索图书详细信息的 GET API:


@RestController@RequestMapping("/books")public class BookController {
@Autowired private BookService bookService ;
@PostMapping public String addBook(@RequestBody Book book){ return bookService.addBook(book); }
@GetMapping public List<Book> retrieveBookList(){ return bookService.retrieveBookList(); }}
复制代码


  1. 测试“图书管理”服务


通过如下命令构建并启动应用程序:


//构建应用成功gradlew build
//启动应用成功java -jar build/libs/bookmanangement-0.0.1-SNAPSHOT.jar
//端点的 url 链接http://localhost:8083/books
复制代码


现在我们可以使用 Swagger UI (http://localhost:8083/swagger-ui.html)来测试该应用程序了。


在开始构建“图书馆管理”服务之前,请先确保该服务已经启动并处于运行状态。


“图书馆管理”服务

在这个服务中,我们将使用 Resilience4j 的全部特性。


  1. Gradle 依赖


该服务也是一个简单的基于 REST 的 API,并且也需要依赖一些 Web 和测试相关的 spring-boot starter jar 包。为了在该 API 中使用断路器和其他 Resilience4j 特性,我们还依赖了一些其他包,比如 resilience4j-spring-boot2、spring-boot starter-actuato r、spring-bootstarter-aop。此外,我们还添加了 micrometer 相关的依赖(micrometer-registry-prometheus,micrometer-registry-new-relic)来启用监控度量。最后,我们使用了 Swagger 来测试该 API:


dependencies {        compile 'org.springframework.boot:spring-boot-starter-web'        //resilience    compile "io.github.resilience4j:resilience4j-spring-boot2:${resilience4jVersion}"    compile 'org.springframework.boot:spring-boot-starter-actuator'    compile('org.springframework.boot:spring-boot-starter-aop')     //swagger    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'    implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
// monitoring compile "io.micrometer:micrometer-registry-prometheus:${resilience4jVersion}" compile 'io.micrometer:micrometer-registry-new-relic:latest.release'
testImplementation 'org.springframework.boot:spring-boot-starter-test'}
复制代码


  1. 配置


此处,我们需要设置的一些配置项。


默认情况下,在 Spring 2.1.x 中,断路器和限流的执行器 API 是禁用的。我们需要使用管理属性来启用它们。在本文末给出的源码链接中可以查看这些属性。此外,我们还需要配置如下属性:


  • 配置 NewRelic 观察 API 的密钥和账户 ID


management:   metrics:    export:      newrelic:        api-key: xxxxxxxxxxxxxxxxxxxxx        account-id: xxxxx        step: 1m
复制代码


  • 为 “add”和“get”服务 API 配置 Resilience4j 断路器属性。


 resilience4j.circuitbreaker:  instances:    add:      registerHealthIndicator: true      ringBufferSizeInClosedState: 5      ringBufferSizeInHalfOpenState: 3      waitDurationInOpenState: 10s      failureRateThreshold: 50      recordExceptions:        - org.springframework.web.client.HttpServerErrorException        - java.io.IOException        - java.util.concurrent.TimeoutException        - org.springframework.web.client.ResourceAccessException        - org.springframework.web.client.HttpClientErrorException      ignoreExceptions:
复制代码


  • 为“add”服务 API 配置 Resilience4j 限流器属性。


resilience4j.ratelimiter:  instances:    add:      limitForPeriod: 5      limitRefreshPeriod: 100000          timeoutDuration: 1000ms
复制代码


  • 为“get”服务 API 配置 Resilience4j 重试属性。


resilience4j.retry:  instances:    get:      maxRetryAttempts: 3      waitDuration: 5000
复制代码


  • 为“get”服务 API 配置 Resilience4j 舱壁隔离属性。


resilience4j.bulkhead:  instances:    get:      maxConcurrentCall: 10      maxWaitDuration: 10ms
复制代码


现在,我们将创建一个 LibraryConfig 类来为 RestTemplate 定义一个 bean,以调用“图书管理”服务。我们还在此处对“图书管理”服务的端点 URL 进行了硬编码。对于要上线运行的应用程序来说,这不是一个好主意,但是此演示示例的目的只是为了展示 Resilience4j 的特性。对于线上的应用程序,我们可能要使用服务发现(service-discovery)服务。


@Configurationpublic class LibraryConfig {    Logger logger = LoggerFactory.getLogger(LibrarymanagementServiceImpl.class);    private static final String baseUrl = "https://bookmanagement-service.apps.np.sdppcf.com";

@Bean RestTemplate restTemplate(RestTemplateBuilder builder) { UriTemplateHandler uriTemplateHandler = new RootUriTemplateHandler(baseUrl); return builder .uriTemplateHandler(uriTemplateHandler) .build(); } }
复制代码


  1. 服务


服务实现包含一些方法,这些方法使用 @CircuitBreaker 、@RateLimiter、@Retry 和 @Bulkhead 注解封装,所有这些注释都支持 fallbackMethod 属性,并且每个模式在观察到失败时,都会将调用重定向到对应的回退(fallback)方法。我们需要定义这些回退方法的实现:


下面这个方法通过 @CircuitBreaker 注解启用了断路器功能。因此,如果 /books 端点无法返回响应,它将会调用 fallbackForaddBook() 方法。


  @Override    @CircuitBreaker(name = "add", fallbackMethod = "fallbackForaddBook")    public String addBook(Book book){        logger.error("Inside addbook call book service. ");        String response = restTemplate.postForObject("/books", book, String.class);        return response;    }
复制代码


下面这个方法通过 @RateLimiter 注解启用了限流功能。如果 /books 端点达到上面配置中定义的阈值,它将调用 fallbackForRatelimitBook() 方法。


    @Override    @RateLimiter(name = "add", fallbackMethod = "fallbackForRatelimitBook")    public String addBookwithRateLimit(Book book){        String response = restTemplate.postForObject("/books", book, String.class);        logger.error("Inside addbook, cause ");        return response;    }
复制代码


下面这个方法通过 @Retry 注解启用了重试功能。如果 /books 端点达到上面配置中定义的阈值,它也将调用 fallbackRetry() 方法。


  @Override    @Retry(name = "get", fallbackMethod = "fallbackRetry")    public List<Book> getBookList(){        return restTemplate.getForObject("/books", List.class);    }
复制代码


下面这个方法通过 @Bulkhead 注释启用了隔离功能。如果 /books 端点达到上面配置中定义的阈值,它也将调用 fallbackBulkhead() 方法。


    @Override    @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE, fallbackMethod = "fallbackBulkhead")    public List<Book> getBookListBulkhead() {        logger.error("Inside getBookList bulk head");        try {            Thread.sleep(100000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return restTemplate.getForObject("/books", List.class);    }
复制代码


搭建完服务层之后,我们需要公开每个方法所对应的 REST API,以便我们对其进行测试。为此,我们需要创建 RestController 类。


  1. 控制器


Rest Controller 公开了 4 个 API:


  • 第一个是一个 POST API,用于添加一本图书

  • 第二个也是一个 POST API,但它用于在限流情况下添加图书

  • 第三个是一个 GET API,用于检索图书的详细信息

  • 第四个也是一个 GET API,用于在舱壁隔离情况下检索图书的详细信息


@RestController@RequestMapping("/library")public class LibrarymanagementController {
@Autowired private LibrarymanagementService librarymanagementService; @PostMapping public String addBook(@RequestBody Book book){ return librarymanagementService.addBook(book); }
@PostMapping ("/ratelimit") public String addBookwithRateLimit(@RequestBody Book book){ return librarymanagementService.addBookwithRateLimit(book); }
@GetMapping public List<Book> getSellersList() { return librarymanagementService.getBookList(); } @GetMapping ("/bulkhead") public List<Book> getSellersListBulkhead() { return librarymanagementService.getBookListBulkhead(); }}
复制代码


现在,代码已经准备好了。但我们必须构建并启动它。


  1. 构建并测试“图书馆管理”服务


通过如下命令构建并启动应用程序:


//构建gradlew build
//启动应用程序java -jar build/libs/librarymanangement-0.0.1-SNAPSHOT.jar
//端点Urlhttp://localhost:8084/library
复制代码


现在我们可以使用 Swagger UI(http://localhost:8084/swagger-ui.html) 来测试该应用程序了。



图 1


运行断路器、限流器、重试、舱壁隔离等测试场景

断路器:断路器已被应用于 addBook API。为了测试它是否有效,我们将停止“图书管理”服务。


  • 首先,通过访问 http://localhost:8084/actuator/health URL 来观察应用程序的运行状况。

  • 现在停止“图书管理”服务,并使用 Swagger UI 点击“图书馆管理”服务的 addBook API


在第一步时,Prometheus 量度应该显示断路器的状态为“CLOSED”。我们是通过 micrometer 依赖来启用 Prometheus 量度的。


执行第二步后,调用将开始失败并重定向到对应回退方法。


一旦超过了阈值(在本例中为 5),它将使触发断路。并且,之后的每个调用都将直接定向到回退方法,而不会尝试使用“图书管理”服务。(您可以到日志文件下观察日志输出语句来验证这一点。现在,我们观察 /health 端点,会发现断路器的状态为“OPEN”。


{    "status": "DOWN",    "details": {        "circuitBreakers": {            "status": "DOWN",            "details": {                "add": {                    "status": "DOWN",                    "details": {                        "failureRate": "100.0%",                        "failureRateThreshold": "50.0%",                        "slowCallRate": "-1.0%",                        "slowCallRateThreshold": "100.0%",                        "bufferedCalls": 5,                        "slowCalls": 0,                        "slowFailedCalls": 0,                        "failedCalls": 5,                        "notPermittedCalls": 0,                        "state": "OPEN"                    }                                        }            }        }    }}
复制代码


我们已经在 PCF(Pivotal Cloud Foundry)上部署了相同的代码,这样我们就可以将它与 NewRelic 集成来创建度量仪表盘。为此,我们使用了 micrometer-registry-new-relic 依赖。



图 2 NewRelic 观察到的断路器关闭图


限流器:我们创建了一个单独的 API(http://localhost:8084/library/ratelimit),它具有和 addBook 相同的功能,但启用了限流功能。在这种情况下,我们需要启动并运行“图书管理”服务。使用如下限流的配置,每 10 秒最多可以有 5 个请求。



图 3 限流配置


一旦我们在 10 秒钟内命中了 API 5 次 ,它就会达到阈值并被限流。为了避免限流,它将调用回退方法,并根据回退方法的实现逻辑作出响应。下图显示,在过去一个小时内,它已经三次达到阈值限流:



图 4 NewRelic 观察到的限流器限流


重试:重试功能可以使 API 不断地重试失败事务,直到配置的最大次数。如果成功,则将计数刷新成零。如果达到阈值,它将重定向到对应的回退方法并执行相应地逻辑。为了模拟这种情况,我们需要在“图书管理”服务关闭时点击 GET API(http://localhost:8084/library)。观察日志会发现它正在打印来自回退方法的响应信息。


舱壁隔离:在这个例子中,我们是采用信号量的实现方式来实现舱壁隔离功能的。为了模拟并发调用,我们使用了 Jmeter 并在“线程”组中设置了 30 个用户调用。



图 5 Jmeter 配置


我们将点击一个启用了舱壁隔离功能的 GET API (),该 API 使用 @Bulkhead 注解,并且我们在其中设置了一段睡眠时间 ,以便它可以达到并发执行的极限。我们观察日志会发现,对于某些线程调用,它将转调对应的回退方法。API 的可用并发调用如下图所示:



图 6 舱壁隔离的可用并发调用指示盘


总结

在本文中,我们学习了一些现在微服务架构必备的特性,这些特性可以使用单个库 Resilience4j 实现。使用带有 Grafana 或 NewRelic 的 Prometheus,我们可以根据度量指标构建仪表盘,并能提高系统的稳定性。


与往常一样,代码可以通过 Github 上找到:spring-boot-resilience4j


作者介绍

Rajesh Bhojwani 是一名解决方案架构师,他帮助团队将应用程序从本地迁移到诸如 PCF 、 AWS 等云平台。他在应用程序开发、设计和运维方面拥有 15 年以上的经验。他是布道者、技术博主和微服务冠军。他的最新博客可以在这里找到。


原文链接:


The Future of Spring Cloud’s Hystrix Project


2020 年 1 月 28 日 09:003026

评论 1 条评论

发布
用户头像
不错不错
2020 年 02 月 02 日 16:07
回复
没有更多了
发现更多内容

券商也“网红”,证券行业IT服务运维发展按下“快进键”

博睿数据

运维 APM 证券 券商 行情

用户故事为什么要关联开发数据?

Worktile

敏捷开发 开发数据

互联网省份数据大揭秘,看看哪些地方是互联网的戈壁滩?

非著名程序员

程序员 互联网 IT

如何设计数据中台

数据社

大数据 数据中台 数据仓库

“Plus Token”传销主犯被公诉!警惕,区块链不是“取款链”!

CECBC区块链专委会

1024讲话 CECBC 区块链技术 人才发展 培训

架构师训练营0期开营

刁架构

架构师

一个在游戏行业摸爬滚打了十几年的人,为何我对这本书情有独钟

图灵社区

游戏开发 游戏制作 世嘉培训教材

GrowingIO 大数据多维分析自动化测试实践

GrowingIO技术专栏

大数据 自动化测试 parewise

卧槽,接到一个阎王的需求

码农神说

程序员

python实现·十大排序算法之基数排序(Radix Sort)

南风以南

Python 排序算法 基数排序

自定义构造python白名单__builtins__

么么哒

Python

写代码没几天,遇到一堆报错,我该怎么办

刘早起😶

Python 程序人生 程序员成长

游戏夜读 | 改写图形API的意义

game1night

从位图到布隆过滤器

王坤祥

位图 布隆过滤器

kudmp介绍和安装

唯爱

重学 Java 设计模式:实战原型模式

小傅哥

Java 设计模式 小傅哥 复杂代码优化 重构

Python deepcopy一个优化

么么哒

Python

“新基建”方兴未艾,Smartbi Mining如何为产业数字化转型赋能?

infoq小陈

这场大数据+AI Meetup,一次性安排了大数据当下热门话题

Apache Flink

大数据 flink 流计算 实时计算 大数据处理

Android 通过opencv实现人脸识别,追踪

sar

android OpenCV 人脸识别

10分钟了解Flink

代码诗人

一款开源且具有交互视图界面的实时 Web 日志分析工具!

JackTian

开源 GoAccess 实时 Web 日志分析工具 交互式查看器

为什么要学习 Markdown?究竟有什么用?

JackTian

markdown markdown语法 markdown编辑器

git | IDEA 中如何压缩提交(压缩commit后再push 图文演示)

YoungZY

开发者工具 IDEA 开发工具

探索 Go 语言数据类型的内部实现

TuringTuring

golang 内存模型 高效

关于软件测试的三点思考

卓然

软件测试 测试 测试的价值 联现技术咖

Server Queue 提高 QPS

风含叶

Python kafka 后端 队列

为什么第三方联调应该先行?

大伟

GitHub 上十个好用的软件

彭宏豪95

GitHub 效率 工具

原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (十一)JUnit概述

编程道与术

Java 编程 软件测试 TDD 单元测试

「首度揭秘」大规模HPC生产环境 IO 特征

焱融科技

sds io 高性能 存储 焱融科技

Spring Cloud Hystrix项目展望:为云原生 Java 项目提供持续支持-InfoQ