第二章 Ribbon & Feign
第一节 Ribbon
1. Ribbon 介绍
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。
2. Ribbon 作用
- 在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
- 当集群里的1台或者多台服务器down的时候,剩余的没有down的服务器可以保证服务的继续使用。
- 使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu急剧上升。
3. Ribbon 与 Eureka 搭配结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JajkF3rU-1641447196499)(imgs\ribbon1.png)]
4. Ribbon 应用
4.1 在所有的user-service服务中添加服务接口
package com.qf.spring.cloud.userservice.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Value("${eureka.instance.hostname}")
private String address;
@Value("${server.port}")
private int port;
@GetMapping("/info")
public String getSeverInfo(){
return address + ":" + port;
}
@GetMapping
public String getUser(){
return "admin " + address + ":" + port;
}
}
4.2 创建工程 goods-service01
<?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>spring-cloud-demo</artifactId>
<groupId>com.qf</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>goods-service01</artifactId>
<name>goods-service01</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--eureka client的jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!--ribbon jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!--spring web的jar包-->
<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>
</dependency>
<!--热部署相关jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
4.3 编写启动类
package com.qf.spring.cloud.goodsservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "user-service")
public class GoodsService01 {
public static void main(String[] args) {
SpringApplication.run(GoodsService01.class, args);
}
@Bean
@LoadBalanced //开启负载均衡,默认使用轮询算法
public RestTemplate restTemplate(){ //基于Http的REST请求对象
return new RestTemplate();
}
}
4.4 编写控制器类
package com.qf.spring.cloud.goodsservice.controller;
import com.qf.spring.cloud.goodsservice.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/buy")
public String getUsername(){
return goodsService.getUsername();
}
}
4.5 编写业务层
package com.qf.spring.cloud.goodsservice.service;
public interface GoodsService {
String getUsername();
}
package com.qf.spring.cloud.goodsservice.service.impl;
import com.qf.spring.cloud.goodsservice.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
private RestTemplate restTemplate;
@Override
public String getUsername() {
return restTemplate.getForObject("http://user-service/user", String.class);
}
}
4.6 yml 配置
server:
port: 3001 #为了方便管理,Eureka服务器的端口设定在3001-3999
eureka:
client: #eureka客户端
service-url:
#对外暴露的查询服务的地址,供服务订阅者获取服务使用
defaultZone: http://eureka01.com:1001/eureka/,http://eureka02.com:1002/eureka/,http://eureka03.com:1003/eureka/
4.6 启动服务,在浏览器中访问
4.7 负载均衡
将user-service服务制作多份,然后在浏览器中继续访问goods-service,查看显示结果
第二节 Ribbon 核心组件 IRule
1. 类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lmm7VqtA-1641447196500)(imgs\ribbon2.png)]
2. 使用说明
- RoundRobinRule–轮询,默认规则
- RandomRule–随机
- AvailabilityFilteringRule --会先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务列表按照轮询的策略进行访问
- WeightedResponseTimeRule–根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则
- RetryRule-- 先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
- BestAvailableRule --会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
- ZoneAvoidanceRule – 复合判断Server所在区域的性能和Server的可用行选择服务器。
3. 更改负载均衡算法
package com.qf.spring.cloud.goodsservice;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "user-service")
public class GoodsService01 {
public static void main(String[] args) {
SpringApplication.run(GoodsService01.class, args);
}
@Bean
@LoadBalanced //开启负载均衡,默认使用轮询算法
public RestTemplate restTemplate(){ //基于Http的REST请求对象
return new RestTemplate();
}
@Bean
public IRule iRule(){
return new RandomRule(); //随机
}
}
启动服务,在浏览器中访问,然后刷新,观察浏览器中内容变化
4 自定义负载均衡算法
4.1 新建类TestRule,注意包名。
根据官方规定,自定义负载均衡算法的类不能放在@SpringBootApplication所扫描的包及其子包下,否则,我们自定义的负载均衡算法经过配置会被所有的Ribbon客户端共享,这就达不到特殊定制的目的了。
package com.qf.spring.cloud.goodsservice.rule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
public class TestRule extends AbstractLoadBalancerRule {
private int index = 0;
private int times = 0;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
ILoadBalancer loadBalancer = getLoadBalancer();
if(null == loadBalancer) return null;
Server server = null;
while (server == null){
List<Server> reachableServers = loadBalancer.getReachableServers();
if(reachableServers == null) return null;
server = reachableServers.get(index);
index += 2;
if(index >= reachableServers.size()){
index = times % 2 == 0 ? 1 : 0;
times++;
}
}
return server;
}
}
4.2 配置自定义负载均衡规则
package com.qf.spring.cloud.goodsservice;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import com.qf.spring.cloud.goodsservice.rule.TestRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "user-service", configuration = TestRule.class)
public class GoodsService01 {
public static void main(String[] args) {
SpringApplication.run(GoodsService01.class, args);
}
@Bean
@LoadBalanced //开启负载均衡,默认使用轮询算法
public RestTemplate restTemplate(){ //基于Http的REST请求对象
return new RestTemplate();
}
@Bean
public IRule iRule(){
return new RandomRule(); //随机
}
}
启动服务,在浏览器中访问,然后刷新,观察浏览器中内容变化
第三节 Feign
1. Feign 介绍
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
2. Feign 的作用
旨在使编写Java Http客户端变得更容易。在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务器依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
3. Feign 应用
3.1 创建工程 goods-service02
<?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>spring-cloud-demo</artifactId>
<groupId>com.qf</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>goods-service02</artifactId>
<name>goods-service02</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--eureka client的jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!--ribbon jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!--spring web的jar包-->
<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>
</dependency>
<!--热部署相关jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
3.2 编写启动类
package com.qf.spring.cloud.goodsservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
//扫描所有使用@EnableFeignClient定义的客户端并进行注册,如果@SpringBootApplication
//扫描不到,则需要使用basePackages属性来指定客户端所在的包
@EnableFeignClients
public class GoodsService01 {
public static void main(String[] args) {
SpringApplication.run(GoodsService01.class, args);
}
}
3.3 编写控制器类
package com.qf.spring.cloud.goodsservice.controller;
import com.qf.spring.cloud.goodsservice.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@GetMapping("/buy")
public String getUsername(){
return goodsService.getUsername();
}
}
3.4 编写FeignClient接口
package com.qf.spring.cloud.goodsservice.service;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
//定义FeignClient,在调用方法时,会使用name属性指定的服务名、path属性指定的路径与
//方法上的地址来组装URL地址
//比如: http://user-service/user
@FeignClient(name = "user-service", path = "/user")
public interface GoodsService {
@GetMapping
String getUsername();
}
3.5 yml配置
server:
port: 3002 #为了方便管理,Eureka服务器的端口设定在3001-3999
eureka:
client: #eureka客户端
service-url:
#对外暴露的查询服务的地址,供服务订阅者获取服务使用
defaultZone: http://eureka01.com:1001/eureka/,http://eureka02.com:1002/eureka/,http://eureka03.com:1003/eureka/
调用方法时,会使用name属性指定的服务名、path属性指定的路径与
//方法上的地址来组装URL地址
//比如: http://user-service/user
@FeignClient(name = “user-service”, path = “/user”)
public interface GoodsService {
@GetMapping
String getUsername();
}
#### 3.5 yml配置
```yaml
server:
port: 3002 #为了方便管理,Eureka服务器的端口设定在3001-3999
eureka:
client: #eureka客户端
service-url:
#对外暴露的查询服务的地址,供服务订阅者获取服务使用
defaultZone: http://eureka01.com:1001/eureka/,http://eureka02.com:1002/eureka/,http://eureka03.com:1003/eureka/
启动服务,在浏览器中访问