SpringCloud(part3)负载均衡Ribbon

    xiaoxiao2022-07-03  135

    负载均衡是分布式架构的重点,负载均衡机制决定着整个服务器集群的性能与稳定,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 { }

     

    最新回复(0)