Spring Cloud 微服务解决方案 详解

1、Spring Cloud概述

1.1、Spring Cloud是什么

  • SpringCloud, 基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。
  • SpringCloud利用SpringBoot的开发便利性,巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用SpringBoot的开发风格做到一键启动和部署。
  • SpringBoot并没有重复造轮子,它只是将目前各家公司开发的比较成熟,经得起实际考研的服务框架组合起来,通过SpringBoot风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂,易部署和易维护的分布式系统开发工具包

1.2、Cloud和Boot的关系

  • SpringBoot专注于快速方便的开发单个个体微服务。
  • SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等集成服务。
  • SpringBoot可以离开SpringClooud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系

2、Hello World

在进行开发的时候,需要注意cloud和对应的boot的版本,在这里本篇文章使用 H SR6版的cloud 和 2.2.5版的Boot进行讲述
Spring Cloud 微服务解决方案 详解_第1张图片
首先先构建一个maven项目,这个项目用来作为所有项目的父项目,用来对cloud和boor进行管理,我们只需要在pom文件当中进行导入相对应的依赖即可。

父项目依赖:用来统一管理boot和cloud的版本,以及后续子组件的使用直接继承这个父项目即可进行使用boot和cloud的组件。

可配合代码浏览master分支 https://github.com/lizuoqun/springcloud_parent.git


<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">

    <groupId>com.lzqgroupId>
    <artifactId>springcloud_parentartifactId>
    <packaging>pompackaging>
    <version>0.0.1-SNAPSHOTversion>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.2.5.RELEASEversion>
    parent>

    <properties>
        <spring.cloud-version>Hoxton.SR6spring.cloud-version>
    properties>

    <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>${spring.cloud-version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
project>

3、服务注册中心

3.1、Eureka

Eureka包含两个组件:EurakaServer 和 Eureka Clinet

  • EurakaServer负责注册服务,各个节点启动后,注册,服务注册表中会有存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到
  • Eureka Clinet是java客户端,用于简化Eureka Server的操作,客户端同时内置使轮询负载算法的负载均衡器,在启动后,会三十秒,向服务器发送心跳,如果服务器有多个心跳周期内没有接受到某个节点的心跳,那么服务器就会在注册表中移除节点,默认周期为90秒

Eureka的自我保护机制

  • 自我保护模式是一个应对网络异常的安全保护措施
  • 在服务注册成功期间,Eureka会接受到服务的心跳,如果90秒内没有接受到服务的心跳,那么就会视为服务的死亡,会使删除服务的注册表
  • 他不会立刻的清理掉,依旧会对这个服务的信息进行保存,在默认情况下,如果服务端没有在一定的时间接受到某个服务示例的心跳,那么服务端将会注销该示例,默认是90秒。但是当网络分区出现故障,微服务和eureka无法正常的通信,以上的行为可能变得非常的危险,微服务本身是健康的,此时本不该注销这个服务,Eureka通过自身的保护机制来解决这个问题。当服务器短时间丢失过多的客户端时(微服务实例)。那么这些节点会进入自我保护模式,一旦进入到这个模式,Eureka就会保护服务在注册表中的信息。不再删除服务注册表中的数据,(也就是不会撤销任何的微服务)当网络的故障恢复后。Euraka会自动的推出自我保护机制,
  • 在自我保护中的EurekaServer会保护注册表中的信息,不在注销任何微服务,等到可以接受到节点的心跳恢复到阈值以上的时候,这个EurekaServer就会自动的推出自我保护机制,设计思想就是宁可保留错误的服务注册信息,也不去盲目的注销任何可能健康的服务示例
  • 当然可以选择不打开这个自我保护的机制,只需要在Springcloud中使用Eureka.Server.enable-self-preservation = false来禁用自我保护【但是并不推荐这样进行处理】
    Spring Cloud 微服务解决方案 详解_第2张图片

3.1.1、Eureka Server

首先创建一个maven项目,maven项目的父项目使用前面的父项目依赖,再给这个项目进行添加eureka server相关依赖,

    <parent>
        <artifactId>springcloud_parentartifactId>
        <groupId>com.lzqgroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <groupId>com.lzqgroupId>
    <artifactId>eureka_serverartifactId>
    <version>0.0.1-SNAPSHOTversion>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
        dependency>
    dependencies>

并且添加了springboot的依赖,之后我们添加一个spring boot的启动类,在类上添加@EnableEurekaServer注解。以及给eureka server进行配置,添加配置文件。

# 使用eureka默认端口
server.port=8761
# 服务名(不能出现下划线)
spring.application.name=eureka-lzq
# 服务暴露端口
eureka.client.service-url.defaultZone = http://localhost:8761/eureka
# 关闭eclient立即注册
eureka.client.fetch-registry=false
# 不注册当前应用
eureka.client.register-with-eureka=false
# 不进行自我保护
eureka.server.enable-self-preservation=false
# 超时
eureka.server.eviction-interval-timer-in-ms=3000

启动项目,访问 http://localhost:8761 进行验证,Eureka Server是作为服务端,这个时候8761上面并没有服务注册上来,之后我们可以通过Eureka Client作为项目单独的小模块来进行注册。

3.1.2、Eureka Client

同样的创建一个maven项目,使用前面的父项目,导入eureka client 相关依赖。

    <parent>
        <artifactId>springcloud_parentartifactId>
        <groupId>com.lzqgroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>erueka_clientartifactId>
    <version>0.0.1-SNAPSHOTversion>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
    dependencies>

在进行添加一个启动类,启动类加上一个@EnableEurekaClient注解,该注解表示开启Eureka 客户端,之后添加配置文件,用来指定服务注册的注册中心以及其他的配置。

server.port=8989
spring.application.name=Eureka-Client

# 指定服务注册地址
eureka.client.service-url.defaultZone = http://localhost:8761/eureka

# 默认最大时间
eureka.instance.lease-expiration-duration-in-seconds=10
# 发一次心跳
eureka.instance.lease-renewal-interval-in-seconds=5

这样之后我们启动这个eureka的客户端,同样的访问8761,这个时候我们可以看到当前这个客户端的服务就已经被我们注册上来了。

Spring Cloud 微服务解决方案 详解_第3张图片

3.2、Consul

consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。首先还是在windows下进行使用consul这个服务注册中心,consul官网,首先将consul下载下来。在安装目录下打开cmd,执行consul agent -dev命令运行,consul会默认提供一个访问的8500端口。这里启动起来的consul也就是对应的服务端,之后我们还是通过cloud来进行开发对应的客户端,首先继承之前的父项目,添加consul相关依赖。

    <parent>
        <artifactId>springcloud_parentartifactId>
        <groupId>com.lzqgroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>consul_clientartifactId>
    <version>0.0.1-SNAPSHOTversion>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-consul-discoveryartifactId>
        dependency>
    dependencies>

添加一个boot的启动类,加上@EnableDiscoveryClient注解。以及配置文件

server.port=8991
spring.application.name=Consul-Client

# 指定服务注册地址
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500

spring.cloud.consul.discovery.service-name=A

# 关闭健康检查
# spring.cloud.consul.discovery.register-health-check=false

最后启动该项目,在8500端口暴露的服务进行查看,可以看到A这个服务就被注册上来了

Spring Cloud 微服务解决方案 详解_第4张图片
3.3、服务之间的调用 RestTemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。

在这里我们还是在父项目下新加两个springboot项目,添加boot的相关依赖后,对每一个项目都添加一个请求,那我们如何在第一个项目当中去调用另一个项目的请求呢?,可以使用到RestTemplate,如下:

    @GetMapping("/order")
    public String getOrder(){
        RestTemplate restTemplate = new RestTemplate();
        String res =restTemplate.getForObject("http://localhost:8888/getUser",String.class);
        System.out.println("res = " + res);
        return "getOrder" + " ====  " + res;
    }

使用RestTemplate存在的问题:

  • 调用服务的路径主机和服务端直接写死在url中无法实现服务集群时请求负载均衡
  • 调用服务的请求路径写死在代码中,日后提供服务服务路轻发生变化时不利于后期维护

解决方案——负载均衡

  • 使用自定义负载均衡(自定义随机)—— 无法对服务进行健康检查
  • 使用Ribbon负载均衡

4、负载均衡

4.1、Ribbon

这里接上面的问题进行阐述,也就是说两个服务之间的通信,使用ribbon进行负载均衡,在这里使用到之前注册到consul上面的两个服务,在consul依赖引入的时候实惠依赖ribbon的依赖,这里就不需要再添加依赖了。参照userorder两个项目

在这里进行处理,主要有三种方式:

  • DiscoveryClient:服务发现客户端对象,可以根据服务ID进行获取对应的服务列表
  • LoadBalancerClient:负载均衡客户端对象,同样的根据ID获取列表,之后根据内部负载均衡机制策略选择其中的一个服务
  • @LoadBalanced:负载均衡注解,作用于方法上
// 方法一
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("Users");
serviceInstances.forEach(serviceInstance -> {
	System.out.println("host = "+ serviceInstance.getHost() +", port ="+ serviceInstance.getPort() +", url ="+ serviceInstance.getUri());
});

// 方法二
ServiceInstance serviceInstances1 = loadBalancerClient.choose("Users");
System.out.println("url = " + serviceInstances1.getUri()+", port = "+serviceInstances1.getPort() + " ,host = "+serviceInstances1.getHost());

在这里都是直接new一个RestTemplate对象,但是在开发过程当中,还是应该避免这种new对象的方式,尽量将对象交由bean工厂进行统一管理。之后进行使用的时候只需要将getRestTemplate这个Bean对象进行注入进来即可。

@Configuration
public class BeanConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

4.2、Ribbon负载均衡原理

首先上来先分析一下源码,如下图:

Spring Cloud 微服务解决方案 详解_第5张图片
从最开始接口进行获取服务,根据服务标识得到对应的单个服务,也就是choose

ServiceInstance serviceInstances1 = loadBalancerClient.choose("Users");

之后ctrl进入该方法,可以看到会进入到一个接口 interface ServiceInstanceChooser 查看该接口的实现类 RibbonLoadBalancerClient,该类重写了choose方法,重写方法调用当前了的choose方法,如下:

	@Override
	public ServiceInstance choose(String serviceId) {
		return choose(serviceId, null);
	}
	
	public ServiceInstance choose(String serviceId, Object hint) {
		Server server = getServer(getLoadBalancer(serviceId), hint);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}

而当前类的choose方法的getServer就根据服务id进行返回服务,所以我们追踪到getServer方法。

	protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

继续进入到chooseServer,还是一个接口,interface ILoadBalancer,到这个接口,就比较男判断后续到底会进入到哪一个实现类,这里可以进行打断点进行查看,到最后进入到BaseLoadBalancer实现类,在这个类当中进行判断,通过rule.choose(key);而这个rule是一个变量,可以看到

    private final static IRule DEFAULT_RULE = new RoundRobinRule();
    protected IRule rule = DEFAULT_RULE;

到这里我们就找到了最终的策略的指定,也就是到这个IRule类。我们可以查看这个类的关系图。

Spring Cloud 微服务解决方案 详解_第6张图片
最后我们可以通过 服务id 来进行配置负载均衡的策略

Orders.ribbon.NFloadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

总结:ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。

4.3、OpenFeign

在前面提出的问题,我们使用到Ribbon进行负载均衡处理,但是代码当中的请求地址还是写死的,这个时候又如何处理呢?这个时候就需要使用到Fegin了。

4.3.1、Feign 和 Ribbon

  • Feign 和 Ribbon 是 Spring Cloud 的 Netflix 中提供的两个实现软负载均衡的组件,Ribbon 和 Feign 都是用于调用其他服务的,方式不同。Ribbon可以在客户端配置RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟 http 请求,步骤相当繁琐。Feign 则是在 Ribbon 的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 http 请求,就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写客户端变得非常容易。。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。
  • 要使用Feign,请创建一个接口并对其进行注释。它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用HttpMessageConvertersSpring Web中默认使用的注释。Spring Cloud集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用Feign时提供负载平衡的http客户端。

4.3.2、Feign 可以用来做什么

  • Feign 旨在使编写 Java HTTP 客户端变得更加容易。
  • 前面在使用 Ribbon + RestTemplate 时, 利用 RestTemplate 请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,对于服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常会针对每个微服务自行封装一些客户端类来包装这些服务依赖的调用。所以,Feign 在此基础上除了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign 的实现下,我们只需要创建一个接口,并使用注解的方式来配置它(以前是Dao 接口上main标注 Mapper 注解,现在是一个微服务接口上面标注一个Feign 注解即可),即可完成对一个服务提供方的接口绑定,简化了使用 Spring cloud Ribbon 时, 自动封装服务调用客户端。
  • 并且Fegin集成了Ribbon,利用 Ribbon 维护了服务方的服务列表信息,并且通过轮询实现了客户端的负载均衡,而 Ribbon 不同的是,通过Feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

4.3.3、Feign 和 OpenFeign 的区别

  • feign 是 spring cloud 组件中的一个轻量级 RESTful的HTTP 服务客户端 Reign 内置了 Ribbon, 用来做客户端服务在均衡,去调用服务注册中心的服务,Feign 的使用方式是:使用Feign 注解自定义接口,调用这个接口,就可以低哦啊用服务注册中心的服务。
  • openfeign 是 spring cloud 在 feign 的基础上支持 spring mvc注解 ,如 @RequestMapping 等等,OpenFeign 的@FeignClient 可以解析Spring MVC 的 @RequestMapping注解下的接口,并通过动态代理的方式生产实现类 ,实现类中做负载均衡并且 低耦合调用其他服务。

4.3.4、使用 OpenFeign 进行服务通信

在这里我们使用服务还是注册到consul上,首先添加 openfegin 的依赖,参照feign_payfeign_puduct两个项目

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

在进行使用的时候我们需要在启动类上加上 @EnableFeignClients 开启fegin客户端调用的注解,到最后我们新加一个关于fegin调用的包,在这里也就是当前服务需要调用的服务对应的接口,

package com.lzq.feginclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 *  调用 Product 服务
 *  @author lizuoqun
 * */
@FeignClient(value="Product")
public interface ProductClient {

    @GetMapping("/getProduct")
    String getProduct();
}

而到最后在当前服务的controller当中我们直接把这个类进行注入进来直接进行调用即可,这个时候我们就可以在当前服务调到getProduct这个接口了。这样也就完成了服务直接的调用了,也就避免了之前说到的问题,关于调用的地址在代码当中写死的问题。

4.3.5、使用 OpenFeign 进行服务通信关于参数传递

关于参数的传递,主要分为以下几种参数的传递:

  • 零散参数传递
  • 对象类型参数传递
  • 数组或集合对象参数传递

4.3.5.1、零散参数传递

零散参数传递通常有两种方式进行,一种就是通过 ? 加参数进行传递,第二种就是通过 / 占位符 进行传递,

首先我们定义接口,并且给改接口传入两个参数用来进行测试,
之后我们在ProductClient当中也添加这个方法,也就是为了feign进行调用服务,同样的需要给定参数,最后直接在当前服务通过ProductClient直接对另一个服务的接口进行调用,传递两个参数过去,这个时候我们直接启动项目,哦豁,启动报错:参数太长?然后把两个参数改成一个参数,又可以了,这是为什么呢?难道就只能传一个参数嘛?当然不是,这是因为在这里openfeign是一个伪HTTP服务,真正调用请求的还是RestTemplate,而我们直接给参数,openfeign跟不久无法判断当前参数是通过?还是 / 进行拼接处理,所以在这里进行使用参数的时候需要加上 @RequestParam 或者 @PathVariable用来告诉openfeign到底是通过那种方式来进行拼接参数。

4.3.5.2、对象参数传递

相同的,我们进行传递对象参数的时候,可以从零散参数对象得到启发,零散参数使用@RequestParam注解,而在Spring当中对象的参数传递使用@RequestBody进行修改对象即可。

4.3.5.3、数组参数传递

对于数组参数传递,还是使用@RequestParam对入参进行修饰即可。

4.3.6、OpenFeign 的相关配置

4.3.6.1、默认超时时间
在使用OpenFeign进行服务调用通信,服务之间调用返回时间默认在1秒之内,在一秒之内没有返回openfeign会抛出异常。

在pay当中进行配置

feign.client.config.服务id.connectTimeout=5000
feign.client.config.服务id.readTimeout=5000

# 修改默认读取和超时时间
# feign.client.config.default.connectTimeout=5000
# feign.client.config.default.readTimeout=5000

4.3.6.2、日志

在构建@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个Feign.Logger实例,可以利用该日志对象的Debug模式来分析Feign的请求细节。

Feign的Level日志级别配置默认是:NONE,不要跟log日志混淆。

日志级别枚举类Logger.Level 说明
NONE 不输出日志
BASIC 输出请求方法、URL、响应状态码、执行时间
HEADERS 基本信息以及请求和响应头
FULL 请求和响应的heads、body、metadata,建议使用这个级别
# 对指定的服务日志监控
feign.client.config.服务id.loggerLevel=full
# 显示openfeign日志
logging.level.com.lzq.feignClient=DEBUG

5、服务熔断

5.1、服务雪崩与服务熔断

服务雪崩:
服务A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算服务A能扛得住请求,服务B和服务C未必能扛得住这突发的请求。此时,如果服务C因为抗不住请求,变得不可用。那么服务B的请求也会阻塞,慢慢耗尽服务B的线程资源,服务B就会变得不可用。紧接着,服务A也会不可用,这一过程如下图所示:
Spring Cloud 微服务解决方案 详解_第7张图片
服务熔断:
当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

  1. 开启熔断

    • 在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。
    • 进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。
  2. 熔断恢复

    • 熔断不可能是永久的。
    • 当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。

5.2、Hystrix

先构建一个maven项目,将consul服务注册中心的依赖导入以及consul对应的配置文件进行配置,之后其余的代码和之前的单个springboot服务一致进行配置,首先我们给这个服务添加一个接口,使用浏览器进行访问,如下接口所示:之后通过传入的id设置为一个负数来进行模拟服务请求抛出异常,

    @GetMapping(value = "/demo")
    public String demo(Integer id) {
        if (id < 0) {
            throw new Error("id 不合法");
        }
        return "demo ok";
    }

之后进行Hystrix依赖引入:

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
        dependency>

在前面所说的,当我们在浏览器当中一直发送请求导致抛出异常,在一定的界限下,该服务就会被熔断,这个如何进行配置呢?,首先给springboot启动器添加一个开启熔断器的注解:@EnableCircuitBreaker,之后在方法上添加 @HystrixCommand(fallbackMethod = demoFallBack) 用来当该服务被熔断之后 fallback 回调使用到的方法,这里的demoFallBack的入参、返回值需要和demo方法一致;

    public String fallbackDemo(Integer id) {
        return "服务已被熔断";
    }

整体流程:
当Hystrix监控到对该服务接口调用触发两个阏值时,会在系统中自动触发熔断器,在熔断器打开期间内,任何到该接口请求均不可用,同时在断路器打开5s后断路器会处于半开状态,此时断路器允许放行一个请求到该服务接口,如果该请求执行成功,断路器彻底关闭,如果该请求执行失败断路器重新打开

5.3、服务降级 Hystrix 与 OpenFeign

服务降级:
客户端从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用此时在客户端,我们可以准备一个FallbackFactory,返回一个默认的值(缺省值),整体的服务水平下降了~ 但是好歹能用

在前面使用Hystrix进行服务降级,是单独只访问量这一个服务,但是在项目当中服务之间是进行相互通信的,当我们当前服务宕机了,其他的服务却通过openfeign还在继续访问,这个时候页面所展现的就是该网站无法访问,

同样的构建一个springboot项目,对consul注册中心、openfeign进行配置,我们使用这个项目通过openfeign来进行调用前面的Hystrix项目。之后我们对openfeign配置对应的client,将前面的demo接口进行写过来,进行feign调用,在这里openfeign下已经依赖了Hystrix,这里我们就不需要再引入Hystix的依赖了,
再配置文件当中开启openfeign对Hystrix的支持

feign.hystix.enabled=true

在FeignClient这个接口当中的 @FeignClient(value="HYSTRIX",fallback=接口实现类.class ) 这里的fallback用来指定当前调用服务不可用时,默认的备选方案,而后定义一个类继承FeignClient这个接口,实现demo方法,并且给实现类加上一个@Configuration注解。

5.4、Hystrix Dashboard仪表盘

作用:
监控每一个@HystrixCommond注解创建一组度量,构建一组信息,然后通过图形化方式展示当前方法@Hystrixcomnond的状态信息

构建仪表盘
首先还是构建一个springboot项目,进行依赖引入,springboot web、Hystrix DashBoard

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>

最后在启动类上加上@EnableHystrixDashboard注解,用表开启仪表盘应用
启动项目,使用localohost+端口+/hystrix 进行访问

问题解决

问题一:仪表盘让监控的application地址一直在loadding
Spring Cloud 微服务解决方案 详解_第8张图片

在启动类当中添加:

//    /**
//     * 入口类配置 你要监控那个服务,就把这个放在那个服务去
//     *
//     * @return ServletRegistrationBean
//     */
    @Bean
    public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        //registrationBean.addUrlMappings("/actuator/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

问题二:版本不一致,JQ版本导致前端报错。

找到对应的jar包,之后在自己的maven仓库当中找到jar包,给里面的monitor.tlh文件进行修改。

Spring Cloud 微服务解决方案 详解_第9张图片
Spring Cloud 微服务解决方案 详解_第10张图片
问题三:页面报错

hystrix.dashboard.proxy-stream-allow-list=""

6、服务网关

6.1、网关概述

6.1.1、网关是什么:

API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。

6.1.2、网关的作用:

  1. 网关统一所有微服务入口
  2. 网关可以实现请求路由转发(router dispatcher)以及请求过程负载均街
  3. 访问服务的身份认证
  4. 防报文重放与防数据算改
  5. 功他调用的业务答.
  6. 响应数据的脱敏
  7. 流量与并发控制,甚至基于冲工调用的计量或者计费等等

6.1.3、核心概念

  • 路由:路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配

    1. id:路由id,没有起名规则,但要求唯一
    2. uri:匹配路由的转发地址
    3. predicates:配置该路由的断言,若符合该断言则会转发到匹配的地址
    4. order:路由的优先级,数字越小优先级越高
  • 断言:Java8中的断言函数。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。

  • 过滤器:一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。

6.2、Gateway
首先构建一个maven项目,导入springboot和网关的依赖

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>

之后需要编写网关的配置文件:(主要是后面的spring.cloud.gateway.routes这一块的配置,这个就是将其他的服务在这里通过网关来进行转发)在之前做RestTemplate新加的两个项目:在这里就使用这两个项目来进行网关转发。在这里插入图片描述
官网 yml 配置:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#fully-expanded-arguments

server:
  port: 7979

spring:
  application:
    name: GATEWAY
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: GATEWAY
    gateway:
      routes:
        - id: user
          uri: http://localhost:8888
          predicates:
            - Path=/getUser

        - id: order
          uri: http://localhost:9999
          predicates:
            - Path=/order

这里之后我们就可以直接使用网关对后面加的user、order服务进行转发访问了。

路由配置:

  1. -:表示数组元素,可以配置多个节点
  2. id:配置的唯一标识,可以和微服务同名,也可以起别的名字,区别于其他 Route。
  3. uri:路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
  4. predicates:断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
  5. Path:路径形式的断言。当匹配这个路径时,断言条件成立
  6. /**:一个或多个层次的路径

通配符网关配置

  1. ? 单个字符
  2. * 任意多个字符,不包含多级路径
  3. ** 任意多个字符,包含多级路径

通过java代码进行路由转发

官网Java配置网关:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-weight-route-predicate-factory

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("user", r -> r.path("/getUser"),
                        uri("http://localhost:8888")).build();
    }
}

网关的负载均衡

存在问题,在前面的配置当中,请求地址是写死的,这样的话就无法进行负载均衡。

uri改成 lb://服务id,也就是 uri: lb://user-lzq

6.3、Gateway的断言和过滤

6.3.1、断言

官网地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-before-route-predicate-factory

6.3.1.1、基于DateTime

  • AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
  • BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
  • BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

可以直接使用Java8之后的ZonedDateTime 获取时间。

    public static void main(String[] args) {
        ZonedDateTime dateTime = ZonedDateTime.now();
        System.out.println(dateTime);
    }

6.3.1.2、基于远程地址

接收一个IP地址段,判断请求主机地址是否在地址段中

  • RemoteAddr=192.168.1.1/24

6.3.1.3、基于Cookie

接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。

  • Cookie=chocolate, ch.

6.3.1.4、基于请求头Header

接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。

  • Header=X-Request-Id, \d+

6.3.1.5、基于Method请求方法

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

  • Method=GET

6.3.1.6、基于Path请求路径

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
最常见的一种断言

  • Path=/user/**

6.3.1.6、基于Query请求参数

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

  • Query=baz, ba.

6.3.2、过滤器

官网地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

6.3.2.1、过滤器是干什么的?

过滤器就是在请求的传递过程中,对请求和响应做一些修改

6.3.2.2、过滤器的生命周期

客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。

  • pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现参数校验、权限校验、流量监控、日志输出、协议转换等;
  • post:这种过滤器在路由到达微服务以后执行。这种过滤器可用做响应内容、响应头的修改,日志的输出,流量监控等。

6.3.2.3、局部过滤器

在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下

过滤器工厂 作用 参数
AddRequestHeader 为原始请求添加Header Header的名称及值
AddRequestParameter 为原始请求添加请求参数 参数名称及值
AddResponseHeader 为原始响应添加Header Header的名称及值
DedupeResponseHeader 剔除响应头中重复的值 需要去重的Header名称及去重策略
Hystrix 为路由引入Hystrix的断路器保护 HystrixCommand 的名称
FallbackHeaders 为fallbackUri的请求头中添加具体的异常信息 Header的名称
PrefixPath 为原始请求路径添加前缀 前缀路径
PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter 用于对请求限流,限流算法为令牌桶 keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo 将原始请求重定向到指定的URL http状态码及重定向的url
RemoveHopByHopHeadersFilter 为原始请求删除IETF组织规定的一系列Header 默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader 为原始请求删除某个Header Header名称
RemoveResponseHeader 为原始响应删除某个Header Header名称
RewritePath 重写原始的请求路径 原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader 重写原始响应中的某个Header Header名称,值的正则表达式,重写后的值
SaveSession 在转发请求之前,强制执行WebSession::save 操作
secureHeaders 为原始响应添加一系列起安全作用的响应头 无,支持修改这些安全响应头的值
SetPath 修改原始的请求路径 修改后的路径
SetResponseHeader 修改原始响应中某个Header的值 Header名称, 修改后的值
SetStatus 修改原始响应的状态码 HTTP 状态码,可以是数字,也可以是字符串
StripPrefix 用于截断原始请求的路径 使用数字表示要截断的路径的数量
Retry 针对不同的响应进行重试 retries、statuses、methods、series
RequestSize 设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload TooLarge 请求包大小,单位为字节,
ModifyRequestBody 在转发请求之前修改原始请求体内容 修改后的请求体内容
ModifyResponseBody 修改原始响应体的内容 修改后的响应体内容

6.3.2.4、内置局部过滤器的使用

  routes:
  - id: user
    uri: lb://user
    predicates:
    - Path=/user/**, /*/edu/**
    filters:
    - SetStatus=250 # 修改返回状态码,其余具体过滤器工厂对应官网文档进行使用即可

6.3.2.5、网关路由规则web页面

在Gateway服务当中,默认会给我们开一个web页面,但是直接进行访问是访问不了的,需要我们进行配置,在yml当中添加配置

management:
  endpoints:
    web:
      exposure:
        include: "*"

直接进行访问:localhost:7979/actuator/gateway/routes

7、统一配置中心

7.1、配置中心概述

比如说有N个微服务的application.yml是相同的,我们要是想修改application.yml需要修改N个这时候我们就需要ConfigServer来帮我们解决这个问题,在配置中心修改一次众多个微服务生效。 SpringCloud Config为服务器架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

首先在configServer当中会集成Git,我们可以将对应的配置文件放到远程仓库进行管理,这是因为在修改配置的时候Git会进行版本记录,方便后期版本回退,
在获取git仓库之后会加到本地缓存,这是为了避免当远程仓库宕机之后,本地微服务还是要得到统一配置。

7.2、远程仓库配置

在进行远程仓库的构建的时候,我们可以使用Github、Gitee、Gitlab等等, 这里还是选择Github,在进入GitHub之后我们创建一个远程仓库项目 config 。

7.3、Config Server

首先我们构建一个项目,继承父项目,之后我们引入相关的依赖,包含springboot、consul等相关依赖,对整个服务进行相关配置。

之后添加ConfigServer依赖

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
		</dependency>

在SpringBoot启动类当中添加@EnableConfigServer注解。最后对远程仓库地址配置:

# 远程仓库配置
spring.cloud.config.server.git.uri=XXX
spring.cloud.config.server.default-label=master

如果这个远程仓库是私有的话就需要配置对应的用户名和密码。

spring.cloud.config.server.git.username=您的账号
spring.cloud.config.server.git.password=您的密码

之后我们直接拉取远程仓库的配置文件进行测试。

远程仓库git地址: https://gitee.com/modify_lzq/config_server.git

在这里我们添加一些properties文件,用来表示拉取的文件

Spring Cloud 微服务解决方案 详解_第11张图片
之后我们直接通过地址进行拉取文件:http://localhost:8880/configclient-xxx.properties

Spring Cloud 微服务解决方案 详解_第12张图片
7.4、Config Client

相同的再构建一个maven项目,这其实就是一个微服务,我们导入springboot、consul依赖,并且添加相对应的配置。并且在这里我们还需要引入config_client的依赖

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-configartifactId>
        dependency>

在前面我们通过了ConfigServer获取到了远程仓库的配置文件,但是在client当中又该如何获取到呢?首先服务都注册到了注册中心,只需要获取到ConfigServer的服务id之后再去找对应的远程仓库即可,而在远程仓库当中又有很多的配置文件,在这里还需要指定获取到哪个配置文件。本地配置文件如下:

# 当前configclient 统一配置中心在注册中心的服务id
spring.cloud.config.discovery.service-id=Config_Server
# 开启当前configclient 根据服务id去注册中心找
spring.cloud.config.discovery.enabled=true

# 指定服务注册地址
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500

# 获取哪个配置文件(Git分支、文件名、环境)
spring.cloud.config.label=master
spring.cloud.config.name=configclient
spring.cloud.config.profile=dev

并且在这里使用application.properties作为配置文件名进行启动的时候是否报错的,在这里需要改成bootstrap.properties或者bootstrap.yml进行启动。

7.5、配置更新

在这里存在一个问题:就是当我们对远程仓库进行修改之后,这里的config_client服务必须进行重启才能够加载最新的配置文件,

解决方案:

  • 手动刷新
  • 自动刷新

7.5.1、手动刷新

当远端git仓库中配置发生变化时,不需要重启微服务就可以直接读取远墙修改之后配置信息,这种就叫手动配置刷新。

  1. 在所有的controller当中添加 @RefreshScope注解,表示不需要重启微服务,将当前scrop域中信息刷新为最新配置信息
  2. 修改远程仓库之后向每一个微服务发送一个POST请求,http://localhost:8881/actuator/refresh
  3. 在微服务配置文件中暴露远程配墙配置刷新墙点 management.endpoints.web.exposure.include="*"

8、消息总线

8.1、Bus概述

Spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。Spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。目前唯一实现的方式是用AMQP消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。

大家可以将它理解为管理和传播所有分布式项目中的消息既可,其实本质是利用了MQ的广播机制在分布式的系统中传播消息,目前常用的有Kafka和RabbitMQ。

8.2、RabbitMQ搭建

RabbitMQ搭建 参考本文:消息中间件之RabbitMQ的安装及消息发送接收

8.3、Bus消息总线自动刷新

首先在ConfigServer当中添加bus依赖:

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-bus-amqpartifactId>
        dependency>

在这里的client同样要配置RabbitMQ的相关配置,这里我们放在远程仓库即可,并且运行启动时出错处理:

spring.cloud.config.fail-fast=true

之后启动项目,在这里遇到了几个坑,首先就是服务总是启动失败,在Client当中找不到Server服务,但是在进行排查的时候,是没有问题的,在最后进行解决的方法是:把当前maven项目进行clear之后再进行启动,人懵了,可能是缓存的问题吧。

再项目启动之后,我们修改远程仓库的配置之后,发送一个Post请求给Server,http://localhost:8880/actuator/bus-refresh,在这里Server服务也需要暴露接口地址,使用management.endpoints.web.exposure.include=*,并且我们也可以对单个服务进行发送Post请求进行更新,在前面的地址最后加上对应的服务ID即可,如:http://localhost:8880/actuator/bus-refresh/CONFIGCLIENT

8.4、WebHook

webhook与异步编程中"订阅-发布模型"非常类似,一端触发事件,一端监听执行。

  • Webhooks是"user-defined HTTP回调"。它们通常由一些事件触发,例如"push 代码到repo",或者"post 一个评论到博客"。
  • **当事件发生时,源网站可以发起一个HTTP请求到webhook配置的URL。**配置之后,用户可以通过在一个站点触发事件,之后再调用另一个站点的行为。可以是任何操作。
  • 普通用户可以使用CI系统或者通知bug追踪系统触发build。
  • 由于webhook使用HTTP协议,因此可以直接被集成到web service。所以他们有时会被用来构建消息队列服务,例如一些RESTful的例子:IronMQ和RestMS。

简单来说:就是当git进行提交文件之后就执行一个Post请求,如下图:

Spring Cloud 微服务解决方案 详解_第13张图片
这个时候可以看到在Gitee上我们直接添加本地Url地址,这很显然是不行的,这个时候就有了内网穿透。

8.5、内网穿透

首先:登录https://natapp.cn/,之后进行注册一个账号并且进行实名,实名之后进行购买隧道,购买一个免费隧道即可,之后对隧道进行配置,只需要指定一个本地要穿透的端口即可。
Spring Cloud 微服务解决方案 详解_第14张图片
之后下载客户端(根据自己的电脑型号进行下载),下载完成之后通过cmd窗口进行启动,使用命令natapp -authtoken=自己的token 进行启动,启动之后本地的8880端口就被穿透给了http://i5pqp3.natappfree.cc,使用这个地址替换掉localohost:8880进行访问也就实现了内网穿透。

Spring Cloud 微服务解决方案 详解_第15张图片

你可能感兴趣的:(#,SpringBoot,Cloud,spring,cloud,微服务,java)