上一篇文章单独介绍了Ribbon框架的使用,及其如何实现客户端对服务访问的负载均衡,但只是单独从Ribbon框架实现,没有涉及spring cloud。本文着力介绍Ribbon的负载均衡机制,下一篇文章再在spring中继承Ribbon。
Ribbon负载均衡器
上一篇文章我们已经实现了一个客户端负载均衡请求web服务的示例。
当时,我们留了一个伏笔,其中的负载均衡的规则策略可以定制,那么本文着重研究策略定制这部分内容,其他的ribbon客户端的构建和请求方法请参见上一篇文章。
ribbon的负载均衡的策略规则是独立的,即这部分功能可以独立于时间的客户端构造和请求发送。我们需要做的是,实现一个IRule接口的对象,对象里面当然会有一些需要你去覆盖实现的方法,这些方法中需要用代码实现你定制的服务器选择策略,但不包括实际的网络请求操作。
除了可以自己定制,Ribbon已经为我们设计了几个现成的规则策略,分别对应于多个不同IRule实现类。我们只需要将这些类对象传给负载均衡器ILoadBalancer的chooseServer()就可以了。其中,默认情况下,BaseLoadBalancer会选择轮询各个server的策略方式,叫做RoundRobinRule。源码如下:
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnections.PrimeConnectionListener, IClientConfigAware { private static Logger logger = LoggerFactory .getLogger(BaseLoadBalancer.class); private final static IRule DEFAULT_RULE = new RoundRobinRule(); private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy(); private static final String DEFAULT_NAME = "default"; private static final String PREFIX = "LoadBalancer_"; protected IRule rule = DEFAULT_RULE; protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY; protected IPing ping = null; @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>()); @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList<Server>());
好,先说到这里,我们来实验一下默认的规则策略:
第一步,我们先构建一个负载均衡器,类型BaseLoadBalancer,它是ILoadBalancer的实现类。
负载均衡器,需要配置两样东西:
1、服务地址列表——谁来参选?
2、选择策略规则——怎么选?
第二步,按照服务方的地址端口列表,配置一个Server的List。添加给负载均衡器。
第三步,构造或选择一个IRule实现类,通过ConfigurationMannager来配置【客户端名称】.ribbon.NFLoadBalancerRuleClassName属性,将配置键赋予一个规则类。这里我们不操作,使用默认的。
package com.happybks.invokers;
import java.util.ArrayList;
import java.util.List;
import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; public class BalancerApplication { public static void main(String[] args) { ILoadBalancer balancer=new BaseLoadBalancer(); List servers = new ArrayList(); servers.add(new Server("127.0.0.1",8091)); servers.add(new Server("127.0.0.1",8092)); balancer.addServers(servers); for(int i=0;i<10;i++) { Server choosedServer = balancer.chooseServer(null); System.out.println(choosedServer); } } }
默认策略,就用BaseLoadBalancer类默认初始化定义好的,chooseServer方法会为我们选择已有的RoundRobinRule。
(本文出自ochina博主happBKs的博文:https://my.oschina.net/happyBKs/blog/1787825)
源码如下:
private final static IRule DEFAULT_RULE = new RoundRobinRule();
/*
* Get the alive server dedicated to key
*
* @return the dedicated server
*/
public Server chooseServer(Object key) { if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } }
我们运行代码:10次选择server的结果打印如下:
16:41:10.754 [main] WARN com.netflix.config.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources. 16:41:10.758 [main] INFO com.netflix.config.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath. 16:41:10.766 [main] INFO com.netflix.config.DynamicPropertyFactory - DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@75bd9247 16:41:10.818 [main] DEBUG com.netflix.loadbalancer.BaseLoadBalancer - LoadBalancer [default]: clearing server list (SET op) 16:41:10.819 [main] DEBUG com.netflix.loadbalancer.BaseLoadBalancer - LoadBalancer [default]: addServer [127.0.0.1:8091] 16:41:10.819 [main] DEBUG com.netflix.loadbalancer.BaseLoadBalancer - LoadBalancer [default]: addServer [127.0.0.1:8092] 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091 127.0.0.1:8092 127.0.0.1:8091
关于负载均衡相关的四个配置项
这些配置项的前缀是【客户端名称】.ribbon
The supported properties are listed below and should be prefixed by
:
NFLoadBalancerClassName
: should implementILoadBalancer
NFLoadBalancerRuleClassName
: should implementIRule
NFLoadBalancerPingClassName
: should implementIPing
NIWSServerListClassName
: should implementServerList
NIWSServerListFilterClassName
should implementServerListFilter
其中比较重要的是NFLoadBalancerRuleClassName
,我们可以通过这个配置项定制需要的负载均衡规则,可以是ribbon提供的原生的几种规则类,也可以是自己实现的规则类,这些类都实现了IRule接口。
NFLoadBalancerPingClassName
用于配置查看服务器是否存活。
NFLoadBalancerRuleClassName
指定负载均衡器的实现类。当然,可以设置自己实现的负载均衡器。
NIWSServerListClassName
是服务器列表的处理类,用来维护服务器列表的。Ribbon已经实现了动态服务器列表。
NIWSServerListFilterClassName
是服务器的拦截类。
负载均衡的两种配置方法:
一种直接调用ConfigurationManager获取配置实例,然后设置配置属性;一种是在application.yml中配置。
自定义策略规则:
下面我们以一个示例来构建一个自己的负载均衡规则。
示例:构建一个60%的概率选择8091,40%概率选择8092的规则
我们构建一个实现IRule接口的实现类:
package com.happybks.invokers;
import java.util.List;
import java.util.Random;
import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; public class MyProbabilityRandomRule implements IRule { ILoadBalancer balancer = new BaseLoadBalancer(); @Override public Server choose(Object key) { List allServers = balancer.getAllServers(); Random random = new Random(); final int number = random.nextInt(10); if (number < 7) { return findServer(allServers,8091); } return findServer(allServers,8092); } private Server findServer(List allServers, int port) { for (Server server : allServers) { if (server.getPort() == port) { return server; } } System.out.println("NULL port="+port); return null; } @Override public void setLoadBalancer(ILoadBalancer lb) { this.balancer = lb; } @Override public ILoadBalancer getLoadBalancer() { return this.balancer; } }
客户端的请求程序这里我们还是直接在一个普通的main方法中实现
我们首先需要配置请求服务器列表,这个上篇文章已经介绍过。
之后我们对对应的客户端配置它的ribbon.NFLoadBalancerRuleClassName配置为我们刚才定义的那个实现了IRule的实现类的类名全名,注意:是IRule实现类的全名,一个字符串,不是class。
package com.happybks.invokers;
import com.netflix.client.ClientException;
import com.netflix.client.ClientFactory;
import com.netflix.client.http.HttpRequest;
import com.netflix.client.http.HttpResponse; import com.netflix.config.ConfigurationManager; import com.netflix.niws.client.http.RestClient; public class MyRuleClientApplication { public static void main(String[] args) throws Exception { // 1、设置请求的服务器 ConfigurationManager.getConfigInstance().setProperty("happybks-client.ribbon.listOfServers", "localhost:8091,localhost:8092"); // 1 // 2、 配置规则处理类 //本示例略,先默认使用其默认负载均衡策略规则 ConfigurationManager.getConfigInstance().setProperty("happybks-client.ribbon.NFLoadBalancerRuleClassName",MyProbabilityRandomRule.class.getName()); // 3、获取 REST 请求客户端 RestClient client = (RestClient) ClientFactory.getNamedClient("happybks-client"); // 4、创建请求实例 HttpRequest request = HttpRequest.newBuilder().uri("/carsInfo/onsale").build(); // 5、发 送 10 次请求到服务器中 for (int i = 0; i < 10; i++) { System.out.println("the "+(i+1)+"th: "); HttpResponse response = client.executeWithLoadBalancer(request); String result = response.getEntity(String.class); System.out.println(result); } } }
响应请求的服务方程序请参见之前的文章,我们不再累述。然后我们看看运行结果:
22:52:27.695 [main] WARN com.netflix.config.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources.
22:52:27.701 [main] INFO com.netflix.config.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath. 22:52:27.834 [main] INFO com.netflix.config.DynamicPropertyFactory - DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@97e1986 22:52:28.787 [main] INFO com.netflix.http4.ConnectionPoolCleaner - Initializing ConnectionPoolCleaner for NFHttpClient:happybks-client 22:52:28.798 [Connection pool clean up thread] DEBUG com.netflix.http4.ConnectionPoolCleaner - Connection pool clean up started for client happybks-client 22:52:28.799 [Connection pool clean up thread] DEBUG com.netflix.http4.MonitoredConnectionManager - Closing expired connections 22:52:28.799 [Connection pool clean up thread] DEBUG com.netflix.http4.NamedConnectionPool - Closing expired connections 22:52:28.799 [Connection pool clean up thread] DEBUG com.netflix.http4.MonitoredConnectionManager - Closing connections idle longer than 30000 MILLISECONDS 22:52:28.800 [Connection pool clean up thread] DEBUG com.netflix.http4.NamedConnectionPool - Closing connections idle longer than 30000 MILLISECONDS 22:52:29.032 [main] WARN com.netflix.client.ClientFactory - Class com.happybks.invokers.MyProbabilityRandomRule neither implements IClientConfigAware nor provides a constructor with IClientConfig as the parameter. Only default constructor will be used. 22:52:29.035 [main] INFO com.netflix.loadbalancer.BaseLoadBalancer - Client: happybks-client instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=happybks-client,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 22:52:29.111 [main] INFO com.netflix.loadbalancer.DynamicServerListLoadBalancer - Using serverListUpdater PollingServerListUpdater 22:52:29.145 [main] WARN com.netflix.client.ClientFactory - Class com.happybks.invokers.MyProbabilityRandomRule neither implements IClientConfigAware nor provides a constructor with IClientConfig as the parameter. Only default constructor will be used. 22:52:29.165 [main] INFO com.netflix.loadbalancer.DynamicServerListLoadBalancer - DynamicServerListLoadBalancer for client happybks-client initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=happybks-client,current list of Servers=[localhost:8091, localhost:8092],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:localhost:8092; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 GMT+08:00 1970; First connection made: Thu Jan 01 08:00:00 GMT+08:00 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] , [Server:localhost:8091; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 GMT+08:00 1970; First connection made: Thu Jan 01 08:00:00 GMT+08:00 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:com.netflix.loadbalancer.ConfigurationBasedServerList@152aa092 22:52:29.165 [main] INFO com.netflix.client.ClientFactory - Client: happybks-client instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=happybks-client,current list of Servers=[localhost:8091, localhost:8092],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:localhost:8092; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 GMT+08:00 1970; First connection made: Thu Jan 01 08:00:00 GMT+08:00 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp