Introduce Resilience to your Application

Step 1: Resilience

考虑以下情况:您正在开发一个云应用程序来为您的客户提供服务。为了让您的客户满意,您当然对实现应用程序的最高可用性感兴趣。

然而,可能跨越多个服务的云应用程序本质上是复杂的。因此,可以安全地假设某事某处会在某个时间点失败。例如,对数据库的调用可能会失败并导致应用程序的一部分失败。如果应用程序的其他部分依赖于失败的部分,这些部分也会失败。因此,单个故障可能会级联整个系统并破坏它。这对于多租户应用程序尤其重要,其中应用程序的单个实例为多个客户提供服务。典型的 S/4HANA 多租户应用程序涉及许多下游服务,例如本地 S/4HANA ERP 系统。

让我们看一个具体的例子:假设您的云应用程序依赖于 30 个系统,每个系统的“完美”可用性为 99.99%。这意味着每个服务每月有 4.32 分钟不可用(43200 分钟 * (1 – 0.9999) = 4.32 分钟)。

现在假设故障是级联的,因此一项服务不可用意味着整个应用程序变得不可用。鉴于上面使用的等式,现在的情况是这样的:

43200 分钟 * (1 – 0.9999^30) = 43200 分钟 * (1 – 0.997) = 129.6 分钟

因此,您的多租户应用程序每个月对于每个客户来说都无法使用超过两个小时!

为了避免这种情况,我们需要为应用程序配备处理故障的能力。如果应用程序可以处理故障,则称为弹性。因此,弹性是我们实现可用性的手段。

Step 2: Resilience4j

SAP Cloud SDK 现在基于 Resilience4j 库构建,以便为您的云应用程序提供弹性。在之前的 2.x 版本中,我们使用了 Hystrix 库,它现在已经处于维护模式有一段时间了。

Resilience4j 带有许多模块来保护您的应用程序免受故障。最重要的是超时、隔板和断路器。

超时:Resilience4j 允许为每个远程服务设置自定义超时持续时间。如果远程服务的响应时间超过指定的超时时间,则认为远程服务调用失败。该值应根据远程服务的平均响应时间进行调整。

隔板:这些允许限制对远程服务的并发请求数。如果并发传入请求的数量超过配置的阈值,则称该隔板已饱和。在这种情况下,在现有请求完成之前,将自动停止进一步的请求。

断路器:Resilience4j 使用断路器模式来确定远程服务当前是否可用。断路器默认关闭。如果远程服务调用失败太多次,Resilience4j 将打开/跳闸断路器。这意味着应该对同一远程服务进行的任何进一步调用都将自动停止。 Resilience4j 将定期检查服务是否再次可用,并相应地再次关闭打开的断路器。有关断路器模式的更多信息,请查看 Martin Fowler 的这篇文章。

此外,SAP Cloud SDK 使您能够提供回退功能。因此,如果调用失败,例如因为隔板饱和或断路器断开/跳闸,SDK 将检查是否实现了回退并自动调用它。因此,即使服务不可用,您仍然可以提供一些有用的结果,例如通过提供缓存数据。

如果您想更深入地了解内部工作原理,请查看 Resilience4j 用户指南。

Step 3: Make your OData call resilient

既然我们已经介绍了为什么弹性很重要,现在是时候将它引入您的应用程序了。 在上一个教程中,您创建了一个简单的 servlet,它使用 SDK 的虚拟数据模型 (VDM) 和其他有用的抽象来从 ERP 系统中检索业务合作伙伴。 为了使此 VDM 调用具有弹性,您必须使用 SAP Cloud SDK 提供的 ResilienceDecorator 类包装代码。

同时,您还将 VDM 调用本身分离到另一个类中,以便在以后的教程中具有更好的可读性和更容易的维护。 请注意,从 SAP Cloud SDK 版本 3 开始,不再需要单独的类来实现弹性。 您还可以直接向现有的 BusinessPartnerServlet 类添加弹性。

所以首先创建以下类:

./application/src/main/java/com/sap/cloud/sdk/tutorial/GetBusinessPartnersCommand.java

package com.sap.cloud.sdk.tutorial;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.time.Duration;
import java.util.Collections;
import java.util.List;import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceIsolationMode;
import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceRuntimeException;
import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException;
import com.sap.cloud.sdk.datamodel.odata.helper.Order;import com.sap.cloud.sdk.s4hana.connectivity.ErpHttpDestination;
import com.sap.cloud.sdk.s4hana.datamodel.odata.namespaces.businesspartner.AddressEmailAddress;
import com.sap.cloud.sdk.s4hana.datamodel.odata.namespaces.businesspartner.BusinessPartner;
import com.sap.cloud.sdk.s4hana.datamodel.odata.namespaces.businesspartner.BusinessPartnerAddress;
import com.sap.cloud.sdk.s4hana.datamodel.odata.services.BusinessPartnerService;
import com.sap.cloud.sdk.s4hana.datamodel.odata.services.DefaultBusinessPartnerService;public class GetBusinessPartnersCommand {private static final Logger logger = LoggerFactory.getLogger(GetBusinessPartnersCommand.class);// TODO: uncomment the lines below and insert your API key, if you are using the sandbox service// private static final String APIKEY_HEADER = "apikey";// private static final String SANDBOX_APIKEY = "";private static final String CATEGORY_PERSON = "1";private final ErpHttpDestination destination;private final BusinessPartnerService businessPartnerService;private final ResilienceConfiguration myResilienceConfig;public GetBusinessPartnersCommand(ErpHttpDestination destination) {this(destination, new DefaultBusinessPartnerService());}public GetBusinessPartnersCommand(ErpHttpDestination destination, BusinessPartnerService service) {this.destination = destination;businessPartnerService = service;myResilienceConfig = ResilienceConfiguration.of(BusinessPartnerService.class).isolationMode(ResilienceIsolationMode.TENANT_AND_USER_OPTIONAL).timeLimiterConfiguration(ResilienceConfiguration.TimeLimiterConfiguration.of().timeoutDuration(Duration.ofMillis(10000))).bulkheadConfiguration(ResilienceConfiguration.BulkheadConfiguration.of().maxConcurrentCalls(20));}public List<BusinessPartner> execute() {return ResilienceDecorator.executeSupplier(this::run, myResilienceConfig, e -> {logger.warn("Fallback called because of exception.", e);return Collections.emptyList();});}private List<BusinessPartner> run() {try {return businessPartnerService.getAllBusinessPartner().select(BusinessPartner.BUSINESS_PARTNER,BusinessPartner.LAST_NAME,BusinessPartner.FIRST_NAME,BusinessPartner.IS_MALE,BusinessPartner.IS_FEMALE,BusinessPartner.CREATION_DATE,BusinessPartner.TO_BUSINESS_PARTNER_ADDRESS.select(BusinessPartnerAddress.CITY_NAME,BusinessPartnerAddress.COUNTRY,BusinessPartnerAddress.TO_EMAIL_ADDRESS.select(AddressEmailAddress.EMAIL_ADDRESS))).filter(BusinessPartner.BUSINESS_PARTNER_CATEGORY.eq(CATEGORY_PERSON)).orderBy(BusinessPartner.LAST_NAME, Order.ASC).top(200)// TODO: uncomment the line below, if you are using the sandbox service// .withHeader(APIKEY_HEADER, SANDBOX_APIKEY).executeRequest(destination);} catch (ODataException e) {throw new ResilienceRuntimeException(e);}}
}

要使用 ResilienceDecorator,您至少需要做两件事:

您希望以弹性方式执行的代码。它可以是 Supplier、Callable、Supplier、方法引用或简单的 lambda 函数。您可能已经注意到,该示例仅采用基于 VDM 的代码调用上一教程中的 OData 服务,并将其放入单独的 run() 方法中。 ResilienceDecorator 提供的方法可以简单地包装所提供的函数并返回一个新函数(decorateSupplier、decorateCallable 等),以及也立即开始执行该函数的方法(executeSupplier、executeCallable 等)。这里 executeSupplier 与基于 VDM 的代码的方法引用一起使用。

带有标识符参数集的 ResilienceConfiguration 实例。这里的示例使用类引用,但也可以使用字符串标识符。除了强制标识符参数外,SAP Cloud SDK 还带有默认的弹性配置,因此您无需自行执行任何其他配置。在大多数情况下,默认配置就足够了。但是,如果您需要更改弹性配置,您可以在 SAP Cloud SDK Javadoc 中找到有关此主题的更多信息

这是自定义弹性配置的示例。这里隔离模式设置为可选租户+用户,隔板最大并发调用数为20,执行超时为10000毫秒。

myResilienceConfig = ResilienceConfiguration.of(BusinessPartnerService.class).isolationMode(ResilienceIsolationMode.TENANT_AND_USER_OPTIONAL).timeLimiterConfiguration(ResilienceConfiguration.TimeLimiterConfiguration.of().timeoutDuration(Duration.ofMillis(10000))).bulkheadConfiguration(ResilienceConfiguration.BulkheadConfiguration.of().maxConcurrentCalls(20));

此外,ResilienceDecorator 的decorate… 和execute… 方法支持可选的第三个参数作为回退函数,以防远程服务调用失败。 在这种情况下,使用返回空列表的 lambda 函数。 您还可以提供静态数据或检查对此调用的响应是否已被缓存。 最佳实践是至少记录提供的 Throwable。

更新您的弹性配置以匹配上述配置。 现在您有了一个工作命令,您需要调整您的 BusinessPartnerServlet 以使用新创建的命令:

./application/src/main/java/com/sap/cloud/sdk/tutorial/BusinessPartnerServlet.java

package com.sap.cloud.sdk.tutorial;import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;import com.sap.cloud.sdk.s4hana.connectivity.ErpHttpDestination;
import com.sap.cloud.sdk.s4hana.connectivity.DefaultErpHttpDestination;
import com.sap.cloud.sdk.s4hana.datamodel.odata.namespaces.businesspartner.BusinessPartner;@WebServlet("/businesspartners")
public class BusinessPartnerServlet extends HttpServlet {private static final long serialVersionUID = 1L;private static final Logger logger = LoggerFactory.getLogger(BusinessPartnerServlet.class);private static final String DESTINATION_NAME = "MyErpSystem";@Overrideprotected void doGet(final HttpServletRequest request, final HttpServletResponse response)throws ServletException, IOException {try {final ErpHttpDestination destination = DestinationAccessor.getDestination(DESTINATION_NAME).asHttp().decorate(DefaultErpHttpDestination::new);final List<BusinessPartner> businessPartners =new GetBusinessPartnersCommand(destination).execute();response.setContentType("application/json");response.getWriter().write(new Gson().toJson(businessPartners));} catch (final Exception e) {logger.error(e.getMessage(), e);response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);response.getWriter().write(e.getMessage());e.printStackTrace(response.getWriter());}}
}

关于 SAP CloudFoundry 应用的 Resilience相关推荐

  1. 5分钟将Docker镜像部署到SAP云平台CloudFoundry

    一键部署Docker镜像到SAP CloudFoundry 阅读这篇blog大约需要5分钟 Docker 是世界领先的软件容器平台.DockerHub 是一个由 Docker 公司运行和管理的基于云的 ...

  2. 部署在SAP Cloud Platform CloudFoundry环境的应用如何消费SAP Leonardo机器学习API

    Jerry的前一篇文章 如何在Web应用里消费SAP Leonardo的机器学习API 里介绍的例子是Neo测试环境的Web应用消费sandbox版本的机器学习API,url如下: https://s ...

  3. SAP Business Application Studio 如何同 SAP BTP CloudFoundry 环境绑定

    在 SAP Business Application Studio space 创建页面,新建一个 Dev Space,应用类型选择为 SAP Mobile Application: 在 Busine ...

  4. SAP云平台CloudFoundry环境里新建SAP UI5应用后,自动生成了哪些组件

    新建一个SAP UI5应用, 自动生成了一个MTA项目,包含一个html5 module,一个app router和一个UI deployer: 生成的完整yaml文件如下: ID: mta_app ...

  5. 一个最简单的SAP UI5应用部署到SAP云平台CloudFoundry环境后,自动生成了哪些资源

    我用SAP WebIDE新建了一个UI5应用: 该MTA应用除了名为app的HTML5 module外,还包含mta_app_Router和mta_app_ui_deployer: 后者是app HT ...

  6. SAP云平台CloudFoundry环境里route 超过quota的错误处理

    试图往SAP Cloud Platform CloudFoundry用命令行CLI部署应用时,遇到如下错误: 原因是因为这个新建的名为Haytham的subaccount没有分配application ...

  7. 如何给SAP Cloud Platform的CloudFoundry环境里的subaccount添加quota

    我在SAP Cloud Platform的CloudFoundry里创建了一个space,但是没有任何memory和route的quota: 结果部署应用时,遇到了如下的错误消息: 解决方案:在ent ...

  8. SAP云平台CloudFoundry环境试用帐号过期了怎么办

    今天我登录SAP云平台的CloudFoundry环境时,收到了下列的提示信息:D Your free trial has expired. To extend your trial period un ...

  9. SAP云平台CloudFoundry上部署应用的log分析

    log: 有一些非常有用的信息: Retrieving logs for app jerry-demo-server in org I042416trial_trial / space dev as ...

最新文章

  1. J2EE业务层模式:服务门面,应用服务,以及业务委托,服务定位器
  2. 一位软件工程师的6年总结(转载)
  3. 怎么共享电脑上的文件_电脑小技巧--远程访问共享文件夹
  4. struts2:struts.xml配置文件详解
  5. Gradle 学习二
  6. 初级Java开发工程师!绝密文档,面试手册全面突击!!!秋招已经到来
  7. JVM PermGen –您在哪里?
  8. 关于SimpleDateFormat线程不安全的源码分析
  9. x264 scan8存储分析
  10. 打磨锤子计算机专业,工大金课 | 在北工大校园里,磨一把“工大锤”
  11. HMM-MEMM-CRF
  12. Here we are 团队简介
  13. PHP高级教程-Session
  14. pytorch、CUDA、cuDNN下载和环境变量设置
  15. 树莓派实现SIM868 ppp拨号上网
  16. 数据恢复工具winhex教程
  17. 语文老师之二小白的边城
  18. 微信小程序分享功能(微信好友,朋友圈)
  19. 硬件使用74hc138的C语言程序,【Arduino教程】第三十一讲:74HC138实验
  20. HDR视频的编码参数详解

热门文章

  1. AI没有偏见?它们从人类的语言中学会了性别和种族歧视
  2. 自定义存储过程和函数
  3. 移动Exchange2007 CCR邮箱存储路径
  4. nfs:client mount成功,但是进入目录时出现Permission denied
  5. Dungeon Master(三维bfs)
  6. Mysql大数据备份和增量备份及还原
  7. Failed to issue method call: Unit httpd.service failed to load: No such file or directory.
  8. 我遇到的CocoaPods的问题(也许后期会解决,持续更新)
  9. C++封装常用对象和对头文件以及预编译机制的探索
  10. 0-1语言建模当中会遇到的问题