负载均衡及ribbon


负载均衡及ribbon

ribbon是什么?

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

ribbon能干嘛?

  • LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。
  • 负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。
  • 常见的负载均衡软件有Nginx,Lvs等等
  • dubbo、SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义
  • 负载均衡简单分类:
    • 集中式LB
      • 即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,反向代理服务器!由该设施负责把访问请求通过某种策略转发至服务的提供方!
    • 进程式LB
      • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
      • Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址

环境搭建

1、导入依赖

在客户端 80 端口,导入ribbon 、eureka依赖:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.allen</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-dept-80</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.allen</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--ribbon-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <!--        热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

</project>

2、编写配置

application.yml

server:
  port: 80

# eureka配置
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/, http://eureka7004.com:7004/eureka/

3、开启注解

在主启动类上,开启 @EnableEurekaClient

package com.allen.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author Allen
 * @date 2021/1/7 22:15
 */

@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {

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

4、配置负载均衡注解

在ConfigBean类中,开启 @LoadBalanced

package com.allen.springcloud.config;

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;

/**
 * @author Allen
 * @date 2021/1/7 22:01
 */

@Configuration      //等价于  spring applicationContext.xml
public class ConfigBean {

    //配置负载均衡实现RestTemplate @LoadBalanced
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

5、配置controller

ribbon,直接通过服务名访问

package com.allen.springcloud.controller;

import com.allen.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @author Allen
 * @date 2021/1/7 21:57
 */

@RestController
public class DeptConsumerController {

    //消费者,不应该有service层
    //RestTemplate,直接调用,注册到spring容器中
    @Autowired
    private RestTemplate restTemplate;  //提供多种便捷访问远程http服务的方法,便捷的Restful服务模版

    //ribbon, 我们应该通过服务名访问
    //private static final String REST_URL_PREFIX = "http://localhost:8001";
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";


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

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

    @RequestMapping("/consumer/dept/list")
    public List<Dept> getAll(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }
}

6、测试

因为80端口可省略,直接访问 http://localhost/consumer/dept/list,输出如下内容:

xml格式数据

问题:如何解决springboot + cloud 集成 eureka 后,返回格式为 xml 而不是 json 的问题?

根源: spring-cloud-starter-eureka 依赖里使用了 jackson-dataformat-xml 依赖。

解决办法:修改pom依赖,排除 jackson-dataformat-xml 依赖。

        <!--eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.dataformat</groupId>
                    <artifactId>jackson-dataformat-xml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

然后,输出 json 格式数据。

json格式数据

负载均衡

负载均衡示意图

1、创建新的子模块 springcloud-provider-dept-8002 和 springcloud-provider-dept-8003

2、导入依赖,修改 application.yml 端口 和 数据库名

server:
  port: 8003

# mybatis配置
mybatis:
  type-aliases-package: com.allen.springcloud.pojo
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

# spring配置
spring:
  application:
    name: springcloud-provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource  #数据源
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db03?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 1005

# eureka配置, 服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/, http://eureka7004.com:7004/eureka/
  instance:
    instance-id: springcloud-provider-dept8003 #修改Eureka的默认描述信息

# info配置
info:
  app.name: allen-springcloud
  company.name: hlgao666

3、修改 mapper 文件的数据库名

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.allen.springcloud.dao.DeptDao">
    <insert id="addDept" parameterType="dept">
        insert into db03.dept(deptname, db_source) VALUES (#{dname},DATABASE())
    </insert>

    <select id="queryDeptById" resultType="dept" parameterType="Long">
        select * from db03.dept where deptno=#{id}
    </select>

    <select id="queryAll" resultType="dept">
        select * from db03.dept
    </select>
</mapper>

4、测试

启动 7002 注册中心,启动8001、8002、8003提供者服务,启动80消费者服务,浏览器地址栏输入: http://localhost/consumer/dept/list

第一次查询:

第一次查询

第二次查询:

第二次查询

第三次查询:

第三次查询

第四次查询:

第四次查询

结论:ribbon 默认负载均衡机制是轮询。

自定义负载均衡算法

1、了解 IRule

打开 IRule.class,可看到有如下实现类:

解释如下:

RoundRobinRule轮询
RandomRuLe随机
AvailabilityFilteringRule: 会先过滤掉,跳闸,访问故障的服务,对剩下的进行轮询
RetryRule: 会先按照轮询获取服务
,如果服务获取失败,则会在指定的时间内进行,重试

2、编写自定义Ribbon类

注意:自定义Ribbon类路径一定不能暴露在主启动类目录路径之下,否则会被扫描到!

AllenRandomRule

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.allen.myRule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
//import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class AllenRandomRule extends AbstractLoadBalancerRule {
    public AllenRandomRule() {
    }


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

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            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) {
                    return null;
                }

//                int index = this.chooseRandomInt(serverCount);    //生成区间随机数
//                server = (Server)upList.get(index);               //从活着的服务中,随机获取一个

                //================================
                //自定义轮询,每个服务访问5次
                if(total<5){
                    total++;
                }else{
                    total=0;
                    total++;
                    currentIndex++;
                    if(currentIndex>=upList.size()){
                        currentIndex=0;
                    }
                }
                server = upList.get(currentIndex);  //从活着的服务中,获得指定服务

                //================================
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

3、使用自定义Ribbon类

return new AllenRandomRule()

package com.allen.myRule;

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

/**
 * @author Allen
 * @date 2021/1/10 16:07
 */

@Configuration
public class AllenRule {

    @Bean
    public IRule myRule(){
        return new AllenRandomRule();   //自定义轮询,每个服务访问5次
    }
}

4、开启注解

在主启动类上,开启 @RibbonClient 注解,添加服务名和Ribbon类

package com.allen.springcloud;

import com.allen.myRule.AllenRule;
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;

/**
 * @author Allen
 * @date 2021/1/7 22:15
 */

@SpringBootApplication
@EnableEurekaClient
//在微服务启动的时候,就能加载我们自己定义的ribbon类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = AllenRule.class)
public class DeptConsumer_80 {

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

文章作者: Hailong Gao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hailong Gao !
评论
  目录