写点什么

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:003045

评论 1 条评论

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

vm

梅花鹿鹿

28天写作 28天挑战 3月日更

程序员成长第二十三篇:员工不符合预期,怎么办?

石云升

程序员成长 28天写作 职场经验 管理经验 3月日更

Elasticsearch Dynamic Mapping

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试

Python 数据类型

HoneyMoose

优雅编程 | javascript代码优化的15个小知识

devpoint

ES6 JS代码优化 JS迭代

【动态规划/总结必看】从一道入门题与你分享关于 DP 的分析技巧 ...

宫水三叶的刷题日记

算法 LeetCode 面试数据结构与算法

如何在 Python 中清屏

HoneyMoose

进步

lenka

3月日更

(28DW-S8-Day17) 讲故事能力

mtfelix

28天写作 讲故事能力 复述能力

雪花算法,到底是个啥?

架构精进之路

算法 七日更 3月日更

准备参加软考的小伙伴注意了!

IT蜗壳-Tango

IT蜗壳 3月日更

冰河公开了进大厂的核心技能,服了!

冰河

程序员 面试 大厂技能 面试总结 硬核技能图谱

正则表达式.04 - 引用

insight

正则表达式 3月日更

更新60篇的复盘:持续书写,见证文字的力量

boshi

写作 七日更

最近12周中国快消品市场再提速;字节跳动正式起诉特朗普政府

󠀛Ferry

七日更 3月日更

币神量化交易系统开发|币神量化交易APP软件开发

开發I852946OIIO

系统开发

什么是职业

ES_her0

28天写作 3月日更

不一样的软件们——GitHub 热点速览 v.21.10

HelloGitHub

数据库 GitHub 开源项目

Wireshark数据包分析学习笔记Day5

穿过生命散发芬芳

Wireshark 数据包分析 3月日更

《接口测试入门》 学习笔记

骆俊

七日更 3月日更

CR量化交易APP开发|CR炒币机器人软件系统开发

开發I852946OIIO

系统开发

鼎昂量化交易系统APP开发|鼎昂炒币机器人软件开发

开發I852946OIIO

系统开发

线上MySQL读写分离,出现写完读不到问题如何解决

程序员历小冰

MySQL 读写分离

算法攻关 - 二叉树最大深度 (O(n))_0104

小诚信驿站

刘晓成 小诚信驿站 28天写作 算法攻关 二叉树最大深度

今日随想

Nydia

事务消息应用场景、实现原理与项目实战(附全部源码)

中间件兴趣圈

RocketMQ 实战 消息中间件 事务消息

算法喜刷刷

Kylin

算法 3月日更 21天挑战

《精通比特币》学习笔记(第五章)

棉花糖

区块链 读书笔记 3月日更

LeetCode题解:518. 零钱兑换 II,动态规划,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

如何用python优雅的写论文

张鹤羽粑粑

28天写作 3月日更

面试被吊打系列 - Redis原理

云流

数据库 架构 面试

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