章
目
录
本节我们主要演示我们原先的分布式系统由于某些接口被高并发访问会导致其他正常的接口也会被拖慢,甚至导致超时报错的线下。为我们引入Hystrix做准备。下面我们要准备好Hystrix案例的演示环境,这里我们将之前的项目只启用cloud-eureka-server7001
,并恢复为单机版,这里就不多说了,只需要修改yml配置,即:
#单机版 defaultZone: http://eureka7001.com:7001/eureka/
接下来,我们开始新建名为cloud-provider-hystrix-payment8001
模块(去除了数据库相关的配置)。
第1步:创建Module
在mscloud
下新建名为cloud-provider-hystrix-payment8001
模块
第2步:修改pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>mscloud</artifactId> <groupId>com.panziye.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-hystrix-payment8001</artifactId> <dependencies> <!-- eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 引入自定义的api通用包,可以使用支付Payment等Entity --> <dependency> <groupId>com.panziye.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
第3步:写yml
application.yml
如下:
server: port: 8001 spring: application: name: cloud-payment-hystrix-service eureka: client: #表示是否将自己注册进eureka服务中心,默认true register-with-eureka: true #表示是否从EurekaServer抓取已有注册信息,默认true。单节点无所谓,集群必须设置true才能配合ribbon使用负载均衡 fetch-registry: true service-url: #指向eureka集群 defaultZone: http://eureka7001.com:7001/eureka
第4步:主启动类
新建PaymentHystrixMain8001
主启动类:
package com.panziye.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } }
第5步:业务类
1)新建接口PaymentService
package com.panziye.springcloud.service; public interface PaymentService { String paymentOk(Long id); String paymentTimeout(Long id); }
2)新建实现类:
package com.panziye.springcloud.service.impl; import com.panziye.springcloud.service.PaymentService; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class PaymentServiceImpl implements PaymentService { //演示成功 @Override public String paymentOk(Long id) { return "当前线程:"+Thread.currentThread().getName()+"\t paymentOk,id="+id; } //演示超时 @Override public String paymentTimeout(Long id) { int num = 3; try { //睡眠num秒 TimeUnit.SECONDS.sleep(num); } catch (InterruptedException e) { e.printStackTrace(); } return "当前线程:"+Thread.currentThread().getName()+"\t paymentTimeout,id="+id; } }
3)新建controller
新建PaymentController
:
package com.panziye.springcloud.controller; import com.panziye.springcloud.service.PaymentService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @GetMapping(value = "/payment/ok/{id}") public String paymentOk(@PathVariable("id") Long id) { return paymentService.paymentOk(id); } @GetMapping(value = "/payment/timeout/{id}") public String paymentTimeout(@PathVariable("id") Long id) { return paymentService.paymentTimeout(id); } }
第6步:测试
启动7001和8001,测试访问,我们发现:
- 请求
http://localhost:8001/payment/ok/1
速度非常快(浏览器基本不转圈) - 请求
http://localhost:8001/payment/timeout/1
会等待几秒钟才有结果
1)说明我们环境搭建的没问题,接下来我们提供Jmeter
测试工具对timeout
请求进行压力测试,如果你没有Jmeter,请参考:
文章目录 一、什么是Jmeter 二、Jmeter下载 二、Jmeter安装与运行 一、什么是Jmeter A […]
2)我们添加线程组,如下200个线程循环发送100次(即20000次并发压力测试),然后保存
3)我们在该线程组下添加取样器->Http请求
,修改如下:
4)运行该测试后,立即再去浏览器请求之前的两个接口,发现两个请求都转圈,原先非常快的ok接口也变慢了。
5)压力测式结论:上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死。
第7步:新建消费端
我们新建名为cloud-consumer-feign-hystrix-order80
消费端模块,参考cloud-consumer-feign-order80
,我们的具体实现如下:
1)pom.xml
,新增了hystrix依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>mscloud</artifactId> <groupId>com.panziye.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-feign-hystrix-order80</artifactId> <dependencies> <!-- OpenFign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 引入自定义的api通用包,可以使用支付Payment等Entity --> <dependency> <groupId>com.panziye.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
2)application.yml
:
server: port: 80 spring: application: name: cloud-order-hystrix-service eureka: client: #表示是否将自己注册进eureka服务中心,默认true register-with-eureka: false #表示是否从EurekaServer抓取已有注册信息,默认true。单节点无所谓,集群必须设置true才能配合ribbon使用负载均衡 fetch-registry: true service-url: #指向eureka注册中心 defaultZone: http://eureka7001.com:7001/eureka
3)主启动OrderHystrixMain80
package com.panziye.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class,args); } }
4)业务类
a)service接口OrderService
:
package com.panziye.springcloud.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Component @FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE") public interface OrderService { @GetMapping(value = "/payment/ok/{id}") public String paymentOk(@PathVariable("id") Long id); @GetMapping(value = "/payment/timeout/{id}") public String paymentTimeout(@PathVariable("id") Long id); }
5)新建OrderController
package com.panziye.springcloud.controller; import com.panziye.springcloud.service.OrderService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class OrderController { @Resource private OrderService orderService; @GetMapping(value = "/consumer/payment/ok/{id}") public String paymentOk(@PathVariable("id") Long id) { String result = orderService.paymentOk(id); return result; } @GetMapping(value = "/consumer/payment/timeout/{id}") public String paymentTimeout(@PathVariable("id") Long id){ String result = orderService.paymentTimeout(id); return result; } }
第8步:压力测试消费端
我们在用Jmeter去压力测试timeout
端口,同时浏览器访问消费端ok
接口http://localhost/consumer/payment/ok/1
,发现同样要么被拖慢了转圈圈,要么直接超时报错(如果你的测试没报错就加大线程数,我加到400直接超时):
我们在学OpenFeign时都知道,OpenFeign默认等待响应时间为1秒,而我们的ok请求被timeout接口拖慢,导致超时报错。
正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生
第9步:如何解决?
1)超时导致服务器变慢(转圈)
超时不再等待
2)出错(宕机或程序运行出错)
出错要有兜底
- 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001 ) 宕机了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001 )正常,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级