Spring Cloud
Eureka
Eureka基础知识
服务治理:Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理;在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现:Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)
Eureka的两个组件
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在Eureka Server中进行注册, 这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备个内置的、 使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。 如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)
单机Eureka构建步骤
一、IDEA生成EurekaServer端服务注册中心 类似物业公司
建立Module:cloud-eureka-server7001
改pom:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
写yml:
server:
port: 7001
spring:
application:
name: cloud-eureka-service
eureka:
instance:
# eureka服务端的实例名称
# 单机 hostname: localhost
hostname: eureka7001.com
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
# 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka7001.com:7001/eureka
主启动:
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args){
SpringApplication.run(EurekaMain7001.class,args);
}
}
二、EurekaClient端cloud-provider-payment8001 将注册进EurekaServer成为服务提供者provider,类似于尚硅谷学校对外提供授课服务
三、EurekaClient端cloud-consumer-order80 将注册进EurekaServer成为服务消费者consumer,类似于学校上课消费的各位同学
建Module:cloud-provider-payment8001
改pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
写yml:
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# mysql驱动类
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
instance:
#在Eureka中显示主机的服务名称
instance-id: payment8001
#左下角显示ip地址;详情可见下图
prefer-ip-address: true
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities
主启动:
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
集群Eureka构建步骤
容易理解一点:集群就是相互注册,把A服务器注册到B,B服务器也注册到A。
然后服务要注册到这两个server上。
一、Eureka集群原理说明
解决办法: 搭建Eureka注册中心集群,实现负载均衡+故障容错
二、Eureka集群环境构建步骤
建Module:参考cloud-eureka-server7001建立cloud-eureka-server7002
改pom:无
修改映射配置:找到hosts文件;添加两行;刷新ipconfig/flushdns
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
写yml:
单机时:
server:
port: 7001
spring:
application:
name: cloud-eureka-service
eureka:
instance:
# eureka服务端的实例名称
单机 hostname: localhost
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
# 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka7001.com:7001/eureka
集群时:
7001yml
server:
port: 7001
spring:
application:
name: cloud-eureka-service
eureka:
instance:
# eureka服务端的实例名称
hostname: eureka7001.com
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka/
7002yml
server:
port: 7002
spring:
application:
name: cloud-eureka-service2
eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
主启动:不变
三、将支付服务8001微服务发布到上面两台Eureka集群配置中
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#如果只是单机版,是没有http://eureka7002.com:7002/eureka的。
四、将订单微服务发布到上面两台Eureka集群配置中
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
五、测试01
- 先要启动EurekaServer,7001/7002服务
- 再要启动服务提供者provider,8001
- 再要启动消费者,80
- http://localhost/consumer/payment/get/31
相互注册,相互守望。
六、支付服务提供者8001集群环境搭建
建Module:参考cloud-provider-payment8001新建8002
改pom:无
yml:
server:
port: 8002
spring:
application:
name: cloud-payment-service8002
datasource:
# 当前数据源操作类型
type: com.alibaba.druid.pool.DruidDataSource
# mysql驱动类
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com/eureka,http://eureka7002.com/eureka
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities
主启动:无
业务类:从8001粘贴。
controller:修改8001和8002的controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverport;
@Resource
private DiscoveryClient discoveryClient;
@PostMapping("/payment/create")
public CommonResult<Integer> create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info("***插入结果***"+result);
if (result > 0) {
return new CommonResult<>(200, "success serverport: "+serverport, result);
}
else {
return new CommonResult<Integer>(444, "failure", null);
}
}
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("****插入结果****"+payment);
if (payment != null) {
return new CommonResult(200, "success serverport: "+serverport, payment);
}
else {
return new CommonResult(444, "failure", null);
}
}
}
七、负载均衡
订单服务访问地址不能写死:
// 通过在eureka上注册过的微服务名称调用
public static final String PAYMENT_URL="http://CLOUD-PAYMENT-SERVICE";
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
八、测试02
- 先启动EurekaServer7001/7002服务
- 在要启动服务提供者provider,8001/8002服务
- http://localhost/consumer/payment/get/31
- 结果:负载均衡效果达到;8001/8002端口交替出现
- Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了。
服务发现Discovery
一、对于注册eureka里面的微服务,可以通过服务发现来获得该服务的信息
二、修改cloud-provider-payment8001的Controller
三、8001启动类
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
四、自测
- 先启动EurekaServer
- 再启动8001主启动类
- http://localhost:8001/payment/discovery
Ribbon负载均衡调用
概述
一、是什么
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目, 主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
二、能做什么
LB(负载均衡)
一句话:负载均衡+RestTemplate调用
Ribbon负载均衡演示
一、架构说明
Ribbon在工作时分成两步:
第一步先选择Eureka Server ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
总结: Ribbon其实就是一个软负载均衡的客户端组件, 他可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例
二、POM
三、二说RestTemplate的使用
getForObject方法/getForEntity方法,同postForObject/postEntity
Ribbon的核心组件IRule
IRule:根据特定算法从服务列表中选取一个要访问的服务
如何替换
修改cloud-consumer-order80
注意配置细节:
新建package:com.atguigu.myrule
建立MySelfRule规则类:
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
// 定义为随机
return new RoundRobinRule();
}
}
主启动类上面添加@RibbonClient
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
测试:http://localhost/consumer/payment/get/31
Ribbon负载均衡算法
原理:
OpenFeign服务接口调用
概述
一、openfeign是什么:
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需 创建一个接口并在接口上添加注解即可
二、能干嘛
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+ RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。 但是在实际开发中,由于对服务依赖的调用可能不止一处, 往往一个接口会被多处调用, 所以通常都会针对每个微服务自行封装-些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装, 由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方 式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口. 上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义
服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
三、feign和openfeign区别
openfeign使用步骤
接口+注解:
微服务调用接口+@FeignClient
建Module
新建cloud-consumer-feign-order80
pom
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
yml
server:
port: 80
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
主启动
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
业务类
业务逻辑接口+@FeignClient配置调用provider服务
新建PaymentFeignService接口并新增注解@FeignClient
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@GetMapping("/payment/feign/timeout")
String paymentFeignTimeout();
}
控制层Controller
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
@GetMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeout() {
return paymentFeignService.paymentFeignTimeout();
}
}
测试
先启动2个eureka集群7001/7002
再启动2个微服务8001/8002
启动OpenFeign
http://localhost/consumer/payment/get/31
Feign自带负载均衡配置项
总结:
openfeign超时控制
超时设置,故意设置超时演示出错情况
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout() {
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return serverport;
}
服务消费方80添加超时方法PaymentFeignService
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@GetMapping("/payment/feign/timeout")
String paymentFeignTimeout();
}
服务消费方80添加超时方法OrderFeignController
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
@GetMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeout() {
return paymentFeignService.paymentFeignTimeout();
}
}
测试
http://localhost/consumer/payment/feign/timeout
OpenFeign默认等待1秒钟,超过后报错
是什么
OpenFeign默认支持Ribbon
YML文件里需要开启OpenFeign客户端超时控制
server:
port: 80
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
openfeign日志打印功能
日志打印功能
是什么
日志级别
配置文件bean
@Configuration
public class FeignConfig {
/**
* feignClient配置日志级别
*
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
// 请求和响应的头信息,请求和响应的正文及元数据
return Logger.Level.FULL;
}
}
yml文件里需要开启日志的Feign客户端
server:
port: 80
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug
后台日志查看
Hystrix断路器
概述
分布式系统面临的问题复杂分布式体系结构中的应用程序 有数10个依赖关系,每个依赖关系在某些时候将不可避免地失败
是什么
能干嘛
服务降级
服务熔断
接近实时监控
Hystrix重要概念
服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback(兜底方案)
当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
发生场景:
程序运行异常
超时
服务熔断触发服务降级
线程池/信号量也会导致服务降级
服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
就是保险丝->服务降级->进而熔断->恢复调用链路
服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
Hystrix案例
构建
建Module:新建cloud-provider-hystrix-payment8001
pom:
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
yml:不变
主启动:
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
业务类:
service
@Service
public class PaymentService {
/**
* 正常访问
*
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "O(∩_∩)O哈哈~";
}
/**
* 超时访问
*
* @param id
* @return
*/
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 3;
try {
// 暂停3秒钟
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" +
"O(∩_∩)O哈哈~ 耗时(秒)" + timeNumber;
}
}
controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String servicePort;
/**
* 正常访问
*
* @param id
* @return
*/
@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;
}
/**
* 超时访问
*
* @param id
* @return
*/
@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;
}
}
测试:
- 启动eureka7001
- 启动eureka-provider-hystrix-payment8001
访问:
- 上述module均ok。以上述为根基平台,从正确->错误->降级熔断->恢复
高并发测试
上述在非高并发情形下,还能勉强满足 but…
Jmeter压测测试
- 开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
- 再来一个访问:http://localhost:8001/payment/hystrix/timeout/31
看演示结果
- 两个都在转圈圈
- 为什么会被卡死
Jmeter压测结论
上面还只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
看热闹不嫌事大,新建80加入
新建cloud-consumer-feign-hystrix-order80
pom与yml,主启动类不变
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
/**
* 正常访问
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
/**
* 超时访问
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
controller
@RestController
@Slf4j
public class OrderHyrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}
正常测试:http://localhost/consumer/payment/hystrix/ok/32
高并发测试:
- 2w个线程压8001
- 消费者80微服务再去访问的OK服务8001地址
- http://localhost/consumer/payment/hystrix/ok/32
消费者80,o(╥﹏╥)o
- 要么转圈圈
- 要么消费端报超时错误
故障和导致现象
8001同一层次的其他接口被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
80此时调用8001,客户端访问响应缓慢,转圈圈
上述结论
正因为有上述故障或不佳表现 才有我们的降级/容错/限流等技术诞生
如何解决?解决的要求是?
超时导致服务器变慢(转圈):超时不再等待
出错(宕机或者程序运行出错):出错要有兜底
解决:
- 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)ok,调用者(80)自己有故障或有自我要求(自己的等待时间小于服务提供者)
服务降级
降级配置:
8001先从自身找问题
设置自身调用超时时间的峰值,峰值内可以正常运行, 超过了需要有兜底的方法处理,做服务降级fallback
8001fallback
业务类启用:一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbckMethod调用类中的指定方法
@HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")}
@RestController
@Slf4j
public class OrderHyrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
// @HystrixCommand/*如果配置了上面那个command,就用上面那个,如果只是一个单独的注解,则使用默认的全局配置*/
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod() {
return "我是消费者80,对方支付系统繁忙请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
主启动类激活:@EnableCircuitBreaker
80fallback
80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端端降级保护
我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务
pom:
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
server:
port: 80
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
feign:
hystrix:
enabled: true
目前问题
每个业务方法对应一个兜底的方法,代码膨胀
统一的方法和自定义的方法分开
解决方法
每个方法都配置一个,膨胀
feign接口系列
@DefaultProperties(defaultFallback=””) ```java @RestController @Slf4j @DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”) public class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
/**
* 全局fallback
*
* @return
*/
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后重试.o(╥﹏╥)o";
}
}
2.
和业务逻辑混在一起,混乱
1. 服务降级,客户端去调用服务端,碰上服务端宕机或关闭
2. 本次案例服务降级处理是在客户端80实现完成,与服务端8001没有关系 只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
3. 未来我们要面对的异常
1. 运行
2. 超时
3. 宕机
4. 再来看看业务类![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127213031028.png#alt=image-20210127213031028)
5. 修改cloud-consumer-feign-hystrix-order80
6. 根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现接口,统一为接口里面的方法进行异常处理
7. PaymentFallbackService类实现PaymentFeginService接口![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127213150154.png#alt=image-20210127213150154)
8. yml![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127213224814.png#alt=image-20210127213224814)
9. PaymentFeignClientService接口![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127213246784.png#alt=image-20210127213246784)
10. 测试
1. 单个eureka先启动7001
2. PaymentHystrixMain8001启动
3. 正常访问测试:[http://localhost/consumer/payment/hystrix/ok/32](http://localhost/consumer/payment/hystrix/ok/32)
4. 故意关闭微服务8001
5. 客户端自己调用提示:此时服务端provider已经downl ,但是我们做了服务降级处理, 让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器
<a name="98cb60e9-1"></a>
#### 服务熔断
<a name="eaea9330"></a>
##### **断路器**
一句话就是家里的保险丝
<a name="13934ec3"></a>
##### **实操**
修改cloud-provider-hystrix-payment8001
@HystrixProperty里面的那么属性需要查询HystrixCommandProperty这个类
PaymentService![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127220733224.png#alt=image-20210127220733224)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127220745688.png#alt=image-20210127220745688)
PaymentController
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210127220816037.png#alt=image-20210127220816037)
测试
1. 自测cloud-provider-hystrix-payment8001
2. 正确:[http://localhost:8001/payment/circuit/31](http://localhost:8001/payment/circuit/31)
3. 错误:[http://localhost:8001/payment/circuit/-31](http://localhost:8001/payment/circuit/-31)
4. 一次正确,一次错误,trytry
5. 重点测试:多次正确,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问也不能进行
<a name="cc1d28d4"></a>
##### 小总结
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214156114.png#alt=image-20210128214156114)
**熔断类型**
- 熔断打开:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态
- 熔断关闭:熔断关闭后不会对服务进行熔断
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
**官网断路器流程图**
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214327679.png#alt=image-20210128214327679)
步骤:![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214337994.png#alt=image-20210128214337994)
断路器在什么情况下开始起作用
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214402277.png#alt=image-20210128214402277)
断路器开启或者关闭的条件
- 当满足一定的阈值的时候(默认10秒钟超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%的请求次数)
- 到达以上阈值,断路器将会开启
- 当开启的时候,所有请求都不会进行转发
- 一段时间之后(默认5秒),这个时候断路器是半开状态,会让其他一个请求进行转发. 如果成功,断路器会关闭,若失败,继续开启.重复4和5
断路器打开之后的效果
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214515716.png#alt=image-20210128214515716)
ALI配置
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214530034.png#alt=image-20210128214530034)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128214539526.png#alt=image-20210128214539526)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128220235439.png#alt=image-20210128220235439)
<a name="9a0f0c74"></a>
### 服务监控hystrixDashboard
<a name="a4d3b02a-3"></a>
#### 概述
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210128221155473.png#alt=image-20210128221155473)
<a name="dc5f8e55"></a>
#### 仪表盘9001的构建
新建HystrixDashboardMain9001
pom
```xml
<!--hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
yml
server:
port: 9001
主启动
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class);
}
}
所有Provider微服务提供类8001/8002/8003都需要以来部署
启动HystrixDashboardMain9001该微服务后续将监控微服务8001:http://localhost:9001/hystrix
断路器演示(服务监控hystrixDashboard)
修改cloud-provider-hystrix-payment8001
注意新版本Hystrix需要在主启动MainAppHystrix8001中指定监控路径
主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args){
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无观,springCloud 升级之后的坑
* ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
* 只要在自己的项目中配置上下面的servlet即可
* @return
*/
@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;
}
}
测试监控
启动一个或者多个Eureka集群均可
观察监控窗口:9001监控8001:http://localhost:8001/hystrix.stream
然后执行localhost:8001/payment/circuit/-31和localhost:8001/payment/circuit/31
Gateway网关
概述简介
是什么
SpringCloud Gateway使用的是Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
源码架构
能干嘛
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
- 。。。。。。
微服务架构中,网关的未知
三大核心概念
路由route
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由
断言predicate
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
过滤filter
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.
总结
gateway工作流程
路由转发+执行过滤器链
入门配置
建module:cloud-gateway-gateway9527
pom: spring-cloud-starter-gateway
主启动类:
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args){
SpringApplication.run(GateWayMain9527.class,args);
}
}
9527如何做路由映射
cloud-provider-payment8001看看controller的访问地址
@GetMapping(value = "/payment/lb")
public String getPaymentLb() {
return serverport;
}
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("****插入结果****" + payment);
if (payment != null) {
return new CommonResult(200, "success serverport: " + serverport, payment);
} else {
return new CommonResult(444, "failure", null);
}
}
不想暴露8001端口,希望在8001外面套一层9527
新增网关配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称j进行路由
routes:
- id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
#匹配后提供服务的路由地址
uri: http://localhost:8001
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_route2
uri: http://localhost:8001
predicates:
Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
测试
启动7001 8001 9527
添加网关前:http://localhost:8001/payment/get/31
添加网关后:http://localhost:9527/payment/get/31
yml配置说明
gateway配置路由网关有两中方式:
yml和写进代码(注入RouteLocator的Bean)
假如想要访问https://news.baidu.com/guonei
通过9527网关访问到外网的百度新闻网址
业务类实现
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocation(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path route atguigu",
r->r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocation2(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path route atguigu",
r->r.path("/guoji")
.uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
通过服务名实现动态
默认情况下Gatway会根据注册中心注册的服务列表, 以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
启动:一个eureka7001+两个服务提供者8001/8002
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes:
- id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
#匹配后提供服务的路由地址
# uri: http://localhost:8001
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_route2
# uri: http://localhost:8001
uri: lb://cloud-payment-service
predicates:
Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
需要注意的是uri的协议lb,表示启用Gateway的负载均衡功能,lb://serverName是spring cloud gatway在微服务中自动为我们创建的负载均衡uri
接着启动http://localhost:9527/payment/lb,8001和8002就互相切换了。
Predicate
是什么
启动9527后有
Route Predicate Factories是什么
常用的Route Predicate
举例说明:After Route Predicate
Filter
是什么
Spring Cloud Gateway的filter
生命周期:
- pre
- post
种类:
- gatewayfilter
- globalgilter
常用的gatewayfilter
AddRequestParameter
自定义过滤器
自定义全局GlobalFilter
两个主要接口:GlobalFilter,OrderId
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("come in global filter: {}", new Date());
ServerHttpRequest request = exchange.getRequest();
String uname = request.getQueryParams().getFirst("uname");
if (uname == null) {
log.info("用户名为null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 放行
return chain.filter(exchange);
}
/**
* 过滤器加载的顺序 越小,优先级别越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
正确:http://localhost:9527/payment/lb?uname=z3
Nacos
nacos简介
能干嘛
替代Eureka做服务注册中心
替代Config做服务配置中心
各注册中心对比
安装并运行nacos
命令运行成功后直接访问http://localhost:8848/nacos
nacos作为服务注册中心演示
基于nacos的服务提供者
建module: cloudalibaba-provider-payment9001
pom:
父pom:
<!--Spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
子pom:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yml
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: "*"
主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}
业务类
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverport;
@GetMapping("/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id) {
return "nacos registry , serverport: "+serverport+" id: "+id;
}
}
测试
http://localhost:9001/payment/nacos/1
再创建一个一摸一样的9002,为了演示负载均衡。
基于nacos的服务消费者
建module:
cloudalibaba-consumer-nacos-order83
pom 为什么nacos支持负载均衡
yml
业务类
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Slf4j
@RestController
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping("/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Integer id) {
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id, String.class);
}
}
http://localhost:83/consumer/payment/nacos/13
83访问9001/9002支持负载均衡。
服务中心的对比
nacos作为服务配置中心演示
Nacos作为配置中心-基础配置
建module cloudalibaba-config-nacos-client3377
pom
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
</dependency>
yml配置
bootstrap.yml
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
group: DEV_GROUP
namespace: 82d8defc-af37-4518-b7e0-9fc6ba3857af
#${spring.application.name}-${spring.profile.active}.${spring.cloud.name.config.file-extension}
#nacos-config-client-dev.yml
application.yml
spring:
profiles:
# active: info
# active: test
active: dev #表示开发环境
#通过不同的active引用不同的dataId
#通过修改active,然后再进bootstrap.yml里修改group,获取不同group的值
业务类
@RestController
@RefreshScope //支持动态刷新,配置自动更新
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
配置信息
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则
最后公式
{spring.profile.active}.${file-extension}
nacos-config-client-dev.yaml
- prefix 默认为 spring.application.name 的值
- spring.profile.active 即为当前环境对应的 profile
- file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置
nacos作为配置中心-分类配置
问题:
Namespace+Group+Data ID三者关系?为什么这么设计?
三种方式加载配置
dataID
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间+默认分组+新建dev和test两个DataId
新建dev配置DataId
新建test配置DataId
通过spring.profile.active属性就能进行多环境下配置文件的读取
Group
通过group环境区分
在nacos图形化界面控制台上面新建配置文件DataID
在config下增加一条Group的配置即可. 可配置为DEV_GROUP或TEST_GROUP
namespace
新建dev/test/的Namespace
yml
bootstrap.yml
application.yml
nacos集群和持久化配置
https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
nacos持久化配置解释
nacos默认值带的是嵌入式数据derby:https://github.com/alibaba/nacos/blob/develop/config/pom.xml
derby到mysql切换步骤
nacos-server-1.1.4/nacos/conf目录下找到sql脚本
nacos-mysql.sql执行
nacos-server-1.1.4/nacos/conf目录下找到application.properties
最下面添加
```properties spring.datasource.platform=mysql
db.num=1 db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=123456
6.
启动Nacos,可以看到是个全新的空记录界面,以前是记录在derby
<a name="092590ff"></a>
#### Linux版Nacos+MySQL生产环境配置
<a name="af29a0fa"></a>
##### 预计需要,1个Nginx+3个nacos注册中心+1个mysql
<a name="96ffd22f"></a>
##### linux安装nacos
1. 1.Linux服务器上mysql数据库配置,按照上面的derby到mysql切换步骤来执行。
2. linux上的nacos集群配置cluster.conf:这个IP不能写127.0.0.1,必须是 Linux命令hostname -i能够识别的IP![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131160612805.png#alt=image-20210131160612805)
<a name="9f0519f3"></a>
##### 编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口
/mynacos/nacos/bin目录下有startup.sh
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131160818787.png#alt=image-20210131160818787)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131160919264.png#alt=image-20210131160919264)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131160929283.png#alt=image-20210131160929283)
<a name="35cbaad5"></a>
##### 配置nginx作为负载均衡器
nginx.conf
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131161021826.png#alt=image-20210131161021826)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131161026078.png#alt=image-20210131161026078)
按照指定配置文件启动
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131161042613.png#alt=image-20210131161042613)
<a name="8e54ddfe"></a>
##### 启动
截止到此处,1个Nginx+3个nacos注册中心+1个mysql
访问1111端口的nacos/#login
新建一个配置测试
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131161139067.png#alt=image-20210131161139067)
会发现linux的mysql插入了一条记录。
<a name="Sentinel"></a>
## Sentinel
<a name="e05dce83"></a>
### 简介
[https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#spring_cloud_alibaba_sentinel](https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#spring_cloud_alibaba_sentinel)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131161338036.png#alt=image-20210131161338036)
![](https://gitee.com/gzh1026/tuc/raw/master/img/image-20210131161318935.png#alt=image-20210131161318935)
<a name="e655a410"></a>
### 安装
下载sentinel,执行jar包。打开localhost:8080,密码账号都是sentinel
<a name="3510b014"></a>
### 初始化功能演示
启动nacos8848,新建Module:cloudalibaba-sentinel-server8401
pom
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yml
server:
port: 8401
spring:
application:
name: sentinel-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
#备用端口,如果被占用则+1
port: 8719
management:
endpoints:
web:
exposure:
include: "*"
业务类
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
log.info("*****" + Thread.currentThread().getName() + "\t" + " /testB");
return "------testB";
}}
启动sentinel8080jar包,启动微服务8401然后查看8080控制台,什么也没出现,是因为sentinel采用懒加载,需要访问一次就出现了
流控规则
基本介绍
流控模式
直接(默认)
直接->快速失败(系统默认)
配置及说明
阈值类型:
- QPS:抵敌于国外
- 线程数:关门打狗
测试
快速点击访问http://localhost:8401/testA
结果:Blocked by Sentinel(flow limiting)
此时没有兜底方案,需要有个类似于fallback的兜底方案。
关联
含义
当关联的资源达到阈值时,就限流自己,当与A关联的资源B达到阈值后,就限流自己(B惹事,A挂了,防止雪崩):支付接口达到阈值后,就限流下订单的接口,防止连坐效应。
配置A
此时用postamn模拟并发密集访问testB
然后访问/testA,会发现A挂了。
链路
多个请求调用了同一个微服务。
流控效果
直接->快速失败(默认的流控处理)
直接抛出异常,报错:Blocked by Sentinel(flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
预热(warmup)
说明:公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值
多次点击http://localhost:8401/testB,应用场景:秒杀。
排队等待
匀速排队,阈值必须是QPS
降级规则
官网
https://github.com/alibaba/Sentinel/wiki/熔断降级
基本介绍
Sentinel的断路器是没有半开状态的,半开的状态系统自动去检测是否请求异常, 没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用.具体可以参考Hystrix。
降级策略实战
RT
是什么—平均响应时间
测试
@GetMapping("/testD")
public String testD() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "------testD";
}
配置
jmeter测压
结论
异常比例
是什么
测试
@GetMapping("/testD")
public String testD() {
int age=10/0;
log.info("testD 测试异常比例");
return "------testD";
}
配置
jmeter测压
结论
异常数
异常数是按分钟统计的
测试
热点key限流
https://github.com/alibaba/Sentinel/wiki/热点参数限流
配置
@SentinelResource(value = "testHotKey")
改为
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
方法testHotKey里面第一个参数(索引0)只要QPS超过每秒1次,马上降级处理,且此时用的是我们自定义的降级方法。
参数例外项
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
特殊情况:普通是超过1s后达到阈值马上被限流,我们希望p1参数当它是某个值的时候,它的限流和平时不同。特例:假如等于5时,阈值为200.
配置
测试
@SentinelResource
按照资源名称+后续处理
启动nacos、sentinel
建module:cloudalibaba-sentinel-service8401。
业务类
@RestController
public class RateLimitController {
@GetMapping("/byresource")
@SentinelResource(value = "byresource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按资源名称限流测试", new Payment(2020L, "serial001"));
}
public CommonResult handleException(BlockException e) {
return new CommonResult(444, e.getClass().getCanonicalName() + "\t服务不可用");
}}
配置流控规则
表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流。
测试
1s一下,ok。
超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生。
通过url地址限流+后续处理
通过访问的URL限流,会返回Sentinel自带默认的限流处理信息
业务类
@RestController
public class RateLimitController {
@GetMapping("/byresource")
@SentinelResource(value = "byresource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按资源名称限流测试", new Payment(2020L, "serial001"));
}
public CommonResult handleException(BlockException e) {
return new CommonResult(444, e.getClass().getCanonicalName() + "\t服务不可用");
}
@GetMapping("/ratelimit/byurl")
@SentinelResource(value = "byurl")
public CommonResult byurl() {
return new CommonResult(200, "按url限流成功", new Payment(2020L, "serial02"));
}
}
访问:http://localhost:8401/rateLimit/byUrl
sentinel控制台配置
测试
疯狂点击http://localhost:8401/rateLimit/byUrl
上述兜底方案面临的问题
用户自定义限流处理逻辑
创建customblockhandler类用于自定义限流处理逻辑
自定义限流处理类customblockhandler
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException e) {
return new CommonResult(4444, "按客户自定义 Method 1 ---- global ExceptionHandler");
}
public static CommonResult handlerException2(BlockException e) {
return new CommonResult(4444, "按客户自定义 Method 2 ---- global ExceptionHandler");
}
}
RateLimitController
@RestController
public class RateLimitController {
@GetMapping("/byresource")
@SentinelResource(value = "byresource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按资源名称限流测试", new Payment(2020L, "serial001"));
}
public CommonResult handleException(BlockException e) {
return new CommonResult(444, e.getClass().getCanonicalName() + "\t服务不可用");
}
@GetMapping("/ratelimit/byurl")
@SentinelResource(value = "byurl")
public CommonResult byurl() {
return new CommonResult(200, "按url限流成功", new Payment(2020L, "serial02"));
}
@GetMapping("/ratelimit/comstomerblockhandler")
@SentinelResource(value = "comstomerblockhandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerException2")
public CommonResult comstomerblockhandler() {
return new CommonResult(200, "按客户自定义限流成功", new Payment(2020L, "serial02"));
}
}
然后我们自定义的出来了。
服务熔断功能
sentinel整合ribbon+openfeign+fallback
ribbon系列
服务提供者
启动nacos和sentinel
建立服务提供者9003/9004
业务类
测试:http://localhost:9003/paymentSQL/1
服务消费者
新建cloudalibaba-consumer-nacos-order84
业务类
ApplicationContextConfig
CircleBreakController
目的:
- fallback管运行异常
- blockhandler管配置违规
测试地址:http://localhost:84/consumer/fallback/1
没有任何配置
直接返回error页面,不太好。
只配置fallback
只配置blockhandler
结果
fallback和blockhandler都配置
sentinel配置
结果
忽略属性
feign系列
修改84模块
业务类
带@FeignClient注解的业务接口
fallback=PaymentFallbackService.class
http://localhost:84/consumer/paymentSQL/1
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死
规则持久化
是什么
一旦我们重启应用,sentinel规则消失,生产环境需要将配置规则进行持久化
怎么做
将限流规则持久进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看得到,只要Nacos里面的配置不删除,针对8401上的流控规则持续有效
步骤
修改cloudalibaba-sentinel-server8401
pom
<!-- sentinel-datasource-nacos 后续持久化用 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
YML
添加nacos数据源配置
添加nacos业务规则配置
启动8401书信sentinel发现业务规则变了
快速访问测试接口
http://localhost:8401/rateLimit/byUrl
停止8401再看sentinel
重新启动8401再看sentinel
需要稍等一会,多次调用:http://localhost:8401/rateLimit/byUrl,重新配置出现了,持久化验证通过。