一、RestTemplate 服务接口调用
- RestTemplate Spring 文档
- getForEntity():返回对象为 响应体中数据 转化成的对象,基本上可以理解为 Json。
- getForObject():返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息。比如:响应头、响应状态码、响应体 等。
- postForEntity():需要 getBody() 获取 Body 数据。
- postForObject():直接返回 Body 数据。
- @LoadBalanced:注解赋予 RestTemplate 负载均衡的能力。
二、Ribbon 服务负载均衡
1. 是什么
- Netflix/ribbon Github
- Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套 客户端负载均衡 的工具。
- 简单的说:Ribbon 是 Netflix 发布的开源项目,主要功能是提供 客户端的软件负载均衡算法 和 服务调用。
- Ribbon 客户端组件提供一系列完善的配置项,如:连接超时、重试等。
- 简单的说:就是在配置文件中列出 Load Balancer(负载均衡 简称 LB) 后面所有的机器,Ribbon 会自动的帮助你基于 某种规则(如:简单轮询、随机连接 等) 去连接这些机器。
- 我们很容易使用 Ribbon 实现 自定义的负载均衡算法。
1.1 Ribbon 架构说明
- Ribbon 在工作时分成两步:
- 第一步:先选择 Server,它优先选择在同一个区域内 负载较少 的 Server。
- 第二步:根据用户指定的策略,再从 Eureka Server 取到的 服务注册列表 中选择一个地址。
- Ribbon 提供了多种策略:轮询、随机 和 根据响应时间加权 等。
- 总结:Ribbon 其实就是一个 软负载均衡 的 客户端组件。
- 他可以和 其他所需请求的客户端 结合使用。
- 和 Eureka 结合只是其中的一个实例。
1.2 Eureka Client 已集成 Ribbon
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--eureka-client 已集成 ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2. 能干嘛
2.1 负载均衡是什么
- 简单的说:就是将 用户的请求平摊 的 分配到多个服务上,从而达到系统的 高可用(HA)。
- 负载均衡解决方法:
- 集中式负载均衡:
即在服务的 消费方 和 提供方 之间使用 独立的 LB 设施(可以是 硬件如 F5,也可以是 软件如 Nginx),由该软件负责把访问请求通过 某种策略 转发至服务的提供方。- 进程内负载均衡:
将 LB 逻辑集成到 消费方,消费方 从 服务注册中心 获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
- 常见的 负载均衡软件 有:软件 Nginx、LVS,硬件 F5 等。
2.2 客户端负载均衡 VS 服务端负载均衡
- Ribbon 本地负载均衡:
在调用微服务接口时候,会在 注册中心 上获取 注册信息服务列表 之后,缓存到本地 JVM,从而在本地实现 RPC 远程服务调用技术。- Nginx 服务器负载均衡:
客户端所有请求都会交给 Nginx,然后由 Nginx 实现转发请求,即 负载均衡 是由服务端实现的。
3. Ribbon 核心组件 IRule
- IRule:根据 特定算法 从 服务列表 中选取一个要访问的服务。
3.1 轮询规则
com.netflix.loadbalancer.RoundRobinRule
3.2 随机规则
com.netflix.loadbalancer.RandomRule
3.3 重试规则
com.netflix.loadbalancer.RetryRule
- 先按照 轮询策略 获取服务,如果 获取服务失败,则在 指定时间 内会进行重试,获取可用的服务。
3.4 加权响应时间规则
WeightedResponseTimeRule
- 对 轮询规则 扩展,响应速度越快 的实例选择 权重越大,越容易被选择。
3.5 最佳可用规则
BestAvailableRule
- 会先过滤掉由于多次访问故障而 处于断路器跳闸状态 的服务,然后选择一个 并发量最小 的服务。
3.6 可用性筛选规则
AvailabilityFilteringRule
- 先过滤掉 故障 实例,再选择 并发较小 的实例。
3.7 分区无效规则【默认规则】
ZoneAvoidanceRule
- 复合判断 Server 所在区域的性能 和 Server 的可用性选择服务器。
4. 自定义随机策略
cloud-consumer-order-eureka80
4.1 修改 RestTemplate
- RestTemplate 加注解
@LoadBalanced
@Configuration
public class ApplicationContextBean {
// 注解 @Bean 等于 applicationContext.xml > <bean id="" class="" />
@Bean
/*
1. 使用 @LoadBalanced 注解,赋予 RestTemplate 负载均衡的能力。
2. 不使用 @LoadBalanced 注解,自定义负载均衡策略。
*/
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4.2 新建规则类 com.qs.rule.MySelfRule
- 官方文档明确给出了警告:
- 这个自定义配置类不能放在
@ComponentScan 注解
所扫描的 当前包下 及 子包下。- 否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的了。
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
// 修改随机策略
return new RandomRule();
}
}
4.3 主启动
- 加注解
@RibbonClient
@SpringBootApplication
// 启用 EurekaClient
@EnableEurekaClient
// 在启动该微服务的时候,就能去加载我们的自定义 Ribbon 随机规则类,从而使配置生效
@RibbonClient(name = "PROVIDER-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
4.4 测试
- http://localhost/order/get/1
5. 自定义轮询策略
cloud-consumer-order-eureka80
- 每次服务重启动后 Rest 接口计数从 1 开始。
- Rest 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
List<ServiceInstance> instances =
discoveryClient.getInstances("PAYMENT-SERVICE-SERVICE");
List [0] instances = 127.0.0.1:8002;
List [1] instances = 127.0.0.1:8001;
/* 8001 + 8002 = 组合成为集群,它们共计 2 台机器,集群总数为 2,按照轮询算法原理。
当总请求数 1 时:1 % 2 = 1,对应下标 1,获得服务地址 127.0.0.1:8001
当总请求数 2 时:2 % 2 = 0,对应下标 0,获得服务地址 127.0.0.1:8002
当总请求数 3 时:3 % 2 = 1,对应下标 1,获得服务地址 127.0.0.1:8001
当总请求数 4 时:4 % 2 = 0,对应下标 0,获得服务地址 127.0.0.1:8002
以此类推 ...
*/
5.1 修改 RestTemplate
- RestTemplate 去掉注解
@LoadBalanced
@Configuration
public class ApplicationContextBean {
// 注解 @Bean 等于 applicationContext.xml > <bean id="" class="" />
@Bean
/*
1. 使用 @LoadBalanced 注解,赋予 RestTemplate 负载均衡的能力。
2. 不使用 @LoadBalanced 注解,自定义负载均衡策略。
*/
/*@LoadBalanced*/
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
5.2 新建接口 com.qs.springcloud.lb.LoadBalancer
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
5.3 新建类 com.qs.springcloud.lb.MyLoadBalancer
@Component
@Slf4j
public class MyLoadBalancer implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement() {
int current, next;
do {
current = atomicInteger.get();
next = current >= 2147483647 ? 0 : current + 1;
// 自旋锁
} while (!atomicInteger.compareAndSet(current, next));
log.info("next:" + next);
return next;
}
/**
* 负载均衡算法:
* 1. 每次服务重启动后 Rest 接口计数从 1 开始。
* 2. Rest 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
*/
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
5.4 主启动
@SpringBootApplication
// 启用 EurekaClient
@EnableEurekaClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
5.5 测试
http://localhost/order/lb
三、OpenFeign 服务接口调用
1. 是什么
- Feign 是一个声明式 WebService 客户端,使用 Feign 能让编写 WebService 客户端更加简单。
- 使用方法是 定义一个 服务接口 然后在上面 添加注解,Feign 也支持 可拔插式的 编码器 和 解码器。
- Spring Cloud 对 Feign 进行了封装,使其支持了 Spring MVC 标准注解 和 HttpMessageConverters。
- Feign 可以与 Eureka 和 Ribbon 组合使用以支持 负载均衡。
2. 能干嘛
- Feign 旨在使编写 Java Http 客户端变的更容易。
- 前面在使用 Ribbon + RestTemplate 时,利用 RestTemplate 对 Http 请求的封装处理,形成了一套模版化的调用方法。
- 但是在实际开发中,由于对服务依赖的调用可能不止一处。
往往一个接口会被多处调用,所以通常都会针对每个微服务,自行封装一些客户端类 来包装这些 依赖服务的调用。- Feign 在此基础上做了进一步封装。
由他来帮助我们 定义和实现依赖服务接口的定义。- 在 Feign 的实现下,我们只需 创建一个接口 并 使用注解的方式来配置它。
以前是 Dao 接口上标注 @Mapper 注解,现在是一个 微服务接口 上面标注一个 Feign 注解即可。
即可完成对服务提供方的接口绑定,简化了使用 Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。
3. Feign 集成了 Ribbon
- 用 Ribbon 维护了 Payment 的服务列表信息,并通过 轮询 实现了 客户端的负载均衡。
- 与 Ribbon 不同的是,通过 Feign 只需要定义 服务绑定接口,且以声明式的方法,优雅而简单的实现了服务调用。
4. Feign 和 OpenFeign 区别
4.1 Feign
- Feign 是 Spring Cloud 组件中的一个轻量级 RestFul 的 Http 服务客户端。
- Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。
- Feign 的使用方式是:使用 Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
4.2 OpenFeign
- OpenFeign 是 Spring Cloud 在 Feign 的基础上支持了 SpringMVC 的注解,如:
@RequesMapping
等。- OpenFeign 的 @FeignClient 可以解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
5. OpenFeign 使用步骤
5.1 新建 Module cloud-consumer-order-feign80
5.2 POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-2020</artifactId>
<groupId>com.qs.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order80-feign</artifactId>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--自定义API通用包-->
<dependency>
<groupId>com.qs.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
5.3 YML
server:
port: 80
eureka:
client:
# false 表示是否将自己注册进 EurekaServer,默认为true
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka
5.4 主启动
@SpringBootApplication
// 开启使用 @FeignClient注解
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
5.5 新建接口 PaymentFeignService
@Component
@FeignClient("PROVIDER-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult get(@PathVariable("id") Long id);
@GetMapping(value = "/payment/feignTimeOut")
String feignTimeOut();
}
5.6 新建类 OrderFeignController
@RestController
@RequestMapping("order")
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("get/{id}")
public CommonResult get(@PathVariable("id") Long id) {
CommonResult commonResult = paymentFeignService.get(id);
log.info("commonResult = " + commonResult);
return commonResult;
}
@GetMapping("feignTimeOut")
public String feignTimeOut() {
return paymentFeignService.feignTimeOut();
}
}
5.7 测试
- http://localhost/order/get/1
5.8 修改 PaymentController
- 线程暂停 3 秒钟(Feign 默认超时时间 1 秒)。
@RestController
@RequestMapping("payment")
@Slf4j
public class PaymentController {
/**
* 测试 Feign连接超时
* @return
*/
@GetMapping("feignTimeOut")
public String feignTimeOut() {
// 线程暂停 3 秒钟(Feign 默认超时时间 1 秒)
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("serverPort = " + serverPort);
return serverPort;
}
}
5.9 超时测试
- http://localhost/order/feignTimeOut
6. OpenFeign 超时控制
- OpenFeign 默认等待1 秒钟,超过后报错。
默认 Feign 客户端只等待 1 秒钟,但是服务端处理需要超过 1 秒钟,导致 Feign 客户端不想等待了,直接返回报错。- 为了避免这样的情况,有时候我们需要设置 Feign 客户端的超时控制。
## OpenFeign 超时控制
## 设置 Feign客户端超时时间(OpenFeign默认支持 Ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
7. OpenFeign 日志打印功能
- Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。
- 说白了就是 对 Feign 接口的 调用情况进行 监控 和 输出。
- 日志级别:
- NONE:默认的,不显示任何日志。
- BASIC:仅记录 请求方法、URL、响应状态码 及 执行时间。
- HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息。
- FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
7.1 新建 FeignConfig
@Configuration
public class FeignConfig {
/**
* OpenFeign 日志打印功能
* @return
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
7.2 YML
## OpenFeign 日志打印功能
logging:
level:
# Feign 日志以什么级别监控哪个接口
com.qs.springcloud.service.PaymentFeignService: debug
7.3 测试
- http://localhost/order/get/1