Ribbon远程调用

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: Ribbon远程调用

文章中的样例会使用Nacos篇中的服务,读者可以看文章也可以直接结合gitee的代码观看

gitee: https://gitee.com/lzj960515/my-micro-service-demo.git

什么是Ribbon

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

  • 负载均衡
  • 容错
  • 支持多协议(HTTP、TCP、UDP)
  • 缓存和批处理

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

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

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

与之对应的便是服务端的负载均衡,常见如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 服务端口:8084
Exception 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-cloud/spring-cloud-netflix

注意:选择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
@Bean
public 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/remain
12:43:33.603 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
/integral/remain
12:43:33.773 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
12: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注解

@Configuration
public class RuleConfiguration {
    @Bean
    public IRule rule(){
        return new NacosRule();
    }
}

以上方式将在全局生效

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

配置方式

my-goods:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

注意,此方式必须带有服务名,单独使用ribbon.NFLoadBalancerRuleClassName是不会生效的

自定义负载均衡策略

除了使用框架中自带的负载均衡策略,我们还可以实现自己的策略,比如相同版本优先调用

@Slf4j
public 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: true
my-goods:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

关于Ribbon的配置信息可查看类:CommonClientConfigKey

小结

本篇介绍了Ribbon相关知识,什么是Ribbon,如何使用,以及如何集成到Spring Cloud中,最后还介绍了如何自定义负载均衡策略。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
1月前
|
JSON Dubbo Java
Feign远程调用
Feign远程调用
21 1
Feign远程调用
|
2月前
|
JSON Java 数据格式
【微服务】SpringCloud之Feign远程调用
本文介绍了使用Feign作为HTTP客户端替代RestTemplate进行远程调用的优势及具体使用方法。Feign通过声明式接口简化了HTTP请求的发送,提高了代码的可读性和维护性。文章详细描述了Feign的搭建步骤,包括引入依赖、添加注解、编写FeignClient接口和调用代码,并提供了自定义配置的示例,如修改日志级别等。
141 1
|
4月前
|
Java 数据库 微服务
使用OpenFeign进行服务调用
本文档介绍如何在微服务架构中使用Spring Cloud的OpenFeign进行服务间的远程调用。首先,需在项目中引入OpenFeign及其负载均衡器依赖。接着,通过`@EnableFeignClients`启用Feign客户端功能,并定义客户端接口以声明远程服务调用逻辑。为确保启动类能正确扫描到这些接口,需指定`basePackages`属性。最后,演示了如何在购物车服务中利用Feign客户端接口调用商品服务,以实现跨服务的数据整合和查询。Feign通过动态代理机制简化了HTTP请求的发起过程,使服务间交互更为直观和便捷。
125 0
|
7月前
|
负载均衡 Java 应用服务中间件
Ribbon、Feign和OpenFeign的区别来了
Ribbon、Feign和OpenFeign的区别来了
326 2
|
7月前
|
缓存 负载均衡 网络协议
基于Ribbon+RestTemplate的服务调用
基于Ribbon+RestTemplate的服务调用
62 1
|
7月前
|
存储 JSON 负载均衡
基于OpenFeign的服务调用
基于OpenFeign的服务调用
68 2
|
7月前
|
API 数据库管理
远程调用-其他服务
远程调用-其他服务
66 1
SpringCloud-Feign-文件服务调用
SpringCloud-Feign-文件服务调用
70 0
|
7月前
|
负载均衡 Java 网络架构
使用OpenFeign实现服务远程调用
当微服务架构日益普及,服务之间的远程调用变得至关重要。在这一背景下,OpenFeign作为一个强大的HTTP客户端框架简化了服务之间的远程通信。本文旨在介绍如何运用OpenFeign实现服务远程调用。
271 0
|
7月前
|
负载均衡 Java 应用服务中间件
springcloud3-服务到服务调用ribbon及openfeign
springcloud3-服务到服务调用ribbon及openfeign
89 0