SpringCloud
1.Spring Cloud简介
Spring Cloud将一些非常流行的一些技术整合到一起。实现了:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等。其主要涉及的组件包括:
NetFix
- Eureka:注册中心
- Zuul:服务网关
- Ribbon:负载均衡
- Feign:服务调用
- Hystrix:熔断器
一部分结构图:
2.版本
Spring Clound 和Spring Boot版本对应关系
Release Train | Boot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
3.使用要点
Spring提供了一个RestTemplate模版工具类,对基本HTTP的客户端进行了封装,并且实现了对象与json的序列化和反序列化。RestTemplate并没有限定HTTP的客户端类型,而是进行了抽象,目前常用的3种都有支持:
- HTTPClient
- OkHTTP
- JDK原生的URLConnection(默认的)
4.Eureka注册中心
4.1Eureka简介
Eureka(相当于管理平台)负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉 Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服 务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
4.2原理图
基本架构
- Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向Eureka注册自己的信息(地址,提供什么服务)
- 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
- 心跳(续约):提供者定期通过HTTP方式向Eureka刷新自己的状态
工作原理图解析
高版本的JDK使用spring cloud可能会出现问题,一些依赖没有默认引入,导致没有读取xml配置文件
4.3Eureka详解
4.3.1基础架构
Eureka架构中的三个核心角色:
- 服务注册中心 Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
- 服务提供者 提供服务的应用,可以是Spring Boot应用,也可以是其它任意技术实现,只要对外提供的是REST风格服务即 可。本例中就是我们实现的user-service
- 服务消费者 消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们 实现的consumer-demo
4.3.2配置高可用的Eureka Server
服务同步
多个Eureka Server之间也会相互注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会 把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任 意一个节点,都可以获取到完整的服务列表信息。
而作为客户端,需要把信息注册到每个Eureka中
如果有三个Eureka,则每一个EurekaServer都需要注册到其他的几个Eureka
搭建高可用EurakeServer
(1)配置文件
server:
port: ${port:10086} #当jvm没有指定端口的时候,默认使用10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址,要注册到其他的服务器上
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka #这里写的都会自注册
# 不注册自己
#register-with-eureka: false
# 不拉取服务
#fetch-registry: false
所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就 能互相发现对方,从而形成集群。
(2)复制一份,配置别的端口
在如下界面中的 VM options 中
设置 -Dport=10087 -DdefaultZone=http:127.0.0.1:10086/eureka
(3)同时启动两台eureka server
(4)客户端注册服务到集群
因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:
eureka:
client:
service-url: # EurekaServer地址,多个地址以','隔开
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
4.3.3 Eureka客户端和服务端配置
- Eureka客户端
- user-service服务提供
- 服务地址使用ip地址
- 续约
- consumer-demo服务消费
- 获取服务地址的频率
- user-service服务提供
- Eureka服务端eureka-server
- 失效剔除
- 自我保护
服务注册
服务提供者在启动时,会检测配置属性中的:eureka.clinet.register-with-erueka=true参数是否为true,不配置默认是true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,EurekaServer会把这些信息保存到一个双层的Map结构中。
- 第一层Map的Key就是服务id,一般是配置中的spring.application.name属性,例如:user-service
- 第二层Map的Key是服务的实例id,一般host+serviceId+port,例如:localhost:user-service:8081
- 值则是服务的实例对象,也就是一个服务,这样可以同时启动多个不同实例,形成集群
默认注册时使用的是主机名或者localhost,如果想用IP进行注册们,可以在user-service中添加配置如下:
eureka:
instance:
ip-address: 127.0.0.1 # ip地址
prefer-ip-address: true # 更倾向于使用ip,而不是host名
注意不是在eureka中的控制台服务实例状态显示
服务续约
在注册完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer服务还活着,称为续约(renew)
修改服务续约行为的参数,在user-service中添加如下配置项:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
- lease-expiration-duration-in-seconds:服务续约的间隔,默认30秒
- lease-renewal-interval-in-seconds:服务失效时间,默认90秒
就是说,默认情况下服务会每30秒向EurekaServer进行一次续约心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除
获取服务列表
当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从Eureka Server服 务的列表拉取只读备份,然后缓存在本地。并且 每隔30秒 会重新拉取并更新数据。可以在 consumer-demo 项目 中通过下面的参数来修改:
eureka:
client:
registry-fetch-interval-seconds: 30
4.3.4失效剔除和自我保护
在EurekaServer服务端进行:
服务下线
当服务进行正常关闭操作时,会触发一个服务下线的REST请求给Eureka Server
失效剔除
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下 线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间 (默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。 可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。
自我保护
当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服 务实例的比例是否超过了85%,当EurekaServer节点在短时间内丢失过多客户端(可能发生了网络分区故障)。在 生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因 为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多 数服务依然可用。
关闭自我保护模式:
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
5.负载均衡Ribbon
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等,也可以自定义负载均衡算法
(1)配置两个服务实例,使用不同端口
(2)开启负载均衡
因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。
在RestTemplate的配置方法上添加 @LoadBalanced
注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用:
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, User.class);
}
5.1源码跟踪
显然是有组件根据service名称,获取到了服务实例的ip和端口。因为 consumer-demo 使用的是RestTemplate, spring使用LoadBalancerInterceptor拦截器 ,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服 务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。
5.2负载均衡策略
Ribbon默认的负载均衡策略是简单的轮询
Ribbon拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个 choose方法
测试demo:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class)
public class LoadBalanceTest {
@Autowired
RibbonLoadBalancerClient client;
@Test
public void test() {
for (int i = 0; i < 100; i++) {
ServiceInstance instance = client.choose("user-service");
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
}
SpringBoot也帮我们提供了修改负载均衡规则的配置入口:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式是: {服务名称}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的实现类。
重启测试类:获取策略会变成随机获取
6.Hystrix
NetFlix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务,防止出现级联失败
6.1雪崩问题
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:
如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。 如果此时,某个服务出现异常:
例如: 微服务I 发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的 用户请求到来,越来越多的线程会阻塞:
服务器支持的线程和并发数是有限的,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其他服务都不可用,形成雪崩效应。
这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么 就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这 个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大
Hystrix解决雪崩问题的手段,主要包括:
- 线程隔离
- 服务降级
6.2线程隔离、服务降级
6.2.1原理
线程隔离示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWdjTQCA-1603723510553)(C:\Users\zhqsocool\Desktop\0603-钟豪强-springcloud\课件与笔记\文档\assets\c36.png)]
解读:
- Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
- 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理
服务降级:可以优先保证核心服务
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其服务没有影响。
触发Hystrix服务降级的情况:
- 线程池已满
- 请求超时
6.2.2实现服务降级
服务降级:及时返回服务调用失败的结果,让线程不因为等待服务而阻塞
(1)引入依赖
在消费端系统的pom.xml文件添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
(2)开启熔断
在启动类中添加注解:@EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端发现功能
@EnableCircuitBreaker //开启熔断
public class ConsumerApplication {
//...
}
也可以直接用@SpringCloudApplication代替上面的三个注解
@SpringCloudApplication
public class ConsumerApplication {
// ...
}
(3)编写降级逻辑
当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,要使用HystrixCommand来完成。
改写一下ConsumerController
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallback")
public String queryById(@PathVariable Long id){
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
public String queryByIdFallback(Long id) {
log.error("查询用户信息失败。id:{}", id);
return "对不起,网络太拥挤了!";
}
}
要注意:因为熔断的降级逻辑方法必须跟正常的逻辑方法保证,相同的参数列表和返回值声明。
失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以把queryById的方法改造为返回String, 反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。
说明:
- @HystrixCommand(fallbackMethod = “queryByIdFallBack”):用来声明一个降级逻辑的方法
测试:
当 user-service 正常提供服务时,访问与以前一致。但是当将 user-service 停机时,会发现页面返回了降级处理信息
(4)默认的fallback
可以把Fallback配置加在类上,实现默认fallback
@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法;该类中所有 方法返回类型要与处理失败的方法的返回类型一致。
@RestController
@RequestMapping("/consumer")
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/{id}")
// @HystrixCommand(fallbackMethod = "queryByIdFallback")
@HystrixCommand
public String queryById(@PathVariable Long id){
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
public String queryByIdFallback(Long id) {
log.error("查询用户信息失败。id:{}", id);
return "对不起,网络太拥挤了!";
}
public String defaultFallback(){
return "默认提示:对不起,网络太拥挤了";
}
}
(5)超时设置
请求在超过1秒后都会返回错误信息,这是因为Hystrix的默认超时时长为1,可以修改超时的时长
,修改配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
这个配置会作用于全局所有方法。为了触发修改的超时效果可以让服务处理的方法休眠2秒;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return userService.queryById(id);
}
6.3服务熔断
6.3.1熔断原理
在分布式系统中应用服务熔断后,服务调用方可以自己进行判断哪些服务反应慢或存在大量超时,可以针对这些服务进行主动熔断,防止整个系统被拖垮。
Hystrix的服务熔断机制,可以实现弹性容错,当服务请求情况好转之后,可以自动重连。通过断路的方式,将后续请求直接拒绝,一段时间(默认5秒)之后允许部分请求通过,如果调用成功则回到断路器关闭状态,否则继续打开,拒绝请求的服务。
Hystrix的熔断状态机模型:
状态机有3个状态:
- Closed:关闭状态(断路器关闭),所有请求都正常访问
- Open:打开状态(断路器打开),所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求 百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20 次。
- Half Open:半开状态,不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半 开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断路器,否则继续保持打开,再次进行休眠计时
修改熔断策略的参数配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
7.Feign
为什么叫伪装? Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等 等操作,一切都交给Feign去做。
(1)导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)Feign的客户端
@FeignClient("user-service")
public interface UserClient {
//String url = "http://user-service/user/" + id;
@GetMapping("/user/{id}")
public User queryById(@PathVariable Long id);
}
- Feign会通过动态代理这个接口,生成实现类。这点跟Mybatis的mapper很像
- @FeignClient,声明这是一个Feign客户端,同时通过value属性指定服务名称
- 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
- @GetMapping中的/user,因为Feign需要拼接可访问的地址
编写新的控制类ConsumerFeignController ,使用UserClient访问:
@RestController
@RequestMapping("/cf")
public class ConsumerFeignController {
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
return userClient.queryById(id);
}
}
(3)开启Feign功能
在 ConsumerApplication 启动类上,添加注解,开启Feign功能
Feign中已经自动集成了Ribbon负载均衡,因此不需要自己定义RestTemplate进行负载均衡的配置
@SpringCloudApplication
@EnableFeignClients //开启Feign客户端
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
//@Bean
//@LoadBalanced
//public RestTemplate restTemplate(){
// return new RestTemplate();
//}
}
7.1负载均衡
Feign中本身已经集成了Ribbon依赖和自动配置:
因此不需要额外引入依赖,也不需要再注册 RestTemplate 对象。 Fegin内置的ribbon默认设置了请求超时时长,默认是1000,我们可以通过手动配置来修改这个超时时长:
ribbon:
ReadTimeout: 2000 # 读取超时时长
ConnectTimeout: 1000 # 建立链接的超时时长
或者为某一个具体的service指定
user-service
ribbon:
ReadTimeout: 2000 # 读取超时时长
ConnectTimeout: 1000 # 建立链接的超时时长
因为ribbon内部有重试机制,一旦超时,会自动重新发起请求。如果不希望重试,可以添加配置:
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
7.2Hystrix支持
Feign默认也有对Hystrix的集成,只不过,默认情况下是关闭的。需要通过下面的参数来开启:
(1)修改配置参数
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
但是,Feign中的Fallback配置不像Ribbon中那样简单了。
(2)首先,要定义一个类,实现刚才编写的UserFeignClient,作为fallback的处理类
@Component
public class UserClientFallback implements UserClient{
@Override
public User queryById(Long id) {
User user = new User();
user.setName("用户异常");
return user;
}
}
(3)然后在UserClient中指定编写的实现类为Configuration
@FeignClient(value = "user-service", fallback = UserClientFallback.class, configuration = FeignConfig.class)
public interface UserClient {
//String url = "http://user-service/user/" + id;
@GetMapping("/user/{id}")
public User queryById(@PathVariable Long id);
}
7.3请求压缩
Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:
feign:
hystrix:
enabled: true #开启服务熔断器
compression:
request:
enabled: true #开启请求压缩
response:
enabled: true #开启响应压缩
同时,也可以对请求的数据类型进行限制,以及触发压缩的大小下限进行设置:
以下设置皆是默认值:
feign:
compression:
request:
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
7.4日志级别
之前虽然是通过logging.level.lxs.xx=debug 来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为 @FeignClient 注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。
(1)在 consumer-demo 的配置文件中设置主包下的日志级别都为 debug 修改添加如下配置:
logging:
level:
com.zhq: debug
(2)编写配置类,定义日志级别
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
这里指定的Level级别是FULL,Feign支持4种级别:
- NONE:不记录任何日志信息,这是默认值
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
(3)在UserClient接口类上的@FeignClient注解中指定配置类:
@FeignClient(value = "user-service", fallback = UserClientFallback.class, configuration = FeignConfig.class)
public interface UserClient {
//String url = "http://user-service/user/" + id;
@GetMapping("/user/{id}")
public User queryById(@PathVariable Long id);
}
8.Spring Cloud Gateway网关
8.1简介
- Spring Cloud Gateway是Spring官网基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关 服务。
- Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。
- Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式。
- Spring Cloud Gateway是替代Netflix Zuul的一套解决方案。
Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对 应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka服务注册中心。
网关的核心功能是:过滤和路由
8.2Gateway加入后的架构
不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都可经过网关,然后再 由网关来实现鉴权、动态路由等等操作。Gateway就是我们服务的统一入口。
8.3核心概念
- **路由(route)**路由信息的组成:有一个ID、一个目的URL、一组断言工厂、遗嘱Filter组成。如果路由断言为真,说明请求URL和配置路由匹配
- **断言(Predicate)**Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的 ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于HTTP Request中的 任何信息比如请求头和参数。
- 过滤器(Filter) 一个标准的Spring WebFilter。 Spring Cloud Gateway中的Filter分为两种类型的Filter,分 别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理
8.4使用过程
(1)新建一个Model,引入依赖,因为要注册到eureka服务器中,所以要引入client
<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>
</dependencies>
(2)编写启动类
@SpringBootApplication
@EnableDiscoveryClient //开启eureka注册服务发现功能
public class GatewallApplication {
public static void main(String[] args) {
SpringApplication.run(GatewallApplication.class, args);
}
}
(3)配置文件
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
uri: http://127.0.0.1:9091
# 路由断言,可以配置映射路径
predicates:
- Path=/user/**
将符合Path规则的一切请求,都代理到uri参数指定地址,这里将路径中包含有 /user/** 开头的请求,代理到http://127.0.0.1:9091
例如:http://localhost:10010/user/7
->http://localhost:9091/user/7
上面的代理被写死了所以
8.5面向服务的路由
应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由!
(1)修改映射配置,通过服务名称获取
路由配置中uri所用的协议为lb时(以uri: lb://user-service为例),gateway将使用 LoadBalancerClient把 user-service通过eureka解析为实际的主机和端口,并进行ribbon负载均衡。
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
#uri: http://127.0.0.1:9091
#负载均衡,通过LoadBalanceClient进行服务名解析,然后去Ribbon去拉取列表
uri: lb://user-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/user/**
8.6路由前缀
客户端的请求地址与微服务的服务地址如果不一致的时候,可以通过配置路径过滤器实现路径前缀的添加和去除。
提供服务的地址:http://127.0.0.1:9091/user/8
-
添加前缀:对请求地址添加前缀路径之后在作为代理的服务地址:
http://127.0.0.1:10010/8
–>http://127.0.0.1:9091/user/8
添加前缀路径/user -
去除前缀:将请求地址中路径去除一些前缀路径之后再作为代理的服务地址:
http://127.0.0.1:10010/api/user/8
–>http://127.0.0.1:9091/user/8
去除前缀路径/api
8.6.1添加前缀
在gateway中可以通过配置路由的过滤器PrefixPath,实现映射路径中地址的添加
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
#uri: http://127.0.0.1:9091
#负载均衡,通过LoadBalanceClient进行服务名解析,然后去Ribbon去拉取列表
uri: lb://user-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/user/**
filters:
#添加请求路径的前缀
- PrefixPath=/user
通过PrefixPath=/xxx 来指定了路由要添加的前缀
- PrefixPath=/user
http://localhost:10010/8
–>http://localhost:9091/user/8
- PrefixPath=/user/abc
http://localhost:10010/8
–>http://localhost:9091/user/abc/8
8.6.2去除前缀
在gateway中可以通过配置路由的过滤器StripPrefix,实现映射路径中地址的去除
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
#uri: http://127.0.0.1:9091
#负载均衡,通过LoadBalanceClient进行服务名解析,然后去Ribbon去拉取列表
uri: lb://user-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/user/**
filters:
#去除请求路径的后缀,1表示过滤一个路径,2表示两个路径,以此类推
- StripPrefix=1
通过 StripPrefix=1 来指定了路由要去掉的前缀个数,如:路径 /api/user/1 将会被代理到 /user/1 。
- StripPrefix=1
http://localhost:10010/api/user/8
–>http://localhost:9091/user/8
- StripPrefix=2
http://localhost:10010/api/user/8
–>http://localhost:9091/8
8.7过滤器
8.7.1简介
Gateway作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作往往是通过网关提供的过滤器来实现 的。去除、添加路由前缀的功能也是使用过滤器实现的。
Gateway自带过滤器有几十个,常见自带过滤器有:
过滤器名称 | 说明 |
---|---|
AddRequestHeader | 对匹配上的请求加上Header |
AddRequestParameters | 对匹配上的请求路由添加参数 |
AddResponseHeader | 对从网关返回的响应添加Header |
StripPrefix | 对匹配上的请求路径去除前缀 |
配置全局默认过滤器
这些自带的过滤器可以和使用 路由前缀 章节中的用法类似,也可以将这些过滤器配置成不只是针对某个路由;而是可以对所有路由生效,也就是配置默认过滤器:
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
#uri: http://127.0.0.1:9091
#负载均衡,通过LoadBalanceClient进行服务名解析,然后去Ribbon去拉取列表
uri: lb://user-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/user/**
filters:
#添加请求路径的前缀
#- PrefixPath=/user
#去除请求路径的后缀,1表示过滤一个路径,2表示两个路径,以此类推
- StripPrefix=1
#默认过滤器,对所有路由都生效
default-filters:
- AddResponseHeader=X-Response-Foo, Bar
- AddResponseHeader=abc-myname,lxs
过滤器类型:Gateway实现方式上,有两种过滤器
- 局部过滤器:通过 spring.cloud.gateway.routes.filters 配置在具体路由下,只作用在当前路由上; 如果配置spring.cloud.gateway.default-filters 上会对所有路由生效也算是全局的过滤器;但是这些过滤器的实现上都是要实现GatewayFilterFactory接口。
- 全局过滤器:不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter接口即可
8.7.2执行生命周期
Spring Cloud Gateway 的 Filter的生命周期也类似Spring MVC的拦截器有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用
这里的pre 和 post 可以通过过滤器的 GatewayFilterChain 执行filter方法前后来实现
8.7.3使用场景
- 请求鉴权:一般 GatewayFilterChain 执行filter方法前,如果发现没有访问权限,直接就返回空
- 异常处理:一般 GatewayFilterChain 执行filter方法后,记录异常并返回。
- 服务调用时长统计: GatewayFilterChain 执行filter方法前后根据时间统计。
8.8自定义过滤器
8.8.1自定义局部过滤器
在过滤器(MyParamGatewayFilterFactory)中将url地址的参数name输出到控制台,参数名name是可变的,可以在配置文件中自己自定义,过滤器的名字就是GatewayFilterFactory前面的部分
(1)修改配置文件
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
#uri: http://127.0.0.1:9091
#负载均衡,通过LoadBalanceClient进行服务名解析,然后去Ribbon去拉取列表
uri: lb://user-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/user/**
filters:
#添加请求路径的前缀
#- PrefixPath=/user
#去除请求路径的后缀,1表示过滤一个路径,2表示两个路径,以此类推
- StripPrefix=1
#自定义的局部过滤器MyParam
- MyParam=name
#默认过滤器,对所有路由都生效
default-filters:
- AddResponseHeader=X-Response-Foo, Bar
- AddResponseHeader=abc-myname, lxs
(2)编写过滤器
实现的规则可以去查看Gatewall接口
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
public MyParamGatewayFilterFactory() {
super(MyParamGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("param");
}
@Override
public GatewayFilter apply(Config config) {
//name被封装成了Config.param
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (request.getQueryParams().containsKey(config.param)) {
request.getQueryParams().get(config.param).forEach((v)->{
System.out.println("---局部过滤器过滤的参数----"+config.param+v);
});
}
return chain.filter(exchange);//执行请求
});
}
//读取过滤器配置的参数
public static class Config{
//对应在配置过滤器的时候指定的参数名
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}
注意:自定义过滤器的命名应该为:***GatewayFilterFactory
8.8.2自定义全局过滤器
在过滤器中检查请求中是否携带token请求头。如果token请求头存在则放行;如果token 为空或者不存在则设置返回的状态码为:未授权也不再执行下去。
在gateway的Model中编写全局过滤器
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("------全局过滤器---------");
String token = exchange.getRequest().getHeaders().getFirst("token");
if (StringUtils.isBlank(token)) {
// 设置响应状态码为未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange); //符合条件放行
}
@Override
public int getOrder() {
//值越小优先级越高
return 1;
}
}
用谷歌插件或者软件在请求中加上请求头测试
8.9负载均衡和熔断
Gateway中默认就已经集成了Ribbon负载均衡和Hystrix熔断机制。但是所有的超时策略都是走的默认值,比如服务超时时间只有1S,很容易就触发了。因此建议手动进行配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 #服务降级超时时间,默认1S
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
8.10Gateway跨域配置
一般网关都是所有微服务的统一入口,必然在被调用的时候会出现跨域问题
跨域:在js请求访问中,如果访问的地址与当前服务器的域名、ip或者端口号不一致则称为跨域请求。若不解决则不能获取到对应地址的返回结果。
如:从在http://localhost:9090
中的js访问http://localhost:9000
的数据,因为端口不同,所以也是跨域请求。
在访问Spring Cloud Gateway网关服务器的时候,出现跨域问题的话;可以在网关服务器中通过配置解决,允许哪些服务是可以跨域请求的;具体配置如下:
server:
port: 10010
eureka:
client:
service-url: # EurekaServer地址,多个地址用‘,’隔开
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true #用ip地址
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: user-service-route
#代理的服务地址
#uri: http://127.0.0.1:9091
#负载均衡,通过LoadBalanceClient进行服务名解析,然后去Ribbon去拉取列表
uri: lb://user-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/user/**
filters:
#添加请求路径的前缀
#- PrefixPath=/user
#去除请求路径的后缀,1表示过滤一个路径,2表示两个路径,以此类推
- StripPrefix=1
#自定义的局部过滤器MyParam
- MyParam=name
#默认过滤器,对所有路由都生效
default-filters:
- AddResponseHeader=X-Response-Foo, Bar
- AddResponseHeader=abc-myname, lxs
globalcors:
corsConfigurations:
'[/**]':
#allowedOrigins: * # 这种写法或者下面的都可以,*表示全部
allowedOrigins:
- "http://docs.spring.io"
allowedMethods:
- GET
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 #服务降级超时时间,默认1S
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
说明
上述配置表示:可以允许来自
http://docs.spring.io
的get请求方式获取服务数据。allowedOrigins 指定允许访问的服务器地址,如:
http://localhost:10000
也是可以的。‘[/**]’ 表示对所有访问到网关服务器的请求地址
8.11Gateway的高可用
启动多个Gateway服务,自动注册到Eureka,形成集群。如果是服务内部访问,访问Gateway,自动负载均衡,没问题。
但是,Gateway更多是外部访问,PC端、移动端等。它们无法通过Eureka进行负载均衡,那么该怎么办? 此时, 可以使用其它的服务网关,来对Gateway进行代理。比如:Nginx
8.12Gateway与Feign的区别
- Gateway 作为整个应用的流量入口,接收所有的请求,如PC、移动端等,并且将不同的请求转- 发至不同的 处理微服务模块,其作用可视为nginx;大部分情况下用作权限鉴定、服务端流量控制
- Feign 则是将当前微服务的部分服务接口暴露出来,并且主要用于各个微服务之间的服务调用
9.Spring Cloud Config分布式配置中心
在分布式系统中,由于服务数量非常多,配置文件分散在不同的微服务项目中,管理不方便。为了方便配置文件集 中管理,需要分布式配置中心组件。在Spring Cloud中,提供了Spring Cloud Config,它支持配置文件放在配置服 务的本地,也支持放在远程Git仓库(GitHub、码云)。
使用Spring Cloud Config配置中心后的架构如下图
9.1Git配置管理
知名的Git远程仓库有国外的GitHub和国内的码云(gitee);但是使用GitHub时,国内的用户经常遇到的问题是访 问速度太慢,有时候还会出现无法连接的情况。如果希望体验更好一些,可以使用国内的Git托管服务——码云 (gitee.com)。 与GitHub相比,码云也提供免费的Git仓库。此外,还集成了代码质量检测、项目演示等功能。 对于团队协作开发,码云还提供了项目管理、代码托管、文档管理的服务。本章中使用的远程Git仓库是码云。
9.1.1创建远程仓库
9.1.2创建配置文件
在新建的仓库中创建需要被统一配置管理的配置文件。
配置文件的命名方式:{application}-{profile}.yml 或 {application}-{profile}.properties
- application为应用名称
- profile用于区分开发环境,测试环境、生产环境等
如user-dev.yml,表示用户微服务开发环境下使用的配置文件。
把服务的配置文件创建一份到仓库中
9.2搭建配置中心微服务
(1)创建config-server项目
(2)引入依赖
<?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>test-springcloud</artifactId>
<groupId>com.zhq</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</project>
(3)启动类
@SpringBootApplication
@EnableConfigServer //开启配置服务
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
(4)配置文件
server:
port: 12000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/zhonghaoqiang/my-config.git
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
9.3获取配置中心配置
要改造一下用户微服务 user-service ,配置文件信息不再由 微服务项目提供,而是从配置中心获取。如下对user-service 工程进行改造
(1)添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
(2)修改配置
- 删除 user-service 工程的 user-service\src\main\resources\application.yml 文件(因为该文件从配置 中心获取)
- 创建 user-service 工程 user-service\src\main\resources\bootstrap.yml 配置文件
spring:
cloud:
config:
# 要与仓库中的配置文件的application保持一致
name: user
# 要和仓库中的配置文件的profile保持一致
profile: dev
# 要与仓库中的配置文件所属的版本分支一样
label: master
discovery:
# 使用配置文件
enabled: true
# 配置中心服务名
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
bootstrap区别
bootstrap.yml文件也是Spring Boot的默认配置文件,而且其加载的时间相比于application.yml更早。
application.yml和bootstrap.yml虽然都是Spring Boot的默认配置文件,但是定位却不相同。bootstrap.yml 可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。application.yml 可以用来定义应用级别 的参数,如果搭配 spring cloud config 使用,application.yml 里面定义的文件可以实现动态替换。
总结就是,bootstrap.yml文件相当于项目启动时的引导文件,内容相对固定。application.yml文件是微服务 的一些常规配置参数,变化比较频繁
10.Spring Cloud Bus服务总线
Spring Cloud Bus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管 理。也就是消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。 Spring Cloud Bus可选的消息代理 有RabbitMQ和Kafka。
10.1改造配置中心
(1)先开启rabbitmq服务
(2)引入spring cloud bus依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
(3)修改配置文件
server:
port: 12000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/zhonghaoqiang/my-config.git
# 配置rabbitmq信息;如果都是与默认值一致则不需要配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
management:
endpoints:
web:
exposure:
#暴露触发消息总线的地址
include: bus-refresh
endpoint:
bus-refresh:
enabled: true
10.2改造用户服务
(1)引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
#actuator用来监控、健康查点
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(2)修改配置文件
spring:
cloud:
config:
# 要与仓库中的配置文件的application保持一致
name: user
# 要和仓库中的配置文件的profile保持一致
profile: dev
# 要与仓库中的配置文件所属的版本分支一样
label: master
discovery:
# 使用配置文件
enabled: true
# 配置中心服务名
service-id: config-server
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
(3)UserController
@RestController
@RequestMapping("/user")
@RefreshScope //刷新配置
public class UserController {
@Autowired
private UserService userService;
@Value("${test.name}")
private String name;
@GetMapping("/{id}")
public User queryById(@PathVariable Long id){
System.out.println("配置信息test.name是:"+name);
return userService.queryById(id);
}
}
请求http://127.0.0.1:12000/actuator/bus-refresh
地址的作用是访问配置中心的消息总线服务,消息总线服务接收到请求后会向消息队列中发送消息,各个微服务会监听消息队列。当微服务接收到队列中的消息后,清除本地配置缓存,重新从配置中心获取最新的配置信息