SpringCloud服务间调用

本篇简介

在上一篇咱们介绍了SpringCloud中的注册中心组件Eureka。Eureka的做用是作服务注册与发现的,目的是让不一样的服务与服务之间均可以经过注册中心进行间接关联,而且能够经过注册中心有效的管理不一样服务与服务的运行状态。但在微服务的架构中,服务与服务只知道对方的服务地址是没有用的,它们的本质仍是须要彼此进行通讯的,这也是微服务最核心的功能之一。java


既然提到了服务与服务之间的通讯,那咱们天然而然会想到大名鼎鼎的HttpClient。由于在其它的项目架构中咱们基本均可以经过它来进行不一样服务与服务之间的调用。在SpringCloud中咱们依然可使用HttpClient进行服务与服务调用,只不过若是采用HttpClient调用的话,会有一些弊端。例如: 若是同一个服务有多个负载的话,采用HttpClient调用时,没有办法处理负载均衡的问题。还有另外一个问题就是HttpClient只是提供了核心调用的方法并无对调用进行封装,因此在使用上不太方便,须要本身对HttpClient进行简单的封装。git


调用方式

在SpringCloud中为了解决服务与服务调用的问题,因而提供了两种方式来进行调用。也就是RestTemplate和Feign。虽然从名字上看这两种调用的方式不一样,但在底层仍是和HttpClient同样,采用http的方式进行调用的。只不过是对HttpClient进行的封装。下面咱们来详细的介绍一下这两种方式的区别,咱们首先看一下RestTemplate的方式。github


RestTemplate方式调用

  • RestTemplate

为了方便掩饰咱们服务间的调用,因此咱们须要建立三个项目。它们分别为eureka(注册中心)、server(服务提供方)、client(服务调用方)。由于上一篇中咱们已经介绍了eureka的相关内容。因此在这一篇中咱们将不在作过多的介绍了。下面咱们看一下server端的配置。由于实际上Server端和Client端是相互的。不必定client端必定要调用server端。server端同样能够调用client端。但对于eureka来讲,它们都是client端。由于上一篇中咱们已经介绍了eureka是分为server端和client端的,而且已经介绍client端相关内容。因此咱们下面咱们直接看一下server端的配置内容:web

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-server
server:
  port: 8082

为了掩饰咱们服务间的调用,因此咱们须要建立一个Controller,并编写一个简单的接口来供client调用。下面为server的源码。spring

package com.jilinwula.feign.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/server")
public class Controller {

    @GetMapping("/get")
    public Object get() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "0");
        map.put("msg", "success");
        map.put("data", "吉林乌拉");
        return map;
    }
}

下面咱们访问一下这个接口看看,是否能正确返回数据。(备注:注意别忘记了在启动类上添加@EnableEurekaClient注解。)下面咱们仍是使用.http文件的方式发起接口请求。json

GET http://127.0.0.1:8082/server/get

返回结果:架构

GET http://127.0.0.1:8082/server/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Mar 2019 08:20:33 GMT

{
  "msg": "success",
  "code": "0",
  "data": "吉林乌拉"
}

Response code: 200; Time: 65ms; Content length: 42 bytes

咱们看已经成功的返回了接口的数据了。下面咱们看一下eureka。看看是否成功的检测到了server端的服务。下面为eureka管理界面地址:app

http://127.0.0.1:8761

  title

咱们看eureka已经成功的检测到了server端注册成功了。下面咱们看一下client端的代码,咱们仍是向server端同样,建立一个Controller,并编写一个接口。下面为具体配置及代码。负载均衡

application.yml:dom

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-client
server:
  port: 8081

  Controller:

package com.jilinwula.feign.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/client")
public class Controller {

    @GetMapping("/get")
    public Object get() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "0");
        map.put("msg", "success");
        map.put("data", "吉林乌拉");
        return map;
    }
}

下面为访问的接口地址:

GET http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Mar 2019 08:56:42 GMT

{
  "msg": "success",
  "code": "0",
  "data": "吉林乌拉"
}

Response code: 200; Time: 273ms; Content length: 42 bytes

如今咱们在访问一下Eureka地址看一下Client服务注册的是否成功。

http://127.0.0.1:8761

  title

RestTemplate实例化

咱们发现server和client端都已经成功的在注册中心注册成功了。这也就是咱们接下来要介绍的服务间调用的前提条件。在开发Spring项目时咱们知道若是咱们想要使有哪一个类或者哪一个对象,那就须要在xml中或者用注解的方式实例化对象。因此既然咱们打算使用RestTemplate类进行调用,那咱们必需要先实例化RestTemplate类。下面咱们就看一下怎么在实例化RestTemplate类。由于不论采用的是RestTemplate方式调用仍是采用Feign方式,均是在服务的client端进行开发的,在服务的server是无需作任何更改的。因此下面咱们看一下client端的改动。下面为项目源码:

package com.jilinwula.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class JilinwulaSpringcloudFeignClientApplication {

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

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

}

RestTemplate调用方式一

为了掩饰方便咱们直接在启动类上添加了一个@Bean注解。而后手动实例化了一个对象,而且要特别注意,在使用RestTemplate时,必需要先实例化,不然会抛出空指针异常。下面咱们演示一下怎么使用RestTemplate来调用server端的接口。下面为Controller中的代码的改动。

package com.jilinwula.feign.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private RestTemplate template;

    @GetMapping("/get")
    public Object get() {
        String result = template.getForObject("http://127.0.0.1:8082/server/get", String.class);
        return result;
    }
}

上面的代码比较简单,咱们就不详细的介绍了,主要是RestTemplate中提供了getForObject方法(实际上RestTemplate提供了不少种调用的方法,主要分为Get或者Post),能够指定要调用接口的地址,指定返回的值的类型。而后就会直接返回要调用接口的结果。下面咱们测试一下,仍是调用client接口,看看可否正确的返回server端的数据。

http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Fri, 15 Mar 2019 09:42:02 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 362ms; Content length: 42 bytes

RestTemplate调用方式二

咱们看结果,已经成功的返回的server端的数据了,虽然返回的数据没有格式化,但返回的结果数据确实是server端的数据。这也就是RestTemplate的简单使用。但上述的代码是有弊端的,由于咱们直接将调用的server端的接口地址直接写死了,这样当服务接口变动时,是须要更改客户端代码的,这显示是不合理的。那怎么办呢?这时就知道注册中心的好处了。由于注册中心知道全部服务的地址,这样咱们经过注册中心就能够知道server端的接口地址,这样就避免了server端服务更改时,要同步更改client代码了。下面咱们在优化一下代码,看看怎么经过注册中心来获取server端的地址。

package com.jilinwula.feign.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private RestTemplate template;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/get")
    public Object get() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("jilinwula-springcloud-feign-server");
        String url = String.format("http://%s:%s/server/get", serviceInstance.getHost(), serviceInstance.getPort());
        String result = template.getForObject(url, String.class);
        return result;
    }
}

在SpringClourd中提供了LoadBalancerClient接口。经过这个接口咱们能够经过用户中心的Application的名字来获取该服务的地址和端口。也就是下图中红色标红的名字(注意名字大小写)。

  title

经过这些咱们就能够获取到完整的服务接口地址了,这样就能够直接经过RestTemplate进行接口调用了。下面咱们在看一下调用的结果。接口地址:

GET http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 09:08:32 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 53ms; Content length: 42 bytes

RestTemplate调用方式三

这样咱们就解决了第一次服务接口地址写死的问题了。但上述的接口还有一个弊端就是咱们每次调用服务时都要先经过Application的名字来获取ServiceInstance对象,而后才能够发起接口调用。实际上在SpringCloud中为咱们提供了@LoadBalanced注解,只要将该注解添加到RestTemplate中的获取的地方就能够了。下面为具体修改:

启动类:

package com.jilinwula.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class JilinwulaSpringcloudFeignClientApplication {

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

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

}

咱们在RestTemplate实例化的地方添加了@LoadBalanced注解,这样在咱们使用RestTemplate时就该注解就会自动将调用接口的地址替换成真正的服务地址。下面咱们看一下Controller中的改动:

package com.jilinwula.feign.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private RestTemplate template;

    @GetMapping("/get")
    public Object get() {
        String url = String.format("http://%s/server/get", "jilinwula-springcloud-feign-server");
        String result = template.getForObject(url, String.class);
        return result;
    }
}

代码和第一次的代码基本同样,惟一的区别就是获取服务地址和端口的地方替换成了注册中心中的Application的名字,而且咱们的RestTemplate在使用上和第一次没有任何区别,只是在url中不一样。下面咱们看一下返回的结果。

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 09:55:46 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 635ms; Content length: 42 bytes

默认负载均衡策略

上述内容就是使用RestTemplate来进行服务间调用的方式。而且采用这样的方式能够很方便的解决负载均衡的问题。由于@LoadBalanced注解会自动采用默信的负载策略。下面咱们看验证一下SpringCloud默认的负载策略是什么。为了掩饰负载策略,因此咱们在新增一个server服务,而且为了掩饰这两个server返回结果的不一样,咱们故意让接口返回的数据不一致,来方便咱们测试。下面为新增的server服务端的配置信息及controller源码。

application.yml:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-server
server:
  port: 8083

  Controller:

package com.jilinwula.feign.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/server")
public class Controller {

    @GetMapping("/get")
    public Object get() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "0");
        map.put("msg", "success");
        map.put("data", "jilinwula");
        return map;
    }
}

调用如下接口:

GET http://127.0.0.1:8083/server/get

返回结果:

GET http://127.0.0.1:8083/server/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 16 Mar 2019 10:49:07 GMT

{
  "msg": "success",
  "code": "0",
  "data": "jilinwula"
}

Response code: 200; Time: 100ms; Content length: 47 bytes

如今咱们访问一下注册中心看一下如今注册中心的变化。注册中心地址:

http://127.0.0.1:8761

  title

咱们看上图注册中心已经显示Application名字为JILINWULA-SPRINGCLOUD-FEIGN-SERVER的有两个服务已经注册成功了。下面咱们直接调用client中的接口,看一下client默认会返回哪一个server端的信息。client接口地址:

GET http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 47
Date: Sat, 16 Mar 2019 10:58:39 GMT

{"msg":"success","code":"0","data":"jilinwula"}

Response code: 200; Time: 24ms; Content length: 47 bytes

看上面返回的结果是server2的接口数据。咱们在请求一下接口在看一下返回的结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 11:01:01 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 15ms; Content length: 42 bytes

更改默认负载均衡策略一

咱们看这回返回的接口数据就是第一个server端的信息了。而且咱们能够频繁的调用client中的接口,并观察发现它们会交替返回的。因此咱们基本能够肯定SpringCloud默认的负载策略为轮询方式。也就是会依次调用。在SpringCloud中提供了不少种负载策略。比较常见的为:随机、轮询、哈希、权重等。下面咱们介绍一下怎么修改默认的负载策略。SpringCloud底层采用的是Ribbon来实现的负载均衡。Ribbon是一个负载均衡器,Ribbon的核心组件为IRule,它也就是全部负载策略的父类。下面为IRule接口的源码:

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

该类只提供了3个方法,它们的做用分别是选择一个服务名字、设置ILoadBalancer和返回ILoadBalancer。下面咱们看一下IRule接口的常见策略子类。常见的有RandomRule、RoundRobinRule、WeightedResponseTimeRule等。分别对应着随机、轮询、和权重。下面咱们看一下怎么更改默认的策略方式。更改默认策略也是在client端中操做的,因此咱们看一下client端的代码更改:

package com.jilinwula.feign;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class JilinwulaSpringcloudFeignClientApplication {

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

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

    @Bean
    public IRule initIRule() {
        return new RandomRule();
    }

}

咱们在启动类上新实例化了一个IRule对象,而且指定该对象实例化的子类为RandomRule,也就是随机的方式。因此当咱们Client端启动服务调用服务时,就会采用随机的方式进行调用,由于咱们已经将IRule对象默认的实例化方式更改了。下面咱们测试一下,继续访问Client端接口:

GET http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 11:36:01 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 15ms; Content length: 42 bytes

更改默认负载均衡策略二

在这里咱们就不依依演示了,但若是咱们屡次调用接口就会发现,Client接口返回的结果不在是轮询的方式了,而是变成了随机了,这就说明咱们已经成功的将SpringCloud默认的负载策略更改了。下面咱们换一种方式来更改默认的负载策略。这种方式和上面的有所不一样,而是在配置文件中配置的,下面为具体的配置。(备注:为了避免影响测试效果,咱们须要将刚刚在启动类中的实例化的IRule注释掉)

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: jilinwula-springcloud-feign-client
server:
  port: 8081
jilinwula-springcloud-feign-server:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

咱们在配置文件中指定了注册中心中的server端的Application名字,而后指定了默认的负载策略类。下面咱们测试一下。访问如下接口:

GET http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 11:54:42 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 13ms; Content length: 42 bytes

Feign方式调用

咱们在实际的开发中,可使用上述两种方式来更改SpringCloud中默认的负载策略。下面咱们看一下SpringCloud中另外一种服务间调用方式也就是Feign方式。使用Feign方式和RestTemplate不一样,咱们须要先添加Feign的依赖,具体依赖以下(备注:该依赖一样是在client端添加的):

pom.xml:

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

其次咱们还须要在启动类中添加@EnableFeignClients注解。具体代码以下:

package com.jilinwula.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class JilinwulaSpringcloudFeignClientApplication {

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

}

接下来咱们须要在Client端建立一个新的接口并定义Client端须要调用的服务方法。具体代码以下:

package com.jilinwula.feign.server;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "jilinwula-springcloud-feign-server")
public interface ServerApi {

    @GetMapping("/server/get")
    String get();
}

上述接口基本上和server端的Controller一致,惟一的不一样就是咱们指定了@FeignClient注解,该注解的须要指定一个名字,也就是注册中心中Applicaiton的名字,也就是要调用的服务名字。下面咱们看一下Controller中的代码更改:

package com.jilinwula.feign.controller;

import com.jilinwula.feign.server.ServerApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/client")
public class Controller {

    @Autowired
    private ServerApi serverApi;

    @GetMapping("/get")
    public Object get() {
        String result = serverApi.get();
        return result;
    }
}

咱们在Controller中直接使用了咱们自定义的接口,并直接调用咱们接口中定义的方法,下面咱们调用一下Client接口看看这样的方式是否能够调用成功。接口地址:

GET http://127.0.0.1:8081/client/get

返回结果:

GET http://127.0.0.1:8081/client/get

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 50
Date: Sat, 16 Mar 2019 12:54:50 GMT

{"msg":"success","code":"0","data":"吉林乌拉"}

Response code: 200; Time: 14ms; Content length: 42 bytes

咱们看这样的方式也是能够成功的调用server端的接口的,只不过这样的方式可能会让觉的不太方便,由于这样的方式是须要Client端定义和Server端同样的接口的。


上述内容就是本篇的所有内容,在实际的项目开发中,这两种方式都可实现服务与服务间的调用,而且这两种方式都有彼此的弊端,因此并无特别推荐的方式。在下一篇中,咱们将介绍配置中心相关内容,谢谢。


项目源码

https://github.com/jilinwula/jilinwula-springcloud-feign


原文连接

http://jilinwula.com/article/...

相关文章
相关标签/搜索