Spring Cloud 学习笔记 - No.2 服务消费 Ribbon & Feign

请先阅读之前的内容:

上一步我们创建了服务提供者 eureka-client 来提供加法服务,并且启动了两个实例 Zones。

该服务对应了两个实例 Zones

下一步,我们去消费服务提供者的接口。在这里我们建立一个项目:

  • eureka-consumer:服务消费者

在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于 HTTP Restful 的。Spring Cloud 有两种服务调用方式,一种是 Ribbon + RestTemplate,另一种是 Feign。

eureka-consumer 服务消费者

可以通过如下的 Spring Assistant 插件来创建项目,添加 Eureka DiscoveryWebActuator 作为依赖。

eureka-consumer 的创建

eureka-consumer 的创建

eureka-consumer 的创建

pom.xml 中可以看出,导入了如下的依赖:

  • 2.0.3.RELEASE 版本的 Spring Boot
  • Finchley.RELEASE 版本的 Spring Cloud
  • spring-cloud-starter-netflix-eureka-client
  • spring-boot-starter-web
  • spring-boot-starter-actuator
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

随后在启动程序中通过 @EnableDiscoveryClient 来激活 Eureka 中的 DiscoveryClient 实现:

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class, args);
    }
}

随后在 application.properties:指定 Eureka 注册中心的地址。消费者自己的端口是 3001。

spring.application.name=eureka-consumer
server.port=3001
eureka.client.serviceUrl.defaultZone=http://localhost:1234/eureka/

随后,我们创建一个 ConsumerController 类来消费服务:

@RestController
public class ConsumerController {

    private final static Logger logger = LoggerFactory.getLogger(ConsumerController.class);

    @Autowired
    LoadBalancerClient loadBalancerClient;

    @Autowired
    DiscoveryClient discoveryClient;

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/consumer")
    public Integer consumer() {

        // 获取到的所有服务清单
        String services = "Services: " + discoveryClient.getServices();
        logger.info(services);

        // 调用加法服务
        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/add";

        UriComponentsBuilder builder = UriComponentsBuilder
                .fromUriString(url)
                // Add query parameter
                .queryParam("operand1", 1)
                .queryParam("operand2", 2);

        logger.info(builder.toUriString());

        return restTemplate.getForObject(builder.toUriString(), Integer.class);
    }
}

org.springframework.cloud.client.discovery.DiscoveryClient 接口是 Spring Cloud 对服务治理做的一层抽象,所以可以屏蔽 Eureka 和 Consul 服务治理的实现细节,我们的程序不需要做任何改变,只需要引入不同的服务治理依赖,并配置相关的配置属性就能轻松的将微服务纳入 Spring Cloud 的各个服务治理框架中。

可以看到这里,我们注入了 LoadBalancerClientRestTemplate ,先通过 loadBalancerClientchoose 函数来 负载均衡 的选出一个 eureka-client 的服务实例,这个服务实例的基本信息存储在 ServiceInstance 中,然后通过这些对象中的信息拼接出访问 /add 接口的详细地址,最后再利用 RestTemplate 对象实现对服务提供者接口的调用。

最后通过 mvn spring-boot:run 命令启动项目,启动完成后,可以通过 http://127.0.0.1:3001/consumer 消费服务。

消费服务

刷新页面多次,从日志中可以看出,依次调用 2001 和 2002 两个端口对应的服务提供者,这是通过 LoadBalancerClient 实现的负载均衡。

通过 LoadBalancerClient 实现的负载均衡

通过 Ribbon 来消费服务

Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。它是一个基于 HTTP 和 TCP 的客户端负载均衡器。它可以通过在客户端中配置 ribbonServerList 来设置服务端列表去轮询访问以达到均衡负载的作用

  • 当 Ribbon 与 Eureka 联合使用时,ribbonServerList 会被 DiscoveryEnabledNIWSServerList 重写,扩展成从 Eureka 服务注册中心中获取服务实例列表。同时它也会用 NIWSDiscoveryPing 来取代 IPing,它将职责委托给 Eureka 来确定服务端是否已经启动。
  • 而当 Ribbon 与 Consul 联合使用时,ribbonServerList 会被 ConsulServerList 来扩展成从 Consul 获取服务实例列表。同时由 ConsulPing 来作为 IPing 接口的实现。

首先在 pom.xml 中添加 Ribbon 依赖:

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

随后在启动程序中通过 @LoadBalanced 来注解 RestTemplate

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

由于 RestTemplate@LoadBalanced 修饰,所以它具备客户端负载均衡的能力,当请求真正发起的时候,URL 中的服务名会根据负载均衡策略从服务清单中挑选出一个实例来进行访问。

随后,我们修改 ConsumerController 类来消费服务:

@GetMapping("/consumer")
public Integer consumer() {

    // 获取到的所有服务清单
    String services = "Services: " + discoveryClient.getServices();
    logger.info(services);

    // 调用加法服务
    String url = "http://eureka-client/add";

    UriComponentsBuilder builder = UriComponentsBuilder
            .fromUriString(url)
            // Add query parameter
            .queryParam("operand1", 1)
            .queryParam("operand2", 2);

    logger.info(builder.toUriString());

    return restTemplate.getForObject(builder.toUriString(), Integer.class);
}

这里请求的 host 位置并没有使用一个具体的 IP 地址和端口的形式,而是采用了服务名的方式组成。
Spring Cloud Ribbon 有一个拦截器,它能够在这里进行实际调用的时候,自动的去选取服务实例,并将实际要请求的 IP 地址和端口替换这里的服务名,从而完成服务接口的调用。

通过 Feign 来消费服务

Spring Cloud Feign 是一套基于 Netflix Feign 实现的声明式服务调用客户端。它使得编写 Web 服务客户端变得更加简单。
我们只需要通过创建接口并用注解来配置它既可完成对 Web 服务接口的绑定。它具备可插拔的注解支持,包括 Feign 注解、JAX-RS 注解。它也支持可插拔的编码器和解码器。
Spring Cloud Feign 还扩展了对 Spring MVC 注解的支持,同时还整合了 Ribbon 和 Eureka 来提供均衡负载的 HTTP 客户端实现。

首先在 pom.xml 中添加 Ribbon 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

随后在启动程序中通过 @EnableFeignClients 注解开启扫描 Spring Cloud Feign 客户端的功能。

创建一个 Feign 的客户端接口定义。使用 @FeignClient 注解来指定这个接口所要调用的服务名称,接口中定义的各个函数使用 Spring MVC 的注解就可以来绑定服务提供方的 REST 接口:

@FeignClient("eureka-client")
public interface CalculatorClient {
    @GetMapping("/add")
    Integer add(@RequestParam Integer operand1, @RequestParam Integer operand2);
}

随后,我们修改 ConsumerController 类来消费服务:

@GetMapping("/consumer")
public Integer consumer() {

    // 获取到的所有服务清单
    String services = "Services: " + discoveryClient.getServices();
    logger.info(services);

    // 调用加法服务
    return calculatorClient.add(1, 2);
}

Ribbon的饥饿加载(eager-load)模式

引用:http://blog.didispace.com/spring-cloud-tips-ribbon-eager/

有时候会发现这样一个问题:我们服务消费方调用服务提供方接口的时候,第一次请求经常会超时,而之后的调用就没有问题了。

造成第一次服务调用出现失败的原因主要是 Ribbon 进行客户端负载均衡的 Client 并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的 Client,所以第一次调用的耗时不仅仅包含发送 HTTP 请求的时间,还包含了创建 RibbonClient 的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的显现。
而 Feign 的实现基于 Ribbon,所以它也有一样的问题。

我们可以通过下面的配置来开启 Ribbon 的饥饿加载模式,这样 RibbonClient 会提前创建,而不是在第一次调用的时候创建。

ribbon.eager-load.enabled=true
ribbon.eager-load.clients=eureka-client

引用:
程序猿DD Spring Cloud基础教程
Spring Cloud构建微服务架构:服务消费(基础)【Dalston版】
Spring Cloud构建微服务架构:服务消费(Ribbon)【Dalston版】
Spring Cloud构建微服务架构:服务消费(Feign)【Dalston版】】
Spring Cloud Dalston中文文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容