用 Hystrix 构建高可用服务架构(下)

2019-05-25  本文已影响0人  久伴_不离

7.基于本地缓存的 fallback 降级机制

Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:

            1.断路器处于打开的状态。

            2.资源池已满(线程池+队列 / 信号量)。

            3.Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。

            4.访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。

两种最经典的降级机制

        1.纯内存数据在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。

        2.默认值 fallback 降级逻辑中,也可以直接返回一个默认值。

在 HystrixCommand,降级逻辑的书写,是通过实现 getFallback() 接口;而在 HystrixObservableCommand 中,则是实现 resumeWithFallback() 方法。

现在,我们用一个简单的栗子,来演示 fallback 降级是怎么做的。

比如,有这么个场景。我们现在有个包含 brandId 的商品数据,假设正常的逻辑是这样:拿到一个商品数据,根据 brandId 去调用品牌服务的接口,获取品牌的最新名称 brandName。

假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。

步骤一:本地缓存获取数据

本地获取品牌名称的代码大致如下。

/** * 品牌名称本地缓存 * */

public class BrandCache {

private static Map<Long, String> brandMap = new HashMap<>();

static {

brandMap.put(1L, "Nike");

}

/** * brandId 获取 brandName * @param brandId 品牌 id * @return 品牌名 */

public static String getBrandName(Long brandId) {

return brandMap.get(brandId);

}

步骤二:实现 GetBrandNameCommand

在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。

这里,我们直接模拟接口调用报错,给它抛出个异常。

而在 getFallback() 方法中,就是我们的降级逻辑,我们直接从本地的缓存中,获取到品牌名称的数据。

/** * 获取品牌名称的 command * */

public class GetBrandNameCommand extends HystrixCommand<String> {

private Long brandId;

public GetBrandNameCommand(Long brandId) {

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BrandService"))

.andCommandKey(HystrixCommandKey.Factory.asKey("GetBrandNameCommand"))

.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()

// 设置降级机制最大并发请求数

.withFallbackIsolationSemaphoreMaxConcurrentRequests(15)));

this.brandId = brandId;

}

@Override

protected String run() throws Exception {

// 这里正常的逻辑应该是去调用一个品牌服务的接口获取名称

// 如果调用失败,报错了,那么就会去调用 fallback 降级机制

// 这里我们直接模拟调用报错,抛出异常

throw new Exception();

}

@Override

protected String getFallback() {

return BrandCache.getBrandName(brandId);

}

}

FallbackIsolationSemaphoreMaxConcurrentRequests 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。

步骤三:CacheController 调用接口

在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用getFallback() 方法走降级逻辑。

@Controllerpublic class CacheController {

@RequestMapping("/getProductInfo")

@ResponseBody

public String getProductInfo(Long productId) {

HystrixCommand<ProductInfo> getProductInfoCommand = new

GetProductInfoCommand(productId);

ProductInfo productInfo = getProductInfoCommand.execute();

Long brandId = productInfo.getBrandId();

HystrixCommand<String> getBrandNameCommand = new GetBrandNameCommand(brandId);

// 执行会抛异常报错,然后走降级

String brandName = getBrandNameCommand.execute();

productInfo.setBrandName(brandName);

System.out.println(productInfo);

return "success";

}

}

关于降级逻辑的演示,基本上就结束了。


8.深入 Hystrix 断路器执行原理

RequestVolumeThreshold

HystrixCommandProperties.Setter()

            .withCircuitBreakerRequestVolumeThreshold(int)

表示在滑动窗口中,至少有多少个请求,才可能触发断路。

Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。

ErrorThresholdPercentage

HystrixCommandProperties.Setter()

            .withCircuitBreakerErrorThresholdPercentage(int)

表示异常比例达到多少,才会触发断路,默认值是 50(%)。

如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。

SleepWindowInMilliseconds

HystrixCommandProperties.Setter()

        .withCircuitBreakerSleepWindowInMilliseconds(int)

断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后

在 SleepWindowInMilliseconds 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。

而在该参数时间过后,断路器会变为 half-open 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。

Enabled

HystrixCommandProperties.Setter()

            .withCircuitBreakerEnabled(boolean)

控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。

默认值是 true。

ForceOpen

HystrixCommandProperties.Setter()

        .withCircuitBreakerForceOpen(boolean)

如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 false。

ForceClosed

HystrixCommandProperties.Setter()

        .withCircuitBreakerForceClosed(boolean)

如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 false。

实例 Demo

HystrixCommand 配置参数在 GetProductInfoCommand 中配置 Setter 断路器相关参数。1.滑动窗口中,最少 20 个请求,才可能触发断路。2.异常比例达到 40% 时,才触发断路。3.断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。run() 方法中,我们判断一下 productId 是否为 -1,是的话,直接抛出异常。这么写,我们之后测试的时候就可以传入 productId=-1,模拟服务执行异常了。在降级逻辑中,我们直接给它返回降级商品就好了。

public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {

private Long productId;private static final HystrixCommandKey KEY =HystrixCommandKey.Factory.asKey("GetProductInfoCommand");public GetProductInfoCommand(Long productId) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService")).andCommandKey(KEY).andCommandPropertiesDefaults(HystrixCommandProperties.Setter()// 是否允许断路器工作.withCircuitBreakerEnabled(true)// 滑动窗口中,最少有多少个请求,才可能触发断路.withCircuitBreakerRequestVolumeThreshold(20)// 异常比例达到多少,才触发断路,默认 50%.withCircuitBreakerErrorThresholdPercentage(40)// 断路后多少时间内直接 reject 请求,之后进入 half-open 状态,默认 5000ms.withCircuitBreakerSleepWindowInMilliseconds(3000)));this.productId = productId;}@Overrideprotected ProductInfo run() throws Exception {

System.out.println("调用接口查询商品数据,productId=" + productId);

if (productId == -1L) {throw new Exception();}String url = "http://localhost:8081/getProductInfo?productId=" + productId;String response = HttpClientUtils.sendGetRequest(url);return JSONObject.parseObject(response, ProductInfo.class);}@Overrideprotected ProductInfo getFallback() {ProductInfo productInfo = new ProductInfo();productInfo.setName("降级商品");return productInfo;}}

断路测试类

我们在测试类中,前 30 次请求,传入 productId=-1,然后休眠 3s,之后 70 次请求,传入 productId=1。

@SpringBootTest@RunWith(SpringRunner.class)public class CircuitBreakerTest {

@Test

public void testCircuitBreaker() {

String baseURL = "http://localhost:8080/getProductInfo?productId=";

for (int i = 0; i < 30; ++i) {

// 传入-1,会抛出异常,然后走降级逻辑

HttpClientUtils.sendGetRequest(baseURL + "-1");

}

TimeUtils.sleep(3);

System.out.println("After sleeping...");

for (int i = 31; i < 100; ++i) {

// 传入 1,走服务正常调用

HttpClientUtils.sendGetRequest(baseURL + "1");

}

}

}

测试结果

测试结果,我们可以明显看出系统断路与恢复的整个过程。

调用接口查询商品数据,productId=-1ProductInfo(id=null, name=降级商品, price=null,

pictureList=null, specification=null, service=null, color=null, size=null, shopId=null,

modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)// ...// 这里重复

打印了 20 次上面的结果

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)// ...// 这里重复打印了 8 次上面的结果

// 休眠 3s 后

调用接口查询商品数据,productId=1

ProductInfo(id=1, name=iphone7 手机, price=5599.0, pictureList=a.jpg,b.jpg,

specification=iphone7 的规格, service=iphone7 的售后服务, color=红色,白色,黑色, size=5.5,shopId=1, modifiedTime=2017-01-01 12:00:00, cityId=1, cityName=null, brandId=1,brandName=null)// ...// 这里重复打印了 69 次上面的结果

前 30 次请求,我们传入的 productId 为 -1,所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用,每次都抛异常并且走降级,21 次过后,断路器就被打开了。

之后的 9 次请求,都不会执行 run() 方法,也就不会打印以下信息。

调用接口查询商品数据,productId=-1

而是直接走降级逻辑,调用 getFallback() 执行。

休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 half-open 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。


9.深入 Hystrix 线程池隔离与接口限流

前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断,这一讲,我们来详细说说 Hystrix 的线程池隔离与接口限流。

Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求,直接 Reject 走降级,从而达到限流的作用。

限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为了避免过大的流量直接打死后端的服务。

线程池隔离技术的设计

Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。

舱壁隔离,是说将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,如此一来船舶在受损时,依然能具有足够的浮力和稳定性,进而减低立即沉船的危险。

Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。

Hystrix 应用线程池机制的场景

1.每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。

2.每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的thrift 依赖。

3.client 调用库随时会变更。

4.client 调用库随时可能会增加新的网络请求的逻辑。

5.client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。

6.client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。

7.在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。

8.即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。

9.有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。

10.大多数网络请求都是同步调用的。

11.调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。

简单来说,就是你必须默认 client 调用库很不靠谱,而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。

线程池机制的优点

 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。

 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。

 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。

 如果一个 client 调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机。

 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层。

简单来说,最大的好处,就是资源隔离,确保说任何一个依赖服务故障,不会拖垮当前的这个服务。

线程池机制的缺点

1.线程池机制最大的缺点就是增加了 CPU 的开销。除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。

2.每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。

3.Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。

我们可以用 Hystrix semaphore 技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。

semaphore 技术可以用来限流和削峰,但是不能用来对调研延迟的服务进行 timeout 和隔离。

execution.isolation.strategy 设置为 SEMAPHORE,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。

接口限流 Demo

假设一个线程池大小为 8,等待队列的大小为 10。timeout 时长我们设置长一些,20s。          在 command 内部,写死代码,做一个 sleep,比如 sleep 3s。

 withCoreSize:设置线程池大小

 withMaxQueueSize:设置等待队列大小

 withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。

如果只设置了线程池大小,另外两个 queue 相关参数没有设置的话,等待队列是处于关闭的状态。

public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {

private Long productId;

private static final HystrixCommandKey KEY =

HystrixCommandKey.Factory.asKey("GetProductInfoCommand");

public GetProductInfoCommand(Long productId) {

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))

.andCommandKey(KEY)

// 线程池相关配置信息

.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()

// 设置线程池大小为 8

.withCoreSize(8)

// 设置等待队列大小为 10

.withMaxQueueSize(10)

.withQueueSizeRejectionThreshold(12))

.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()

.withCircuitBreakerEnabled(true)

.withCircuitBreakerRequestVolumeThreshold(20)

.withCircuitBreakerErrorThresholdPercentage(40)

.withCircuitBreakerSleepWindowInMilliseconds(3000)

// 设置超时时间

.withExecutionTimeoutInMilliseconds(20000)

// 设置 fallback 最大请求并发数

.withFallbackIsolationSemaphoreMaxConcurrentRequests(30)));

this.productId = productId;

}

@Override

protected ProductInfo run() throws Exception {

System.out.println("调用接口查询商品数据,productId=" + productId);

if (productId == -1L) {

throw new Exception();

}

// 请求过来,会在这里 hang 住 3 秒钟

if (productId == -2L) {

TimeUtils.sleep(3);

}

String url = "http://localhost:8081/getProductInfo?productId=" + productId;

String response = HttpClientUtils.sendGetRequest(url);

System.out.println(response);

return JSONObject.parseObject(response, ProductInfo.class);

}

@Override

protected ProductInfo getFallback() {

ProductInfo productInfo = new ProductInfo();

productInfo.setName("降级商品");

return productInfo;

}

}

我们模拟 25 个请求。前 8 个请求,调用接口时会直接被 hang 住 3s,那么后面的 10 个请求会先进入等待队列中等待前面的请求执行完毕。最后的 7 个请求过来,会直接被 reject,调用 fallback 降级逻辑。

@SpringBootTest@RunWith(SpringRunner.class)public class RejectTest {

@Test

public void testReject() {

for (int i = 0; i < 25; ++i) {

new Thread(() ->

HttpClientUtils.sendGetRequest("http://localhost:8080/getProductInfo?productId=-2")).start()

;

}

// 防止主线程提前结束执行

TimeUtils.sleep(50);

}

}

从执行结果中,我们可以明显看出一共打印出了 7 个降级商品。这也就是请求数超过线程池+队列的数量

而直接被 reject 的结果。

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

调用接口查询商品数据,productId=-2

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

{"id": -2, "name": "iphone7 手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification":

"iphone7 的规格", "service": "iphone7 的售后服务", "color": "红色,白色,黑色", "size": "5.5",

"shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1}// 后面都是一些

正常的商品信息,就不贴出来了//...


10.基于 timeout 机制为服务接口调用超时提供安全保护

一般来说,在调用依赖服务的接口的时候,比较常见的一个问题就是超时。超时是在一个复杂的分布式系统中,导致系统不稳定,或者系统抖动。出现大量超时,线程资源会被 hang 死,从而导致吞吐量大幅度下降,甚至服务崩溃。

你去调用各种各样的依赖服务,特别是在大公司,你甚至都不认识开发一个服务的人,你都不知道那个人的技术水平怎么样,对那个人根本不了解。

Peter Steiner 说过,"On the Internet, nobody knows you're a dog",也就是说在互联网的另外一头,你都不知道甚至坐着一条狗。

像特别复杂的分布式系统,特别是在大公司里,多个团队、大型协作,你可能都不知道服务是谁的,很可能说开发服务的那个哥儿们甚至是一个实习生。依赖服务的接口性能可能很不稳定,有时候 2ms,有时候200ms,甚至 2s,都有可能。

如果你不对各种依赖服务接口的调用做超时控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了。大量的接口调用很慢,大量的线程被卡死。如果你做了资源的隔离,那么也就是线程池的线程被卡死,但其实我们可以做超时控制,没必要让它们全卡死。

TimeoutMilliseconds

在 Hystrix 中,我们可以手动设置 timeout 时长,如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。

TimeoutMilliseconds 默认值是 1000,也就是 1000ms。

HystrixCommandProperties.Setter()

        ..withExecutionTimeoutInMilliseconds(int)

TimeoutEnabled

这个参数用于控制是否要打开 timeout 机制,默认值是 true。

HystrixCommandProperties.Setter()

        .withExecutionTimeoutEnabled(boolean)

实例 Demo

我们在 command 中,将超时时间设置为 500ms,然后在 run() 方法中,设置休眠时间 1s,这样一个请求过来,直接休眠 1s,结果就会因为超时而执行降级逻辑。

public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {

private Long productId;

private static final HystrixCommandKey KEY =

HystrixCommandKey.Factory.asKey("GetProductInfoCommand");

public GetProductInfoCommand(Long productId) {

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))

.andCommandKey(KEY)

.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()

.withCoreSize(8)

.withMaxQueueSize(10)

.withQueueSizeRejectionThreshold(8))

.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()

.withCircuitBreakerEnabled(true)

.withCircuitBreakerRequestVolumeThreshold(20)

.withCircuitBreakerErrorThresholdPercentage(40)

.withCircuitBreakerSleepWindowInMilliseconds(3000)

// 设置是否打开超时,默认是 true

.withExecutionTimeoutEnabled(true)

// 设置超时时间,默认 1000(ms)

.withExecutionTimeoutInMilliseconds(500)

.withFallbackIsolationSemaphoreMaxConcurrentRequests(30)));

this.productId = productId;

}

@Override

protected ProductInfo run() throws Exception {

System.out.println("调用接口查询商品数据,productId=" + productId);

// 休眠 1s

TimeUtils.sleep(1);

String url = "http://localhost:8081/getProductInfo?productId=" + productId;

String response = HttpClientUtils.sendGetRequest(url);

System.out.println(response);

return JSONObject.parseObject(response, ProductInfo.class);

}

@Override

protected ProductInfo getFallback() {

ProductInfo productInfo = new ProductInfo();

productInfo.setName("降级商品");

return productInfo;

}

}

在测试类中,我们直接发起请求。

@SpringBootTest@RunWith(SpringRunner.class)public class TimeoutTest {

@Test

public void testTimeout() {

HttpClientUtils.sendGetRequest("http://localhost:8080/getProductInfo?productId=1");

}

}

结果中可以看到,打印出了降级商品相关信息。

ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null,

service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null,

brandId=null, brandName=null)

{"id": 1, "name": "iphone7 手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification":

"iphone7 的规格", "service": "iphone7 的售后服务", "color": "红色,白色,黑色", "size": "5.5",

"shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1}


上一篇下一篇

猜你喜欢

热点阅读