SpringCloud五大组件:
1.注册中心:Eureka,Nacos,Consul;
官方推荐使用的是Eureka,但是常用的是阿里提供的Nacos,而Consul基本很少使用
注册中心的作用:
避免服务与服务之间的调用(服务之间的调用是远程的)时把互相的地址/ip写死。一旦写死,如果有一方服务的地址发生改变,那么调用它的那一方就得修改地址,耦合性太高。
所以这时候就需要使用注册中心来作为一个中间件,服务的提供者负责将自己的ip发送给注册中心,而服务的消费者只需要从注册中心拉取对应提供服务的地址/ip,这样服务的消费者就可以动态获取提供者的地址进行调用了。很好的实现了解耦。
这里就讲一下Nacos的配置,因为Nacos是常用的,而且配置方法都相差不多
Nacos的配置:
首先官网下载Nacos:Nacos1.1.3的版本下载地址
1. 下载完成解压后,进入bin目录,在终端中输入:sh startup.sh -m standalone 指令开启
2.创建一个Maven工程,然后创建一个服务的提供者模块,和一个服务的消费者模块
3.在两个模块中的pom文件中导入依赖:
4.在provider(服务的提供者)模块中创建配置文件:application.yml,并配置参数
server:
port: 8000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服务端地址
application:
name: nacos-provider # 服务名称,大小写不敏感,一般与模块名一致(唯一的)
这样就将该服务注册到了Nacos注册中心中
consumer模块也是一样的配置 将server.port端口和application.name属性改一改就可以了
调用方法演示
package com.MrWang.nacos.controller;
import com.MrWang.nacos.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("/order")
public class OrderController {
/*
这个模板类可以自己创建一个包,在包下创建一个spring的配置类,加上@Configuration声明
在写一个方法,在方法上加上@Bean,返回值就是RestTemplate,方法体直接new RestTemplate()就可以了
*/
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
//演示discoveryClient 使用
List
//判断集合是否有数据
if(instances == null || instances.size() == 0){
//集合没有数据
return null;
}
ServiceInstance instance = instances.get(0);
String host = instance.getHost();//获取ip
int port = instance.getPort();//获取端口
System.out.println(host);
System.out.println(port);
String url = "http://"+host+":"+port+"/goods/findOne/"+id;
// 3. 调用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
6.创建启动类,启动即可
注意事项:Eureka如果想配置注册中心需要单独创建一个模块作为配置中心,不像Nacos和Consul是一个服务,直接下载启动就可以
2.客服端负载均衡:Ribbon
为什么要使用负载均衡策略?
答:服务的提供方可能不仅仅只有一台服务器,而是一个集群,比如上面调用服务时,使用的是instances.get(0);这样就将调用的服务写死了,拿到的永远都是第一个服务,没有实现服务的均衡。
Ribbon可以简化restTemplate的调用
使用:
在创建restTemplate方法时的@Bean上加上@LoadBalanced注解
这样在contorller中代码就可以这样编写了:
package com.MrWang.nacos.controller;
import com.MrWang.nacos.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
String url = "http://nacos-provider/goods/findOne/"+id;
// 3. 调用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
这样就实现了客户端的一个负载均衡
Ribbon负责均衡策略:
随机: RandomRule
轮询 : RoundRobinRule (默认)
最小并发: BestAvailableRule
过滤: AvailabilityFilteringRule
响应时间: WeightedResponse TimeRule
轮询重试: RetryRule
性能可用性: ZoneAvoidanceRule
Ribbon还不是最终解决方案,因为路径写死了String url = “http://nacos-provider/goods/findOne/”+id;
而且每启动一个服务都需要配置该路径,比较麻烦,所以就需要Feign组件完善
3.声明式服务调用:Feign
Feign概述:是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端配置。之前在netfilx公司时是不支持SpringMvc注解的,后来别SpringCloud整合后支持了SpringMvc的注解
1
使用步凑:
导入依赖
2.编写Feign调用接口,接口编写规则:消费方调用提供方时,接口的编写尽量与消费方暴露的方法(也就是controller中的方法)一直
package com.MrWang.consumer.feign;
import com.MrWang.consumer.config.FeignLogConfig;
import com.MrWang.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
*
* feign声明式接口。发起远程调用的。
*
String url = "http://nacos-provider/goods/findOne/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
*
* 1. 定义接口
* 2. 接口上添加注解 @FeignClient,设置value属性为 服务提供者的 应用名称
* 3. 编写调用接口,接口的声明规则 和 提供方接口保持一致。
* 4. 注入该接口对象,调用接口方法完成远程调用
*/
@FeignClient(value = "nacos-provider")
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
3.消费方的controller中直接注入该接口并调用方法即可
注意:接口一般是不能注入使用的,但是spring会自动产生接口的代理对象,所以可以直接使用
Feign的超时:两种
1.连接超时 (服务调用服务的时间,默认时间1s)
2.业务逻辑处理超时 (被调用方处理业务的时间,默认时间1s)
可以自己配置,在yml配置文件中
# 设置Ribbon的超时时间
ribbon:
ConnectTimeout: 1000 # 连接超时时间 默认1s 默认单位毫秒
ReadTimeout: 3000 # 逻辑处理的超时时间 默认1s 默认单位毫秒
Feign的日志记录
Feign 只能记录 debug 级别的日志信息。也是在yml配置文件中可以配置
# 设置当前的日志级别 debug,feign只支持记录debug级别的日志
logging:
level:
com.itheima: debug
定义Feign日志级别Bean
FeignLogConfig
package com.MrWang.consumer.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignLogConfig {
/*
NONE,不记录
BASIC,记录基本的请求行,响应状态码数据
HEADERS,记录基本的请求行,响应状态码数据,记录响应头信息
FULL;记录完成的请求 响应数据
*/
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
最后在声明的接口中的@FeignClient注解中加上configuration = FeignLogConfig.class属性即可
4.熔断器:Hystrix
Hystrix也是Netflix公司的
用于隔离访问远程服务、第三方服务时出现级联失败(雪崩)
也就是用户访问服务时,而这个服务也需要访问其它的服务,如果最底层的服务挂了,从而导致整条链路的服务都挂了,这就称为雪崩
解决方案:
隔离:
1.线程池隔离(默认)
例如:服务的调用方有一个线程池大小为100,这个服务需要调用其它的四个服务。这个时候,就会对100的线程池进行拆分,给每一个需要调用的服务配置一个单独的线程池,比如都是25的线程容量,如果其中的一个服务挂了,请求一直发一直发,直到25的线程容量用完后就不会在调用了,这样也不会影响其它服务的使用
2.线程量隔离
给服务的提供方设置一个阈值,也就是每次请求该服务的线程最大数是多少,如果超过了这个阈值,请求将不会在发送,也有效的隔离了服务之间的耦合
降级:异常,超时
服务的提供方和消费方都可以配置,也就是说在出现异常或则超时错误时采用自定义解决方案
熔断
用于监控微服务之间的调用情况
该机制默认是关闭状态
当请求的失败率达到一个阈值时(5秒失败20次),就会打开熔断器,打开后的一段时间内(默认5秒)所有的请求都将拒绝,直 到过了这段时间,会呈现一个半开状态,这时会放一些少量请求进来,如果请求失败,回到打开状态;如果成功,就回到关闭状态
限流
具体实现自己另外搜
熔断的监控(可视化工具)
Hystrix 提供了 Hystrix-dashboard 功能,用于实时监控微服务运行状态。
但是Hystrix-dashboard只能监控一个微服务。
Netflix 还提供了 Turbine ,进行聚合监控。
Turbine搭建流程:添加链接描述
5.网关:Gateway(需要单独起一个模块)
概述:
网关旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
即:用于管理客服端发起请求时的路径,一个请求可能会调用多个微服务,如果每个微服务都需要客户端去调用,那么增加了客服端的压力;所以在这时候就需要一个Gateway组件,用于管理客服端的请求,由Gateway统一接收客户端的请求,在由Gateway去调用微服务。
网关模块yml配置如下:
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
uri: http://localhost:8001/
predicates:
- Path=/goods/**
该配置是有问题的,直接写死了微服务的地址,如果微服务的地址发生了改变,或者是个集群,那么这边也需要修改,耦合性太高
解决:网关与微服务之间使用注册中心交互
修改yml文件:
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
# uri: http://localhost:8001/
# 动态路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
5.Gateway过滤器:
局部过滤器:
也就是过滤单独的一个微服务;在yml配置文件中配置:
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
# uri: http://localhost:8001/
# 动态路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
# 配置局部过滤器
filters:
- AddRequestParameter=username,zhangsan
全局过滤器
过滤所有的微服务
1、 在Gateway模块中定义一个类实现GlobalFilter,Ordered接口
2、在重写filter和getOrder方法
代码:
package com.MrWang.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyFilter implements GlobalFilter, Ordered {
@Override
public Mono
System.out.println("自定义全局过滤器执行了~~~");
return chain.filter(exchange);//放行
}
/**
* 过滤器排序
* @return 数值越小 越先执行
*/
@Override
public int getOrder() {
return 0;
}
}