负载均衡是分布式架构的重点,负载均衡机制决定着整个服务器集群的性能与稳定,Eureka服务实例可以进行集群部署,每个实例都均衡处理服务请求,那么这些请求是如何分摊到各个服务实例中的呢?
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:服务器列表,通过静态的配置确定负载的服务器,也可以动态指定服务器列表。由后台的线程来刷新该列表。
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已经帮我们实现了负载均衡的能力。
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的规则逻辑来选择服务器。
设计自己的负载规定
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(); } } } }Ribbon提供了若干个内置的负载规则,使用者完全可以直接使用 RoundRobinRule:系统默认的规则,通过简单的轮询服务列表来选择服务器,其他的规则在很多情况下,仍然使用RoundRobinRule
AvailablilityFilteringRule:该各种会忽略以下服务器
无法连接的服务器:在默认情况下,如果3次连接失败,该服务器将会被置为“短路”的状态,该状态将持续30秒,如果再次连接失败,“短路”状态的持续时间将会以几何级增加。可以通过修改niws.loadbalance..connerctionFailureCountThreshold属性来配置连接失败的次数 并发数过高的服务器:如果连接到该服务器的并发数过高,也会被这个规则忽略,可以通过修改.ribbon.ActiveConnectionLimit属性来设定最高并发数
WeightedResponseTimeRule:为每个服务器赋予一个权重值,服务器的响应时间越长,该权重值就越少,这个规则会随机选择服务器,这个权重值有可以能会决定服务器的选择 ZoneAvoidanceRule:该规则以区域、可用服务器为基础,进行服务器选择。使用Zone对服务器进行分类,可以理解为机架或者机房 BestAvailiableRule:忽略“短路”的服务器,并选择并发数较低的服务器 RandomRule:随机选择可用服务器 RetryRule:含有重试的选择逻辑,如果使用RoundRobinRule
以上提供的负载规则,基本上可以满足大部分的需求
在负载均衡器中,提供了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:用于处理服务器列表拦截。
1、创建服务器项目:
server: port: 8888 eureka: client: register-with-eureka: false fetch-registry: false @SpringBootApplication @EnableEurekaServer2.创建服务提供项目
#将应用配置为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; } }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 { }