共计 12272 个字符,预计需要花费 31 分钟才能阅读完成。
1.Ribbon 是什么
2.Ribbon 负载平衡演示
3.Ribbon 轮询算法原理和手写
4.OpenFeign 是什么
5.OpenFeign 应用步骤
6.OpenFeign 超时管制
7.OpenFeign 日志打印
8.OpenFeign 和 Feign 的区别
1.Ribbon 是什么
后面咱们学过了深刻学习 springCloud——服务注册核心之 eureka,然而 服务和服务之间该如何通过注册核心进行调用 ,以及如果有多个服务提供者的话, 如何提供负载平衡的策略,这都是咱们要思考的问题,此时就呈现了 Ribbon!
Ribbon 是 spring Cloud 的一套 客户端负载平衡 的工具!
Ribbon 次要提供的性能是 提供客户端软件 的负载平衡算法 和服务调用 。简略地说,Ribbon 能 获取该服务的所有实例 ,他会 主动地帮你基于某种规定(比方轮询,随机等)算法去调用这些机器。咱们很容易地应用 Ribbon 实现自定义的的 Load Balancer(简称 LB)算法。
然而 Ribbon 目前也进入了保护模式,代替计划是 OpenFeign,不过为了更好地学习 OpenFeign,咱们就从 Ribbon 开始解说,缓缓过渡到 OpenFeign.
为了更好地学习 Ribbon,咱们先来理解一下 负载平衡(LB)是什么 :
因为 咱们的服务都是集群部署的 ,所以负载平衡就是将客户端的申请, 均匀地调配到同一种集群服务的多个实例下面 ,从而让每一个服务承受到的申请数量差不多, 达到 HA(高可用)。
常见的负载平衡形式:
1)Ribbon本地负载平衡
在调用微服务接口的时候,调用者会从注册核心获取服务器注册表,而后通过算法进行均匀调用,负载平衡是由客户端实现的(过程内 LB)。
2)Nginx服务端负载平衡
客户端先把所有申请交给 nginx,而后由 nginx 转发实现申请,负载平衡是由服务器端实现的(集中式 LB)。
2.Ribbon 负载平衡演示
用 Ribbon 调用服务的流程图:
Ribbon 在工作的时候,有以下的步骤:
1)调用者先从 Eureka中取出被调用者的服务列表
2) 依据用户的策略(轮询,随机等),从 server 取到的服务注册表中选取一个地址,进行调用。
咱们先筹备两个服务提供者,以便更好地显示出负载平衡的成果。
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>service-provider-8501</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-provider-8501</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
</properties>
<dependencies>
<!-- 这里应用 eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 留神要援用 spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yml:
server:
port: 8501
eureka:
instance:
hostname: service-provider #eureka 服务端的实例名称
prefer-ip-address: true #拜访门路能够显示 IP 地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}-service-provider #拜访门路的名称格局
client:
register-with-eureka: true #false 示意不向注册核心注册本人。fetch-registry: true #false 示意本人端就是注册核心,我的职责就是保护服务实例,并不需要去检索服务
service-url:
#集群指向其它 eureka
defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/
spring:
application:
name: service-provider
主启动:
@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);
}
}
测试的 controller:
@RestController
public class ProviderController {@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/get/{id}")
public String getPaymentById(@PathVariable("id") Long id)
{return "调用胜利,服务端口为"+serverPort;}
}
咱们复制一个截然不同的,改改端口号和名字就能够了。
接下来是服务消费者:
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>ribbon-cunsumer-8401</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ribbon-cunsumer-8401</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
</properties>
<dependencies>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yml:
server:
port: 8401
eureka:
instance:
hostname: ribbon-cunsumer #eureka 服务端的实例名称
prefer-ip-address: true #拜访门路能够显示 IP 地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}-ribbon-cunsumer #拜访门路的名称格局
client:
register-with-eureka: true #false 示意不向注册核心注册本人。fetch-registry: true #false 示意本人端就是注册核心,我的职责就是保护服务实例,并不需要去检索服务
service-url:
#集群指向其它 eureka
defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/
spring:
application:
name: ribbon-cunsumer
主启动:
@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);
}
}
测试类:
config:
// 这是一个不便咱们应用 http 调用的一个服务类
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced// 负载平衡调用
public RestTemplate getRestTemplate()
{return new RestTemplate();
}
}
controller:
@RestController
public class consumerController {
// 通过 eureka 的调用的服务地址
public static final String PAYMENT_URL = "http://SERVICE-PROVIDER";
@Resource
private RestTemplate restTemplate;
@GetMapping("/payment/get/{id}")
public String create(@PathVariable("id") Long id)
{return restTemplate.getForObject(PAYMENT_URL +"/payment/get/"+id,String.class);
}
}
咱们启动服务后,调用 http://localhost:8401/payment…,始终 F5 刷新,就会轮询两个服务,达到负载平衡的目标了。
3.Ribbon 轮询算法的更换和原理
api 的调用其实并不是难事,咱们能够试着来配一下其它负载平衡的算法,以及更进一步地看看负载平衡算法的源码。
配置类:
@Configuration
public class MyLoadBalanceConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory){String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 随机拜访为 RandomLoadBalancer,轮询拜访为 RoundRobinLoadBalancer
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),//RoundRobinLoadBalancer
name);
}
}
主启动:
@SpringBootApplication
@EnableEurekaClient
@LoadBalancerClients(defaultConfiguration = {MyLoadBalanceConfig.class})
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);
}
}
重启一下,看看拜访的规定,是随机的。
轮询的源码剖析:
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service:" + serviceId);
}
return new EmptyResponse();}
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
// 接口的申请次数 % 集群服务器的总数量 = 理论调用服务的下标,每次重启服务后,接口拜访次数从 0 开始
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
4.OpenFeign 是什么
咱们先上 spring cloud 官网看一下,openFeign 是一个什么样的工具:https://cloud.spring.io/sprin…
Feign 是一个申明式的 WebService 客户端,应用 Feign 能让编写 Web Service 客户端更加简略 。它的应用办法是 定义一个服务接口而后再下面减少注解。Spring cloud 对 feign 进行了封装,使其反对 Spring MVC 规范注解和 HttpMessageConverters。Feign 能够与 Eureka 和 Ribbon 组合应用以反对负载平衡。
说了这么多,咱们可能还是一头雾水,简略地说:
Feign 旨在使编写 java Http 客户端变得更容易。
后面在应用 Ribbon+RestTemplate 时,利用 RestTemplate 对 http 申请的时候,造成了一套模板化的调用办法 。然而在理论开发的过程中, 对其它微服务的调用可能不止一处 , 一个接口会被屡次应用 ,所以 针对每一个微服务 , 都自行封装成一些客户端类 来包装这些依赖服务的调用,就显得尤为重要。所以 Feign 依据这个理念进行了封装,它对每个微服务的调用都封装成了一个独立的类 。在 Feign 的实现下,咱们 只须要创立一个接口并应用注解的形式来配置它 ,就能够实现对服务提供方的绑定, 进行对立治理 ,简化了 Ribbon 的应用, 升高了服务调用客户端的开发量。
OpenFeign 同时也集成了 Ribbon
利用 Ribbon 保护了服务提供者的列表,并且通过轮询的形式实现了客户端的负载平衡,而与 Ribbon 不同的是,feign 只须要定义服务绑定接口且以申明式的办法,优雅而简略地实现了服务调用。
5.OpenFeign 应用步骤
咱们新建一个我的项目:
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>feign-cunsumer-8402</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>feign-cunsumer-8402</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yml
server:
port: 8402
eureka:
instance:
hostname: feign-cunsumer #eureka 服务端的实例名称
prefer-ip-address: true #拜访门路能够显示 IP 地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}-feign-cunsumer #拜访门路的名称格局
client:
register-with-eureka: true #false 示意不向注册核心注册本人。fetch-registry: true #false 示意本人端就是注册核心,我的职责就是保护服务实例,并不需要去检索服务
service-url:
#集群指向其它 eureka
defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/
spring:
application:
name: feign-cunsumer
主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);
}
}
测试类:
@Component
@FeignClient(value="SERVICE-PROVIDER")// 要调用的服务名字
public interface ProviderFeignService {
// 要调用的接口
@GetMapping(value = "/payment/get/{id}")
String getPaymentById(@PathVariable("id") Long id);
}
@RestController
public class ConsumerController {
@Autowired
private ProviderFeignService providerFeignService;
@GetMapping("/payment/get/{id}")
public String create(@PathVariable("id") Long id)
{
// 间接应用
return providerFeignService.getPaymentById(id);
}
}
调用后果:
6.OpenFeign 超时管制
在调用一个接口的时候,肯定要有超时管制 ,这在工作中有两个益处:
1) 避免一个接口拖垮整个零碎,导致整个性能不可用。
2)能够爱护本人 ,如果这是他人的接口,超时了,能够把问题丢给他人,避免本人背锅,因为 这原本就是他人的锅 , 报的错不应该在你的接口上。
办法很简略,咱们做一个配置就行了,
yml
ribbon:
#建设连贯所用工夫
ReadTimeout: 60000
#建设连贯后从服务端获取资源的最大工夫
ConnectTimeout: 60000
7.OpenFeign 日志打印
Feign 提供了日志打印性能,咱们能够通过配置来调整日志级别,从而理解 Feign 中 Http 申请的详情,从而进行监控和输入。
yml
logging:
level:
# feign 日志以什么级别监控哪个接口
com.example.demo.feign.*: debug
配置类:
@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
//NONE:默认的,不显示任何日志;//BASIC:仅记录申请办法、URL、响应状态码及执行工夫;//HEADERS:除了 BASIC 中定义的信息之外,还有申请和响应的头信息;//FULL:除了 HEADERS 中定义的信息之外,还有申请和响应的注释及元数据。return Logger.Level.FULL;
}
}
运行后果:
8.OpenFeign 和 Feign 的区别
其实 OpenFeign 进去之前,还有一个 Feign 组件,不过 Feign 曾经进行保护了,所以咱们间接应用 OpenFeign 就好。
feign | OpenFeign |
---|---|
内置了 Ribbon,用来做客户端的负载平衡,去调用服务列表的服务 | 在 Feign 的根底上反对了 Spring MVC 的注解 |
应用 Feign 注解调用接口,调用这个接口,就能够调用服务注册核心的服务 | @FeignClient 能够解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动静代理的形式产生实现类,实现类中做负载平衡并调用其余服务。 |