0
点赞
收藏
分享

微信扫一扫

十一、Spring cloud服务调用(Feign)

老王420 2022-06-21 阅读 104

一、服务调用 核心概念

  • 远程过程调用(RPC)
  • 接口定义语言(IDL)
  • 通讯协议(Protocol)
  • Netflix Feign

(一)远程过程调用(RPC)
  远程过程调用(RPC)是一个计算机通信协议。该协议容许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程调用亦可称为远程调用或远程方法调用。

  例如:

  • Java RMI (二进制协议)
  • WebServices(文本协议)

1、消息传递
  RPC 是一种请求-响应协议,一次 RPC 在客户端初始化,再由客户端将请求消息传递到远程的服务器,执行指定的带有参数的过程。经过远程服务器执行过程后,将结果作为响应内容返回到客户端。

2、存根(Stub)
  存根(Stub)是在一次分布式计算 RPC 中,客户端和服务器转换参数的一段代码。由于存根的参数转化,RPC 执行过程如同本地执行函数调用。存根必须在客户端和服务器两端均装载,并且保持兼容。

二、整合 Feign 框架图
十一、Spring cloud服务调用(Feign)_ide

  本次整合使用九、Spring cloud服务短路(Hystrix)中的3个项目:user-api、user-ribbon-client、user-service-provider。

(一)添加依赖

  在项目 user-api 添加 feign 依赖:

<!-- 依赖 Spring Cloud Netflix Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

(二)申明 Feign 客户端

  这里改造的是 user-api 项目下的 服务接口:UserService

/**
* 注解 @FeignClient:申明 Feign 客户端
* @author 咸鱼
* @date 2018/11/11 16:37
*/
@FeignClient(name = "${user.service.name}")//利用占位符,避免未来整合时硬编码
public interface UserService {
/**
* 保存用户
* @param user 待保存对象 {@link User}
* @return true 成功 false 失败
*/
@PostMapping("/user/save")
boolean saveUser(User user);

/**
* 查找所有用户
* @return 用户列表
*/
@GetMapping("/user/find/all")
List<User> findAll();
}

注意:在使用 ​​@FeignClient​​​ ​​name​​ 属性尽量使用占位符,避免硬编码。否则,未来升级时,不得不升级客户端版本。

  对上面的改造做一个简单的解析:
  在以前,我们 客户端(服务调用方) 调用 服务端(服务提供方) 提供的服务时,使用的是 ​​​restTemplate.getForObject("http://" + serviceProviderName + "/user/find/all",List.class)​​​,而我们这里的改造,就是将其转换成这段代码。
  比如 ​​​@FeignClient(name = "${user.service.name}")​​​ 这里的 ​​${user.service.name}​​​ 就是我们的 服务端(服务提供方)应用名,也就是上面的 ​​serviceProviderName​​​;而 ​​@GetMapping("/user/find/all")​​​ 也就是 上面的 ​​"/user/find/all"​​​ 路径。
  在改造完成以后,Feign 框架会自动组装 ​​​@FeignClient(name = "${user.service.name}")​​​ 和 ​​@GetMapping("/user/find/all")​​​ 变为 ​​"http://" + serviceProviderName + "/user/find/all"​​。

(三)激活 Feign 客户端

  需要在 客户端(服务调用方) 激活 Feign,这里的服务调用方就是 ​​user-ribbon-client​​ 项目。

  使用 @EnableFeignClients 激活 Feign 客户端。

/**
* 注解 @RibbonClient:激活 Ribbon
* 注解 @EnableCircuitBreaker:激活 服务短路
* 注解 @EnableFeignClients:激活 Feign 客户端
*/
@EnableFeignClients(clients = UserService.class)//clients 属性:申明 UserService 接口作为 Feign Client 调用
@EnableCircuitBreaker
@SpringBootApplication
@RibbonClient("user-service-provider")//指定目标应用名称
public class UserServiceClientApplication {
......
}

三、Spring Cloud 再整合

(一)整合负载均衡:Nertflix Ribbon

1、客户端:激活 ​​@EnableFeignClients​​​ ​​UserService​

/**
* 注解 @RibbonClient:激活 Ribbon
* 注解 @EnableCircuitBreaker:激活 服务短路
* 注解 @EnableFeignClients:激活 Feign 客户端
*/
@EnableFeignClients(clients = UserService.class)//clients 属性:申明 UserService 接口作为 Feign Client 调用
@EnableCircuitBreaker
@SpringBootApplication
@RibbonClient("user-service-provider")//指定目标应用名称
public class UserServiceClientApplication {
......
}

2、客户端:配置 @FeignClient(name = “${​​user.service.name​​​}”) 中的占位符.
  调整​​​application.properties​

#用户 Ribbon 客户端应用
spring.application.name=spring-cloud-user-service-client

#服务端口
server.port=8080

#关闭 Eureka Client,显示地通过配置方式注册 Ribbon 服务地址(未配置 Eureka 时使用)
eureka.client.enabled=false

#服务提供方名称
service.provider-name=user-service-provider
service.provider.host=localhost
service.provider.port=9090

#定义 user-service-provider Ribbon 的服务器地址
#为 RibbonLoadBalancerClient 提供服务列表
user-service-provider.ribbon.listOfServers=http://${service.provider.host}:${service.provider.port}

#扩展 IPing 实现
user-service-provider.ribbon.NFLoadBalancerPingClassName=org.pc.ping.MyPing

management.endpoints.web.exposure.include=*

#配置 @FeignClient(name = "${user.service.name}") 中的占位符
#user.service.name 实际需要指定 UserService 接口的提供方,也就是 user-service-provider
user.service.name=${service.provider-name}

3、服务端:实现 UserService,即暴露 HTTP REST 服务
 &emsp调整应用:user-service-provider

(1)增加 ​​InMemoryUserServiceImpl​​ Bean 名称

/**
* 内存实现{@link UserService}
* @author 咸鱼
* @date 2018/11/11 16:39
*/
@Service("inMemoryUserServiceImpl")//Bean 名称
public class InMemoryUserServiceImpl implements UserService {
private Map<Long, User> userMap = new HashMap<>();
@Override
public boolean saveUser(User user) {
return userMap.put(user.getId(), user) == null;
}

@Override
public List<User> findAll() {
return new ArrayList(userMap.values());
}
}

(2)调整 ​​UserServiceProviderController​​​ 实现 Feign 客户端接口 UserService,否则需要Controller中的映射 URL 和 UserService 接口中的映射保持一致!!
方式一:调整 ​​​UserServiceProviderController​​ 实现 Feign 客户端接口 UserService

/**
* 用户服务提供方 Controller
* @author 咸鱼
* @date 2018/11/12 18:42
*/
@RestController
public class UserServiceProviderController implements UserService {

@Autowired
@Qualifier("inMemoryUserServiceImpl") //实现 Bean :InMemoryUserServiceImpl
private UserService userService;

/**
* 通过方法继承,URL 映射:"/user/save"
* @param user 待保存对象 {@link User}
* @return
*/
@Override
public boolean saveUser(@RequestBody User user){
return userService.saveUser(user);
}

/**
* 通过方法继承,URL 映射:"/user/find/all"
*/
@Override
public List<User> findAll() {
return userService.findAll();
}
}

方式二:调整Controller中的映射 URL 和 UserService 接口中的映射保持一致

/**
* 用户服务提供方 Controller
* @author 咸鱼
* @date 2018/11/12 18:42
*/
@RestController
public class UserServiceProviderController {

@Autowired
@Qualifier("inMemoryUserServiceImpl") //实现 Bean :InMemoryUserServiceImpl
private UserService userService;

/**
* @param user 待保存对象 {@link User}
*/
@PostMapping("/user/save")
public boolean saveUser(@RequestBody User user){
return userService.saveUser(user);
}

@GetMapping("/user/find/all")
public List<User> findAll() {
return userService.findAll();
}
}

4、客户端:使用 UserService 直接调用远程 HTTP REST 服务

方式一:Controller 实现 UserService 接口(不推荐)

/**
* 注意:官方建议 客户端和服务端不要同时实现 Feign 接口,
* 这里的代码只是一个说明,实际情况最好使用组合的方式,而不是继承。
* 这里的组合就是其中一方实现 Feign 接口,另一方使用映射!!!
* {@link UserService} 客户端 {@link RestController}
*/
@RestController
public class UserServiceClientController implements UserService {

@Autowired
private UserService userService;

/**
* 通过方法继承,URL 映射:"/user/save"
* @param user 待保存对象 {@link User}
*/
@Override
public boolean saveUser(@RequestBody User user){
return userService.saveUser(user);
}

/**
* 通过方法继承,URL 映射:"/user/find/all"
*/
@Override
public List<User> findAll() {
return userService.findAll();
}
}

方式二:直接加映射(推荐)

/**
* {@link UserService} 客户端 {@link RestController}
* @author 咸鱼
* @date 2018/11/15 22:01
*/
@RestController
public class UserServiceClientController {

@Autowired
private UserService userService;

@PostMapping("/user/save")
public boolean saveUser(@RequestBody User user){
return userService.saveUser(user);
}

@GetMapping("/user/find/all")
public List<User> findAll() {
return userService.findAll();
}
}

(二)整合服务短路:Nertflix Hystrix

  这里的整合有两种方式:

  • 第一种:调整 user-api 应用中的服务接口,在 @FeignClient 注解中增加熔断属性类
  • 第二种:直接在 服务端 接口的实现上,使用 @HystrixCommand 注解

方式一:

1、user-api 应用: ​​UserService​​ Fallback实现

/**
* {@link UserService} Fallback 实现
* @author 咸鱼
* @date 2018/11/16 17:27
*/
public class UserServiceFallback implements UserService{
@Override
public boolean saveUser(User user) {
return false;
}

@Override
public List<User> findAll() {
return Collections.emptyList();
}
}

2、user-api 应用: 调整​​UserService​​ @FeignClient属性

/**
* 注解 @FeignClient:申明 Feign 客户端
* name:服务提供方应用名
* fallback:熔断处理类(实现了 {@link UserService},为接口中的每一种方法都实现了熔断处理)
* @author 咸鱼
* @date 2018/11/11 16:37
*/
@FeignClient(name = "${user.service.name}", fallback = UserServiceFallback.class)//利用占位符,避免未来整合时硬编码
public interface UserService {
/**
* 保存用户
* @param user 待保存对象 {@link User}
* @return true 成功 false 失败
*/
@PostMapping("/user/save")
boolean saveUser(User user);

/**
* 查找所有用户
* @return 用户列表
*/
@GetMapping("/user/find/all")
List<User> findAll();
}

方式二:
  服务端:在 ​​​UserServiceProviderController#findAll()​​​ 方法上整合 ​​@HystrixCommand​

/**
* 获取所有用户列表
*/
@HystrixCommand(
//Command 配置
commandProperties = {
//设置超时时间为 100ms
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")
},
//设置熔断方法(PS:当异常发生后的处理方法)
fallbackMethod = "fallbackForGetUsers"
)
@GetMapping("/user/find/all")
public List<User> findAll() {
return userService.findAll();
}

(三)整合服务发现:Nertflix Eureka

1、创建 Eureka Server 子项目

(1)增加 Eureka Server 依赖

<!-- Eureka Server -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

(2)创建引导类 EurekaServerApplication

/**
* Eureka Server 引导类
* @author 咸鱼
* @date 2018/11/19 20:51
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

(3)配置Eureka Server 服务端口

#应用服务名
spring.application.name=user-eureka-server

#服务端口
server.port=7070

#是否需要向其他 Eureka 服务器注册(单机版需要设置 否)
eureka.client.register-with-eureka=false

#是否需要从其他 Eureka 服务器获取注册信息(单机版需要设置 否)
eureka.client.fetch-registry=false

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

2、客户端:配置服务发现客户端

配置应用:user-service-client

(1)增加 Eureka Client 依赖

<!-- 依赖 eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)激活服务发现

/**
* 注解 @RibbonClient:激活 Ribbon
* 注解 @EnableCircuitBreaker:激活 服务短路
* 注解 @EnableFeignClients:激活 Feign 客户端
* 注解 @EnableDiscoveryClient:激活 Eureka 客户端
* @author 咸鱼
* @date 2018/11/11 18:05
*/
@EnableDiscoveryClient
@EnableFeignClients(clients = UserService.class)//clients 属性:申明 UserService 接口作为 Feign Client 调用
@EnableCircuitBreaker
@SpringBootApplication
@RibbonClient("user-service-provider")//指定目标应用名称
public class UserServiceClientApplication {
....
}

(3)配置 Eureka 注册中心:application.properties

#用户 Ribbon 客户端应用
spring.application.name=spring-cloud-user-service-client

#服务端口
server.port=8080

#服务提供方名称
service.provider-name=user-service-provider

#扩展 IPing 实现
user-service-provider.ribbon.NFLoadBalancerPingClassName=org.pc.ping.MyPing

#配置 @FeignClient(name = "${user.service.name}") 中的占位符
#user.service.name 实际需要指定 UserService 接口的提供方,也就是 user-service-provider
user.service.name=${service.provider-name}

#Spring Cloud Eureka 客户端 注册到 Eureka 服务器
eureka.client.service-url.defaultZone=http://localhost:7070/eureka

management.endpoints.web.exposure.include=*

2、服务端:配置服务发现客户端

配置应用:user-service-provider

(1)增加 Eureka Client 依赖

<!-- 依赖 eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)激活服务发现

/**
* 注解 @EnableHystrix:激活 Hystrix
* 注解 @EnableDiscoveryClient:激活 Eureka Client
*/
@EnableDiscoveryClient
@EnableHystrix
@SpringBootApplication
public class UserServiceProviderApplication {
....
}

(3)配置 Eureka 注册中心:application.properties

#用户服务提供方应用信息
spring.application.name=user-service-provider

#服务端口
server.port=9090

#Spring Cloud Eureka 客户端 注册到 Eureka 服务器
eureka.client.service-url.defaultZone=http://localhost:7070/eureka

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

(四)整合配置服务器:Config Server

创建 Config Server 应用

1、增加 Config Server 依赖

<!-- 增加 Config Server 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

2、整合 基于文件系统(File System) 实现

注意:​​user-service-client​​​ ​​application.properties​​​ 中以下内容将会被 ​​配置服务器​​​ 中的 ​​user-service.properties​​ 替代:

(1)激活应用配置服务器
在引导类上标注​​​@EnableConfigServer​

@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

(2)创建本地目录
  理解 Java 中的 ​​​${user.dir}​​​:简单点说,就是当前项目所在物理路径。比如项目所在目录为 ​​E:/spring-cloud​​​ ,那么 ​​user.dir = E:\springDemo\spring-cloud-basis​​​。
  在IDEA 中​​​\src\main\resources​​​目录下,创建一个名为 configs 目录,它的绝对路径:​​${user.dir}\feign\config-server-feign\src\main\resources\configs​

(3)创建 ​​user-service.properties​

#User Service 配置内容

#服务提供方名称
service.provider-name=user-service-provider

#配置 @FeignClient(name = "${user.service.name}") 中的占位符
#user.service.name 实际需要指定 UserService 接口的提供方,也就是 user-service-provider
user.service.name=${service.provider-name}

(4)在本地目录中创建 git 仓库

//进入本地目录
cd E:\springdemo\spring-cloud-basis\feign\config-server-feign\src\main\resources\configs`

//创建 git 仓库
git init .
//添加文件进 git 仓库,并提交
git add .
git commit -m "第一次提交"

(5)配置 git 本地仓库 URI(在 application.properties中配置)

#Spring Cloud Config Server 应用名称
spring.application.name=config-server-feign

#服务端口
server.port=6060

#配置服务器文件系统 git 仓库(PS:user.dir = E:\springDemo\spring-cloud-basis 项目根目录)
#使用 ${user.dir} 减少平台文件系统的不一致
spring.cloud.config.server.git.uri=${user.dir}/feign/config-server-feign/src/main/resources/configs

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

3、配置服务发现客户端
(1)增加 Eureka Client 依赖

<!-- 依赖 eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)激活服务发现

@EnableDiscoveryClient
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
....
}

(3)配置 Eureka 注册中心:application.properties

#Spring Cloud Config Server 注册到 Eureka 服务器
eureka.client.service-url.defaultZone=http://localhost:7070/eureka

4、测试是否可以找到配置项:

​http://localhost:6060/user-service/default​

(五)整合配置客户端:Config Client

  调整应用 ​​user-service-client​​ 作为 Config Client。

1、增加 Config Client 依赖

<!-- 依赖 Config Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

2、ClassPath 下创建 bootstrap.properties

3、配置 bootstrap.properties

(1)bootstrap.properties 配置以 spring.cloud.config. 开头的配置信息

#配置 客户端应用 关联的 应用(通过该选项与 服务端 相连)
# spring.cloud.config.name 是可选的,若未配置,采用 ${spring.application.name}
# 若要配置,就配置为 配置文件(user-service.properties)的 name
spring.cloud.config.name=user-service

#关联 profile
spring.cloud.config.profile=default

#关联 label
spring.cloud.config.label=master

#配置 Config Server 服务器URI
spring.cloud.config.uri=http://127.0.0.1:6060

#Config Server 服务器应用名称
spring.cloud.config.discovery.service-id=config-server-feign

4、配置 Config Client 服务发现客户端
  尽管应用 ​​​user-service-client​​​ 已经整合了 Eureka Client,但是在整合 Config Client 之后,还需要配置相关属性,使应用 ​​user-service-client​​ 能成功注册到 Eureka Server中去。

注意:
  如果当前应用需要提前获取应用信息,那么需要将 Eureka 客户端 注册到 Eureka 服务器配置项“eureka.client.service-url.defaultZone”提前至 bootstrap.properties文件。
原因:
  我们在配置Config 服务器的应用名称时,实质是 Eureka 客户端在 Eureka 服务器中通过应用名称,找到对应的 Config 服务器,所以前提是,必须先将 Eureka 客户端 注册到 Eureka服务器。而bootstrap 上下文是 Spring Boot 上下文的父上下文,它是最先加载的,所以需要将“eureka.client.service-url.defaultZone”配置项放到bootstrap.properties中。

(1)在 bootstrap.properties 激活服务发现

#激活 Config Server 服务发现
spring.cloud.config.discovery.enabled=true

(2)将eureka.client.service-url.defaultZone配置项由application.properties转至bootstrap.propertiess
调整后的bootstrap.properties如下:

#用户 Ribbon 客户端应用
spring.application.name=spring-cloud-user-service-client

#配置 客户端应用 关联的 应用(通过该选项与 服务端 相连)
# spring.cloud.config.name 是可选的,若未配置,采用 ${spring.application.name}
# 若要配置,就配置为 配置文件(user-service.properties)的 name
spring.cloud.config.name=user-service

#关联 profile
spring.cloud.config.profile=default

#关联 label
spring.cloud.config.label=master

#Config Server 服务器应用名称
spring.cloud.config.discovery.service-id=config-server-feign

#激活 Config Server 服务发现
spring.cloud.config.discovery.enabled=true

#Spring Cloud Eureka 客户端 注册到 Eureka 服务器
eureka.client.service-url.defaultZone=http://localhost:7070/eureka

调整后的application.properties如下:

#服务端口
server.port=8080

#扩展 IPing 实现
user-service-provider.ribbon.NFLoadBalancerPingClassName=org.pc.ping.MyPing

management.endpoints.web.exposure.include=*


举报

相关推荐

0 条评论