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父项目
- 新建基于Maven的项目
作为SpringCloud其他子模块的父项目,new->Project,选择基于Maven的项目,填入GroupId和AtrifactId(项目名称)然后创建。
- 父项目不需要写源码,所以删除项目的src文件夹。
- 配置父项目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服务程序,管理其他微服务的状态。
- 在父项目中,新建子模块项目:new->Module依然选择maven项目。
Eureka服务中心项目名称我们命名为EurekaService。
- 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>
- 配置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
- 主类main方法:
//当前应用程序作为Eureka服务器
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class,args);
}
}
1.2.2.4 创建三个微服务子模块并注册为Eureka客户端
- 创建三个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
- 配置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>
- 配置三个子项目的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/
- 三个子项目的主类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);
}
}
- 三个子项目的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 结果验证
- 按顺序启动各个子项目
先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!
再启动ServiceOne、ServiceTwo和ServiceThree,启动后如下图:
- 进入Eureka服务状态页面,查看服务注册情况
在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们主要查看当前注册情况:
如上图所示,三个Eureka客户端子项目都注册成功。
- 验证ServiceOne的接口
在浏览器地址栏或者PostMan中请求ServiceOne的接口地址http://127.0.0.1:8001/serviceOne,验证请求结果:
- 验证ServiceTwo的接口
在浏览器地址栏或者PostMan中请求ServiceTwo的接口地址http://127.0.0.1:8002/serviceTwo,验证请求结果:
- 验证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的分流程序。
- 在父项目中,新建子模块项目:new->Module依然选择maven项目。
本项目名称我们命名为ServiceOneCopy。
- 模仿ServiceOne子项目“复制”出ServiceOneCopy子项目
ServiceOneCopy的作用是对ServiceOne提供的业务进行分流,因此我们把ServiceOne中的所有内容全部复制到新项目中一份,包括:pom.xml、application.yml、SpringBoot应用主类“ServiceOneApplication”和业务控制器“ServiceOneController”:
然后再做接下来的修改。
- 修改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/
- 修改SpringBoot应用主类名称
在idea中直接运行项目时,会按主类的名称作为SpringBoot启动栏目的名称,因此我们需要把主类名称修改一下,我们修改为:“ServiceOneCopyApplication”。
@EnableEurekaClient
@SpringBootApplication
public class ServiceOneCopyApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOneCopyApplication.class, args);
}
}
- 修改业务接口的响应结果
我们要分流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文件也不做任何修改。
下面对需要修改的两处进行说明:
- 修改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);
}
}
- 修改请求其他内部业务接口的调用方式
由于需要对某一业务接口“负载均衡”,那么我们就不能再使用原先的IP+端口号的方式调用了,否则还是会把请求指定到一个部署的服务中。
ribbon的负载均衡是通过使用“应用程序名”来代替IP+端口号,来调用业务接口的。
因此我们需要在控制器中修改调用ServieOne的业务接口方式:
从:http://127.0.0.1:8001/serviceOne
修改为:http://service-one/serviceOne
1.3.2.4结果验证
- 按顺序启动各个子项目
先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!
再启动ServiceOne、ServiceOneCopy和ServiceThree,启动后如下图:
- 进入Eureka服务状态页面,查看服务注册情况
在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们主要查看当前注册情况:
如上图所示,名为“service-one”的微服务,在Eureka中注册数量为2,一个是ServiceOne启动的实例、另一个是ServiceOneCopy启动的实例。
- 验证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子项目
- 在父项目中,新建子模块项目:new->Module依然选择maven项目。
本项目名称我们命名为Gateway。
- 配置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依赖!
- 配置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/
- 编写SpringBoot应用主类
//当前应用为Eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
2.2.3 结果验证
- 按顺序启动各个子项目
先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!
再启动Gateway、ServiceOne、ServiceOneCopy、ServiceTwo和ServiceThree,启动后如下图:
- 进入Eureka服务状态页面,查看服务注册情况
在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们查看当前注册情况:
- 验证已有的各业务接口
在使用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包内编写要封装(内部调用其它微服务)的接口。
- 封装“SERVICE-ONE”接口
新增“ServiceOneFeign.java”文件,编写接口:
//value值为要调用的服务提供者(其他微服务)的服务ID名称
@FeignClient("SERVICE-ONE")
public interface ServiceOneFeign {
//内部调用“SERVICE-ONE”微服务的“serviceOne”方法
@RequestMapping("serviceOne")
JSONObject serviceOne();
}
- 封装“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();
}
- 封装“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结果验证
- 按顺序启动各个子项目
先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!
再启动Gateway、ServiceOne、ServiceOneCopy、ServiceTwo和ServiceThree,启动后如下图:
- 进入Eureka服务状态页面,查看服务注册情况
在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们查看当前注册情况:
- 验证新增统一服务接口
在引入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.自行编写完成示例程序并验证结果