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

一、服务调用 核心概念

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

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

  例如:

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

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

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

二、整合 Feign 框架图
整合Feign

  本次整合使用九、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=*

猜你喜欢

转载自blog.csdn.net/panchang199266/article/details/84112250