Spring Cloud(2020版) 学习笔记(二)
- 30.Hystrix是什么
- 31.Hystrix停更进维
- 32. Hystrix的服务降级熔断限流概念初讲
- 33.Hystrix支付微服务修改
- 34.Hystrix订单微服务修改
- 35.降级容错解决的维度要求
- 36.Hystrix之服务降级支付侧fallback
- 37.Hystrix之服务降级订单侧fallback
- 38.Hystrix之全局服务降级DefaultProperties
- 39.Hystrix之通配服务降级FeignFallback
- 40.Hystrix之服务熔断
- 41.Hystrix之服务熔断
- 42. Hystrix之服务熔断总结
- 43.Hystrix图形化Dashboard
- 44.GateWay是什么
- 45. GateWay非阻塞异步模型
- 46.Gateway工作流程
- 47.Gateway9527搭建
- 48.Gateway配置路由的两种方式
- 49.GateWay配置动态路由
- 50.GateWay常用的Predicate
- 51.GateWay的Filter
- 52.Config分布式配置中心介绍
- 53.Config配置总控中心搭建
- 54.Config客户端配置与测试
- 56.Config动态刷新之手动版
30.Hystrix是什么
- 概述
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
- 服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后避依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
- Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
31.Hystrix停更进维
-
能干嘛
- 服务降级
- 服务熔断
- 接近实对的监控
- …
-
官网资料
- Hystrix官宣,停更进维
32. Hystrix的服务降级熔断限流概念初讲
- 服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
- 哪些情况会出发降级
程序运行导常
超时
服务熔断触发服务降级
线程池/信号量打满也会导致服务降级
- 服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务的降级 -> 进而熔断 -> 恢复调用链路
- 服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
33.Hystrix支付微服务修改
- 修改cloud-provider-payment的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>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment</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.chenyc</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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <!– SpringBoot整合zookeeper客户端 –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>-->
<!-- <!–先排除自带的zookeeper3.5.3 防止与3.4.9起冲突–>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.apache.zookeeper</groupId>-->
<!-- <artifactId>zookeeper</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<!-- <!–添加zookeeper3.4.9版本–>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.zookeeper</groupId>-->
<!-- <artifactId>zookeeper</artifactId>-->
<!-- <version>3.4.12</version>-->
<!-- </dependency>-->
<!-- <!–SpringCloud consul-server –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>-->
<!-- </dependency>-->
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
</project>
- 修改yaml,将eureka集群改成单机,每次都启用集群太麻烦
cloud-eureka-server的yaml
spring:
profiles:
active: eureka7001
---
server:
port: 7001
spring:
profiles: eureka7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 单机版本
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群版本,机器配置不行,只启动2台eureka,组成集群
# defaultZone: http://eureka7002.com:7002/eureka/
server:
enable-self-preservation: false # 设为false,关闭自我保护
#清理间隔(单位毫秒,默认是0,不清理)启用主动失效,并且每次主动失效检测间隔为3s
eviction-interval-timer-in-ms: 3000
---
server:
port: 7002
spring:
profiles: eureka7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 单机版本
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群版本,机器配置不行,只启动2台eureka,组成集群
# defaultZone: http://eureka7002.com:7002/eureka/
server:
enable-self-preservation: false # 设为false,关闭自我保护
#清理间隔(单位毫秒,默认是0,不清理)启用主动失效,并且每次主动失效检测间隔为3s
eviction-interval-timer-in-ms: 3000
修改cloud-provider-payment的yaml
spring:
profiles:
active: payment8001
---
# 8001和8002测试用eureka作为注册中心
server:
port: 8001
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://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8001
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
server:
port: 8002
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://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
# 8003测试用zookeeper作为注册中心
server:
port: 8003
spring:
profiles: payment8003
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 172.18.8.112:2181
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
# 8004测试用consul作为注册中心
server:
port: 8004
spring:
profiles: payment8004
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- 业务类
service
package com.chenyc.springcloud.service;
import com.chenyc.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;
public interface PaymentService
{
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
public String paymentInfo_OK(Integer id);
public String paymentInfo_TimeOut(Integer id);
}
PaymentServiceImpl
package com.chenyc.springcloud.service.impl;
import com.chenyc.springcloud.dao.PaymentDao;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService
{
@Resource
PaymentDao paymentDao;
public int create(Payment payment)
{
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id)
{
return paymentDao.getPaymentById(id);
}
public String paymentInfo_OK(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
public String paymentInfo_TimeOut(Integer id)
{
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
}
}
controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
/*********************************************zk注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
/*********************************************consul注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
/*********************************************自定义负载均衡测试*************************************************************************/
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;//返回服务接口
}
/*********************************************hystrix测试*************************************************************************/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_OK(id);
log.info("*****result: "+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result: "+result);
return result;
}
}
-
测试
http://localhost:8001/payment/hystrix/ok/1
http://localhost:8001/payment/hystrix/timeout/1 -
Jmeter压测测试
开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
1.测试计划中右键添加-》线程-》线程组(线程组202102,线程数:200,线程数:100,其他参数默认)
2.刚刚新建线程组202102,右键它-》添加-》取样器-》Http请求-》基本 输入http://localhost:8001/payment/hystrix/ok/1
3.点击绿色三角形图标启动。
看演示结果:拖慢,原因:tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。
34.Hystrix订单微服务修改
- 修改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>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<dependencies>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- <!– SpringBoot整合zookeeper客户端 –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>-->
<!-- <!–先排除自带的zookeeper–>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.apache.zookeeper</groupId>-->
<!-- <artifactId>zookeeper</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<!-- <!–添加zookeeper3.4.9版本–>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.zookeeper</groupId>-->
<!-- <artifactId>zookeeper</artifactId>-->
<!-- <version>3.4.9</version>-->
<!-- </dependency>-->
<!-- <!–SpringCloud consul-server –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>-->
<!-- </dependency>-->
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
</project>
- 修改yaml
spring:
profiles:
active: orderEureka
---
#将order服务注册到eureka的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderEureka
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
logging:
level:
# feign日志以什么级别监控哪个接口
com.chenyc.springcloud.openFeign.PaymentFeignService: debug
---
#将order服务注册到zookeeper的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderZookeeper
cloud:
zookeeper:
connect-string: 172.18.8.112:2181
---
#将order服务注册到consul的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderConsul
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
- 启动类,放开了之前为了测试自定义轮询规则注释的@RibbonClient,不过注释不注释不影响现在feign的调用
package com.chenyc.springcloud;
import com.chenyc.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
@EnableEurekaClient//<--- Enable服务注册添加该标签
@EnableDiscoveryClient//<--- zookeeper服务注册添加该标签
//添加到此处
@RibbonClient(name = "CLOUD-PROVIDER-PAYMENT", configuration = MySelfRule.class)
@EnableFeignClients
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- controller修改
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.lb.LoadBalancer;
import com.chenyc.springcloud.openFeign.PaymentFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper和consul远程调用的地址
// public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
PaymentFeignService paymentFeignService;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
@GetMapping(value = "/payment/consul")
public String paymentInfoConsul()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/consul",String.class);
return result;
}
@GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENY_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();//getForObject()
}else{
return new CommonResult<>(444,"操作失败");
}
}
/**********************************************自定义轮询*************************************************************************/
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
/**********************************************openFeign*************************************************************************/
@GetMapping(value = "/payment/openFeign/get/{id}")
public CommonResult<Payment> openFeign(@PathVariable("id") Long id)
{
return paymentFeignService.getPaymentById(id);
}
/**********************************************hystrix*************************************************************************/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentFeignService.paymentInfo_OK(id);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentFeignService.paymentInfo_TimeOut(id);
return result;
}
}
- 放开了注释@LoadBalanced的注解,不过不影响本次feign测试调用
package com.chenyc.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:33
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced//使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 测试
http://localhost/consumer/payment/hystrix/ok/1
http://localhost/consumer/payment/hystrix/timeout/1
35.降级容错解决的维度要求
超时导致服务器变慢(转圈) - 超时不再等待
出错(宕机或程序运行出错) - 出错要有兜底
解决:
- 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级。
- 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级。
- 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级。
36.Hystrix之服务降级支付侧fallback
- 修改主启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
@EnableCircuitBreaker//添加到此处
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- 修改业务类
package com.chenyc.springcloud.service.impl;
import com.chenyc.springcloud.dao.PaymentDao;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService
{
@Resource
PaymentDao paymentDao;
public int create(Payment payment)
{
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id)
{
return paymentDao.getPaymentById(id);
}
public String paymentInfo_OK(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler"/*指定善后方法名*/,commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")
})
public String paymentInfo_TimeOut(Integer id)
{
//int age = 10/0;
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
}
//用来善后的方法
public String paymentInfo_TimeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";
}
}
- 测试
故意制造异常
- 我们能接受2秒钟,它运行3秒钟,超时异常。
- int age = 10/0,计算异常,得到同样结果
37.Hystrix之服务降级订单侧fallback
- 修改yaml文件
spring:
profiles:
active: payment8001
---
# 8001和8002测试用eureka作为注册中心
server:
port: 8001
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://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8001
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
#开启
feign:
hystrix:
enabled: true
---
server:
port: 8002
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://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
#开启
feign:
hystrix:
enabled: true
---
# 8003测试用zookeeper作为注册中心
server:
port: 8003
spring:
profiles: payment8003
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 172.18.8.112:2181
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
# 8004测试用consul作为注册中心
server:
port: 8004
spring:
profiles: payment8004
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- 修改启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
@EnableCircuitBreaker//添加到此处
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- 修改controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
/*********************************************zk注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
/*********************************************consul注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
/*********************************************自定义负载均衡测试*************************************************************************/
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;//返回服务接口
}
/*********************************************hystrix测试*************************************************************************/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_OK(id);
log.info("*****result: "+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result: "+result);
return result;
}
}
- 测试方法同上
制造两种错误,超时和报错
38.Hystrix之全局服务降级DefaultProperties
除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = “”)统一跳转到统一处理结果页面通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量
- 修改controller
import com.lun.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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 = "payment_Global_FallbackMethod")
public class OrderHystirxController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
// @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
// })
@HystrixCommand//用全局的fallback方法
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
// 下面是全局fallback方法
public String payment_Global_FallbackMethod()
{
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
39.Hystrix之通配服务降级FeignFallback
已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackServiceImpl)实现该接口,统一为接口里面的方法进行异常处理
- 业务类
package com.chenyc.springcloud.hystrix;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.openFeign.PaymentFeignService;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackServiceImpl implements PaymentFeignService
{
@Override
public CommonResult<Payment> getPaymentById(Long id) {
return null;
}
@Override
public String paymentInfo_OK(Integer id)
{
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id)
{
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
PaymentFeignService
package com.chenyc.springcloud.openFeign;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.hystrix.PaymentFallbackServiceImpl;
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;
/**
* @author chenyc
* @create 2021-04-04 15:40
*/
@Component
@FeignClient(value = "CLOUD-PROVIDER-PAYMENT",fallback = PaymentFallbackServiceImpl.class)
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
- controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.lb.LoadBalancer;
import com.chenyc.springcloud.openFeign.PaymentFeignService;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper和consul远程调用的地址
// public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
PaymentFeignService paymentFeignService;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
@GetMapping(value = "/payment/consul")
public String paymentInfoConsul()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/consul",String.class);
return result;
}
@GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENY_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();//getForObject()
}else{
return new CommonResult<>(444,"操作失败");
}
}
/**********************************************自定义轮询*************************************************************************/
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
/**********************************************openFeign*************************************************************************/
@GetMapping(value = "/payment/openFeign/get/{id}")
public CommonResult<Payment> openFeign(@PathVariable("id") Long id)
{
return paymentFeignService.getPaymentById(id);
}
/**********************************************hystrix*************************************************************************/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentFeignService.paymentInfo_OK(id);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
// int age = 10/0;
String result = paymentFeignService.paymentInfo_TimeOut(id);
return result;
}
@GetMapping("/payment/hystrix/global/timeout/{id}")
@HystrixCommand//用全局的fallback方法
public String paymentInfoGlobalTimeOut(@PathVariable("id") Integer id) {
int age = 10/0;
String result = paymentFeignService.paymentInfo_TimeOut(id);
return result;
}
@GetMapping("/payment/hystrix/currency/timeout/{id}")
public String paymentInfoCurrencyTimeOut(@PathVariable("id") Integer id) {
String result = paymentFeignService.paymentInfo_TimeOut(id);
return result;
}
//善后方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
// 下面是全局fallback方法
public String payment_Global_FallbackMethod()
{
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
- 测试
http://localhost/consumer/payment/hystrix/currency/timeout/1
故意关闭微服务8001
客户端自己调用提示 - 此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
40.Hystrix之服务熔断
- 熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
41.Hystrix之服务熔断
修改cloud-provider-payment
- service
package com.chenyc.springcloud.service;
import com.chenyc.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;
import org.springframework.web.bind.annotation.PathVariable;
public interface PaymentService
{
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
public String paymentInfo_OK(Integer id);
public String paymentInfo_TimeOut(Integer id);
public String paymentCircuitBreaker(@PathVariable("id") Integer id);
}
package com.chenyc.springcloud.service.impl;
import cn.hutool.core.util.IdUtil;
import com.chenyc.springcloud.dao.PaymentDao;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService
{
@Resource
PaymentDao paymentDao;
public int create(Payment payment)
{
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id)
{
return paymentDao.getPaymentById(id);
}
public String paymentInfo_OK(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler"/*指定善后方法名*/,commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")
})
public String paymentInfo_TimeOut(Integer id)
{
// int age = 10/0;
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
}
//用来善后的方法
public String paymentInfo_TimeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";
}
//=====服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
})
@Override
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if(id < 0) {
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
}
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
/*********************************************zk注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
/*********************************************consul注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
/*********************************************自定义负载均衡测试*************************************************************************/
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;//返回服务接口
}
/*********************************************hystrix测试*************************************************************************/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_OK(id);
log.info("*****result: "+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result: "+result);
return result;
}
//====服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
}
- 测试
正确:http://localhost:8001/payment/circuit/1
错误:http://localhost:8001/payment/circuit/-1
多次错误,当再访问正确连接时,也无法访问,一段时间后正确连接才可访问
42. Hystrix之服务熔断总结
断路器在什么情况下开始起作用
//=====服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
...
}
涉及到断路器的三个重要参数:
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
- 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
- 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
断路器开启或者关闭的条件
到达以下阀值,断路器将会开启:
- 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
- 当开启的时候,所有请求都不会进行转发
一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。
断路器打开之后
再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
All配置
@HystrixCommand(fallbackMethod = "fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String doSomething() {
...
}
43.Hystrix图形化Dashboard
- 新建项目cloud-hystrix-dashboard
- 修改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>spring-cloud2020</artifactId>
<groupId>com.chenyc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-hystrix-dashboard</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</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>
- 创建启动类
package com.chenyc.dashboard;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
/**
* @author chenyc
* @create 2021-04-07 10:10
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain.class,args);
}
}
- 创建yaml文件
server:
port: 9001
- 修改cloud-provider-payment
package com.chenyc.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
@EnableCircuitBreaker//添加到此处
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*否则,Unable to connect to Command Metric Stream 404
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
- 测试
http://localhost:8001/payment/circuit/1
http://localhost:8001/payment/circuit/-1
- 如何看?
- 1圈
实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。
- 1线
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
- 整图说明
- 整图说明2
44.GateWay是什么
- 概述
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway—句话:gateway是原zuul1.x版的替代
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式。
SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
作用
- 方向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
- …
微服务架构中网关的位置
45. GateWay非阻塞异步模型
- 有Zuull了怎么又出来Gateway?我们为什么选择Gateway?
-
netflix不太靠谱,zuul2.0一直跳票,迟迟不发布。
- 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。
- Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
- 多方面综合考虑Gateway是很理想的网关选择。
-
SpringCloud Gateway具有如下特性
- 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;
- 动态路由:能够匹配任何请求属性;
- 可以对路由指定Predicate (断言)和Filter(过滤器);
- 集成Hystrix的断路器功能;
- 集成Spring Cloud 服务发现功能;
- 易于编写的Predicate (断言)和Filter (过滤器);
- 请求限流功能;
- 支持路径重写。
-
SpringCloud Gateway与Zuul的区别
- 在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul。
- Zuul 1.x,是一个基于阻塞I/O的API Gateway。
- Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第-次加载较慢的情况,使得Zuul的性能相对较差。
- Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
- Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和- Spring Boot2之上,使用非阻塞API。
- Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验
- Zuul1.x模型
Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Serviet IO处理模型。
Servlet的生命周期?servlet由servlet container进行生命周期管理。
- container启动时构造servlet对象并调用servlet init()进行初始化;
- container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service);
- container关闭时调用servlet destory()销毁servlet。
- 上述模式的缺点:
Servlet是一个简单的网络IO模型,当请求进入Servlet container时,Servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(如抽风用Jmeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。
所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即Spring实现了处理所有request请求的一个servlet (DispatcherServlet)并由该servlet阻塞式处理处理。所以SpringCloud Zuul无法摆脱servlet模型的弊端。
- Gateway模型
WebFlux是什么?官方文档
传统的Web框架,比如说: Struts2,SpringMVC等都是基于Servlet APl与Servlet容器基础之上运行的。
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring 5必须让你使用Java 8)。
Spring WebFlux是Spring 5.0 引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
46.Gateway工作流程
三大核心概念
- Route(路由) - 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由;
- Predicate(断言) - 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
- Filter(过滤) - 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;而fliter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post")执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑:路由转发 + 执行过滤器链。
47.Gateway9527搭建
- 新建 cloud-gateway-gateway
- 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>spring-cloud2020</artifactId>
<groupId>com.chenyc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway</artifactId>
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</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.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
- YML
server:
port: 9527
spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
#uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
#uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
####################################################################
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
- 主启动类
package com.chenyc.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-04-07 14:35
*/
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain {
public static void main(String[] args) {
SpringApplication.run(GateWayMain.class, args);
}
}
- 测试
启动7001-euraka
启动8001-cloud-provider-payment
启动9527-gateway
访问说明
添加网关前 - http://localhost:8001/payment/get/1
添加网关后 - http://localhost:9527/payment/get/1
两者访问成功,返回相同结果
48.Gateway配置路由的两种方式
- java配置
package com.chenyc.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GateWayConfig
{
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
- 测试
浏览器输入http://localhost:9527/guonei,返回http://news.baidu.com/guonei相同的页面。
49.GateWay配置动态路由
YML
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri。
server:
port: 9527
spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
####################################################################
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
测试
浏览器输入 - http://localhost:9527/payment/lb
结果
不停刷新页面,8001/8002两个端口切换。
50.GateWay常用的Predicate
- 常用的Route Predicate Factory
- The After Route Predicate Factory
- The Before Route Predicate Factory
- The Between Route Predicate Factory
- The Cookie Route Predicate Factory
- The Header Route Predicate Factory
- The Host Route Predicate Factory
- The Method Route Predicate Factory
- The Path Route Predicate Factory
- The Query Route Predicate Factory
- The RemoteAddr Route Predicate Factory
- The weight Route Predicate Factory
测试几个Route Predicate Factory
The After Route Predicate Factory
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
# 这个时间后才能起效
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
可以通过下述方法获得上述格式的时间戳字符串
import java.time.ZonedDateTime;
public class T2
{
public static void main(String[] args)
{
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
//2021-02-22T15:51:37.485+08:00[Asia/Shanghai]
}
}
The Between Route Predicate Factory
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
# 两个时间点之间
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
The Cookie Route Predicate Factory
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
测试
# 该命令相当于发get请求,且没带cookie
curl http://localhost:9527/payment/lb
# 带cookie的
curl http://localhost:9527/payment/lb --cookie "chocolate=chip"
51.GateWay的Filter
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
Spring Cloud Gateway的Filter:
生命周期:
pre
post
种类(具体看官方文档):
GatewayFilter - 有31种
GlobalFilter - 有10种
常用的GatewayFilter:AddRequestParameter GatewayFilter
自定义全局GlobalFilter:
两个主要接口介绍:
GlobalFilter
Ordered
能干什么:
全局日志记录
统一网关鉴权
…
代码案例:
GateWay9527项目添加MyLogGateWayFilter类:
package com.chenyc.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
log.info("***********come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null)
{
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder()
{
return 0;
}
}
浏览器输入:
http://localhost:9527/payment/lb - 反问异常
http://localhost:9527/payment/lb?uname=abc - 正常访问
52.Config分布式配置中心介绍
- 分布式系统面临的配置问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理.……
- 是什么
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
- 怎么玩
SpringCloud Config分为服务端和客户端两部分。
-
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
-
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
-
能干嘛
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文- 件,服务会向配置中心统一拉取配置自己的信息
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
- 将配置信息以REST接口的形式暴露 - post/crul访问刷新即可…
- 与GitHub整合配置
由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式。
官网
https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
53.Config配置总控中心搭建
用你自己的账号在GitHub上新建一个名为springcloud-config的新Repository。
由上一步获得刚新建的git地址 -https://github.com/20086996/springcloud-config.git
本地硬盘目录上新建git仓库并clone。
-
工作目录为C:\eclipse-workspace-photon
-
git clone https://github.com/20086996/springcloud-config.git
此时在工作目录会创建名为springcloud-config的文件夹。
在springcloud-config的文件夹种创建三个配置文件(为本次教学使用的),随后git add .,git commit -m "sth"等一系列上传操作上传到springcloud-config的新Repository。
- 创建application-dev.yml文件并上传git
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.xxx.xxx.xxx:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: xxx
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
#开启
feign:
hystrix:
enabled: true
config:
info: "master branch,springcloud-config/application-dev.yml version=7"
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
- 新建项目cloud-config-center
- 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>spring-cloud2020</artifactId>
<groupId>com.chenyc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-center</artifactId>
<dependencies>
<!-- <!–添加消息总线RabbitMQ支持–>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-bus-amqp</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</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.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>
- 启动类
package com.chenyc.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain
{
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain.class, args);
}
}
- yaml文件
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://github.com/20086996/springcloud-config.git #GitHub上面的git仓库名字
username: 20086996
password: xxxxxxxx
####搜索目录
search-paths:
- springcloud-config
####读取分支
label: master
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
- 测试,浏览器输入http://localhost:3344/master/application-dev.yml
- 配置读取规则
/{label}/{application}-{profile}.yml(推荐)
master分支
http://config-3344.com:3344/master/config-dev.yml
http://config-3344.com:3344/master/config-test.yml
http://config-3344.com:3344/master/config-prod.yml
dev分支
http://config-3344.com:3344/dev/config-dev.yml
http://config-3344.com:3344/dev/config-test.yml
http://config-3344.com:3344/dev/config-prod.yml
/{application}-{profile}.yml
http://config-3344.com:3344/config-dev.yml
http://config-3344.com:3344/config-test.yml
http://config-3344.com:3344/config-prod.yml
http://config-3344.com:3344/config-xxxx.yml(不存在的配置)
/{application}/{profile}[/{label}]
http://config-3344.com:3344/config/dev/master
http://config-3344.com:3344/config/test/master
http://config-3344.com:3344/config/test/dev
重要配置细节总结
/{name}-{profiles}.yml
/{label}-{name}-{profiles}.yml
label:分支(branch)
name:服务名
profiles:环境(dev/test/prod)
成功实现了用SpringCloud Config通过GitHub获取配置信息
54.Config客户端配置与测试
- 改造cloud-provider-payment
- 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>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment</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.chenyc</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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <!– SpringBoot整合zookeeper客户端 –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>-->
<!-- <!–先排除自带的zookeeper3.5.3 防止与3.4.9起冲突–>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.apache.zookeeper</groupId>-->
<!-- <artifactId>zookeeper</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<!-- <!–添加zookeeper3.4.9版本–>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.zookeeper</groupId>-->
<!-- <artifactId>zookeeper</artifactId>-->
<!-- <version>3.4.12</version>-->
<!-- </dependency>-->
<!-- <!–SpringCloud consul-server –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>-->
<!-- </dependency>-->
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--config-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
</project>
- 注释或者删除原项目下application.yml,新增bootstrap.yml
spring:
profiles:
active: payment8001
---
# 8001和8002测试用eureka作为注册中心
server:
port: 8001
spring:
application:
name: cloud-provider-payment
profiles: payment8001
cloud:
config:
label: master #分支名称
name: application #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址k
---
server:
port: 8002
spring:
application:
name: cloud-provider-payment
profiles: payment8002
cloud:
config:
label: master #分支名称
name: application #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址k
- controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
@RefreshScope
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@Value("${config.info}")
private String configInfo;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
/*********************************************zk注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
/*********************************************consul注册中心测试*************************************************************************/
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
/*********************************************自定义负载均衡测试*************************************************************************/
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;//返回服务接口
}
/*********************************************hystrix测试*************************************************************************/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_OK(id);
log.info("*****result: "+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result: "+result);
return result;
}
//====服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
/*********************************************config文件中心测试*************************************************************************/
@GetMapping("/configInfo")
public String getConfigInfo()
{
return configInfo;
}
}
- 测试
http://localhost:8001/configInfo
成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息可题随时而来
分布式配置的动态刷新问题
- Linux运维修改GitHub上的配置文件内容做调整
- 刷新3344,发现ConfigServer配置中心立刻响应
- 刷新8001,发现ConfigClient客户端没有任何响应
- 8001没有变化除非自己重启或者重新加载
- 难到每次运维修改配置文件,客户端都需要重启??噩梦
56.Config动态刷新之手动版
避免每次更新配置都要重启客户端
- cloud-provider-payment的POM引入actuator监控(之前加过了)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 修改YML,添加暴露监控端口配置:
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
- 业务类Controller上加@RefreshScope
@RestController
@Slf4j
@RefreshScope
public class PaymentController{
......
}
- 测试
- 修改yaml
- 刷新客户端配置http://localhost:8001/actuator/refresh
- 访问http://localhost:8001/configInfo,得到最新修改的配置