SpringCloud-Hoxton.SR1学习(2)
6. 服务注册发现Zookeeper
Zookeeper是一个分布式协调工具,可以实现注册中心功能
-
关闭Linux防火墙
systemctl stop firewalld
以启动Zookeeper服务器 -
启动Zookeeper 服务器
./zkServer.sh start
-
启动Zookeeper客户端
./zkCli.sh
创建一个Zookeeper客户端微服务的Module
- 新建cloud-provider-payment8004
- 修改其中的POM文件,之前是使用Eureka作为客户端来注册发现,现在需要注册到Zookeeper中
<!--SpringBoot整合Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--排除自带的zookeeper3.5.3-beta版本-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加Zookeeper3.5.7版本,保证和Linux上安装的版本一致 排除掉其中自带的Slf4j依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.7</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
- 修改yaml
server:
port: 8004
# 注册到Zookeeper服务器的支付微服务端口号
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql:///cloud?serverTimezone=UTC&useSSL=false
username: root
password: 123456
cloud:
zookeeper:
connect-string: 192.168.45.128:2181 # Zookeeper的IP:端口号
- 主程序类:添加注解==@EnableDiscoveryClient==将该微服务注册到注册中心中去
@SpringBootApplication(scanBasePackages = "com.hz")
@EnableDiscoveryClient // 该注解适用于Consul或者Zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(PaymentMain8004.class, args);
}
}
- Controller
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String servePort;
@GetMapping("/payment/zk")
public String paymentZk(){
return "Spring cloud with zookeeper: " + servePort + "\t" + UUID.randomUUID().toString();
}
}
- 启动主程序类并且注册到Zookeeper
注意:
- 由于spring-cloud-starter-zookeeper-discovery自带一个Zookeeper3.5.3-beta版本,需要排除掉这个版本,并且添加一个与Linux环境上所安装的Zookeeper版本一致的依赖,这里安装的是Zookeeper3.5.7
- 由于在pom文件中导入了lombok依赖,而Zookeeper中自带一个slf4j-log4j12的依赖,需要将这个依赖排除掉,否则一启动就会报错
SLF4J: Class path contains multiple SLF4J bindings.
启动主程序类,没有报错,查看在Zookeeper注册中心是否注册成功该服务cloud-provider-payment,注册成功之后会自动生成一个service流水号
该微服务即为一个znode节点存入到Zookeeper,保存的是json字符串
服务节点是临时节点还是持久节点?
存储的是临时节点,一旦服务宕机或者下限,那么就无法从服务中心中找到该服务,可以实现数据的一致性和分区容错性CP,但是高可用性降低
7. 服务注册发现Consul
1. 简介
Consul是一套开源的分布式服务发现和配置管理系统,基于Go实现的
提供了微服务系统的服务治理,配置中心,控制总线等功能。基于raft协议,支持健康检查,同时支持HTTP和DNS协议支持跨数据中心的广域网集群,提供图形界面跨平台,支持Linux,Mac,以及Windows
- Service Discovery服务发现:提供HTTP和DNS两种发现方式
- Health Checking
- KV Store
- Secure Service Communication
- Multi Datacenter
- 可视化界面:下载地址为
https://www.consul.io/downloads
2. 安装
consul --version 查看版本
consul agent -dev 启动consul
输入地址:localhost:8500查看是否启动成功
3. 创建Consul支付服务8006
- 新建Module支付服务provider8006
- POM:与Zookeeper支付微服务类似,只需要删除掉Zookeeper相关的依赖,并且导入consul-discovery的依赖即可
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
- YAML
server:
port: 8006
# 注册到Consul服务器的支付微服务端口号
spring:
application:
name: cloud-provider-payment-consul8006
# Consul server注册地址
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
- 主启动类:加上EnableDiscoveryClient注解
- 业务类Controller
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/consul")
public String paymentConsul(){
return "spring cloud consul: " + serverPort + "\t" + UUID.randomUUID().toString();
}
}
- 验证测试
localhost:8500
查看是否已经注册成功,http://8006/payment/consul查看controller能否正常执行
4. 创建Consul订单服务80
5. 三种注册中心的区别
组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | Spring Cloud集成 |
---|---|---|---|---|---|
Eureka | Java | AP(自我保护机制) | 可配支持 | HTTP | 已集成 |
Zookeeper | Java | CP | 支持 | 客户端(没有web界面) | 已集成 |
Consul | Go | CP | 支持 | HTTP/DNS | 已集成 |
最多只能满足CAP其中两个,CAP核心思想:关注粒度是数据而不是整体系统设置,一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性三个需求
因此,根据CAP理论将NoSQL数据库分成了满足AP,CP,以及AC三大类:
AC----单点集群,满足一致性,可用性的系统,通常在扩展性上较为欠缺
CP----满足一致性和分区容忍性的系统,性能不是很高
AP----满足可用性和分区容忍性的系统,对一致性要求较低
8. 负载均衡服务调用Ribbon
1. 简介
Ribbon是基于Netfix Ribbon实现的一套==客户端负载均衡工具==
主要功能是:提供==客户端的软件负载均衡算法和服务调用==,Ribbon客户端组件提供一系列配置项如连接超时,重试等。也就是在配置文件中列出Load Balance后面的所有机器,Ribbon会自动基于某种规则(如简单轮询,随机连接等)去连接机器,可以使用Ribbon实现自定义的负载均衡算法
Ribbon目前进入维护模式
2. Load Balance是什么
负载均衡:将用户的请求平摊到多个服务器上,从而达到系统的HA(高可用)
常见的负载均衡软件有Nginx,LVS,F5等
Ribbon本地负载均衡客户端与Nginx服务端负载均衡的区别
- Nginx是服务器负载均衡(集中式的LB),客户端所有的请求都会交给Nginx,然后由Nginx实现转发请求,负载均衡由服务器端实现
- Ribbon是客户端负载均衡(进程内的LB),在调用微服务接口时,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
进程内的LB:将LB逻辑集成到消费方,消费方从服务注册中心获取有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon属于进程内LB,只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
集中式的LB:在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该设施负责将访问请求通过某种策略发送至服务的提供方
3. 使用
Ribbon其实就是一个软负载均衡的客户端组件,可以和其他所需请求的客户端结合使用,和Eureka结合仅仅是其中的一个实例
工作流程:
- 选择EurekaServer,优先选择在同一个区域内负载较少的Server
- 根据用户指定的策略,从Server获取到的服务注册列表中选择一个地址
RestTemplate的使用
getForObject/getForEntitiy
方法
getForObject:返回对象为响应体中的数据转换为JSON格式的对象
getForEntitiy:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,如响应头,响应状态码,响应体等
- postForObject/postForEntity
- GET
- POST
4. Ribbon的负载规则
查看Ribbon下的IRule接口以及实现类,主要的负载规则有
1. RoundRobinRule:轮询
2. RandomRule:随机
3. RetryRule:先按照轮询策略获取服务,如果获取失败则在指定时间内进行重试,在获取可用的服务
4. WeightedResponseTimeRule:轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择
5. BestAvailableRule:先过滤掉由于多次访问故障而处于断路状态的服务,然后选择并发量最小的服务
6. AvailabilityFilteringRule:过滤掉故障实例,再选择并发量最小的实例
7. ZoneAvoidanceRule:默认规则,复合判断server所在的区域性能和server的可用性去选择服务器
Ribbon负载规则的替换
- 在订单微服务(客户端)自定义类MyRibbonRule,并且不能在SpringBootApplication注解中的ComponentScan所扫描的范围内
- 在主程序类上增加注解==@RibbonClient(name = “CLOUD-PAYMENT-SERVICE”, configuration = MyRibbonRule.class)==,表示从CLOUD-PAYMENT-SERVICE中获取服务,使用自定义的规则类,不再使用默认规则
@Configuration
public class MyRibbonRule {
@Bean
public IRule myRandomRule(){
return new RandomRule();
}
// @Bean
// public IRule myBestAvailableRule(){
// return new BestAvailableRule();
// }
}
5. Ribbon默认负载轮询算法
rest接口第几次请求参数 % 服务器集群总数量(提供某个服务的实例数) = 实际调用服务器位置下标
每次服务重启后rest接口会从1开始计数,从1开始递增
获取该服务的所有实例数:List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
instances[0] = 127.0.0.1:8001,instances[1] = 127.0.0.1:8002
8001+8002组合成集群,集群总数为2,按照轮询算法:
当请求次数为1,1%2=1,对应下标位置为1,获取服务器地址为127.0.0.1:8002
当请求次数为2,2%2=0,对应下标位置为0,获取服务器地址为127.0.0.1:8001 以此类推…
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
// 通过自旋得到当前需要哪个微服务的位置下标
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
自定义轮询算法并使用该规则
- 在订单模块中自定义MyLoadBalancer接口
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
public interface MyLoadBalancer {
ServiceInstance getInstance(List<ServiceInstance> serviceInstances);
}
- 自定义该接口的实现类
@Component
public class MyLB implements MyLoadBalancer{
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){
int cur;
int next;
do{
cur = this.atomicInteger.get();
next = cur >= Integer.MAX_VALUE ? 0 : cur+1;
}while (!this.atomicInteger.compareAndSet(cur, next));
System.out.println("第:" + next + "次访问");
return next;
}
@Override
public ServiceInstance getInstance(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
ServiceInstance instance = serviceInstances.get(index);
if(instance == null){
try {
throw new Exception("get service failed");
} catch (Exception e) {
e.printStackTrace();
}
}
return instance;
}
}
- 在CLOUD-PAYMENT-SERVICE微服务的两个实例中的controller中增加一个方法,用于返回当前使用哪一个实例(端口号)
@GetMapping("/payment/lb")
public String getPaymentLB(){
return "当前提供的微服务实例端口:" + serverPort;
}
- 在订单模块中的controller方法中增加方法,使用自定义的轮询获取当前所提供的服务实例是哪一个
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;
@Autowired
private MyLoadBalancer loaderHandler;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/payment/lb")
public String getPaymentMyLB(){
// 获取该微服务下的所有实例
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size() <= 0){
return null;
}
// 根据微服务实例列表通过自定义的轮询得到当前需要访问的实例是哪一个
ServiceInstance service = loaderHandler.getInstance(instances);
URI uri = service.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
- 测试,根据http://localhost/consumer/payment/lb去访问当前提供的服务实例是哪一个,并且是否是否符合轮询的要求,8001和8002每次切换
9. 服务接口调用OpenFeign
1. 简介
Fegin是一个声明式WebService客户端,使用方法是定义一个接口然后再此基础上添加注解。Spring CLoud对其进行了封装,使其支持Spring MVC标准注解和HttpMessageConverters。另外,Fegin也可以和Eureka,Ribbon组合使用达到负载均衡
Fegin的作用
前面使用Ribbon+restTemplate结合时,利用RestTemplate对Http请求的封装,形成了一套模板化的调用方式,但是在实际开中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对个每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Fegin在此基础上作进一步封装,由他来帮助我们实现定义和实现依赖服务接口的定义。在Fegin的实现下,只需要创建一个接口并使用注解的方式来配置他(以前是在Dao接口上使用Mapper注解,现在是一个微服务接口上面标注一个Fegin注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
创建一个微服务接口,并且添加与OpenFeign相关的注解,即可实现接口之间的调用
Feign集成了Ribbon
利用Ribbon维护了PAYMENT服务列表信息,并且通过轮询实现了客户端的负载均衡,与Ribbon不同的是,通过Fegin只需要要定义服务绑定接口并以声明式的方法,即可实现服务调用
2. Feign和OpenFeign的区别
Feign | OpenFeign |
---|---|
Fegin是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,Fegin内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Fegin的使用方式:使用Fegin的注解定义接口,调用这个接口就可以调用服务注册中心的服务 | OpenFegin是Spring Cloud在Fegin的基础上支持了Spring MVC的注解,如RequestMapping注解。OpenFegin的FeginClient注解可以解析SpringMVC的RequestMapping注解下的接口,并通过动态代理的方式得到实现类,实现类用于负载均衡并调用其他服务 |
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> | <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> |
3. 使用
-
创建cloud-order-openFeign80工程
-
主程序类
@SpringBootApplication @EnableFeignClients // 表示当前类是一个Feign客户端 public class OrderMainFeign80 { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(OrderMainFeign80.class, args); } }
-
配置文件
server: port: 80 # 注册到Consul服务器的订单微服务端口号 spring: application: name: cloud-order-openFeign80 datasource: type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型 driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 url: jdbc:mysql:///cloud?serverTimezone=UTC&useSSL=false username: root password: 123456 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
-
FeignService层:使用注解FeignClient并且指明获取哪个微服务地址,抽象方法与PAYMENT微服务中的service接口保持一致,注意这里的返回值类型与调用service层方法的返回值泛型保持一致
@Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") // 获取哪个微服务的地址 public interface PaymentFeignService { // 注意这里返回值类型必须与需要调用的某个微服务的该方法泛型保持一致 @GetMapping("/payment/get/{id}") // 对应该方法的uri CommonResult<Payment> getPaymentById(@PathVariable("id") Long id); @GetMapping("/payment/selectall") CommonResult getAllPayment(); @PostMapping("/payment/save") CommonResult save(@RequestBody Payment payment); }
-
controller层
@RestController @Slf4j public class orderFeignController { @Resource private PaymentFeignService service; @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){ return service.getPaymentById(id); } @GetMapping("/consumer/payment/getAll") public CommonResult<Payment> getAllPayment(){ return service.getAllPayment(); } // 使用RequestBody注解表示使用json数据进行传输 @PostMapping("/consumer/payment/save") public CommonResult save(@RequestBody Payment payment){ CommonResult res = service.save(payment); log.info("插入记录数:" + res); return res; } }
-
测试
调用controller层的getById方法,输出结果如下,并且其中已经内置了轮询算法
4. OpenFeign的超时控制
默认Feign客户端只等待1s,但是服务器端处理需要超过1s,导致Feign客户端直接放弃等待,直接返回报错信息。为了避免这种情况,需要设置Feign客户端的超时控制
在cloud-order-openFeign80工程的配置文件中开启配置
# 设置Feign客户端超时时间 OpenFeign默认支持Ribbon
ribbon:
ReadTimeout: 5000 # 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用时间
ConnectTimeoue: 5000 # 建立连接后从服务器读取到可用资源所用的时间
5. 日志增强
Feign提供了日志打印功能,可以通过配置来调整日志级别,从而了解Feign中Http请求的细节,也就是对Feign接口的调用情况进行监控和输出
日志级别:
- **NONE:**默认的,不显示任何日志
- **BASIC:**记录请求方法,URL,响应状态码以及执行时间
- **HEADERS:**除了BASIC中定义的信息,还有请求和响应的头信息
- **FULL:**除了HEADERS中定义的头信息之外,还有请求和响应的正文以及元数据
在cloud-order-openFeign80工程定义配置类
@Configuration
public class LoggerConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
在在cloud-order-openFeign80工程的配置文件中指明对哪个接口进行日志监控
# Feign日志以什么级别去监控接口
logging:
level:
com.hz.openfeign.service.PaymentFeignService: debug
10. 服务降级Hystrix
1. 简介
分布式系统面临的问题:复杂分布式体系结构中的应用程序中有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等。Hystrix能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
通过断路器的故障监控,向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,甚至雪崩
2. Hystrix中几个重要概念
-
服务降级fallback:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,出现以下情况会出现服务降级
- 程序运行异常
- 超时
- 服务器宕机
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
-
服务熔断break:达到最大服务访问后,直接拒绝访问,然后调用服务降级方法并返回友好提示
-
服务限流:秒杀高并发操作,请求进行排队,有序进行
3. 使用以及JMeter高并发测试
a.hystrix-payment8001微服务
b. JMeter高并发测试
开启JMeter,2w个并发请求都去访问paymentInfo_timeout服务,瞬间压垮8001端口
此时Tomcat的默认工作线程都会去处理该服务,没有多余的线程来分解压力和处理paymentInfo_success服务,导致该服务也会在等待响应中
结论:JMeter测试仅仅针对于服务提供者8001本身,假如此时外部的消费者80端口也来进行访问,那么消费者只能等待,最终80端口服务无法得到满足
c. 创建消费者80微服务(详情见cloud-consumer-hystrix-openfeign-order80)
使用2w个线程去访问8001,消费者80微服务再去访问正常的hystrix-payment微服务8001地址,要么页面在转圈圈等待,要么消费者端报超时错误
4. 解决方案
解决的要求:
- 超时导致服务器处理变慢(页面一直在转圈) --> 不应该返回ErrorPage,可以选择不再等待的友好界面
- 出错(宕机或者程序运行出错) --> 出错的处理逻辑
解决思路:
- 服务8001超时了,调用者80不能一直卡死等待,必须有服务降级
- 服务8001宕机了,调用者80不能一直卡死等待,必须有服务降级
- 服务8001正常运行,调用者80自身出现故障或者有自我要求(自己的等待时间小于服务提供者),自己处理服务降级
a. 服务降级
-
8001服务自身的问题:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有对应的方法进行处理,作为服务降级fallback
-
业务类启用注解==HystrixCommand==,报异常后如何处理
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "3000")}) // 设置运行的最大等待时间为3s,超过3s进行服务降级,运行paymentInfo_TimeoutHandler方法 public String paymentInfo_timeout(Integer id){ // int i = 10 / 0; 故意运行出错 try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "Thread pool: " + Thread.currentThread().getName() + " paymentInfo_id: " + id + " time out"; } public String paymentInfo_TimeoutHandler(Integer id){ return "Thread pool: " + Thread.currentThread().getName()+"\t 系统繁忙或者运行报错, id: " + id+" ErrorHandler"; }
-
主启动类激活,添加注解==EnableCircuitBreaker==,测试8001微服务自身超时异常情况的fallback
-
其次,对于80订单微服务,进行客户端的服务降级保护
- 主配置类中开启==EnableHystrix==注解
@SpringBootApplication @EnableFeignClients @EnableHystrix public class HystrixOrderMain { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(HystrixOrderMain.class, args); } }
- yaml
feign: hystrix: enabled: true
- controller
// 设置客户端运行时间的上限,超过2s或者程序运行错误执行服务降级paymentInfoTimeoutHandler @GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentInfoTimeoutHandler", commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000")}) public String paymentInfo_timeout(@PathVariable("id") Integer id){ // int i = 10 / 0; String res = service.paymentInfo_timeout(id); return res; } public String paymentInfoTimeoutHandler(Integer id){ return "openfeign-hystrix-order80,client server run time over limit or runTime Exception o(-<>-)o"; }
注意:对于每个方法配置一个服务降级方法,代码量太大,除了个别重要核心业务有专属的服务降级,其他的可通过注解**DefaultProperties(defaultFallback = “GlobalFallback”)**统一跳转到全局服务降级方法。将通用的和独享的分开,避免代码冗余,减少代码量,仅仅是使用注解HystrixCommand,没有配置任何参数,出现异常则执行默认的全局服务降级方法,指明了则执行对应的服务降级方法
import com.hz.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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
@DefaultProperties(defaultFallback = "GlobalFallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService service;
@GetMapping("/consumer/payment/hystrix/success/{id}")
@HystrixCommand // 没有特别指明就是用统一的服务降级DefaultProperties
public String paymentInfo_success(@PathVariable("id") Integer id){
int i=10/0;
String res = service.paymentInfo_success(id);
return res;
}
// 设置客户端运行时间的上限,超过2s或者程序运行错误执行服务降级paymentInfoTimeoutHandler
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfoTimeoutHandler",
commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "2000")})
public String paymentInfo_timeout(@PathVariable("id") Integer id){
// int i = 10 / 0;
String res = service.paymentInfo_timeout(id);
return res;
}
public String paymentInfoTimeoutHandler(Integer id){
return "openfeign-hystrix-order80,client server run time over limit or runTime Exception o(-<>-)o";
}
// 全局服务降级GlobalFallbackMethod
public String GlobalFallbackMethod(){
return "GlobalFallback Method running...";
}
}
服务降级,客户端调用服务端时服务端宕机或关闭
本次服务降级处理是在客户端80实现的,与服务端8001没有关系,只需要为Feign客户端接口添加一个服务降级处理的实现类即可实现解耦
注意该方法只能适用于客户端连接服务器端时服务器端宕机,对于程序本身的错误是不能进行使用的
// Hystrix之通配服务降级FeignFallback, 当服务器宕机或者关闭时调用改接口的实现类中的fallback方法进行服务降级
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = OrderFallbackService.class)
public interface OrderHystrixService {
@Component
public class OrderFallbackService implements OrderHystrixService {
@Override
public String paymentInfo_success(Integer id) {
return "OrderFallbackService paymentInfo_success exec...Server probably is died";
}
@Override
public String paymentInfo_timeout(Integer id) {
return "OrderFallbackService paymentInfo_timeout exec...Server probably is died";
}
}
b. 服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制,当某个微服务出错不可用或者响应时间太长,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路
熔断机制通过Hystrix实现,当失败的调用到一定阈值,默认是5s内20次调用失败,会启动熔断机制,使用的注解是**HystrixCommand**
服务的降级 -> 进而熔断 -> 恢复调用链路
- 修改hystrix-payment8001微服务
- 在PaymentHystrixService中增加方法,并且配置参数
- 在PaymentHystrixController中调用service中的方法
- 测试:输入正确的id以及输入错误的id,连续不断的输入错误的id并且测试,再使用正确的id,发现刚开始即使输入正确的id也会报错,慢慢的恢复正常
总结:
熔断类型分为三种
- 熔断打开open:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
- 熔断关闭closed:不会对服务进行熔断,正常调用
- 熔断半开half-open:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
三个重要参数:快照时间窗,请求总数阈值,错误百分比阈值
- **sleepWindowInMilliseconds:**断路器确定是否打开统计一些请求和错误数据,而统计的时间范围就是时间快照,默认为最近的10s
- **requestVolumeThreshold:**在快照时间窗口内,必须满足请求总数阈值才有资格熔断,默认为20,也就是说在10s内如果Hystrix命令调用次数不足20次,即使所有请求都超时或者其他原因失败,断路器都不会打开
- **errorThresholdPercentage:**当请求总数在快照时间窗口内超过了阈值(默认50%),如发生30次调用,有15次发生了超时异常,那么断路器就会打开
断路器开启或关闭的条件
- 当满足一定的阈值(默认10s内超过20个请求)
- 当失败率达到一定阈值(默认10s内超过50%的请求访问失败)
- 满足以上两个阈值,断路器开启
- 开启时,所有请求都不会进行转发
- 一段时间以后(默认5s),此时断路器处于半开状态,允许其中的部分请求进行转发,如果成功,断路器会关闭,否则继续处于开启状态
所有Hystrix的参数配置如下图
c. 服务限流使用SpringCloud Alibaba中的Sentinel(见后续…)
d. Hystrix图形化Dashboard
-
新建cloud-consumer-hystrix-dashboard9001
-
POM:spring-cloud-starter-netflix-hystrix-dashboard
-
YAML:server.port=9001
-
主程序类+新注解EnableHystrixDashboard
-
所有Provider微服务提供者(8001,8002,8003)都需要监控依赖配置spring-boot-starter-actuator
-
启动cloud-consumer-hystrix-dashboard9001测试监控微服务8001
-
如果想要监控某个微服务,需要在该微服务的主程序类中加上关于Servlet的配置
@SpringBootApplication
@EnableEurekaClient // 注册到Eureka Server
@EnableCircuitBreaker // 开启Hystrix服务熔断的支持
public class HystrixPaymentMain {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(HystrixPaymentMain.class, args);
}
// 在主启动类中指定监控路径的配置 仅仅是为了服务监控而配置 由于SpringCloud升级后的ServletRegistrationBean的默认路径不是
// "/hystrix.stream", 需要在所需要监控的服务中配置下面的Servlet
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
开启Eureka7001,Hystrix-payment8001以及cloud-consumer-hystrix-dashboard9001三个微服务,输入链接http://localhost:9001/hystrix
监控页
输入所需要监控的微服务地址http://localhost:8001/hystrix.stream,延迟默认2000ms,Title使用T3。访问8001的服务,查看监控