Spring Cloud认知学习(二):Ribbon使用


💡接上回Spring Cloud认知学习(一):Spring Cloud介绍与Eureka使用
💡上一篇介绍了Spring Cloud,以及服务注册与发现的组件Eureka的简单使用。
💡这一篇我们来介绍微服务构建起来后,使用Ribbon来解决多个服务的负载均衡问题。


Ribbon负载均衡

客户端负载均衡的意思是,让客户端来进行负载均衡,而不是服务端来进行负载均衡,是什么意思呢?比如说你要去排队买东西,有三条队,你自然而然地会选择队伍短的队去排咯😀,这是由你去进行的负载均衡,而不是服务员帮你去安排你该排哪条队。
💡为什么要采用客户端负载均衡呢?

  • 主要是,客户端的负载均衡是会减少服务端的资源消耗的,就好像如果有很多消费者的话,你要么就雇佣很多服务员,要么就让消费者等服务员有空。
  • 其次呢,这也是从架构上考虑的,因为客户端会从eureka中拉取服务可用列表,那么如果此时顺便拉取了此时各种服务的负载状态的话,那么也就顺手地可以使用这些信息来进行客户端负载均衡了。


简单使用步骤:

下面的代码可以参考:Ribbon负载均衡简单实验

1.新建模块,用于负载均衡

新建模块spring-cloud-user-service-8002spring-cloud-user-service-8003


2.修改模块代码:

  • 给模块spring-cloud-user-service-8002spring-cloud-user-service-8003导入pom.xml、修改application.yml和修改主启动类。
  • 代码基本与模块spring-cloud-user-service-8001一样的。
  • pom.xml与spring-cloud-user-service-8001一样的。
  • 控制器,mapper,都跟spring-cloud-user-service-8001一样的。只是主程序类的名字问题
  • 主要是修改服务端口和数据库。
    20200511220322

由于新建了服务提供者,所以为了让不同的服务使用不同的数据库,所以要执行下面的SQL来创建额外的数据库:

sql:

-- db2
DROP DATABASE IF EXISTS cloud02;
CREATE DATABASE cloud02 CHARACTER SET UTF8;
USE cloud03;
CREATE TABLE user
(
  id int PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(255),
  fullName  VARCHAR(255)
);


INSERT INTO user(username,fullName) VALUES('zhangsan','张三2');
INSERT INTO user(username,fullName) VALUES('lisi','李四2');
INSERT INTO user(username,fullName) VALUES('wangwu','王五2');
INSERT INTO user(username,fullName) VALUES('zhaoliu','赵六2');
INSERT INTO user(username,fullName) VALUES('lidazhuang','李大壮2');

 
SELECT * FROM user;

--- db3
DROP DATABASE IF EXISTS cloud03;
CREATE DATABASE cloud03 CHARACTER SET UTF8;
USE cloud03;
CREATE TABLE user
(
  id int PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(255),
  fullName  VARCHAR(255)
);


INSERT INTO user(username,fullName) VALUES('zhangsan','张三3');
INSERT INTO user(username,fullName) VALUES('lisi','李四3');
INSERT INTO user(username,fullName) VALUES('wangwu','王五3');
INSERT INTO user(username,fullName) VALUES('zhaoliu','赵六3');
INSERT INTO user(username,fullName) VALUES('lidazhuang','李大壮3');

 
SELECT * FROM user;


3.启动模块

3.1启动服务生产者:
spring-cloud-eureka-server-7001spring-cloud-user-service-8001spring-cloud-user-service-8002spring-cloud-user-service-8003
查看eureka内部注册的服务实例有多少个,你可以看到我们启动的三个服务实例都显示出来了。
20200410223800

3.2启动服务消费者:
现在对于USERSERIVE服务有三个服务实例了,然后我们启动服务消费者模块,看多次调用下他是怎么调用的吧。
由于上面对不同服务的数据库有了小修改(为了有区分,所以我修改了一下数据,但实际业务中他们应该是相同的),所以可以根据数据来判断当前调用了哪个数据库。
多次调用http://localhost/user/list,你应该能看到数据在变化,说明默认是有负载均衡的。


⚪你可能有点疑惑,默认的负载均衡是什么时候配置的呢?还记得我们之前在配置消费者从eureka中获取服务列表时配置了什么吗?我们给我们的restTemplate加了一个注解@LoadBalanced,而LoadBalanced就是负载均衡的意思。

当你调用服务之后,你会看到消费者拉取服务列表的时候会拉取到一些服务的健康信息。
20200419011434

默认情况下,从eureka中拉取的服务会使用轮询调用(加入有ABC三个服务实例,会顺序的逐一的调用,比如说可能会是不断按BCA的顺序来调用;),但ribbon能帮我们做更多。

下面使用Ribbon来进行客户端的负载均衡。



4.修改消费者模块

由于Ribbon是客户端的负载均衡,所以要修改消费者模块spring-cloud-user-consumer-80
4.1修改pom.xml,添加ribbon依赖:

        <!--增加ribbon依赖 start-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <!--旧版的需要去掉-netflix-->
        </dependency>
        <!--增加ribbon依赖 end-->

4.2.修改负载均衡策略:
⚪当导入ribbon的时候,如果你不做其他操作,默认的负载均衡还是轮询。下面我们修改一下负载均衡策略:
20200419012057

@Configuration
public class AppConfig {
    @Bean
    @LoadBalanced // eureka与这个配合,要使用LoadBalanced才会调用eureka中注册的服务
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule myRule() {
//        return new RoundRobinRule();
		return  new RandomRule();
//        return new RetryRule();
    }
}

然后你重新调用几次http://localhost/user/list,你应该能看到负载均衡策略变了。

上面的例子中已经演示了修改负载均衡策略。下面来讲负责均衡相关的东西。



负载均衡算法:

Ribbon中负责负载均衡策略的就是IRule,所以我们上面的代码就新创建了一个IRule的bean。
下面讲一下这个IRule的几个常见的实现类。

  • RandomRule:随机调用服务
  • RoundRobinRule:轮询调用服务
  • WeightedResponseTimeRule:是加权策略,某个服务权重高的话调用的次数就会多。内部会维护一个权重表,每隔一段时间就依据服务的响应时间来更新权重,响应时间短的权重高。(刚启动的时候由于没有服务调用的相关信息,会先使用轮询策略。)
  • RetryRule:默认内部是轮询调用(注意这个策略可以更改),不过是带重试机制的轮询,ABC三个服务实例,如果B服务实例突然挂了,那么默认的轮询策略轮询到B的时候应该会调用失败,而如果采用了RetryRule,那么B调用不了的时候,就会尝试调用C,以保证此次调用是成功的。
@Configuration
public class AppConfig {
    @Bean
    @LoadBalanced // eureka与这个配合,要使用LoadBalanced才会调用eureka中注册的服务
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule myRule(){
//        return new RandomRule(); // 随机调用服务
//        return  new RoundRobinRule();// 轮询策略
        return new RetryRule();// 带重试的轮询
    }
}

自定义负载规则:

除了以上的规则,你还可以自定义负载均衡的规则。
你可能会想,那么我应该可以参考一下RandomRule或RoundRobinRule的实现重写一下,然后像上面指定IRule这个Bean的实现对象就行了。
💡 但要注意一点,如果你是在主程序类的同级目录或下级目录下(也就是能被主程序类的ComponentScan扫描到的目录),那么这个规则会对这个消费者的所有调用的服务生效
1.写一个负载均衡规则:只调用8003的服务。

package com.progor.study.myrule;

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 MyLoadBalancedRule extends AbstractLoadBalancerRule {

    // 这个choose方法就是选择哪个服务来进行调用
    public Server choose(ILoadBalancer lb, Object key) {
        // ILoadBalancer是服务注册列表
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();//可用服务
            List<Server> allList = lb.getAllServers();// 所有服务

            int serverCount = allList.size();
            if (serverCount == 0) {
                // 服务数为0
                return null;
            }

            // 看一下下面的代码(此段代码来自RandomRule),应该能判断出来,就是这里指定了返回的server,
            // 所以我们也在这里修改策略,假如说我们指定只调用8003的
//            int index = rand.nextInt(serverCount);
//            server = upList.get(index);
            // 修改 start
            if (upList.size()==0){
                return null;
            }
            for (int i = 0; i < upList.size() ; i++) {
                Server item = upList.get(i);
                int httpPort = item.getPort();
                // 你可以通过Server和ILoadBalancer的各种参数来自定义你的规则()
                if (httpPort == 8003){
                    server = item; // 这里由于只是示例,所以就随便写了,所以安全逻辑并没有做完全。
                }
            }
            // 修改 end

            if (server == null) {
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            server = null;
            Thread.yield();
        }

        return server;

    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub

    }
}

2.在主程序类的外部目录创建一个Configuration:
20200419024200

package com.progor.config;

import com.netflix.loadbalancer.IRule;
import com.progor.study.myrule.MyLoadBalancedRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {

    @Bean
    public IRule myRule(){
        return new MyLoadBalancedRule();// 使用我们自定义的规则
    }
}

3.修改主程序类,增加@RibbonClient
如果你的Configuration放在了主程序类外部的时候就要加上这个才能扫描到外部的Configuration:
💡这个注解用来指定某个服务的负载均衡规则,如果不使用这个注解来给对应服务配置负载均衡策略,并且你在内层目录的配置类中指定了IRule的实例为我们创建的实现类实例,那么所有的服务都会采用这个策略。

package com.progor.study;

import com.progor.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="USERSERIVE",configuration= MyConfig.class)
public class UserConsumer80Application {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumer80Application.class, args);
    }
}


4.调用http://localhost/user/list,你会发现现在只会有8003的数据返回了,这说明我们的负载均衡生效了。


🐶对于不同服务的负载均衡规则的问题,也可以参考代码中的MessageService。这里面我写了一个Message服务的两个服务实例8004和8005,并且让消费者来调用了这个服务,效果如下:

  • 当不使用@RibbonClient时:由于那个Configuration并没有被扫描到,所以不会生效,此时应该所有的服务都使用内部配置的负载均衡规则
    如果在内部配置中采用内部的定义规则:
    @Bean
    public IRule myRule(){s
//        return new RandomRule(); // 随机调用服务
//        return  new RoundRobinRule();// 轮询策略
        return new RetryRule();// 带重试的轮询
    }

当在内部配置规则的额时候,如果使用我们定义的规则:由于我们之前为USERSERVICE定义了只使用8003服务的规则,此时MESSAGESERVICE也会使用这个规则,那么这时候MESSAGESERVICE会一直请求不到。

    @Bean
    public IRule myRule() {
//		return  new RoundRobinRule();
//		return  new RandomRule();
//        return new RetryRule();
        return new MyLoadBalancedRule();
    }

  • 当使用@RibbonClient时:对于指定的服务,会使用指定的负载均衡规则。【从其他资料(不确定😓)中了解到以前的@RibbonClient的name应该是可以为空的,从这个注解的name有default默认值,我觉得这个说法应该是对的。但现在不允许了,应该是为了避免专属的负载均衡规则覆盖全局规则的问题】
//  自定义负载均衡的代码
package com.progor.config;

import com.netflix.loadbalancer.IRule;
import com.progor.study.myrule.MyLoadBalancedRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {

    @Bean
    public IRule myRule() {
        return new MyLoadBalancedRule();// 使用我们自定义的规则
    }
}

//  主程序类代码
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "USERSERIVE", configuration = MyConfig.class) 
public class UserConsumer80Application {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumer80Application.class, args);
    }
}


posted @ 2020-05-15 10:35  随风行云  阅读(1362)  评论(0编辑  收藏  举报