SpringCloud学习笔记(三)——Ribbon

一、restTemplate的使用

我们直接通过实例来说明和理解。

首先新建一个子模块,用来测试restTemplate的使用

 

 在测试的主类中添加如下代码,我们就能够获取百度界面的html文件。

代码如下:

package com.example.resttemplate01;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
class RestTemplate01ApplicationTests {

    @Test
    void contextLoads() {
        //在java中发送http请求
        RestTemplate restTemplate = new RestTemplate();
        //这个对象有get、post、put、delete方法,可以返回一个实体或者对象
        String url = "https://www.baidu.com";
        String result = restTemplate.getForObject(url,String.class);
        System.out.println(result);
    }
}

执行结果如下:

 

 然后我们尝试访问自己写的接口,在这个项目中新建一个controller,编写一个接口进行测试。

首先我们测试get类型的请求,get类型不需要传递太多的参数。

在测试的主类中新增加一个测试get接口的方法,代码如下:

我们先把整个项目启动起来,这样才能访问到接口,然后运行这个测试方法,会发现相应内容都会成功打印。

 

接下来我们测试post类型的请求,post类型有两种传参方式,一种是json格式数据,实参需要用json注解来接收,另外一种是表单的形式,可以直接使用实体类型来接收。

首先我们新增一个User的实体类:

我们在新增测试接口。

注意两种接收方式的请求头不一样,json格式的请求头为:

 

 而表单形式的请求头为:

 

 最后我们在测试主类中用restTemplate来访问这几个接口。

以json形式访问代码如下:

    @Test
    void testPost1() {
        RestTemplate restTemplate = new RestTemplate();
        String url = "http://localhost:8080/testpost1";
        User user = new User("zhangsan",15,10000D);
        //这里之所以可以直接传对象,是因为对象在web中传输时会被自动转成json格式
        String result = restTemplate.postForObject(url,user,String.class);
        System.out.println(result);
    }

以表单形式访问代码如下:

    @Test
    void testPost2() {
        RestTemplate restTemplate = new RestTemplate();
        String url = "http://localhost:8080/testpost2";
        //传递表单参数
        LinkedMultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
        map.add("name","lisi");
        map.add("age",15);
        map.add("salary",15859D);
        String result = restTemplate.postForObject(url,map,String.class);
        System.out.println(result);
    }

都可以实现http的通信。

二、Ribbon的简介

 2.1 Ribbon

Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。 轮询 hash 权重 ...
简单的说 Ribbon 就是 netfix 公司的一个开源项目,主要功能是提供客户端负载均衡算法和服务调用。Ribbon 客户端组件提供了一套完善的配置项,比如连接超时,重试等。在 Spring Cloud 构建的微服务系统中, Ribbon 作为服务消费者的负载均衡器,有两种使用方式,一种是和 RestTemplate 相结合,另一种是和 OpenFeign 相结合。OpenFeign 已经默认集成了 Ribbon,关于 OpenFeign 的内容将会在下一章进行详细讲解。Ribbon 有很多子模块,但很多模块没有用于生产环境!

2.2 负载均衡

负载均衡,英文名称为 Load Balance(LB)http:// lb://(负载均衡协议) ,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如 Web 服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。负载均衡构建在原有网络结构之上,它提供了一种透明且廉价有效的方法扩展服务器和网络设备的带宽、加强网络数据处理能力、增加吞吐量、提高网络的可用性和灵活性。

三、Ribbon入门

我们这里直接用一个实例来说明Ribbon的使用。

3.1 示例设计图

 注意几点:

1.provider1、2和consumer都是Eureka客户端,并且provider1与2是集群关系

2.可以把server端部署到服务器上,这样就不用每次都启动了(具体可以参见上一篇文章的docker部署)

3.2 启动一台server,并建立三台客户端。

consumer 和 provider-1 和 provider-2 都是 eureka-client,注意这三个依赖是 eureka-client,注意 provider-1 和 provider-2 的 spring.application.name=provider,注意启动类的注解和配置文件的端口以及服务名称。
创建的模块结构图如下图所示:
 
 

首先创建provide1与provide2模块,注意这两个是集群关系。

 

 修改pom文件:

 

 配置文件中增加一些基本配置:

server:
  port: 8081
spring:
  application:
    name: provide
eureka:
  client:
    service-url: #eureka ????????????,???,??
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #????

注意:两个模块中的application.name是一样的,但是端口肯定是不一样的。

然后在两个模块中分别创建接口进行负载均衡测试。

 

 

 

 接下来创建一台consumer客户端,然后手动来实现负载均衡,访问provider中的接口。

 因为没法选择ribbon依赖,我们先选择这两个依赖,后续再在pm文件中手动添加上ribbon的依赖。

 

 同样需要修改springboot和springcloud的版本,然后再添加上ribbon的依赖,代码如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>

配置文件中代码如下:

server:
  port: 8083
spring:
  application:
    name: consumer
eureka:
  client:
    service-url: #eureka ????????????,???,??
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #????

然后我们修改consumer的主类:

@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {

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

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

增加一个controller类,这里我们实现了http请求的负载均衡。

这里说明一下:DiscoveryClient是用来做服务发现的,即根据服务id获取到它的IP和端口,Eureka中已经有这个对象了,所以可以直接注入
RestTemplate是用来主动发送http请求的,必须要显示的new或者在IOC容器中新建
之前我们学习RestTemplate的时候,是直接把IP地址和端口写死的,应用场景中必须要根据服务名称手动获取IP和地址,因为对面很有可能是个集群,所以每次的IP地址和端口都会改变

具体代码如下:

@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    static Random random = new Random();

    //自己尝试用RestTemplate写一个负载均衡
    //这里说明一下:DiscoveryClient是用来做服务发现的,即根据服务id获取到它的IP和端口,Eureka中已经有这个对象了,所以可以直接注入
    //RestTemplate是用来主动发送http请求的,必须要显示的new或者在IOC容器中新建
    //之前我们学习RestTemplate的时候,是直接把IP地址和端口写死的,应用场景中必须要根据服务名称手动获取IP和地址,因为对面很有可能是个集群,所以每次的IP地址和端口都会改变
    @GetMapping("testBalance")
    public String testBalance(String serviceId) {
        //获取服务列表
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        if (ObjectUtils.isEmpty(instances)) {
            return "服务列表为空";
        }
//如果服务列表不为空,先自己做一个负载均衡
        ServiceInstance serviceInstance = loadBalance(instances);
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        String url = "http://" + host + ":" + port + "/hello";
        System.out.println("本次我调用的是" + url);
        String forObject = restTemplate.getForObject(url, String.class);
        System.out.println(forObject);
        return forObject;
    }

    private ServiceInstance loadBalance(List<ServiceInstance> instances) {
//拼接 url 去调用 ip:port 先自己实现不用 ribbon
        ServiceInstance serviceInstance =
                instances.get(random.nextInt(instances.size()));
        return serviceInstance;
    }
}

然后我们在浏览器中输入:http://localhost:8083/testBalance?serviceId=provide

会发现交替访问provideA和B。

 

 使用Ribbon来实现负载均衡。

只需要改动consumer中的两个类,其中一个是主类,一个是controller类。

主类中修改代码如下:

 

controller类中增加如下方法:

@RequestMapping("/testRibbonBalance")
public String testRibbonBalance(String serviceId) {
  //直接用服务名称替换 ip:port
  String url = "http://" + serviceId + "/info";
  String forObject = restTemplate.getForObject(url,String.class);
  System.out.println(forObject);
  return forObject;
}

注意:一旦加上@LoadBalance这个注解以后,这个restTemplate就交给Ribbon来处理了,然后如果想单独使用restTemplate发送请求的话,需要再新建一个,不能在使用这个了。

 然后我们在浏览器中输入:我是服务提供者A!

就会发现A和B交替出现,因为负载均衡算法是轮询问,我们之前自己写的是随机出现的。

 四、Ribbon负载均衡的几种算法

在 ribbon 中有一个核心的负载均衡算法接口 IRule。
1.RoundRobinRule--轮询   请求次数 % 机器数量
2.RandomRule--随机
3.权重
4. iphash
3.AvailabilityFilteringRule --会先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务列表按照轮询的策略进行访问
4.WeightedResponseTimeRule--根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果同统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则
5.RetryRule-- 先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
6.BestAvailableRule --会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
7.ZoneAvoidanceRule -- 默认规则,复合判断 Server 所在区域的性能和 Server 的可用行选择服务器。
Ribbon 默认使用哪一个负载均衡算法:
ZoneAvoidanceRule :区间内亲和轮询的算法!通过一个 key 来区分

五、如何修改负载均衡算法

5.1 单个客户端的调用配置

修改yml配置文件:

provider: #提供者的服务名称,那么访问该服务的时候就会按照自定义的负载均衡算法
    ribbon:
        NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule#几种算法的全限定类名    

然后测试调用就可以了。

5.2 将所有客户端配置相同负载均衡算法

@Bean
public IRule myRule() {
  //指定调用所有的服务都用此算法
  return new RandomRule();
}

六、Ribbon的配置文件和常用配置

ribbon: #全局的设置
  eager-load:
    enabled: false # ribbon 一启动不会主动去拉取服务列表,当实际使用时才去拉取 是否立即加载
  http:
    client:
      enabled: false # 在 ribbon 最后要发起 Http 的调用调用,我们认为是RestTemplate 完成的,其实最后是 HttpURLConnection 来完成的,这里面设置为 true ,可以把 HttpUrlConnection->HttpClient
  okhttp:
    enabled: false #HttpUrlConnection 来完成的,这里面设置为 true ,可以把 HttpUrlConnection->OkHttpClient(也是发 http 请求的,它在移动端的开发用的多)
provider: #提供者的服务名称,那么访问该服务的时候就会按照自定义的负载均衡算法
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#修改默认负载均衡算法,几种算法的全限定类名
# NFLoadBalancerClassName: #loadBalance 策略
# NFLoadBalancerPingClassName: #ping 机制策略
# NIWSServerListClassName: #服务列表策略
# NIWSServerListFilterClassName: #服务列表过滤策略
ZonePreferenceServerListFilter 默认是优先过滤非一个区的服务列表

 

 

 

 

 

 

 

 

 

posted @ 2022-10-16 23:21  一直学习的程序小白  阅读(134)  评论(0编辑  收藏  举报