Ribbon和@LoadBalanced

Ribbon概念

Ribbon是Netflix开发的客户端负载均衡器,为Ribbon配置服务提供者地址列表后,Ribbon就可以基于某种负载均衡策略算法,自动地帮助服务消费者去请求提供者

Ribbon默认为我们提供了很多负载均衡算法,例如轮询、随机等,我们也可以实现自定义负载均衡算法

Ribbon作为Spring Cloud的负载均衡机制的实现,

  • Ribbon可以单独使用,作为一个独立的负载均衡组件,需要手动配置服务地址列表。
  • Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表(DiscoveryClient),并基于负载均衡算法,请求其中一个服务提供者实例
  • Ribbon与OpenFeign和RestTemplate进行无缝对接,让二者具有负载均衡的能力,OpenFeign默认集成了ribbon

Ribbon组成

github地址:https://github.com/Netflix/ribbon

  • ribbon-core: 核心的通用性代码。api一些配置。
  • ribbon-eureka:基于eureka封装的模块,能快速集成eureka。
  • ribbon-examples:学习示例。
  • ribbon-httpclient:基于apache httpClient封装的rest客户端,集成了负载均衡模块,可以直接在项目中使用。
  • ribbon-loadbalancer:负载均衡模块。
  • ribbon-transport:基于netty实现多协议的支持。比如http,tcp,udp等

编码及测试

利用Eureka手写负载均衡

在api-driver:ShortMsgServiceImpl中

调用方:调用服务,通过loadBalance(我们自定义的方法)选出一个服务

手写ribbon调用

ServiceInstance instance = loadBalance(serviceName);
url = http + instance.getHost()+":"+instance.getPort()+uri;
ResponseEntity<ResponseResult> resultEntity = restTemplate.postForEntity(url, smsSendRequest, ResponseResult.class);
ResponseResult result = resultEntity.getBody();

负载均衡方法loadBalance

import org.springframework.cloud.client.discovery.DiscoveryClient;

@Autowired
DiscoveryClient discoveryClient;

private ServiceInstance loadBalance(String serviceName) {
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
    ServiceInstance instance = instances.get(new Random().nextInt(instances.size()));
    log.info("负载均衡 选出来的ip:"+instance.getHost()+",端口:"+instance.getPort());
    return instance;
}

引入RestTemplate

/**
* 手写简单ribbon
* @return
*/
@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

便于理解,下面是基于:RandomRule,基于Ribbon做选择

ribbon loadbalance

yapi:api-driver:学习:根据serviceName获取服务端信息

进入方法

@GetMapping("/choseServiceName")
    public ResponseResult choseServiceName() {
    String serviceName = "service-sms";
    ServiceInstance si = loadBalancerClient.choose(serviceName);
    System.out.println("sms节点信息:url:"+si.getHost()+",port:"+si.getPort());

    return ResponseResult.success("");
}

loadBalancerClient

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient的choose方法

@Override
    public ServiceInstance choose(String serviceId) {
    return choose(serviceId, null);
}

进入choose

public ServiceInstance choose(String serviceId, Object hint) {
    Server server = getServer(getLoadBalancer(serviceId), hint);
    if (server == null) {
        return null;
    }
    return new RibbonServer(serviceId, server, isSecure(server, serviceId),
    serverIntrospector(serviceId).getMetadata(server));
}

进入getLoadBalancer

protected ILoadBalancer getLoadBalancer(String serviceId) {
    return this.clientFactory.getLoadBalancer(serviceId);
}

再进入org.springframework.cloud.netflix.ribbon.SpringClientFactory,此时类换了。

public ILoadBalancer getLoadBalancer(String name) {
    return getInstance(name, ILoadBalancer.class);
}

进入getInstance

@Override
public <C> C getInstance(String name, Class<C> type) {
    C instance = super.getInstance(name, type);
    if (instance != null) {
        return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    return instantiateWithConfig(getContext(name), type, config);
}

进入 super.getInstance,此方法获取到:ILoadBalancer,从spring ioc容器中来

public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
        return context.getBean(type);
    }
    return null;
}

全量拉取和增量拉取

F7跳下一个断点

Server server = getServer(getLoadBalancer(serviceId), hint);

进入getServer

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    if (loadBalancer == null) {
        return null;
    }
    // Use ‘default‘ on a null hint, or just pass it on?
    return loadBalancer.chooseServer(hint != null ? hint : "default");
}

鼠标放到loadBalancer,看看里面内容。主要看看它的rule属性。

进入loadBalancer.chooseServer

public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
        logger.debug("Zone aware logic disabled or there is only one zone");
        return super.chooseServer(key);
    }
}

进入super.chooseServer(key)

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;
        }
    }
}

 

走到return rule.choose(key);
com.netflix.loadbalancer.RandomRule@1b73fec7,是因为我们在外面配置了它是随机规则

进入choose

@Override
public Server choose(Object key) {
    return choose(getLoadBalancer(), key);
}

再进入:choose

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        return null;
    }
    Server server = null;

    while (server == null) {
        if (Thread.interrupted()) {
            return null;
        }
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();

        int serverCount = allList.size();
        if (serverCount == 0) {
            /*
             * No servers. End regardless of pass, because subsequent passes
             * only get more restrictive.
             */
            return null;
        }

        int index = chooseRandomInt(serverCount);
        server = upList.get(index);

        if (server == null) {
            /*
            * The only time this should happen is if the server list were
            * somehow trimmed. This is a transient condition. Retry after
            * yielding.
            */
            Thread.yield();
            continue;
        }

        if (server.isAlive()) {
            return (server);
        }

        // Shouldn‘t actually happen.. but must be transient or a bug.
        server = null;
        Thread.yield();
    }
    return server;
}

重点

int index = chooseRandomInt(serverCount);
server = upList.get(index);

进去

protected int chooseRandomInt(int serverCount) {
    return ThreadLocalRandom.current().nextInt(serverCount);
}

最后获取到服务。

上面是选择服务的过程。和我们前面手写过比较:都是随机数选出一个服务

将yml中service-sms的配置 随机规则去掉,则ILoadBalancer的 rule就变了

核心类:ILoadBalancer

里面包括了所有的服务提供者集群 的:ip和端口。service-sms:8002,8003

每个服务都有一个ILoadBalancer,ILoadBalancer里面有该服务列表。

每个服务

Map<服务名,ILoadBalancer>

服务列表来源

打开:com.netflix.loadbalancer.ILoadBalancer

它是定义负载均衡操作过程的接口

通过SpringClientFactory的getLoadBalancer方法获取(前面跟踪源码看到的)

ILoadBalancer的实例实在RibbonClientConfiguration中配置的

通过下面两种方式:1.默认RibbonClientConfiguration(下面) 2自定义。

org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
    IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
        return this.propertiesFactory.get(ILoadBalancer.class, config, name);
    }
    return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
    serverListFilter, serverListUpdater);
}

ILoadBalancer的默认的实现类是:ZoneAwareLoadBalancer

Rule默认:com.netflix.loadbalancer.ZoneAvoidanceRule@34b82630

配置说明

| Bean 类型                |             说明            |
| ---------------- | ---------------- |
| ILoadBalancer          |     负载均衡器的抽象  |
| IClientConfig            |        client配置类      |
| IRule                       |       负载均衡策略      |
| IPing                       |     服务可用性检测     |
| ServerList                |       服务列表获取      |
| ServerListFilter         |      服务列表过滤       |

ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、获取所有的服务器列表、获取可用的服务器列表等等

// ILoadbalancer
// 添加所有该服务的服务列表
Initial list of servers.
public void addServers(List<Server> newServers);

// 得到可以访问的服务列表
public List<Server> getReachableServers();

Choose a server from load balancer.(和负载均衡算法关联)
// 选择一个可以调用的server
public Server chooseServer(Object key);

上面方法:实现了:

  • 列出所有可用服务public List<Server> getReachableServers();
  • 然后选一个服务出来chooseServer(Object key);

饥饿模式

com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList

方法: private List<DiscoveryEnabledServer> obtainServersViaDiscovery() 中:

List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);

得到所有服务,对应的服务列表,借助eurekaClient

服务列表:DynamicServerListLoadBalancer

初始化下面的类时,执行了服务列表拉取

com.netflix.loadbalancer.DynamicServerListLoadBalancer

@Override
public void setServersList(List lsrv) {
    super.setServersList(lsrv);
    List<T> serverList = (List<T>) lsrv;
    Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>();
    for (Server server : serverList) {
        // coding
    }
}

最终会存储到

com.netflix.loadbalancer.LoadBalancerStats的
volatile Map<String, List<? extends Server>> upServerListZoneMap。

处理无用的服务

更新机制,更新最新的服务

DynamicServerListLoadBalancer

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
    @Override
    public void doUpdate() {
        updateListOfServers();
    }
}

ping机制,试试服务好不好

public List<Server> getAllServers();

获取所有服务。有的已经挂了。怎么办?

从eureka获得一系列 server。不知道server挂了没有。用定时任务,间隔去ping

执行:com.netflix.loadbalancer.IPing

有个实现类:NIWSDiscoveryPing

public boolean isAlive(Server server) {
    boolean isAlive = true;
    if (server!=null && server instanceof DiscoveryEnabledServer){
        DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server; 
        InstanceInfo instanceInfo = dServer.getInstanceInfo();
        if (instanceInfo!=null){ 
            InstanceStatus status = instanceInfo.getStatus();
            if (status!=null){
                isAlive = status.equals(InstanceStatus.UP);
            }
        }
    }
    return isAlive;
}

上两种机制不能同时发生

选择算法

IRule,默认

com.netflix.loadbalancer.ZoneAvoidanceRule@505fb311:区域内轮询。

IRule负载均衡策略:通过实现该接口定义自己的负载均衡策略

它的choose方法就是从一堆服务器列表中按规则选出一个服务器。

所有规则

ZoneAvoidanceRule(区域权衡策略)

  • 复合判断Server所在区域的性能和Server的可用性,轮询选择服务器

BestAvailableRule(最低并发策略)

  • 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • 逐个找服务,如果断路器打开,则忽略

RoundRobinRule(轮询策略)

  • 以简单轮询选择一个服务器,按顺序循环选择一个server。

RandomRule(随机策略)

  • 随机选择一个服务器

AvailabilityFilteringRule(可用过滤策略)

  • 会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务
  • 然后对剩余的服务列表安装轮询策略进行访问

WeightedResponseTimeRule(响应时间加权策略)

  • 据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高
  • 刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule
  • 响应时间长,权重低,被选择的概率低。反之,同样道理此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间

RetryRule(重试策略)

  • 先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务
  • 如多次获取某个服务失败,就不会再次获取该服务
  • 主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时

如果要用其他负载均衡策略:只需要更改。

@Bean
public IRule myRule(){
    //return new RoundRobinRule();
    //return new RandomRule();
    return new RetryRule();
}

Iloadbalancer,irule,choose()。

Ribbon负载均衡

Ribbon可以独立使用,自己写服务列表,也是一个简单配置。

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

配置文件

service-sms: 
ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
eureka: 
    enable: false
    listOfServers: localhost:8002,localhost:8003

可以配置轮询,可以配置随机,上面是随机,默认是轮询 

Ribbon可以和RestTemplate一起使用,也可以集成到Feign中使用 

编码

Spring Cloud为客户端负载均衡创建了特定的注解@LoadBalanced

  • 我们只需要使用该注解修饰创建RestTemplate实例的@Bean函数,Spring Cloud就会让RestTemplate使用相关的负载均衡策略,默认情况下是使用Ribbon
  • 除了@LoadBalanced之外,Ribbon还提供@RibbonClient注解
  • 该注解可以为Ribbon客户端声明名称和自定义配置
  • name属性可以设置客户端的名称,configuration属性则会设置Ribbon相关的自定义配置类

在eureka-client中使用Ribbon时, 不需要引入jar包,因为erueka-client已经包括ribbon的jar包了

用@LoadBalance修饰RestTemplate可以实现负载均衡

由于RestTemplate的Bean实例化方法restTemplate被@LoadBalanced修饰

所以当调用restTemplate的postForObject方法发送HTTP请求时,会使用Ribbon进行负载均衡

//使用ribbon,添加@LoadBalance,使RestTemplate具备负载均衡能力。
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

@Autowired
private RestTemplate restTemplate;
//serviceName=虚拟主机名。默认情况下,虚拟主机名和服务名一致。
String url = "http://"+serviceName+"/send/alisms-template";
//调用
ResponseEntity<ResponseResult> resultEntity = restTemplate.postForEntity(url, smsSendRequest, ResponseResult.class);

//测试根据serviceName获取服务提供者信息。此时不需要@LoadBalance,默认是轮训。
@Autowired
private LoadBalancerClient loadBalancerClient;
// 不能将choseServiceName和 restTemplate写在一起,因为后者中已经有前者了。
@GetMapping("/choseServiceName")
public ResponseResult choseServiceName() {
    String serviceName = "service-sms";
    ServiceInstance si = loadBalancerClient.choose(serviceName);
    System.out.println("sms节点信息:url:"+si.getHost()+",port:"+si.getPort());

    return ResponseResult.success("");
}

扩展

默认情况下,虚拟主机名=服务名称,虚拟主机名最好不要用"_"

虚拟主机名可以配置:

eureka: 
instance: 
virtual-host-name: service-sms

原理

通过前面的例子,我们可知:

  • 拦截请求
  • 获取url
  • 通过url中 serviceName 获取 List<ServiceInstance>
  • 通过负载均衡算法选取一个ServiceInstance
  • 替换请求,将原来url中的 serviceName换成ip+port

@LoadBalanced原理源码

如果用了正常的调用 ribbon,调用的服务名,而没有加@LoadBalance

会报:java.net.UnknownHostException 就走了拦截器。

RibbonLoadBalancerClient的方法

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    Server server = getServer(loadBalancer, hint);
// coding
}

上面方法

  • 负载均衡选出一个server
  • 给RestTemplate增加了拦截器
  • 在请求之前,将请求的地址进行替换(根据具体的负载策略选择请求地址,将服务名替换成 ip:port)

然后再进行调用

在ioc容器初始化时

org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

加了个bean

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
        restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };    
}

给restTemplate设置了拦截器

进入拦截器:final LoadBalancerInterceptor loadBalancerInterceptor

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
    final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null,
    "Request URI does not contain a valid hostname: " + originalUri);    
    return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}

此方法,可以类比我们的spring mvc拦截器。每次请求都拦截一下

return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    //此时完成了负载均衡选择
    Server server = getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
    isSecure(server, serviceId),
    serverIntrospector(serviceId).getMetadata(server));

    return execute(serviceId, ribbonServer, request);
}

通过ILoadBalancer,获取服务地址

再点return execute(serviceId, ribbonServer, request);

T returnVal = request.apply(serviceInstance);

apply处打断点,其实在getUri

在org.springframework.http.client;InterceptingClientHttpRequest中

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
        return nextInterceptor.intercept(request, body, this);
    }
    else {
        HttpMethod method = request.getMethod();
        Assert.state(method != null, "No standard HTTP method");
        ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
        request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
        if (body.length > 0) {
            if (delegate instanceof StreamingHttpOutputMessage) {
                StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
                streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
            }else {
                StreamUtils.copy(body, delegate.getBody());
            }
        }
    return delegate.execute();
    }
}
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);

最终

com.netflix.loadbalancer;LoadBalancerContext

public URI reconstructURIWithServer(Server server, URI original) {
    String host = server.getHost();
    int port = server.getPort();
    String scheme = server.getScheme();

    if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
        return original;
    }
    if (scheme == null) {
        scheme = original.getScheme();
    }
    if (scheme == null) {
        scheme = deriveSchemeAndPortFromPartialUri(original).first();
    }

    try {
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append("://");
        if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
            sb.append(original.getRawUserInfo()).append("@");
        }
        sb.append(host);
        if (port >= 0) {
        sb.append(":").append(port);
        }
        sb.append(original.getRawPath());
        if (!Strings.isNullOrEmpty(original.getRawQuery())) {
            sb.append("?").append(original.getRawQuery());
        }
        if (!Strings.isNullOrEmpty(original.getRawFragment())) {
            sb.append("#").append(original.getRawFragment());
        }
        URI newURI = new URI(sb.toString());
        return newURI; 
    } catch (URISyntaxException e) {
        throw new RuntimeException(e);
    }
}

总结

  • 由于加了@LoadBalanced注解,使用RestTemplateCustomizer对所有标注了@LoadBalanced的RestTemplate Bean添加了一个LoadBalancerInterceptor拦截器
  • 利用RestTempllate的拦截器,spring可以对restTemplate bean进行定制
  • 加入loadbalance拦截器进行ip:port的替换,也就是将请求的地址中的服务逻辑名转为具体的服务地址

源码总结

ILoadBalancer 承接 eureka 和 ribbon

获取服务地址列表,选择一个

每个服务都有ILoadBalancer

选择服务用 IRule(负载均衡策略)

自定义Ribbon配置

IRule 

Spring Cloud默认的Ribbon配置类是:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration。

全局:

ribbon:
eager-load:
enabled: true

启动拉取服务列表

默认false:当服务调用时,采取拉取服务列表

ribbon:
http:
client:
enabled: true 

默认的请求发起是:HttpURLConnection,true:意思是:改成:HttpClinet

okhttp:
enabled: true

true:改成OKHttpClient

单个服务配置 org.springframework.cloud.netflix.ribbon.PropertiesFactory

public PropertiesFactory() {
    classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
    classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
    classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
    classToProperty.put(ServerList.class, "NIWSServerListClassName");
    classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}

相应配置如下

service-sms: 
    ribbon: 
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Java代码定义

只看标数字的步骤

修改扫描包配置,使不扫描RibbonConfiguration所在的包com.online.taxi.passenger.ribbonconfig。

@ComponentScan({"com.online.taxi.passenger.controller",
"com.online.taxi.passenger.dao",
"com.online.taxi.passenger.service",
"com.online.taxi.passenger.ribbonconfigscan"})

巧妙的办法,用注解,单独排除注解修饰的类

@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value=ExcudeRibbonConfig.class)})

定义com.online.taxi.passenger.ribbonconfig.RibbonConfiguration

package com.online.taxi.passenger.ribbonconfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
/**
* 该类不应该在主应用程序的扫描之下,需要修改启动类的扫描配置。否则将被所有的Ribbon client共享,
* 比如此例中:ribbonRule 对象 会共享。
* @author yueyi2019
*
*/
@Configuration
@ExcudeRibbonConfig
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule() {
        return new RandomRule();
    }
}

创建一个空类,配置 service-sms 对应的 ribbon规则

package com.online.taxi.passenger.ribbonconfigscan;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;

import com.online.taxi.passenger.ribbonconfig.RibbonConfiguration;

@Configuration
@RibbonClient(name = "service-sms",configuration = RibbonConfiguration.class)
public class TestConfiguration {

}

添加配置注解

@Configuration
@RibbonClient(name = "service-sms",configuration = RibbonConfiguration.class)

此方法只改变service-sms的 负载均衡策略。其他服务名没有影响。

给所有client设置随机策略

@RibbonClients(defaultConfiguration = RibbonConfiguration.class)

属性定义

针对服务定ribbon策略:

service-sms: 
    ribbon: 
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

给所有服务定ribbon策略:

ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

属性配置方式优先级高于Java代码

Ribbon脱离Eureka

service-sms:
    ribbon:
        eureka:
            # 将Eureka关闭,则Ribbon无法从Eureka中获取服务端列表信息
            enabled: false
            # listOfServers可以设置服务端列表
            listOfServers:localhost:8090,localhost:9092,localhost:9999

为service-sms设置 请求的网络地址列表

Ribbon可以和服务注册中心Eureka一起工作,从服务注册中心获取服务端的地址信息,也可以在配置文件中使用listOfServers字段来设置服务端地址

饥饿加载

ribbon:
    eager-load:
    enabled: true
    clients:
        - SERVICE-SMS

Spring Cloud默认是懒加载,指定名称的Ribbon Client第一次请求时,对应的上下文才会被加载,所以第一次访问慢 

改成以上饥饿加载后,将在启动时加载对应的程序上下文,从而提高首次请求的访问速度

在private List<DiscoveryEnabledServer> obtainServersViaDiscovery()首行,打断点。

饥饿进入此代码

2020-01-21 16:08:03.605 INFO [api-driver,,,] 13400 --- [ main] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client SERVICE-SMS initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SERVICE-SMS,current list of Servers=[30.136.133.11:8002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:30.136.133.11:8002; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 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:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@5af4328e
2020-01-21 16:08:04.574 INFO [api-driver,,,] 13400 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: SERVICE-SMS.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

PS:除了和RestTemplate进行配套使用之外,Ribbon还默认被集成到了OpenFeign中

当使用@FeignClient时,OpenFeign默认使用Ribbon来进行网络请求的负载均衡。

实践,在api-passenger的yml中,添加 service-sms ribbon NFLoad

Ribbon和@LoadBalanced

上一篇:Maya7.0 粒子系统创建粒子的方法介绍


下一篇:Photoshop打造绚丽多彩的弧形组成的小圆