SpringCloud

1【Eureka Ribbon】

1.1Springcloud

1.1.1 springcloud简介

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

1.2Eureka

1.2.1 简介

1.2.1.1 Eureka介绍

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现(请对比Zookeeper)。

Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。SpringCloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。

Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务

各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到

EurekaClient是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

1.2.1.2 Eureka基本原理

基本原理

上图是来自eureka的官方架构图,这是基于集群配置的eureka;

- 处于不同节点的eureka通过Replicate进行数据同步

- Application Service为服务提供者

- Application Client为服务消费者

- Make Remote Call完成一次服务调用

  • 服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
  • 当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。
  • 服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。

1.2.1.3 Eureka 官方文档地址

https://spring.io/projects/spring-cloud-netflix

1.2.2Eureka代码编写

1.2.2.1 总体说明

本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloud Gateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)。

本案例说明为第一步:搭建SpringCloud项目,完成Eureka服务器、客户端子项目注册并提供各自的服务。

本案例在idea中创建了一个父项目和四个子项目,父项目为微服务项目的总体管理模块,在其中统一引入SpringBoot、SpringCloud所需要的依赖包,并统一设置打包插件。

四个子项目作用如下:

EurekaService:Eureka服务中心子项目,作用是作为Eureka服务程序,管理其他微服务的状态。其他Eureka客户端子项目运行时需在此模块进行注册。

ServiceOne:Eureka客户端-微服务1,提供了一个业务接口“serviceOne”。

ServiceTwo:Eureka客户端-微服务2,提供了一个业务接口“serviceTwo”。

ServiceThree:Eureka客户端-微服务3,提供了两个业务接口:

“serviceThree_toOne”:内部调用ServiceOne的“serviceOne接口”

“serviceThree_toTwo”:内部调用ServiceTwo的“serviceTwo接口”

1.2.2.2 新建SpringCloud父项目

  1. 新建基于Maven的项目

作为SpringCloud其他子模块的父项目,new->Project,选择基于Maven的项目,填入GroupId和AtrifactId(项目名称)然后创建。

  1. 父项目不需要写源码,所以删除项目的src文件夹。
  2. 配置父项目xml中需增加的内容:
<!--指定版本属性,下面引用-->
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2021.0.3</spring-cloud.version>
    <fastjson.version>1.2.47</fastjson.version>
</properties>
<!--引入SpringBoot父项目,不要通过dependencyManagement方式引入,否则打包会有问题-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.8</version>
    <relativePath/> <!--lookup parent from repository-->
</parent>

<!--dependencyManagement可以理解为多继承父项目-->
<dependencyManagement>
    <dependencies>
        <!--引入SpringCloud父项目-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--阿里巴巴Fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<!--统一设置打包插件-->
<build>
    <plugins>
        <!--SpringBoot帮助maven打包项目的插件-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

1.2.2.3 新建Eureka服务中心模块

Eureka服务中心子项目,作用是作为Eureka服务程序,管理其他微服务的状态。

  1. 在父项目中,新建子模块项目:new->Module依然选择maven项目。

Eureka服务中心项目名称我们命名为EurekaService。

  1. Eureka的xml中新增对Eureka的依赖包和maven打包插件:
<!--Eureka项目核心包-->
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  1. 配置Eureka服务中心的yml配置文件
server:
  port: 8000
#eureka配置
eureka:
  instance:
    hostname: 127.0.0.1
  client:
    #由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己
    registerWithEureka: false
    #由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false
    fetchRegistry: false
    serviceUrl:
      #客户端注册Eureka服务端的地址
      defaultZone:  http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
  application:
    #当前应用名称
    name: service-eureka
  1. 主类main方法:
//当前应用程序作为Eureka服务器
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServiceApplication.class,args);
    }
}

1.2.2.4 创建三个微服务子模块并注册为Eureka客户端

  1. 创建三个Eureka客户端子项目

我们创建3个Eureka客户端子项目,命名为ServiceOne、ServiceTwo、ServiceThree。它们的配置都基本一致。

①ServiceOne:在Eureka中注册为客户端,提供一个业务接口:http://127.0.0.1:8001/serviceOne

②ServiceTwo:在Eureka中注册为客户端,提供一个业务接口:http://127.0.0.1:8002/serviceTwo

③ServiceThree:在Eureka中注册为客户端,提供两个业务接口(内部调用ServiceOne和ServiceTwo的接口):

http://127.0.0.1:8003/serviceThree_toOne

http://127.0.0.1:8003/serviceThree_toTwo

  1. 配置Eureka客户端子项目的xml

三个子项目pom.xml的依赖包完全一致:

<dependencies>
    <!--    Eureka客户端    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>
  1. 配置三个子项目的yml

三个子项目的application.yml配置文件基本一样:

  • ServiceOne的yml
server:
  port: 8001
spring:
  application:
    #SpringCloud中使用负载均衡和Gateway网关时,都要求应用名称中不能出现下划线,因此应用名称我们用中划线“-”!
    name: service-one
#eureka配置
eureka:
  client:
    service-url:
      #配置本客户端要注册的Eureka服务端地址(在服务端对应配置)
      defaultZone:  http://127.0.0.1:8000/eureka/
  • ServiceTwo的yml
server:
  port: 8002
spring:
  application:
    #SpringCloud中使用负载均衡和Gateway网关时,都要求应用名称中不能出现下划线,因此应用名称我们用中划线“-”!
    name: service-two
#eureka配置
eureka:
  client:
    service-url:
      #配置本客户端要注册的Eureka服务端地址(在服务端对应配置)
      defaultZone:  http://127.0.0.1:8000/eureka/
  • ServiceThree的yml
server:
  port: 8003
spring:
  application:
    #SpringCloud中使用负载均衡和Gateway网关时,都要求应用名称中不能出现下划线,因此应用名称我们用中划线“-”!
    name: service-three
#eureka配置
eureka:
  client:
    service-url:
      #配置本客户端要注册的Eureka服务端地址(在服务端对应配置)
      defaultZone:  http://127.0.0.1:8000/eureka/
  1. 三个子项目的主类main方法
  • ServiceOne的主类(与ServiceTwo基本一致)
//当前应用为Eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class ServiceOneApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceOneApplication.class, args);
    }
}
  • ServiceTwo的主类(与ServiceOne基本一致)
@EnableEurekaClient
@SpringBootApplication
public class ServiceTwoApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceTwoApplication.class, args);
    }
}
  • ServiceThree的主类,略有不同,需配置RestTemplate的Bean
@EnableEurekaClient
@SpringBootApplication
public class ServiceThreeApplication {
    //服务3需要在内部通过RestTemplate调用服务1、服务2的接口 
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ServiceThreeApplication.class, args);
    }
}
  1. 三个子项目的Controller控制器
  • ServiceOne的控制器(与ServiceTwo基本一致)
@RestController
public class ServiceOneController {
    @RequestMapping("serviceOne")
    public JSONObject serviceOne() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("message", "Service one method return!");
        return jsonObject;
    }
}

  • ServiceTwo的控制器(与ServiceOne基本一致)
@RestController
public class ServiceTwoController {
    @RequestMapping("serviceTwo")
    public JSONObject serviceTwo() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("message", "Service two method return!");
        return jsonObject;
    }
}

  • ServiceThree的控制器

与前两个略有不同,需注入主类配置的RestTemplate,通过它内部调用微服务ServiceOne和ServiceTwo的业务接口。

@RestController
public class ServiceThreeController {
    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping("serviceThree_toOne")
    public JSONObject serviceThree_toOne() {
        //http请求serviceOne的URL并接收返回值
        String result = restTemplate
                .getForObject("http://127.0.0.1:8001/serviceOne", String.class);
        JSONObject jsonObject = new JSONObject();
        //将响应结果拼接在本接口的data结果中,作为接口的响应结果返回
        jsonObject.put("code", 200);
        jsonObject.put("message", "Service three to one method return!");
        jsonObject.put("data", result);
        return jsonObject;
    }
    @RequestMapping("serviceThree_toTwo")
    public JSONObject serviceThree_toTwo() {
        //内部请求ServiceTwo的接口,获取响应结果
        String result = restTemplate
                .getForObject("http://127.0.0.1:8002/serviceTwo", String.class);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("message", "Service three to  two  method return!");
        jsonObject.put("data", result);
        return jsonObject;
    }
    @RequestMapping("serviceThree")
    public JSONObject serviceThree() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("message", "Service three method return!");
        return jsonObject;
    }
}

1.2.2.5 结果验证

  1. 按顺序启动各个子项目

先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!

再启动ServiceOne、ServiceTwo和ServiceThree,启动后如下图:

  1. 进入Eureka服务状态页面,查看服务注册情况

在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们主要查看当前注册情况:

如上图所示,三个Eureka客户端子项目都注册成功。

  1. 验证ServiceOne的接口

在浏览器地址栏或者PostMan中请求ServiceOne的接口地址http://127.0.0.1:8001/serviceOne,验证请求结果:

  1. 验证ServiceTwo的接口

在浏览器地址栏或者PostMan中请求ServiceTwo的接口地址http://127.0.0.1:8002/serviceTwo,验证请求结果:

  1. 验证ServiceThree包装的接口

在浏览器地址栏或者PostMan中请求ServiceThree的两个接口地址。

①http://127.0.0.1:8003/serviceThree_toOne,验证请求结果:

如上图所示,其内部成功的调用了ServiceOne提供的接口。

②http://127.0.0.1:8003/serviceThree_toTwo,验证请求结果:

如上图所示,其内部成功的调用了ServiceTwo提供的接口。

1.3第三节Ribbon

1.3.1 Ribbon简介

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。

负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。

常见的负载均衡有软件Nginx,LVS,硬件 F5等。

相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。

Ribbon在工作时分成两步

第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.

第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

1.3.2 Ribbon代码编写

1.3.2.1总体说明

本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloud Gateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)。

本案例说明为第二步:在第一步完成的基础上,使用ribbon的负载均衡功能,调用ServiceOne的serviceOne接口时,会被“分流”。

本案例在第一步案例基础上,修改了部分配置文件和代码,并新建了用于承担负载分流的接口请求的微服务。我们“复制”了原有的ServiceOne微服务子项目,创建出ServiceOneCopy微服务子项目,有变化的内容如图:

注意:由于Eureka的包已经引用了ribbon依赖包,因此本例无需新增依赖项!

1.3.2.2新建ServiceOneCopy子项目

我们为了验证ribbon的负载均衡功能,需要新建一个用于“业务分流”的子模块,因此我们在此新建一个ServiceOne的分流程序。

  1. 在父项目中,新建子模块项目:new->Module依然选择maven项目。

本项目名称我们命名为ServiceOneCopy。

  1. 模仿ServiceOne子项目“复制”出ServiceOneCopy子项目

ServiceOneCopy的作用是对ServiceOne提供的业务进行分流,因此我们把ServiceOne中的所有内容全部复制到新项目中一份,包括:pom.xml、application.yml、SpringBoot应用主类“ServiceOneApplication”和业务控制器“ServiceOneController”:

然后再做接下来的修改。

  1. 修改yml

ServiceOne中也作了说明:由于在ServiceThree中开启了ribbon负载均衡后,需要通过ServiceOne的“应用名称”而不是IP+端口号访问业务接口,因此我们要把需要负载均衡的业务模块,其应用名称保持一致!

server:
  #同一台设备部署,因此需要不同端口
  port: 8004
spring:
  application:
    #服务名称要与需要负载均衡的服务名称一样!
    name: service-one
#eureka客户端配置(不做改变)
eureka:
  client:
    service-url:
      #配置本客户端要注册的Eureka服务端地址(在服务端对应配置)
      defaultZone:  http://127.0.0.1:8000/eureka/
  1. 修改SpringBoot应用主类名称

在idea中直接运行项目时,会按主类的名称作为SpringBoot启动栏目的名称,因此我们需要把主类名称修改一下,我们修改为:“ServiceOneCopyApplication”。

@EnableEurekaClient
@SpringBootApplication
public class ServiceOneCopyApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceOneCopyApplication.class, args);
    }
}
  1. 修改业务接口的响应结果

我们要分流ServiceOne的业务方法“serviceOne”,因此方法本身不能做改变(方法名、参数、返回结果格式),但为了验证分流的结果,我们在返回的响应结果中增加了“copy”字样文字:

@RestController
public class ServiceOneCopyController {
    @RequestMapping("serviceOne")
    public JSONObject serviceOne() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 200);
        jsonObject.put("message", "Service one  Copy  method return!");
        return jsonObject;
    }
}

1.3.2.3修改ServiceThree子项目

我们需要在ServiceThree中开启ribbon的负载均衡功能,由于ribbon依赖包已被Eureka引用,因此我们无需引入任何新依赖包,所以不需要修改pom.xml文件。

application.yml文件也不做任何修改。

下面对需要修改的两处进行说明:

  1. 修改SpringBoot应用主类,提供负载均衡能力

我们在“ServiceThreeApplication”主类中,给RestTemplate的Bean方法上添加@LoadBalanced注解,给RestTemplate对象开启ribbon负载均衡能力。

之后RestTemplate发送请求时,就会自动进行请求的分流。

@EnableEurekaClient
@SpringBootApplication
public class ServiceThreeApplication {
    //表示此RestTemplate在发出请求时会使用Ribbon的负载均衡功能
    @LoadBalanced
    //服务3需要在内部通过RestTemplate调用服务1、服务2的接口
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ServiceThreeApplication.class, args);
    }
}
  1. 修改请求其他内部业务接口的调用方式

由于需要对某一业务接口“负载均衡”,那么我们就不能再使用原先的IP+端口号的方式调用了,否则还是会把请求指定到一个部署的服务中。

ribbon的负载均衡是通过使用“应用程序名”来代替IP+端口号,来调用业务接口的。

因此我们需要在控制器中修改调用ServieOne的业务接口方式:

从:http://127.0.0.1:8001/serviceOne

修改为:http://service-one/serviceOne

1.3.2.4结果验证

  1. 按顺序启动各个子项目

先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!

再启动ServiceOne、ServiceOneCopy和ServiceThree,启动后如下图:

  1. 进入Eureka服务状态页面,查看服务注册情况

在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们主要查看当前注册情况:

如上图所示,名为“service-one”的微服务,在Eureka中注册数量为2,一个是ServiceOne启动的实例、另一个是ServiceOneCopy启动的实例。

  1. 验证ServiceThree的接口,验证负载均衡情况

在浏览器地址栏或者PostMan中重复请求ServiceThree的接口地址:

http://127.0.0.1:8003/serviceThree_toOne

第一次调用:

第二次调用:

如上图所示,其内部成功的调用了ServiceOne提供的接口,并负载均衡分流至了两个模块!

2【Gateway Feign】

2.1 Gateway

2.1.1 简介

Spring Cloud Gateway旨在提供一种简单而有效的方式来对API进行路由,并为他们提供切面,例如:安全性,监控/指标 和弹性等。

2.1.2 Gateway原理

客户端向spring-cloud-gateway请求网关映射处理程序(gateway handler mapping),如果确认请求与路由匹配,则将请求发送到web处理程序(gateway web handler),web处理程序通过特定于该请求的过滤器链处理请求,图中filters被虚线划分的原因是filters可以在发送代理请求之前(pre filter)或之后执行逻辑(post filter)。先执行所有pre filter逻辑,然后进行请求代理。在请求代理执行完后,执行post filter逻辑。

2.2第二节Gateway程序案例

2.2.1 总体说明

本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloud Gateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)。

本案例说明为第三步:在前两步完成的基础上,引入SpringCloud-Gateway,通过网关统一调用服务。

本案例在第二步案例基础上,原有项目不做改变,只是新建了Gateway子项目,有变化的内容如图:

注意:由于SpringCloud-Gateway是基于webflux的,它跟传统的springboot-mvc是冲突的,因此不需在其中排除springmvc相关包!

2.2.2 新建SpringCloud-Gateway子项目

  1. 在父项目中,新建子模块项目:new->Module依然选择maven项目。

本项目名称我们命名为Gateway。

  1. 配置xml

依赖项配置如下:

<dependencies>
    <!--    Eureka客户端    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- SpringCloud网关Gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

注意:由于SpringCloud-Gateway是基于webflux的,它与spring boot mvc方式不兼容,启动Gateway会报错,因此不要引入spring-boot-starter-web依赖!

  1. 配置yml文件

Gateway项目也是一个Eureka客户端,并且需要SpringCloud-Gateway自身的配置:

server:
  #以后外部访问内部其他微服务接口,都通过Gateway的此端口访问
  port: 8088

spring:
  application:
    name: gateway
  #SprongCloud-Gateway相关配置
  cloud:
    gateway:
      discovery:
        locator:
          # 是否可以通过其他服务的serviceId来转发到具体的服务实例。默认为false
          # 为true,自动创建路由,路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,
          #其中微服务应用名默认大写访问
          enabled: true

#eureka客户端配置
eureka:
  client:
    service-url:
      #配置本客户端要注册的Eureka服务端地址(在服务端对应配置)
      defaultZone: http://127.0.0.1:8000/eureka/
  1. 编写SpringBoot应用主类
//当前应用为Eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

2.2.3 结果验证

  1. 按顺序启动各个子项目

先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!

再启动Gateway、ServiceOne、ServiceOneCopy、ServiceTwo和ServiceThree,启动后如下图:

  1. 进入Eureka服务状态页面,查看服务注册情况

在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们查看当前注册情况:

  1. 验证已有的各业务接口

在使用Gateway之后,所有的请求都应当通过Gateway来统一调用各个微服务业务模块的接口,根据Gateway的配置,调用形式统一为:

http://Gateway_HOST:Gateway_PORT/大写的serviceId/**

我们在浏览器地址栏或者PostMan中依次请求已存在的几个接口地址:

  • ServiceOne模块业务接口

http://127.0.0.1:8088/SERVICE-ONE/serviceOne

由于ServiceOne和ServiceOneCopy都使用同一个应用ID:“service-one”,因此Gateway通过应用ID请求“serviceOne”接口时,已经自动进行了负载均衡,第二次重复调用的结果:

  • ServiceTwo模块业务接口

http://127.0.0.1:8088/SERVICE-TWO/serviceTwo

  • ServiceThree模块业务接口(负载均衡)

http://127.0.0.1:8088/SERVICE-THREE/serviceThree_toOne

第一次调用:

第二次调用:

2.2.4 总结

结论:通过Gateway作为服务网关,可统一调用其他微服务提供的业务接口!

且Gateway会自动对映射为同一服务名称(应用ID)的模块中的相同接口进行负载均衡。

另外,目前还可以通过各微服务子模块的具体业务地址,继续访问它的业务接口。这是因为SpringCloud-Gateway对于各子业务模块是“无感知”、“透明”的。将来需要在各模块添加认证拦截功能,可以保证未经认证的请求不能直接进入各微服务接口!(也可以通过运维手段只开放Gateway端口,其他微服务端口全部为内网端口)

2.3openfeign

2.3.1 介绍

Spring OpenFeign是一个轻量级的http请求调用框架。基于Netflix Feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix。Spring OpenFeign具有可插拔注解支持,包含Feign注解和JAX-RS注解,同时扩展了对Spring-MVC的注解支持。入参和请求都比较直观。Feign封装了HTTP调用流程,通过面向接口的方式,让微服务之间的接口调用变得简单,默认使用的是JDK的httpUrlConnection。 Feign是一个伪客户端,不会做任何的请求处理。

官方文档在这里http://cloud.spring.io/spring-cloud-openfeign/single/spring-cloud-openfeign.html

2.4第四节openfeign程序编写

2.4.1总体说明

本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloud Gateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)

本案例说明为第四步:在前三步完成的基础上,引入Open Feign,通过在Gateway网关中统一调用服务接口(请求时不再出现各微服务名称)。

本案例在第三步案例基础上,原有项目不做改变,只是在Gateway子项目中引入OpenFeign并作相应配置,有变化的内容如图:

2.4.2 修改pom.xml引入OpenFeign的依赖

我们在原有Gateway子项目的pom.xml文件中,引入OpenFeign的依赖:

<!--    feign依赖包    -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.4.3 application.yml不做变化

本次使用Feign,无需改变application.yml配置文件!

2.4.4 修改主类,启用Feign

接下来,我们需要在SpringBootApplication的主类中启用Feign,给主类前添加“@EnableFeignClients”。

//当前应用为Eureka客户端
@EnableEurekaClient
//开启feign
@EnableFeignClients
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

2.4.5 编写Feign接口

在Gateway项目增加feign包,再接下来,我们需要在项目feign包内编写要封装(内部调用其它微服务)的接口。

  1. 封装“SERVICE-ONE”接口

新增“ServiceOneFeign.java”文件,编写接口:

//value值为要调用的服务提供者(其他微服务)的服务ID名称
@FeignClient("SERVICE-ONE")
public interface ServiceOneFeign {
    //内部调用“SERVICE-ONE”微服务的“serviceOne”方法
    @RequestMapping("serviceOne")
    JSONObject serviceOne();
}
  1. 封装“SERVICE-TWO”接口

新增“ServiceTwoFeign.java”文件,编写接口:

//@FeignClient写服务名称(大写),接口要调用对应服务的api
@FeignClient("SERVICE-TWO")
/*1、方法的返回值要与service-one服务的serviceOne 的api对应方法的返回值一致
  2、方法参数列表要与service-one服务的serviceOne 的api对应方法参数列表一致*/
public interface ServiceTwoFeign {
    //访向service-two服务的serviceTwo的api也就是url
    @RequestMapping("serviceTwo")
    //定义方法
    JSONObject serviceTwo();
}
  1. 封装“SERVICE-THREE”接口

新增“ServiceThreeFeign.java”文件,编写接口:

@FeignClient("SERVICE-THREE")
public interface ServiceThreeFeign {
    //内部调用“SERVICE-THREE”微服务的“serviceThree_toOne”方法
    @RequestMapping("serviceThree_toOne")
    JSONObject serviceThree_toOne();
    //内部调用“SERVICE-THREE”微服务的“serviceThree_toTwo”方法
    @RequestMapping("serviceThree_toTwo")
    JSONObject serviceThree_toTwo();
}

.6 提供统一对外服务接口

最后,在Gateway中,编写对外统一提供服务的Controller接口,从此刻起外部只需要同意调用这些“统一形式的服务接口”即可,无需关心具体的微服务子模块了!

我们新增一个控制器“FeignUniformController”:

@RestController
@RequestMapping("feignUniform")
public class FeignUniformController {
    //自动引入上一步编写的Feign接口
    @Autowired
    private ServiceOneFeign serviceOneFeign;
    @Autowired
    private ServiceTwoFeign serviceTwoFeign;
    @Autowired
    private ServiceThreeFeign serviceThreeFeign;
    //内部调用“SERVICE-ONE”微服务的“serviceOne”方法
    @RequestMapping("serviceOne")
    public JSONObject serviceOne() {
        return serviceOneFeign.serviceOne();
    }
    //内部调用“SERVICE-TWO”微服务的“serviceTwo”方法
    @RequestMapping("serviceTwo")
    public JSONObject serviceTwo() {
        return serviceTwoFeign.serviceTwo();
    }
    //内部调用“SERVICE-THREE”微服务的“serviceThree_toOne”方法
    @RequestMapping("serviceThree_toOne")
    public JSONObject serviceThree_toOne() {
        return serviceThreeFeign.serviceThree_toOne();
    }
    //内部调用“SERVICE-THREE”微服务的“serviceThree_toTwo”方法
    @RequestMapping("serviceThree_toTwo")
    public JSONObject serviceThree_toTwo() {
        return serviceThreeFeign.serviceThree_toTwo();
    }
}

注意1:要把ServiceThree项目中api使用服务调用

 

注意2:由于springcloud的Gateway使用openfeign有错误,需要修正代码如下:

package com.tjetc.configuration;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import reactor.core.publisher.Mono;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;


public class CustomBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
    private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;


    public CustomBlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
                                            LoadBalancerProperties properties) {
        super(loadBalancerClientFactory, properties);
        this.loadBalancerClientFactory = loadBalancerClientFactory;
    }

    public CustomBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
        super(loadBalancerClientFactory);
        this.loadBalancerClientFactory = loadBalancerClientFactory;
    }


    @Override
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
        ReactiveLoadBalancer<ServiceInstance> loadBalancer =
                loadBalancerClientFactory.getInstance(serviceId);
        if (loadBalancer == null) {
            return null;
        }
        CompletableFuture<Response<ServiceInstance>> f =
                CompletableFuture.supplyAsync(() -> {
                    Response<ServiceInstance> loadBalancerResponse =
                            Mono.from(loadBalancer.choose(request)).block();
                    return loadBalancerResponse;
                });
        Response<ServiceInstance> loadBalancerResponse = null;
        try {
            loadBalancerResponse = f.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        if (loadBalancerResponse == null) {
            return null;
        }
        return loadBalancerResponse.getServer();
    }
}
package com.tjetc.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoadBalancerClientConfig {
    @Autowired
    private LoadBalancerClientFactory loadBalancerClientFactory;

    @Bean
    public LoadBalancerClient blockingLoadBalancerClient() {
        return new CustomBlockingLoadBalancerClient(loadBalancerClientFactory);
    }
}
package com.tjetc.configuration;

import feign.codec.Decoder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class FeignConfig {
    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
    }

    public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
        final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new GateWayMappingJackson2HttpMessageConverter());
        return new ObjectFactory<HttpMessageConverters>() {
            @Override
            public HttpMessageConverters getObject() throws BeansException {
                return httpMessageConverters;
            }
        };
    }

    public class GateWayMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
        GateWayMappingJackson2HttpMessageConverter() {
            List<MediaType> mediaTypes = new ArrayList<>();
            mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
            setSupportedMediaTypes(mediaTypes);
        }
    }
}

2.4.7结果验证

  1. 按顺序启动各个子项目

先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!

再启动Gateway、ServiceOne、ServiceOneCopy、ServiceTwo和ServiceThree,启动后如下图:

  1. 进入Eureka服务状态页面,查看服务注册情况

在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们查看当前注册情况:

  1. 验证新增统一服务接口

在引入Feign后,我们需要调用Gateway的统一服务接口,形式为:

http://Gateway_HOST:Gateway_PORT/feignUniform/**

我们在浏览器地址栏或者PostMan中依次请求封装好的几个接口地址:

  • feignUniform/serviceOne(ServiceOne模块)业务接口

http://127.0.0.1:8088/feignUniform/serviceOne

由于ServiceOne和ServiceOneCopy都使用同一个应用ID:“service-one”,因此Gateway通过封装的请求“serviceOne”接口时,已经自动进行了负载均衡,第二次重复调用的结果:

  • feignUniform/serviceTwo(ServiceTwo模块)业务接口

http://127.0.0.1:8088/feignUniform/serviceTwo

  • feignUniform/serviceThree_toOne(ServiceThree模块,负载均衡)

http://127.0.0.1:8088/feignUniform/serviceThree_toOne

第一次调用:

第二次调用:

2.4.8 总结

结论:通过Gateway作为服务网关,我们已经做到统一调用其他微服务提供的业务接口。但需要在请求接口路径上添加各业务模块的应用名称,比如:

http://127.0.0.1:8088/SERVICE-ONE/serviceOne

http://127.0.0.1:8088/SERVICE-TWO/serviceTwo

在引入了Feign后,可以做到对外统一服务接口形式,因此暴露给外部的接口地址变为:

http://127.0.0.1:8088/feignUniform/serviceOne

http://127.0.0.1:8088/feignUniform/serviceTwo

外部系统再也无须获知内部的微服务信息了,从而做到了统一服务接口的封装(内部负载均衡)!

2.4.9本节作业

1.掌握Feign的基本概念和作用

2.自行编写完成示例程序并验证结果

posted @ 2022-07-22 23:05  carat9588  阅读(159)  评论(0编辑  收藏  举报