SpringCloud组件使用
1、Eureka
1、什么是服务治理
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
2、什么是服务注册
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以別名方式注册到注册中心上。另一方(消费者或服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用。RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间依赖的关系 ( 服务治理概念 ) 。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)。
3、Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server:提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
EurekaClient:通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
4、eureka的集群搭建
一个原则:互相注册,相互守望
eureka的配置:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
client:
# 表示不向注册中心注册自己
register-with-eureka: false
# 表示自己就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 将自己配置进其他的eureka,多个用逗号分隔开
defaultZone: http://eureka7002.com:7002/eureka/
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
# 表示不向注册中心注册自己
register-with-eureka: false
# 表示自己就是注册中心,我的职责就是维护服务实例,并不需要检索服务
fetch-registry: false
service-url:
# 将自己配置进其他的eureka,多个用逗号分隔开
defaultZone: http://eureka7001.com:7001/eureka/
各个服务的配置:
server:
port: 80
eureka:
client:
# 将自己注册进eureka
register-with-eureka: true
# 是否从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
# 注册进的eureka位置
service-url:
# 配置注册进多个eureka,实现集群的效果
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
spring:
application:
name: cloud-order-service
5、修改主机名显示
修改前:
缺点:容易暴露主机信息
增加如下配置:
eureka:
client:
# 将自己注册进eureka
register-with-eureka: true
# 是否从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
# 注册进的eureka位置
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#########################################
instance:
instance-id: payment8002
prefer-ip-address: true # 鼠标放连接上可以显示ip信息
#########################################
修改后:
6、服务发现
对于注册进eureka里面的微服务,可以通过服务发现(即通过http请求)来获得该服务的信息。
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "/payment/discovery")
public Object discovery() {
// 获取所有的微服务名称
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("*****element:" + service);
}
// 获取指定服务下面不同的集群
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;
}
// 在主启动类上加上@EnableDiscoveryClient注解
7、eureka的自我保护
什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了–因为微服务本身其实是健康的,此时本不应该注绡这个微服务。Eureka通过"自我保护模式"来解决这个问题:当EurekaServer节点在短时间内丟失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
如何禁止自我保护?
2、Ribbon
1、什么是Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。 Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
2、Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给nginx, 然 后 由 nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
3、负载均衡的策略
RoundRobinRule:轮询
原理:rest接口第几次请求数%服务器 集群总数量=实际调用服务位置下标,每次重启后rest接口计数从1开始
RandomRule:随机
RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在短时间内进行重试,获取可用的服务
WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度快的实例选择权重越大,越容易被选择
BestAvailableRule:会先过滤掉由于多次访问故障而处于短期期跳闸状态的服务,先后选择一个并发量小的服务
AvailablityFilterRule:先过滤掉故障实例,再选择并发量小的服务
ZoneAviodanceRule:默认规则,复合判断server所在的区域性能和server的可用性来选择服务器
4、如何替换默认规则
// 注意这个配置类所在的位置,不要和主启动类在同一个包下
@Configuration
public class MyselRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
// 在主启动类上添加这个注解
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselRule.class)
3、OpenFeign
1、简述
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Rib|bon组合使用以支持负载均衡。
2、Feign能干什么
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon + RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper主解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign 集成了Ribbon
利用Ribbon维护了 Payment的服务列表信息,并且通过轮淘实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
3、如何使用
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value="/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
注意:默认自带轮询效果
4、超时控制
5、日志控制
// 自定义日志配置类
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
logging:
level:
#feign日志以什么级别监控那个接口
com.eiletxie.springcloud.service.PaymentFeignService: debug
4、Hystrix
1、分布式系统面临的问题
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C, 微 服 务 B和微服务C又调用其它的微服务,这就是所谓的"扇出〃。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的"雪崩效应"。
2、什么是Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库。在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性。
"断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
3、三个重要的概念
服务降级:
当服务发生异常情况时,不让客户端等待并立刻返回一个友好提示,即fallbck机制
哪些情况会发生服务降级?
程序运行异常、超时、服务熔断触发服务降级、线程池满等情况
服务熔断:
等待补充。。。
服务限流:
等待补充。。。
4、如何使用
// 指定兜底的方法和设置超时时间
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id "+id + "O(∩_∩)O呜呜~";
}
public String paymentInfo_TimeOutHandler(Integer id){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池"+Thread.currentThread().getName()+"paymentInfo_TimeOutHandler,id "+id + "O(∩_∩)O呜呜~";
}
@SpringBootApplication
@EnableEurekaClient
// 主启动类需要加入的注解
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
问题:如果每个类上都指定兜底的方法会导致代码膨胀,因此可以设置全局的兜底方法。只需要在类上加一个
注解,然后在方法上加上
注解即可。如果方法上同样指定了兜底方法,则使用方法指定的方法,即就近原则。
5、zuul2和gateway
说明:zuul2不再使用
1、gateway简介
SpringCloud Gateway的目标是提供统一的路由方式且基于Filter链的方式提供了网关的基本功能。例如:安全、监控指标、限流
2、三大核心概念
Route(路由)
路由是构建网关的基本模块,它由ID、目标URL、一系列的断言和过滤器组成,如果断言为true则匹配该路由。
Predicate(断言)
参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
Filter(过滤)
指的是Spring框架中的GatewayFilter的实例,使用过滤器,可以在请求被路由之前或之后对请求进行修改。
总结:web请求,通过一些匹配条件,定位到真正服务的节点,并在这个转发过程的前后,进行一些精细化的控制,断言就是我们的匹配条件,而过滤器就可以理解为无所不能的拦截器。有了这两个元素,再加上目标URI,就可以实现一个具体的路由了。
3、网关的路由配置
1、yml配置文件的方式
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
#匹配后提供服务的路由地址
uri: http://localhost:8001
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
2、代码中注入RouteLocator的Bean
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder locatorBuilder){
RouteLocatorBuilder.Builder routes = locatorBuilder.routes();
routes.route("path_route_atguigu",r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
4、动态路由配置
1、配置文件的修改
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/** # 断言,路径相匹配的进行路由
5、Pridicate的使用
在yml配置文件中指定相应的配置项即可,如:
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/** # 断言,路径相匹配的进行路由
- After=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #在这个时间之后的请求才有效果
- Cookie=username,eiletxie #带Cookie,并且username的值为eiletxie
- Header=X-Request-Id,\d+ #请求头要有 X-Request-Id属性并且值为整数的正则表达式
6、Filter的使用
1、Filter的声明周期
pre
post
2、Filter的种类
GatewayFilter:单一过滤器
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
filters:
- AddReauestHeader=X-Request, Blue-{segment}
GlobalFilter:全局过滤器
3、自定义过滤器
实现两个接口:GlobalFilter、Ordered
作用:全局日志记录、统一网关权限等
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("进入自定义的过滤器..." + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null){
log.info("用户名为null,非法用户");
// 回应消息
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// 加载过滤器的顺序,数字越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}