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注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。