微服务-5-Ribbon负载均衡

Ribbon负载均衡

项目地址
项目都是基于前面的文章微服务项目而来

Ribbon是什么?

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

能干什么?

LB(负载均衡)

1.集中式LB

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;

2.进程内LB

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

官网官网

Ribbon配置初步

修改microservice-consumer-dept-80工程
修改pom文件

 
        
            org.springframework.cloud
            spring-cloud-starter-eureka
            1.4.7.RELEASE
        
        
            org.springframework.cloud
            spring-cloud-starter-ribbon
            1.4.7.RELEASE
        
        
            org.springframework.cloud
            spring-cloud-starter-config
            2.0.2.RELEASE
        

修改application.yml 追加eureka的服务注册地址

eureka:
  client:
    service-url:
      defaultZone: eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
    register-with-eureka: false

对ConfigBean进行新注解@LoadBalanced 获得Rest时加入Ribbon的配置

package com.microservice.cfgbeans;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @create 2019-12-12 15:37
 */
@Configuration
public class ConfigBean {
 /**
     * 配置RestTemplate
     * 通过RestTemplate调用提供者服务 ,发送rest请求
     * 提供了多种访问http服务的方法,
     * 针对于访问rest服务客户端的调用的模板类
     */
//    @RestTemplate
    @Bean
    @LoadBalanced //打开Ribbon的负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

主启动类DeptConsumer80_App添加@EnableEurekaClient

@SpringBootApplication
@EnableEurekaClient
//@RibbonClient(name="MICROSERVICE-DEPT")
public class Deptconsumer80_APP {
    public static void main(String[] args) {
        SpringApplication.run(Deptconsumer80_APP.class,args);
    }
}

修改DeptController_Consumer客户端访问类

@RestController
@RequestMapping("consumer")
public class DeptController_consumer {
//这里修改:原来为地址改为服务名
    private static final String REST_URL_PREFIX="http://MICROSERVICE-DEPT";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("dept/add")
    public boolean addDept(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }

    @RequestMapping("dept/find/{id}")
    public Dept findDeptById(@PathVariable("id")Long id){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
    }

    @RequestMapping("dept/list")
    public List addDept(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }

    @RequestMapping("dept/discovery")
    public Object discovery(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/dept/discovery",Object.class);
    }
}

先启动3个eureka集群后,再启动microservice-provider-dept-8001并注册进eureka

启动microservice-consumer-dept-80

测试

http://localhost/consumer/dept/get/1
http://localhost/consumer/dept/list
http://localhost/consumer/dept/add?dname=ddd

小结

Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号

Ribbon负载均衡

架构说明

Ribbon在工作时分成两步
第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

1.参考microservice-provider-dept-8001,新建两份,分别命名为8002,8003

2.新建8002/8003数据库,各自微服务分别连各自的数据库

8002脚本:

 
DROP DATABASE IF EXISTS springcloud02;
 
CREATE DATABASE springcloud02CHARACTER SET UTF8;


USE springcloud02;


CREATE TABLE dept
(
  deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  dname VARCHAR(60),
  db_source   VARCHAR(60)
);
 
INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('运维部',DATABASE());
 
SELECT * FROM dept;
 

8003脚本

DROP DATABASE IF EXISTS springcloud03;
CREATE DATABASE springcloud03 CHARACTER SET UTF8;
USE springcloud03;
CREATE TABLE dept
(
  deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  dname VARCHAR(60),
  db_source   VARCHAR(60)
);
 
INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('运维部',DATABASE());

3.修改8002/8003各自YML

修改Eureka配置的默认名称和注册中心访问地址
8002

server:
  port: 8002

mybatis:
  configlocation: classpath:mybatis/mybatis.cfg.xml #Mybatis配置文件所在路径
  type-aliases-package: com.microservice.bean   #所有bean别名所在包
  mapper-locations:
  - classpath:mybatis/mapper/**/*.xml #mapper映射文件

spring:
  application:
    name: microservice-dept            #对外暴露的微服务名字
  datasource:                         #数据库连接
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/springcloud02?characterEncoding=utf8&useSSL=false
    username: root
    password: root
      #druid连接池配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的filters,去掉监控界面sql无法统计,‘wall’用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    userGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500


eureka:
  instance:
    instance-id: dept-service8002 #默认名称修改
    prefer-ip-address: true #访问路径可以显示IP地址
  client:
    register-with-eureka: true #是否注册
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

#      defaultZone: http://localhost:7001/eureka/  # 注册中心访问地址

#Info信息显示
info:
  app.name: microservice
  companny.name: www.baidu.com
  build.artifactId: $project.artifactId$
  build.version: $project.version$

8003

server:
  port: 8003

mybatis:
  configlocation: classpath:mybatis/mybatis.cfg.xml #Mybatis配置文件所在路径
  type-aliases-package: com.microservice.bean   #所有bean别名所在包
  mapper-locations:
  - classpath:mybatis/mapper/**/*.xml #mapper映射文件

spring:
  application:
    name: microservice-dept            #对外暴露的微服务名字
  datasource:                         #数据库连接
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/springcloud03?characterEncoding=utf8&useSSL=false
    username: root
    password: root
      #druid连接池配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的filters,去掉监控界面sql无法统计,‘wall’用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    userGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500


eureka:
  instance:
    instance-id: dept-service8003 #默认名称修改
    prefer-ip-address: true #访问路径可以显示IP地址
  client:
    register-with-eureka: true #是否注册
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

#      defaultZone: http://localhost:7001/eureka/  # 注册中心访问地址

#Info信息显示
info:
  app.name: microservice
  companny.name: www.baidu.com
  build.artifactId: $project.artifactId$
  build.version: $project.version$

备注
端口
在这里插入图片描述
数据库链接
在这里插入图片描述

对外暴露的统一的服务实例名–不修改
在这里插入图片描述

测试

启动3个eureka集群配置区
启动3个Dept微服务并各自测试通过
http://localhost:8001/dept/list
http://localhost:8002/dept/list
http://localhost:8003/dept/list

启动microservice-consumer-dept-80

测试

客户端通过Ribbon完成负载均衡并访问上一步的Dept微服务

http://localhost/consumer/dept/list
注意观察看到返回的数据库名字,各不相同,负载均衡实现

总结:

Ribbon其实就是一个软负载均衡的客户端组件,
他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

Ribbon核心组件IRule

IRule:根据特定算法中从服务列表中选取一个要访问的服务

RoundRobinRule
轮询
RandomRule
随机
AvailabilityFilteringRule
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,
还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
WeightedResponseTimeRule
根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。
刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,
会切换到WeightedResponseTimeRule
RetryRule
先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
BestAvailableRule
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule
默认规则,复合判断server所在区域的性能和server的可用性选择服务器

Ribbon自定义

修改microservice-consumer-dept-80
主启动类添加@RibbonClient
在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,形如:
@RibbonClient(name=“MICROSERVICEDEPT”,configuration=MySelfRule.class)

注意配置细节

官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说
我们达不到特殊化定制的目的了。

新建自定义Robbin规则类

@Configuration
public class MySelfRule
{
  @Bean
  public IRule myRule()
  {
   return new RandomRule();//Ribbon默认是轮询,我自定义为随机
  }
}

修改主启动类

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="MICROSERVICE-DEPT",configuration=MySelfRule.class)
public class DeptConsumer80_App
{
  public static void main(String[] args)
  {
   SpringApplication.run(DeptConsumer80_App.class, args);
  }
}

测试
http://localhost/consumer/dept/list

自定义规则深度解析

问题:依旧轮询策略,但是加上新需求,每个服务器要求被调用5次。也即
以前是每台机器一次,现在是每台机器5次
源码解析

参考源码修改为我们需求要求的RandomRule_ZY.java

import java.util.List;
import java.util.Random;
 
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
 
public class RandomRule_ZY extends AbstractLoadBalancerRule {
 
  private int total = 0;    //总共被调用的次数,目前要求每台被调用5次
  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 upList = lb.getReachableServers();
            List 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;
            }
 
            
//            int index = rand.nextInt(serverCount);
//            server = upList.get(index);
            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 clientConfig) {
   
  }
}
 

MySelfRule.java

@Configuration
public class MySelfRule
{
  @Bean
  public IRule myRule()
  {
   //return new RandomRule();//Ribbon默认是轮询,我自定义为随机
   
   return new RandomRule_ZY();//我自定义为每个机器被访问5次
  }
}

测试
http://localhost/consumer/dept/list

你可能感兴趣的:(微服务)