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