摘要:原创地址:https://zijiancode.cn/archive... 欢送转载,转载时请保留摘要,谢谢!

文章中的样例会应用Nacos篇中的服务,读者能够看文章也能够间接联合gitee的代码观看

gitee: https://gitee.com/lzj960515/m...

什么是Ribbon

Ribbon是由Netflix公司开发的,一个客户端的IPC(过程间通信)库,它提供了以下个性

  • 负载平衡
  • 容错
  • 反对多协定(HTTP、TCP、UDP)
  • 缓存和批处理
github地址:https://github.com/Netflix/ri...

什么是客户端的负载平衡?

在过程间通信时,服务提供者(服务端)具备多个实例,由服务消费者(客户端)通过负载平衡算法自主抉择某个实例发动调用,就是客户端的负载平衡。

与之对应的便是服务端的负载平衡,常见如Nginx

Ribbon入门

1.引入Ribbon相干依赖

<dependencies>  <dependency>    <groupId>com.netflix.ribbon</groupId>    <artifactId>ribbon</artifactId>    <version>2.3.0</version>  </dependency>  <dependency>    <groupId>com.netflix.ribbon</groupId>    <artifactId>ribbon-core</artifactId>  </dependency>  <dependency>    <groupId>com.netflix.ribbon</groupId>    <artifactId>ribbon-loadbalancer</artifactId>  </dependency>  <dependency>    <groupId>com.google.guava</groupId>    <artifactId>guava</artifactId>  </dependency>  <dependency>    <groupId>io.reactivex</groupId>    <artifactId>rxjava</artifactId>  </dependency>  <dependency>    <groupId>io.reactivex</groupId>    <artifactId>rxnetty</artifactId>    <version>0.4.9</version>  </dependency>  <dependency>    <groupId>io.netty</groupId>    <artifactId>netty-all</artifactId>  </dependency>  <!-- 用于发送申请,当然也能够有JDK自带的 -->  <dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-http</artifactId>    <version>5.6.3</version>  </dependency>  <dependency>    <groupId>commons-logging</groupId>    <artifactId>commons-logging</artifactId>    <version>1.2</version>  </dependency></dependencies>

2. 编写代码

public class RibbonDemo {    public static void main(String[] args) throws Exception {        // 重试handler 第一个参数为调用同一个实例的次数,第二个为再次尝试调用其余实例的个数,1就是调用失败后,换一个实例再来一次,还是失败就返回失败。        final RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(0, 1, true);        List<Server> serverList = Lists.newArrayList(                new Server("127.0.0.1", 8083),                new Server("127.0.0.1", 8084));        // 创立一个负载均衡器,默认负载平衡算法为轮询        ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder()                .buildFixedServerListLoadBalancer(serverList);        for (int i = 0; i < 6; i++) {            String result = LoadBalancerCommand.<String>builder()                    .withLoadBalancer(loadBalancer)                    .withRetryHandler(retryHandler)                    .build()                    .submit(server -> {                        String url = "http://" + server.getHost() + ":" + server.getPort() + "/integral/remain";                        return Observable.just(HttpUtil.get(url));                    }).toBlocking().first();            System.out.println(result);        }    }}
这里我应用了Nacos篇的积分服务,小伙伴能够本人轻易起一个服务。

3. 测试

起两个实例,端口别离为8083和8084,测试后果:

您以后的积分为:31 服务端口:8084您以后的积分为:78 服务端口:8083您以后的积分为:37 服务端口:8084您以后的积分为:31 服务端口:8083您以后的积分为:59 服务端口:8084您以后的积分为:54 服务端口:8083

停掉8083,再次测试

您以后的积分为:66 服务端口:8084您以后的积分为:32 服务端口:8084您以后的积分为:52 服务端口:8084您以后的积分为:87 服务端口:8084您以后的积分为:66 服务端口:8084您以后的积分为:46 服务端口:8084
能够发现6次都是胜利,并且都调到了8084端口的服务

将RetryHandler改为以下配置后测试

RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(0, 0, true);
您以后的积分为:59 服务端口:8084Exception in thread "main" cn.hutool.core.io.IORuntimeException: ConnectException: Connection refused (Connection refused)
第一次调用了8084端口的服务,第二次调用8083端口的服务时抛出了异样

Spring Cloud 整合Ribbon

理解了Ribbon的根本应用后,接下来就是学习如何整合到微服务零碎中了,Netflix公司同样提供了一个spring-cloud-starter-netflix-ribbon供咱们疾速整合到Spring Cloud中。

github地址:https://github.com/spring-clo...

留神:抉择2.x的tag,3.x的分支已移除Ribbon

根本应用

1.在my-order服务中引入依赖

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
其实引入Nacos依赖时,Nacos曾经引入了该依赖,这里就做做样子嘻嘻

2.配置RestTemplate

@LoadBalanced@Beanpublic RestTemplate restTemplate(){  return new RestTemplate();}

3.发动调用

restTemplate.getForObject("http://my-goods/goods/get", String.class);
这就没了?嗯,这就没了~

RestTemplate Interceptor

看完根本应用后,小伙伴必定感觉很神奇,就加了个@LoadBalanced注解,就功败垂成了?

咱们当初就来简略唠一下他的原理吧。

RestTemplate中,实现了拦截器机制,举个栗子

public class RestTemplateDemo {    public static void main(String[] args) {        RestTemplate restTemplate = new RestTemplate();        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();        interceptors.add(new RestTemplateInterceptor());        restTemplate.setInterceptors(interceptors);        System.out.println(restTemplate.getForObject("http://127.0.0.1:8083/integral/remain", String.class));    }    static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {        @Override        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {            URI uri = request.getURI();            System.out.println(uri.getRawPath());            return execution.execute(request, body);        }    }}

发动调用前增加了自定义拦截器RestTemplateInterceptor,调用时则将进入到intercept办法

@LoadBalanced注解便是在服务启动时往RestTemplate中增加了一个拦截器LoadBalancerInterceptor

测试成果:

12:43:33.585 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://127.0.0.1:8083/integral/remain12:43:33.603 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]/integral/remain12:43:33.773 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK12:43:33.775 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"您以后的积分为:83 服务端口:8083

当初咱们来模仿一下Ribbon中将server替换成实在的ip地址

public class RestTemplateDemo {    public static void main(String[] args) {        RestTemplate restTemplate = new RestTemplate();        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();        interceptors.add(new RestTemplateInterceptor());        restTemplate.setInterceptors(interceptors);        System.out.println(restTemplate.getForObject("http://my-goods/goods/get", String.class));    }    static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {        @SneakyThrows        @Override        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {            return execution.execute(new MyRequestWrapper(request), body);        }    }    static class MyRequestWrapper extends HttpRequestWrapper {        /**         * 模仿注册核心存储服务信息         */        private final Map<String, String> serverMap = Maps.newHashMap("my-goods", "127.0.0.1:8081");        public MyRequestWrapper(HttpRequest request) {            super(request);        }        @SneakyThrows        @Override        public URI getURI() {            URI uri = super.getRequest().getURI();            String server = uri.getHost();            // 模仿从注册核心取出实在ip            String host = serverMap.get(server);            // 替换URI            return new URI(uri.getScheme() + "://" + host + uri.getRawPath());        }    }}

这里自定义了一个MyRequestWrapper,并且重写了getURI办法,由该办法进行uri替换,模仿实现了Ribbon的将服务名替换实在ip的过程。

从serverMap里get服务信息的逻辑能够再进一步,封装成取出多个服务信息,应用负载平衡策略取出其中一个。

负载平衡策略

Ribbon中实现了许多的负载平衡策略,它们都实现于一个IRule接口

RetryRule: 重试策略,外部蕴含一个subRule,应用subRule(默认为轮询)抉择服务,当服务不可用时,在配置的工夫内始终重试,间接找到可用的服务或者超时。

RoundRobinRule: 轮询策略。

WeightedResponseTimeRule: 依据响应工夫加权,响应工夫越长,权重越小,被选中的可能性越低。

ZoneAvoidanceRule: 默认的负载平衡策略,依据服务的区域和可用性失去一个服务列表,而后轮询。没有区域的话等同于轮询。

AvailabilityFilteringRule: 应用轮询抉择服务,对抉择的服务进行状态判断,过滤掉始终连贯失败的服务,直到找到失常可用的服务。

BestAvaliableRule: 抉择并发申请最小的服务。

RandomRule: 随机策略。

NacosRule: Nacos提供的策略,同集群优先策略。

应用形式

应用@Bean注解

@Configurationpublic class RuleConfiguration {    @Bean    public IRule rule(){        return new NacosRule();    }}
以上形式将在全局失效

如果想要为每个服务设置不同的负载策略可按如下配置:

配置形式

my-goods:    ribbon:      NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
留神,此形式必须带有服务名,独自应用ribbon.NFLoadBalancerRuleClassName是不会失效的

自定义负载平衡策略

除了应用框架中自带的负载平衡策略,咱们还能够实现本人的策略,比方雷同版本优先调用

@Slf4jpublic class VersionRule extends AbstractLoadBalancerRule {    @Autowired    private NacosDiscoveryProperties nacosDiscoveryProperties;    @Autowired    private NacosServiceManager nacosServiceManager;    @Override    public Server choose(Object key) {        try {            // 失去元数据信息            Map<String, String> metadata = this.nacosDiscoveryProperties.getMetadata();            // 取出版本信息            String version = metadata.get("version");            // 获取调用的服务列表            String group = this.nacosDiscoveryProperties.getGroup();            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();            String name = loadBalancer.getName();            NamingService namingService = nacosServiceManager                    .getNamingService(nacosDiscoveryProperties.getNacosProperties());            List<Instance> instances = namingService.selectInstances(name, group, true);            if (CollectionUtils.isEmpty(instances)) {                log.warn("no instance in service {}", name);                return null;            }            List<Instance> instancesToChoose = instances;            if (StringUtils.isNotBlank(version)) {                // 筛选出雷同版本的服务                List<Instance> sameClusterInstances = instances.stream()                        .filter(instance -> Objects.equals(version,                                instance.getMetadata().get("version")))                        .collect(Collectors.toList());                if (!CollectionUtils.isEmpty(sameClusterInstances)) {                    instancesToChoose = sameClusterInstances;                }                else {                    log.warn(                            "A cross-cluster call occurs,name = {}, version = {}, instance = {}",                            name, version, instances);                }            }            Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);            return new NacosServer(instance);        }        catch (Exception e) {            log.warn("NacosRule error", e);            return null;        }    }    @Override    public void initWithNiwsConfig(IClientConfig iClientConfig) {    }}

减少version配置:

spring:  application:    name: my-order  main:    allow-bean-definition-overriding: true  cloud:    nacos:      discovery:        server-addr: 192.168.1.11:8850        namespace: public        username: nacos        password: nacos        metadata:          version: 1.0
metadata是个map构造,键值对可自定义

服务容错

有时候申请一个服务呈现网络抖动或者其余问题时,导致申请失败,此时咱们心愿能够重试或者换一个服务发动调用,就能够加上如下配置

ribbon:  # 同服务重试次数  MaxAutoRetries: 1  # 再次尝试调用其余实例的个数  MaxAutoRetriesNextServer: 1  # 所以操作都发动重试,默认只重试get申请  OkToRetryOnAllOperations: true  # 服务连贯超时工夫  ConnectTimeout: 1000  # 服务响应超时工夫  ReadTimeout: 2000  # 应用restclient 否则以上配置有效  restclient:    enabled: true

残缺配置

ribbon:  # 饥饿加载,启动时就创立客户端  eager-load:    enabled: true    clients:      - my-goods  # 同服务重试次数  MaxAutoRetries: 1  # 再次尝试调用其余实例的个数  MaxAutoRetriesNextServer: 1  # 所以操作都发动重试,默认只重试get申请  OkToRetryOnAllOperations: false  # 服务连贯超时工夫  ConnectTimeout: 1000  # 服务响应超时工夫  ReadTimeout: 2000  restclient:    enabled: truemy-goods:  ribbon:    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
对于Ribbon的配置信息可查看类:CommonClientConfigKey

小结

本篇介绍了Ribbon相干常识,什么是Ribbon,如何应用,以及如何集成到Spring Cloud中,最初还介绍了如何自定义负载平衡策略。

看完之后想必有所播种吧~ 想要理解更多精彩内容,欢送关注公众号:程序员阿鉴,阿鉴在公众号欢送你的到来~