SpringCloud 微服务
Hystrix
Hystrix概述
分布式系统面临的问题:
复杂分布式体系结构中的应用程序 有数10个依赖关系,每个依赖关系在某些时候将不可避免地失败
Hystrix是网飞公司出品的框架,虽然已经停止更新,但其设计理念优秀,出道即巅峰,其设计理念,设计思想在其他断路器都有借鉴
Hystrix主要工作:服务降级、服务熔断、接近实时的监控…
HyStrix重要概念
1、服务降级
2、服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
就是保险丝: 服务的降级->进而熔断->恢复调用链路
3、服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
hystrix案例(服务降级)
构建一个模块
JMeter高并发压测后卡顿
开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
演示结果:
两个都在转圈圈
为什么会被卡死,tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理
Jmeter压测结论:
上面还只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
其他服务再做远程调用这个需要3秒完成的接口,会出现服务卡顿和读取超时
严重影响影响业务逻辑和体验
故障和导致现象:
8001同一层次的其他接口被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
80此时调用8001,客户端访问响应缓慢,转圈圈
正因为有上述故障或不佳表现 才有我们的降级/容错/限流等技术诞生
-
如何解决?解决的要求
超时导致服务器变慢(转圈):超时不再等待
出错(宕机或程序运行出错):出错要有兜底 -
对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
-
对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
-
对方服务(8001)ok,调用者(80)自己有故障或有自我要求(自己的等待时间小于服务提供者)
Hystrix之服务降级支付侧(服务提供者)fallback
先处理服务提供者(8001),8001先从自身找问题,设置自身调用超时时间的峰值,峰值内可以正常运行, 超过了需要有兜底的方法处理,做服务降级fallback。
业务方法中,@HystrixCommand
注解指派兜底方法(在同一个类中)
@HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String getPaymentInfo_Error(Integer id) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,Id"+id+"\t"+"😄"+"耗时3秒";
}
public String payment_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+"系统繁忙或运行错误,请稍后再试";
}
8001服务提供者的主启动类添加注解@EnableCircuitBreaker
启用服务降级兜底功能。
此时8001本身再调用此方法,超过设定的等待时间,会直接转到payment_TimeOutHandler
方法
Hystrix之服务降级订单侧(服务调用者)fallback
同上操作做好调用方的兜底
需在配置开启
feign:
hystrix:
enabled: true #在feign中开启hystrix
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_ERROR_Handler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public String paymentInfo_timeout(@PathVariable("id") Integer id){
String res = paymentHystrix.paymentInfo_TimeOut(id);
System.out.println(res);
return res;
}
public String paymentInfo_ERROR_Handler(@PathVariable("id") Integer id){
return "我是消费者80____对方系统繁忙,请稍后再试paymentInfo_ERROR_Handler___异常返回";
}
Hystrix之全局服务降级DefaultProperties
目前问题:每个业务方法对应一个兜底的方法,代码膨胀
需要做到统一和自定义的分开
@RestController
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderController {
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentInfo_ERROR_Handler",commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
// })
@HystrixCommand
public String paymentInfo_timeout(@PathVariable("id") Integer id){
String res = paymentHystrix.paymentInfo_TimeOut(id);
System.out.println(res);
return res;
}
public String payment_Global_FallbackMethod() {
return "Global 异常处理信息 , 请稍后重试 .o(╥ ﹏ ╥)o";
}
}
若精确指定了,则使用指定的方法
Hystrix之通配服务降级FeignFallback
和业务逻辑混在一起???混乱,由于Fallback都是写在业务类中,造成了高耦合,类功能混乱。
创建一个类实现远程服务业务接口,接口@FeignClient
中指定这个类,就可以实现Fallback的剥离,完成解耦
@Component
public class PaymentHystrixFallback implements PaymentHystrix{
@Override
public String paymentInfo_OK(Integer id) {
return "通配服务降级FeignFallback";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "通配服务降级FeignFallback";
}
}
hystrix案例(服务熔断)
断路器:一句话就是家里的保险丝
熔断是什么:马丁福乐提出的概念
熔断案例
服务熔断 @HystrixCommand
注解,配置了如下参数,当一直抛出异常失败率达到后,会跳匝,此时无论是正常请求还是错误请求都会返回fallbackMethod
,持续一会儿后会自动恢复。
//---服务的熔断
@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) {
if (id<0) {
throw new RuntimeException("******id不能为负数");
}
String simpleUUID = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t" + "成功调用,流水号是:" + simpleUUID;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id不能为负数,请稍后再试............"+id;
}
熔断状态
-
熔断打开:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态
-
熔断关闭:熔断关闭后不会对服务进行熔断
-
熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
GateWay
概述
gateway是什么:
概述:
微服务架构中网关在哪里
为什么选择Gateway
1、netflix不太靠谱,zuul2.0一直跳票,迟迟不发布
2、SpringCloud Gateway具有如下特性
3、SpringCloud Gateway与Zuul的区别
Zuul1.x模型:
Gateway模型:
总结选择gateway主要原因,非阻塞异步、亲儿子
三大核心概念
-
Route(路由):
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由 -
Predicate(断言):
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由 -
Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.
入门案例配置
1、新建一个网关模块 cloud-gateway-gateway9527
2、导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
3、启动类
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class,args);
}
}
4、application.yml
中配置网关路由
server:
port: 9527
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
routes:
- id: payment_routh
# uri: http://localhost:8001
uri: lb://CLOUD-PROVIDER-PAYMENT #动态路由
predicates:
- Path=/payment/get/**
- id: payment_routh2
# uri: http://localhost:8001
uri: lb://CLOUD-PROVIDER-PAYMENT
predicates:
- Path=/payment/lb/**
discovery:
locator:
enabled: true #开启从注册中心动态生成路由的功能,用微服务名进行路由
eureka:
instance:
hostname: cloud_gateway_service
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
register-with-eureka: true
fetch-registry: true
此时就可以通过网关http://localhost:9527/payment/get/2
访问到 http://localhost:8001/payment/get/2
GateWay的动态路由uri: lb://CLOUD-PROVIDER-PAYMENT
就能动态路由到相同服务名的服务
GateWay常用的Predicate 断言
各个断言的详解可以参考网上各种解答–各种断言
GateWay的Filter
GateWay有自带的31种过滤器,不过都用不到且很麻烦,这里自定义GateWay的全局过滤器,只需实现两个接口
@Component
public class MyGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("**************come in MylogGateWayGilter: " + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
System.out.println("********用户名为空,非法用户。");
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?uname=123123
SpringCloud Stream消息驱动
概念
是什么:
一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
设计思想
Stream如何统一底层差异
Binder
INPUT适用于消费者
OUTPUT适用于生产者
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Stream中的消息通讯方式遵循了发布-订阅模式
- Topic主题进行广播
- 在RabbitMQ就是Exchange
- 在Kafka中就是Topic
Stream编码常用注解简介
Stream消息驱动之生产者
1、新建模块cloud-stream-rabbitmq-provider8801
2、导入依赖,关键依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3、配置yml文件
server:
port: 8801
spring:
application:
name: cloud-stream-privider
cloud:
stream:
binders: #自此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 115.159.35.229
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的exchange名称定义
content-type: application/json #设置消息类型,本次为json
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
instance-id: send-8801.com
prefer-ip-address: true #访问的路径变为IP地址
4、service中定义消息的推送管道
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {
@Autowired
private MessageChannel output;//消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("****serial"+serial);
return null;
}
}
-----------------
@RestController
public class SendMessageController {
@Autowired
private IMessageProvider messageProvider;
@GetMapping("/sendMessage")
public String sendMessage(){
return messageProvider.send();
}
}
5、先启动RabbitMq,启动Eureka,启动Stream服务端,此时根据yml中配置的output会自动创建Exchange
Stream消息驱动之消费者
同生产者一样的依赖,区别在于配置文件中,bindings为input
server:
port: 8802
spring:
application:
name: cloud-stream-privider
cloud:
stream:
binders: #自此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 115.159.35.229
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
input: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的exchange名称定义
content-type: application/json #设置消息类型,本次为json
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
instance-id: send-8802.com
prefer-ip-address: true #访问的路径变为IP地址
controller中定义消费者
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("我是消费者1号,-----》接受到的消息是:"+message.getPayload()+"\t"+serverPort);
}
}
此就可以监听到生产者studyExchange交换机中的消息
Stream之消息重复消费与解决办法
再创建一个消费者,配置和8802一模一样,此时这两个消费者就会产生重复消费问题
故障现象:重复消费
导致原因:默认分组group组流水号是不同的,被认为是不同组,导致重复消费
解决办法:自定义配置分组分为同一个组
在两个服务的yml配置中 指定相同的分组名,就可将其划分至同一分组
此时就只存在一个分组,不会再出现重复消费
配置了group属性的才有消息队列持久化
Sleuth
SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin