SpringCloud之负载均衡

Posted 笨兮兮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud之负载均衡相关的知识,希望对你有一定的参考价值。

1.Ribbon

1.1负载均衡LB

全称Load Balance,将用户的请求平摊到多个服务器上,从而达到系统的HA。
集中式LB:在服务消费者和服务提供者之间使用独立的LB设施,如硬件,由该设施负责把访问请求通过某种策略转发至服务提供方。
进程内LB:将LB逻辑继承到服务消费者,消费者从服务注册中心获知有哪些地址可用,然后从这些地址中选择一个合适的来使用。Ribbon属于进程内LB。

1.2Ribbon定义

基于NetFlix Ribbon实现的一套客户端负载均衡的工具。

1.3项目开发

源代码:https://github.com/zhongyushi-git/springcloud-ribbon.git

1)创建TestTemplate模块以及Eureka服务器集群。具体步骤可参考https://www.cnblogs.com/zys2019/p/12636241.html#_label0。可直接下载代码,在此代码基础上修改。

2)在cloud-consumer80中添加eureka依赖 

<!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
View Code

不需要引入ribbon依赖,原因是在eureka中已经引入了。

3)修改cloud-consumer80的配置文件yml,配置eureka

#把客户端注册到服务列表中
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true
    register-with-eureka: false
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
View Code

4)在cloud-consumer80的ConfigBean类添加@LoadBalance注解

 @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
View Code

5)主启动类添加@EnableEurekaClient

@SpringBootApplication
@EnableEurekaClient
public class ConsumerMain80 {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerMain80.class, args);
    }
}
View Code

6)修改Usercontroller类中访问的基本路径BASE_URL为微服务名

private final  String BASE_URL="http://CLOUD-PROVIDER";
View Code

7)运行测试

先分别启动7001,7002,7003,然后启动8001和80。

使用postman工具输入http://127.0.0.1/consumer/get/1使用get方式测试,再输入http://127.0.0.1/consumer/add?name=嘻哈哈&phone=101使用post方式测试。

1.4Ribbon负载均衡配置  

1)创建服务提供者集群cloud-provider8002,cloud-provider8003,然后按照8001的配置直接复制到8002和8003

2)创建对应的数据库db2021和bd2022

create database db2021;
use db2021;
create table user (
  id bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'id\',
  name varchar(200) DEFAULT \'\',
    phone varchar(200) DEFAULT \'\',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


insert into user values(null,\'李明\',\'102\');
insert into user values(null,\'赵慧\',\'102\');
insert into user values(null,\'李凯\',\'102\');

create database db2022;
use db2022;
create table user (
  id bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'id\',
  name varchar(200) DEFAULT \'\',
    phone varchar(200) DEFAULT \'\',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


insert into user values(null,\'李明\',\'103\');
insert into user values(null,\'赵慧\',\'103\');
insert into user values(null,\'李凯\',\'103\');
View Code

不同的数据库表根据phone字段区分不同的库。

3)修改8002和8003的yml,包括端口号、数据库名和实例id。应用程序的名字一定不要改,要保持一致。

4)先启动eureka集群,然后启动微服务提供者集群,最后启动微服务消费者。

5)访问http://localhost/consumer/get/1,刷新几次会发现查询的是不同的数据库数据。

由此可以看出ribbon是对服务提供者进行负载均衡。

1.5Ribbon负载均衡策略

1.5.1策略

Ribbon的核心组件是IRule,它的作用就是根据特定的算法从服务列表中选择一个要访问的服务。

策略 描述
RandomRule 随机
RoundRobinRule 轮询,按照顺序选择server,是Ribbon默认的策略
RetryRule 重试,在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server
BestAvailableRule 最低并发,逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server
AvailabilityFilteringRule 可用过滤,过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server
ResponseTimeWeightedRule 响应时间加权重,根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。
ZoneAvoidanceRule 区域权重,综合判断server所在区域的性能和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server

1.5.2切换默认的策略

在服务消费者中config包的配置类添加

 //切换默认的策略
    @Bean
    public IRule myRule(){
        return new RandomRule();
    }
View Code

1.5.3自定义策略

在消费者中新建包com.zys.rule,新建配置类MySelfRule 

package com.zys.rule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new MyRandomRule();
    }
}
View Code

新建策略类MyRandomRule 

package com.zys.rule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.awt.*;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 自定义轮询规则,每执行5次再切换下一个服务器
 */

public class MyRandomRule extends AbstractLoadBalancerRule {

    private int total = 0;
    private int currentIndex = 0;

    public Server choose(ILoadBalancer lb, Object key) {
        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) {
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            }
            if (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex >= upList.size()) {
                    currentIndex = 0;
                }
            }
            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }

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

            // Shouldn\'t actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }

        return server;

    }

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

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }
}
View Code

重新启动消费者,每刷新5次才会切换下一个服务器。

2.OpenFeign

2.1定义

在ribbon中,使用微服务名和RestTemolate的方式进行服务的调用,而feign是一个声明式的Web服务客户端,是面向接口编程的。也就是说使用Feign,只需要创建一个接口并使用注解方式配置它,就可以完成对微服务提供方的接口绑定。openfeign对feign进行进一步的封装,添加了springmvc的一些功能,更加强大。

2.2项目开发

源代码:https://github.com/zhongyushi-git/cloud-feign.git

1)在ribbon的基础上,按照消费者创建模块cloud-feign-consumer80,然后删除之前的消费者模块
2)在cloud-feign-consumer80的pom中导入依赖

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

3)创建接口UserClientService
新建包com.zys.cloud.service,在包下新建接口并添加注解@FeiginClient,指定微服务的名称

package com.zys.cloud.service;

import com.zys.cloud.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

//添加注解,指定微服务名称
@FeignClient(value="CLOUD-PROVIDER")
public interface UserClientService {

    @GetMapping("/user/get/{id}")
    public User getUser(@PathVariable("id")long id);

    @PostMapping("/user/add")
    int addUser(User user);
}
View Code

4)在controller中注入UserClientService接口
修改调用的方法,使用service的方式调用服务

 @Resource
    private UserClientService userClientService;

    @GetMapping("/get/{id}")
    public User getUser(@PathVariable("id") long id){
        return userClientService.getUser(id);
    }

    @PostMapping("/add")
    public int addUser(User user){
        return userClientService.addUser(user);
    }
View Code

5)在启动类上添加注解@EnableFeignClients

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ConsumerFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerFeignMain80.class, args);
    }
}
View Code

7)删除config包,如果没有就不删除。
8)启动测试
先启动eureka集群,然后启动服务提供者集群,再启动服务消费者,在浏览器输入http://localhost://consumer/get/3
刷新几次,发现和ribbon的效果一样,是轮询的切换。与ribbon的主要区别就是这里的服务调用是真正通过controller调用service的方式。

2.4OpenFeign超时控制和日志打印

2.4.1超时控制

有时候需要对服务提供者进行快速的响应,Feign就可以。Feign默认等待1秒,超过后报错。也就是说Feigin客户端只等待一秒,但是服务端处理过程超过一秒,导致客户端会出错,这就需要我们进行设置超时时间,避免出现这样的情况。

在客户端(服务消费者80)的yml进行配置 

#设置feign 客户端超时时间
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000
View Code

2.4.2日志打印

日志级别

NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、URL、响应状态码及执行时间
HEADERS:BASIC信息以及请求和响应的头信息	
FULL:HEADERS信息以及请求和响应的正文和元数据

在客户端(服务消费者80)添加配置类LogConfig 

package com.zys.cloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//配置feign日志级别
@Configuration
public class LogConfig {
    @Bean
    Logger.Level feignLevel(){
        return Logger.Level.FULL;
    }
}
View Code

yml配置,指定监控的接口路径

logging:
  level:
    #配置feign的日志级别 和监控的接口
    com.zys.cloud.service.UserClientService: debug
View Code

先启动eureka集群,然后服务提供者集群,然后80启动。输入http://localhost/consumer/get/3在idea控制台可以看出多了很多的打印信息。

 

 

以上是关于SpringCloud之负载均衡的主要内容,如果未能解决你的问题,请参考以下文章

云原生SpringCloud系列之客户端负载均衡Ribbon

springcloud之Ribbon负载均衡

SpringCloud之 LoadBalancer和Feign负载均衡

SpringCloud系列之三---Ribbon负载均衡使用

SpringCloud之实现服务器端的负载均衡Ribbon

微服务之四:轻松搞定SpringCloud微服务-负载均衡Ribbon