SpringCloud(part3)负载均衡Ribbon

负载均衡是分布式架构的重点,负载均衡机制决定着整个服务器集群的性能与稳定,Eureka服务实例可以进行集群部署,每个实例都均衡处理服务请求,那么这些请求是如何分摊到各个服务实例中的呢?

1、Ribbon简介

Ribbon在集群中为各个客户端的通信提供了支持,主要实现中间层应用程序的负载均衡。

Ribbon主要提供以下特性:

    1.负载均衡器,可支持插拔式的负载均衡规则。

    2.对多种协议提供了支持,例如HTTP,TCP,UDP。

    3.集成了负债均衡功能的客户端。

Ribbon子模块:

     1.ribbon-core :ribbon的核心,主要包括了负载均衡器接口定义,客户端接口定义,内置的负载均衡器实现等API,

     2.ribbon-eureka:为Eureka客户端提供的负载均衡实现类

     3.ribbon-httpclient:对Apache的HTTPClient进行封装,该模块提供了含有负载均衡的功能的REST客户端。

负载均衡器组件的功能:

     1.维护服务器IP,DNS名称等信息

     2.根据特定的逻辑在服务器列表中循环

     三大模块:

            (1)rule:一个逻辑组件,这些逻辑将会决定从服务器列表中返回哪个服务器实例

         (2)Ping:该组件主要使用定时器来确定服务器网络可以连接

         (3)Serverlist:服务器列表,通过静态的配置确定负载的服务器,也可以动态指定服务器列表。由后台的线程来刷新该列表。

2.第一个Ribbon程序

1.创建服务器端:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
从不同的端口运行,得到不同的服务器
public static void main(String[] args) {
    //输入从哪个端口来访问
    Scanner scanner=new Scanner(System.in);
    String port=scanner.nextLine();
    new SpringApplicationBuilder(FirstRibbonServerApplication.class).properties("server.port="+port).run(args);
}

编写服务接口

@RestController
public class MyController {

    @RequestMapping(value = "/person/{pid}",method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Person findPerson(@PathVariable("pid") Integer pid, HttpServletRequest request){
        Person person=new Person();
        person.setPid(pid);
        person.setPname("朱海涛");
        person.setMessage(request.getRequestURI().toString());
        return person;
    }
    @RequestMapping(value = "/",method = RequestMethod.GET)
    public String hello(){
        return "hello ribbon";
    }
}

2.创建客服端

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

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
public static void main(String[] args) throws Exception {
    //设置请求的服务器
    ConfigurationManager.getConfigInstance().setProperty(
            "my-client.ribbon.listOfServers",
            "localhost:8082,localhost:8081"
    );
    //获取服务器列表并输入 ,必须
    System.out.println("服务列表:" + ConfigurationManager.getConfigInstance().getProperty("my-client.ribbon.listOfServers"));
    //获取Rest请求客户端
    RestClient client= (RestClient) ClientFactory.getNamedClient("my-client");
    //创建请求实例
    HttpRequest request=HttpRequest.newBuilder().uri("/person/1").build();
    //发送请求
    for (int i=0;i<6;i++){
        HttpResponse response=client.executeWithLoadBalancer(request);
        String result=response.getEntity(String.class);
        System.out.println("请求结果:"+result);
    }
}

通过不同的端口(8081,8082)先运行服务器端,然后在运行客户端函数,可以发现RestClient轮流向8081和8082端口发送请求,可见RestClient已经帮我们实现了负载均衡的能力。

3.Ribbon的负载均衡机制

Ribbon提供了几个负载均衡的组件,其目的就是让请求转给合适的服务器进行处理,因此,如何选择合适的服务器便成为了负载均衡机制的核心。下面介绍Ribbon负载均衡的实现机制。

前面的例子中在发送请求时,会自动使用负载均衡器,根据特定的逻辑来选择端口,服务器列表可以通过listOfServers来设置,也可以使用动态更新机制。

public static void main(String[] args) {
    //创建负载均衡器
    ILoadBalancer lb=new BaseLoadBalancer();
    //添加服务器
    List<Server> servers=new ArrayList<Server>();
    servers.add(new Server("localhost",8081));
    servers.add(new Server("localhost",8082));
    lb.addServers(servers);
    //进行6次服务器选择
    for(int i=0;i<6;i++){
        Server server=lb.chooseServer(null);
        System.out.println(server);
    }
}

localhost:8082
localhost:8081
localhost:8082
localhost:8081
localhost:8082
localhost:8081

通过该例子可以看出默认情况下会使用RoundRobinRule的规则逻辑来选择服务器。

4.自定义负载规则

设计自己的负载规定

public class MyRule implements IRule {

    ILoadBalancer lb;

    public MyRule() {
    }

    public MyRule(ILoadBalancer lb) {
        this.lb = lb;
    }

    @Override
    public Server choose(Object o) {
        //获取全部的服务器
        List<Server> servers=lb.getAllServers();
        return servers.get(0);
    }

    @Override
    public void setLoadBalancer(ILoadBalancer iLoadBalancer) {
        this.lb=lb;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
        return this.lb;
    }
}

1.利用编码的方式设定负载规则

public class TestMyRule {
    public static void main(String[] args) {
        //创建负载均衡器
        BaseLoadBalancer lb=new BaseLoadBalancer();
        //设置自定义的负载规则
        lb.setRule(new MyRule(lb));
        //添加服务器
        List<Server> servers=new ArrayList<Server>();
        servers.add(new Server("localhost",8081));
        servers.add(new Server("localhost",8082));
        lb.addServers(servers);
        //进行6次服务器选择
        for(int i=0;i<6;i++){
            Server server=lb.chooseServer(null);
            System.out.println(server);
        }

    }
}

2.利用配置的方式设定负载规则

public class TestMyRule2 {
    public static void main(String[] args) {
        // 设置请求的服务器
        ConfigurationManager.getConfigInstance().setProperty(
                "my-client.ribbon.listOfServers",
                "localhost:8082,localhost:8081");
        //获取服务器列表并输入
        System.out.println("服务列表:" + ConfigurationManager.getConfigInstance().getProperty("my-client.ribbon.listOfServers"));
        // 获取REST请求客户端
        RestClient client = (RestClient) ClientFactory .getNamedClient("my-client");

        // *****配置规则处理类 必须放在已经获取请求客户端的下面
        ConfigurationManager.getConfigInstance().setProperty(
                "my-client.ribbon.NFLoadBalancerRuleClassName",
                MyRule.class.getName());

        // 创建请求实例
        HttpRequest request = HttpRequest.newBuilder().uri("/person/1").build();
        // 发送6次请求到服务器中

        for (int i=0;i<6;i++){
            HttpResponse response= null;
            try {
                response = client.executeWithLoadBalancer(request);
                String result=response.getEntity(String.class);
                System.out.println("请求结果:"+result);
            } catch (ClientException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

5.Ribbon自带的负载规则

    Ribbon提供了若干个内置的负载规则,使用者完全可以直接使用
        RoundRobinRule:系统默认的规则,通过简单的轮询服务列表来选择服务器,其他的规则在很多情况下,仍然使用RoundRobinRule

        AvailablilityFilteringRule:该各种会忽略以下服务器

            无法连接的服务器:在默认情况下,如果3次连接失败,该服务器将会被置为“短路”的状态,该状态将持续30秒,如果再次连接失败,“短路”状态的持续时间将会以几何级增加。可以通过修改niws.loadbalance..connerctionFailureCountThreshold属性来配置连接失败的次数
            并发数过高的服务器:如果连接到该服务器的并发数过高,也会被这个规则忽略,可以通过修改.ribbon.ActiveConnectionLimit属性来设定最高并发数

        WeightedResponseTimeRule:为每个服务器赋予一个权重值,服务器的响应时间越长,该权重值就越少,这个规则会随机选择服务器,这个权重值有可以能会决定服务器的选择
        ZoneAvoidanceRule:该规则以区域、可用服务器为基础,进行服务器选择。使用Zone对服务器进行分类,可以理解为机架或者机房
        BestAvailiableRule:忽略“短路”的服务器,并选择并发数较低的服务器
        RandomRule:随机选择可用服务器
        RetryRule:含有重试的选择逻辑,如果使用RoundRobinRule

    以上提供的负载规则,基本上可以满足大部分的需求
 

5.Ping机制

   在负载均衡器中,提供了Ping的机制,每隔一段时间,会去Ping服务器,判断服务器是否存活
   该工作由IPing接口的实现类负责,如果单独使用Ribbon,在默认情况下,不会激活Ping机制,默认的实现类为DummyPing

package com.atm.cloud;

import java.util.ArrayList;
import java.util.List;

import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.Server;

public class MyTestPingUrl {

    public static void main(String[] args) throws InterruptedException {
        // 创建负载均衡器
        BaseLoadBalancer lb = new BaseLoadBalancer();

        // 添加服务器
        List<Server> servers = new ArrayList<Server>();

        // 8080 端口连接正常
        servers.add(new Server("localhost", 8080));
        // 一个不存在的端口
        servers.add(new Server("localhost", 8888));

        lb.addServers(servers);

        // 设置 IPing 实现类
        lb.setPing(new PingUrl());

        // 设置 Ping 时间间隔为 2 秒
        lb.setPingInterval(2);

        Thread.sleep(6000);

        for (Server s : lb.getAllServers()) {
            System.out.println(s.getHostPort() + " 状态:" + s.isAlive());
        }
    }

}

  

    使用了代码的方法来设置负载均衡器使用PingUrl,设置了每隔2秒,就向两个服务器请求
    PingUrl实际是使用的是HttpClient

    除了在代码中配置IPing类外,还可以在配置中设置IPing实现类

package com.atm.cloud;

import java.util.List;

import com.netflix.client.ClientFactory;
import com.netflix.config.ConfigurationManager;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.client.http.RestClient;

public class MyPingConfig {

    public static void main(String[] args) throws InterruptedException {
        // 设置请求的服务器
        ConfigurationManager.getConfigInstance().setProperty(
                "my-client.ribbon.listOfServers",
                "localhost:8080,localhost:8888");

        // 配置 Ping 处理类
        ConfigurationManager.getConfigInstance().setProperty(
                "my-client.ribbon.NFLoadBalancerPingClassName",
                PingUrl.class.getName());

        // 配置 Ping 时间间隔
        ConfigurationManager.getConfigInstance().setProperty(
                "my-client.ribbon.NFLoadBalancerPingInterval", 2);

        // 获取 REST 请求客户端
        RestClient client = (RestClient) ClientFactory
                .getNamedClient("my-client");

        Thread.sleep(6000);

        // 获取全部服务器
        List<Server> servers = client.getLoadBalancer().getAllServers();
        System.out.println(servers.size());

        // 输出状态
        for (Server s : servers) {
            System.out.println(s.getHostPort() + " 状态:" + s.isAlive());
        }

    }
}

  

    my-client.ribbon.NFLoadBalancerPingClassName:配置 IPing 的实现类
    my-client.ribbon.NFLoadBalancerPingInterval:配置 Ping 操作的时间间隔。
    以上两个配置,同样可以使用在配置文件中

1.5、自定义Ping

    实现自定义Ping较为简单,先实现IPing接口,再通过配置来设定具体的Ping实现类

package com.atm.cloud;

import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.Server;

public class MyPing implements IPing{

    public boolean isAlive(Server server) {
        System.out.println("这是自定义 Ping 实现类:" + server.getHostPort());
        return true;
    }

}

    要使用自定义的Ping类,通 过 修 改client.nameSpace.NFLoadBalancerPingClassName 配置即可

1.6、其他配置

    NFLoadBalancerClassName:指定负载均衡器的实现类,可利用该配置,实现自己的负载均衡器。

    NIWSServerListClassName:服务器列表处理类,用来维护服务器列表,Ribbon
    已经实现动态服务器列表。

    NIWSServerListFilterClassName:用于处理服务器列表拦截。
 

 

在SpringCloud中使用Ribbon

 

1、创建服务器项目:

server:
  port: 8888
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
@SpringBootApplication
@EnableEurekaServer

2.创建服务提供项目

#将应用配置为first-service-provider,
spring.application.name=cloud-ribbon-provider
#配置服务实例的主机名称
eureka.instance.hostname=localhost
eureka.client.service-url.defaultZone=http://localhost:8888/eureka/
@RestController
public class FirstController {
    @RequestMapping(value = "/person/{pid}",method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Person findPerson(@PathVariable("pid") Integer pid, HttpServletRequest request){
        Person person=new Person();
        person.setPid(pid);
        person.setPname("朱海涛");
        person.setMessage(request.getRequestURL().toString()
                +"服务端口:"+request.getServerPort()
                +"远程端口:"+request.getRemotePort());

        return person;
    }
}
@SpringBootApplication
@EnableEurekaClient
public class CloudRibbonProviderApplication {

    public static void main(String[] args) {
        //通过不同的端口号来启动服务
        Scanner scanner=new Scanner(System.in);
        String port=scanner.nextLine();
        new SpringApplicationBuilder(CloudRibbonProviderApplication.class).
                properties("server.port="+port).run(args);
    }

}

3.创建客户端项目

@SpringBootApplication
@EnableDiscoveryClient
server:
  port: 9000
spring:
  application:
    name: cloud-ribbon-invoker

eureka:
  instance:
    hostname: localhost
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka/

# 服务器名称 这样利用配置文件可以省去MyConfig类和CloudProviderConfig类
#cloud-ribbon-invoker:
#  ribbin:
#    NFLoadBalancerRuleClassName: com.example.cloudribboninvoker.ribbonConfig.MyConfig
#    NFLoadBalancerPingClassName: com.example.cloudribboninvoker.ribbonConfig.MyPing
#    listOfServers: http://localhost:8001/,http://localhost:8002/
@RestController
@Configuration
public class InvokerController {
    @Bean
    @LoadBalanced//让这个实例具有分布式的功能 负载均衡的能力
    public RestTemplate getRestTemplate (){
        return new RestTemplate();
    }
    @RequestMapping(value = "/router",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE)
    public String router(){
        RestTemplate restTemplate=getRestTemplate();
        String json=restTemplate.getForObject("http://cloud-ribbon-provider/person/1",String.class);
        return json;
    }
}

自定义 Ribbon负载均衡规则的类:

public class MyRule implements IRule {

    private ILoadBalancer iLoadBalancer;

    @Override
    public Server choose(Object key) {
        List<Server> servers=iLoadBalancer.getAllServers();
        System.out.println("自定义负载均衡规则...");
        for (Server s:servers
             ) {
            System.out.println("      "+s.getHostPort());
        }
        return servers.get(0);
    }

    @Override
    public void setLoadBalancer(ILoadBalancer iLoadBalancer) {
        this.iLoadBalancer=iLoadBalancer;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
        return iLoadBalancer;
    }
}
public class MyPing implements IPing {
    @Override
    public boolean isAlive(Server server) {
        System.out.println("自定义Ping类,服务器信息:"+server.getHostPort());
        return true;
    }
}

应用该配置的两种方式:

1、在配置文件中配置

# 服务器名称 这样利用配置文件可以省去MyConfig类和CloudProviderConfig类
#cloud-ribbon-invoker:
#  ribbin:
#    NFLoadBalancerRuleClassName: com.example.cloudribboninvoker.ribbonConfig.MyConfig
#    NFLoadBalancerPingClassName: com.example.cloudribboninvoker.ribbonConfig.MyPing
#    listOfServers: http://localhost:8001/,http://localhost:8002/

2.通过类和注解配置

public class MyConfig {
    @Bean
    public IRule getRule(){
        return new MyRule();
    }
    @Bean
    public IPing getPing(){
        return new MyPing();
    }
}
//使用这个注解
// name表示对那些服务进行Ribbon的负载均衡自定义设置,
// configuration表示使用哪个自定义配置类
@RibbonClient(name = "cloud-ribbon-provider",configuration = MyConfig.class)
public class CloudProviderConfig {
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值