一、服务调用 核心概念
- 远程过程调用(RPC)
- 接口定义语言(IDL)
- 通讯协议(Protocol)
- Netflix Feign
(一)远程过程调用(RPC)
远程过程调用(RPC)是一个计算机通信协议。该协议容许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程调用亦可称为远程调用或远程方法调用。
例如:
- Java RMI (二进制协议)
- WebServices(文本协议)
1、消息传递
RPC 是一种请求-响应协议,一次 RPC 在客户端初始化,再由客户端将请求消息传递到远程的服务器,执行指定的带有参数的过程。经过远程服务器执行过程后,将结果作为响应内容返回到客户端。
2、存根(Stub)
存根(Stub)是在一次分布式计算 RPC 中,客户端和服务器转换参数的一段代码。由于存根的参数转化,RPC 执行过程如同本地执行函数调用。存根必须在客户端和服务器两端均装载,并且保持兼容。
二、整合 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=*