Spring Cloud 快速入门(三)OpenFeign 与 Ribbon

1. 概述

1.1 OpenFeign 简介

(1) 官网简介
在这里插入图片描述

【翻译】声明式 REST 客户端:Feign 通过使用 JAX-RS(Java Api eXtensions for RESTful Web Services,简单来说,就是一种使用注解来实现 RESTful 的技术)或 SpringMVC 注解的装饰方式,生成接口的动态实现。

(2) 综合说明

Feign,假装,伪装。

OpenFeign可以将提供者提供的Restful服务伪装为接口进行消费,消费者只需使用“feign接口 + 注解”的方式即可直接调用提供者提供的 Restful 服务,而无需再使用 RestTemplate。

需要注意:

  • 该伪装的 Feign 接口是由消费者调用,与提供者没有任何关系。
  • Feign 仅是一个伪客户端,其不会对请求做任何处理。
  • Feign 是通过注解的方式实现 RESTful 请求的。

1.2 OpenFeign 与 Feign

Spring Cloud D 版及之前的版本使用的是 Feign,而该项目现已更新为了 OpenFeign。所以后续使用的依赖也发生了变化。

<!--feign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在这里插入图片描述

1.3 Ribbon 与 OpenFeign

说到 OpenFeign,不得不提的就是 Ribbon。Ribbon 是 Netflix 公司的一个开源的负载均衡项目,是一个客户端负载均衡器,运行在消费者端。

OpenFeign 也是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了 Ribbon。即在导入 OpenFeign 依赖后,无需再专门导入 Ribbon 依赖了。
在这里插入图片描述

2. 声明式 Rest 客户端 OpenFeign

2.1 创建消费者工程 03-consumer-feign-8080

这里无需修改提供者工程,只需修改消费者工程即可。

总步骤

  • 添加 OpenFeign 依赖
  • 定义 Feign 接口,@FeignClient注解指定要访问的微服务
  • 修改处理器,使用 Feign 接口来消费微服务
  • 将 JavaConfig 中的 RestTemplate 的创建方法删除
  • 在启动类上添加@EnableFeignClients 注解

(1) 创建工程

复制上一章的 02-consumer-8080工程,并重命名为 03-consumer-feign-8080。

(2) 添加 openfeign 依赖

<!--feign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

(3) 定义 Feign 接口

// 指定当前为Feign客户端,参数为提供者的微服务名称
@FeignClient("abcmsc-provider-depart")
@RequestMapping("/provider/depart")
public interface DepartService {
    
    
	// 关于Feign的说明:
	// 1)Feign接口名一般是与业务接口名相同的,但不是必须的
	// 2)Feign接口中的方法名一般也是与业务接口方法名相同,但也不是必须的
	// 3)Feign接口中的方法返回值类型,方法参数要求与业务接口中的相同
	// 4)接口上与方法上的Mapping的参数URI要与提供者处理器相应方法上的Mapping的URI相同

    @PostMapping("/save")
    boolean saveDepart(@RequestBody Depart depart);

    @DeleteMapping("/del/{id}")
    boolean removeDepartById(@PathVariable("id") Integer id);

    @PutMapping("/update")
    boolean modifyDepart(@RequestBody Depart depart);

    @GetMapping("/get/{id}")
    Depart getDepartById(@PathVariable("id") Integer id);

    @GetMapping("/list")
    List<Depart> listAllDeparts();
}

关于Feign的说明:

  • 1)Feign接口名一般是与业务接口名相同的,但不是必须的
  • 2)Feign接口中的方法名一般也是与业务接口方法名相同,但也不是必须的
  • 3)Feign接口中的方法返回值类型,方法参数要求与业务接口中的相同
  • 4)接口上与方法上的Mapping的参数URI要与提供者处理器相应方法上的Mapping的URI相同

(4) 修改 JavaConfig 类

直接删除,或者注释掉:

//@Configuration
public class DepartCodeConfigure {
    
    

    // 负载均衡
//    @LoadBalanced
//    @Bean
//    public RestTemplate restTemplate() {
    
    
//        return new RestTemplate();
//    }
}

(5) 修改处理器

将原来的RestTemplate改成直接用Feign接口:
在这里插入图片描述

@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
    
    

    @Autowired
    private DepartService service;

    @PostMapping("/save")
    public boolean saveHandler(@RequestBody Depart depart) {
    
    
        return service.saveDepart(depart);
    }

    @DeleteMapping("/del/{id}")
    public boolean deleteHandler(@PathVariable("id") int id) {
    
    
        return service.removeDepartById(id);
    }

    @PutMapping("/update")
    public boolean updateHandler(@RequestBody Depart depart) {
    
    
        return service.modifyDepart(depart);
    }

    @GetMapping("/get/{id}")
    public Depart getByIdHandler(@PathVariable("id") int id) {
    
    
        return service.getDepartById(id);
    }

    @GetMapping("/list")
    public List<Depart> listHandler() {
    
    
        return service.listAllDeparts();
    }
}

(6) 修改启动类

在启动类上添加@EnableFeignClients 注解:

@EnableFeignClients  // 开启Feign客户端
@SpringBootApplication
public class ApplicationConsumer8080 {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationConsumer8080.class, args);
    }

}

(7) 测试
在这里插入图片描述

在这里插入图片描述

2.2 超时设置

Feign 连接提供者、对于提供者的调用均可设置超时时限。
在这里插入图片描述

(1) 修改配置文件

在 03-consumer-feign-8080 工程的配置文件中直接添加如下内容:
在这里插入图片描述

spring:
  application:
    name: abcmsc-consumer-depart

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka


feign:
  client:
    config:
      default:
        connectTimeout: 5000   # 指定Feign客户端连接提供者的超时时限
        readTimeout: 5000      # 指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交时刻开始,到接收到响应,这个时段的超时时限

(2) 创建提供者工程 03-provider-8081

A、创建工程
复制上一章的 02-provider-8081工程,并重命名为 03-provider-8081。

B、 修改 Service 接口实现类
修改getDepartById方法,模拟超时的情况:
在这里插入图片描述

@Service
public class DepartServiceImpl implements DepartService {
    
    

    @Autowired
    private DepartRepository repository;
	...	
    @Override
    public Depart getDepartById(int id) {
    
    

        try {
    
    
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        if(repository.existsById(id)) {
    
    
            // 在DB中指定的id若不存在,该方法会抛出异常
            return repository.getOne(id);
        }
        Depart depart = new Depart();
        depart.setName("no this depart");
        return depart;
       }
       ...
}

(3) 演示
在这里插入图片描述

在这里插入图片描述

2.3 Gzip 压缩设置

Feign 支持对请求(Feign 客户端向提供者的请求)和响应(Feign 客户端向客户端浏览器的响应)进行 Gzip 压缩以提高通信效率。
在这里插入图片描述

在 03-consumer-feign-8080 工程的配置文件中直接添加如下内容:
在这里插入图片描述

在这里插入图片描述

PS:有的时候很小的文件压缩反而会变大,所以需要指定启用压缩的最小文件大小

3. Ribbon 负载均衡

3.1 负载均衡示意图

在这里插入图片描述

3.2 环境搭建

3.2.1 创建提供者 03-provider-8082

(1) 复制提供者工程 8081

复制上一章 02-provider-8081 工程,并重命名为 03-provider-8082。

(2) 修改配置文件
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

去掉一些日志相关、和本节无关的配置,最终配置如下:

server:
  port: 8082

spring:
  # 配置spring data jpa
  jpa:
    # 指定是否在spring容器启动时创建表,默认false
    generate-ddl: true
    # 指定在控制台是否显示SQL语句,默认false
    show-sql: true
    # 指定应用重启后不重新更新表内容
    hibernate:
      ddl-auto: none
  # 配置数据源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///test?useUnicode=true&amp;characterEncoding=utf8
    username: root
    password: 111

  # 指定当前微服务名称
  application:
    name: abcmsc-provider-depart

eureka:
  client:
    service-url:
      # 指定当前Client所要连接的eureka Server
       defaultZone: http://localhost:8000/eureka
  instance:
    # 设置当前Client每1秒向Server发送一次心跳,单位秒
    lease-renewal-interval-in-seconds: 1
    # 指定让Server认定当前Client已经失效的时间,将来可以从注册表中剔除了,单位秒
    lease-expiration-duration-in-seconds: 3

(3) 修改处理器

为了测试时候方便看出区别,我们添加端口号:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

@Service
public class DepartServiceImpl implements DepartService {
    
    

    @Autowired
    private DepartRepository repository;

    @Value("${server.port}")
    private int port;
	...
    @Override
    public Depart getDepartById(int id) {
    
    
        if(repository.existsById(id)) {
    
    
            // 在DB中指定的id若不存在,该方法会抛出异常
            Depart depart = repository.getOne(id);
            // 在部门名称后添加了当前运行主机的端口号
            depart.setName(depart.getName() + port);
            return depart;
        }
        Depart depart = new Depart();
        // 在部门名称后添加了当前运行主机的端口号
        depart.setName("no this depart" + port);
        return depart;
    }

    @Override
    public List<Depart> listAllDeparts() {
    
    
        List<Depart> departs = repository.findAll();
        for (Depart depart : departs) {
    
    
            // 在部门名称后添加了当前运行主机的端口号
            depart.setName(depart.getName() + port);
        }
        return departs;
    }
}

(4) pom和启动类更名
在这里插入图片描述

@SpringBootApplication
public class ApplicationProvider8082 {
    
    //启动类更名方便区分

    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationProvider8082.class, args);
    }

}

3.2.2 创建提供者 03-provider-8083

以相同的方式创建提供者工程 03-provider-8083。

3.2.3 创建提供者 03-provider-8084

以相同的方式创建提供者工程 03-provider-8084。

3.2.4 启动测试

在这里插入图片描述

在这里插入图片描述

没有特殊指定,默认就是轮询方式进行负载均衡。

3.3 更换负载均衡策略

3.3.1 常见内置负载均衡策略

(1) RoundRobinRule
轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null。

(2) RandomRule
随机策略,从所有可用的 provider 中随机选择一个。

(3) RetryRule
重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。

(4) BestAvailableRule
最可用策略。选择并发量最小的 provider,即连接的消费者数量最少的 provider。

(5) AvailabilityFilteringRule
可用过滤算法。该算法规则是:先采用轮询方式选择一个 Server,然后判断其是否处于熔断状态,是否已经超过连接极限。若没有,则直接选择。否则再重新按照相同的方式进行再选择。最多重试 10 次。

若 10 次后仍没有找到,则重新将所有 Server 进行判断,挑选出所有未熔断,未超过连接极限的 Server,然后再采用轮询方式选择一个。若还没有符合条件的,则返回 null。

(6) ZoneAvoidanceRule
zone 回避策略。根据 provider 所在 zone 及 provider 的可用性,对 provider 进行选择。

(7) WeightedResponseTimeRule
“权重响应时间”策略。根据每个 provider 的平均响应时间计算其权重,响应时间越快权重越大,被选中的机率就越高。在刚启动时采用轮询策略。后面就会根据权重进行选择了。

3.3.2 更换内置策略

Ribbon 默认采用的是 RoundRobinRule,即轮询策略。但通过修改消费者工程的配置文件,或修改消费者的启动类或 JavaConfig 类可以实现更换负载均衡策略的目的。

(1) 创建工程
直接在03-consumer-feign-8080工程上改

(2) 方式一:修改配置文件

在这里插入图片描述
微服务名称.命名空间(SpringCloud里面写死的就是ribbon).NFLoadBalancerRuleClassName=xxxxx

修改配置文件,在其中添加如下内容:

# 修改负载均衡策略为:随机策略
abcmsc-provider-depart: # 要负载均衡的提供者微服务名称
  ribbon: # 指定要使用的负载均衡策略
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

配置文件方式可以针对不同微服务,采用不同的负载均衡策略。

(3) 方式二:修改 JavaConfig 类

在 JavaConfig 类中添加负载负载 Bean 方法。
在这里插入图片描述

@Configuration
public class DepartCodeConfigure {
    
    

    // 修改负载均衡策略为:随机策略
     @Bean
     public IRule loadBalanceRule() {
    
    
	     // return new RandomRule();
         return new RoundRobinRule();
     }
}

JavaConfig添加负载Bean的优先级比配置文件方式高!

3.3.3 自定义负载均衡策略

直接在 03-consumer-feign-8080工程上修改。

(1) 定义 CustomRule 类

Ribbon 支持自定义负载均衡策略。负载均衡算法类需要实现 IRule 接口。
在这里插入图片描述

我们实现的负载均衡策略的思路是:从所有可用的 provider 中排除掉指定端口号的 provider,剩余 provider 进行随机选择。

/**
 * 从所有可用的provider中排除掉指定端口号的provider,剩余provider进行随机选择。
 */
public class CustomRule implements IRule {
    
    

    private ILoadBalancer lb;
    // 记录所有要排除的端口号
    private List<Integer> excludePorts;

    public CustomRule() {
    
    
    }

    public CustomRule(List<Integer> excludePorts) {
    
    
        this.excludePorts = excludePorts;
    }
    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
    
    
        this.lb = lb;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
    
    
        return lb;
    }

    @Override
    public Server choose(Object key) {
    
    
        // 获取所有UP状态的server,lb.getAllServers():获取所有的server
        List<Server> servers = lb.getReachableServers();
        // 获取到排除了指定端口的所有剩余Servers
        // 我们用两种方式,普通的和Lambda的
        List<Server> availableServers = getAvailableServers(servers);
        // 对剩余的Servers通过随机方式获取一个Server
        return getAvailableRandomServer(availableServers);
    }

    // 对剩余的Servers通过随机方式获取一个Server
    private Server getAvailableRandomServer(List<Server> servers) {
    
    
        // 获取一个[0,servers.size())的随机整数
        int index = new Random().nextInt(servers.size());
        return servers.get(index);
    }

    // 获取到排除了指定端口的所有剩余Servers
    // 使用普通代码方式实现
    // private List<Server> getAvailableServers(List<Server> servers) {
    
    
    //     // 若没有指定要排除的port,则直接返回所有Server
    //     if(excludePorts == null || excludePorts.size() == 0) {
    
    
    //         return servers;
    //     }
    //
    //     // 用于存放真正可用的Server
    //     List<Server> aservers = new ArrayList<>();
    //     for(Server server : servers) {
    
    
    //         boolean isExclude = false;
    //         // 将当前遍历Server的端口号与要排除的端口号进行对比
    //         for (Integer port : excludePorts) {
    
    
    //             if(server.getPort() == port) {
    
    
    //                 isExclude = true;
    //                 break;
    //             }
    //         }
    //         if(!isExclude) {
    
    
    //             aservers.add(server);
    //         }
    //     }
    //     return aservers;
    // }


    // 获取到排除了指定端口的所有剩余Servers
    // 使用Lambda方式实现
    private List<Server> getAvailableServers(List<Server> servers) {
    
    
        // 若没有指定要排除的port,则直接返回所有Server
        if(excludePorts == null || excludePorts.size() == 0) {
    
    
            return servers;
        }

        // 用于存放真正可用的Server
        List<Server> aservers = servers.stream()  // 将list变为stream
                    // filter():只要是能使filter()参数结果为true的元素就能通过过滤
                    // noneMatch():用于判断stream中的元素是否全部都不符合。只要找到一个符合的元素该方法就返回false
                    .filter(server -> excludePorts.stream().noneMatch(port -> server.getPort() == port))
                    // 将最终的stream变为list
                    .collect(Collectors.toList());

        return aservers;
    }

}

(2) 修改 JavaConfig 类
将原来的负载均衡 Bean 方法注释掉,添加新的负载均衡策略方法。

@Configuration
public class DepartCodeConfigure {
    
    

    // 修改负载均衡策略为:随机策略
    // @Bean
    // public IRule loadBalanceRule() {
    
    
    //     // return new RandomRule();
    //     return new RoundRobinRule();
    // }

    // 修改负载均衡策略为:自定义策略
    @Bean
    public IRule loadBalanceRule() {
    
    
        List<Integer> excludePorts = new ArrayList<>();
        excludePorts.add(8083);//排除掉端口是8083的
        return new CustomRule(excludePorts);
    }

}

(3) 演示
在这里插入图片描述

在这里插入图片描述

只有8082和8084。

猜你喜欢

转载自blog.csdn.net/weixin_41947378/article/details/109025133