在上一篇文章,讲了服务的注册和发现。在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。在这一篇文章首先讲解下基于ribbon+rest。
一、前期准备
RestTemplate是一个Http客户端,类似于HTTPClient,org但比HTTPClient更简单。我们通过RestTemplate来简单演示一下服务之间的调用,我们使用两个服务来做演示。一个商品服务,一个订单服务。首先创建一个商品服务工程:(这里我们基于我们上一篇博客的项目来做demo),如果大家对于eureka还有疑问的可以看一下我上篇的博客:https://blog.csdn.net/jokeMqc/article/details/93593928
创建完成后,我们的pom文件代码为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jokermqc</groupId>
<artifactId>feign-product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>feign-product</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
完成之后我们需要修改我们的配置文件,配置项目的端口号和eureka的服务注册中心的地址:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8763
spring:
application:
name: feign-product
之后需要在我们的启动类加上对应的注解@EnableEureakClient
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class FeignProductApplication {
public static void main(String[] args) {
SpringApplication.run(FeignProductApplication.class, args);
}
}
我们新建一个controller类来模拟提供查询商品的接口提供给订单服务调用。
package com.jokermqc.feignproduct.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.ArrayList;
import java.util.List;
/**
* @Auther: maoqichuan
* @Date: 2019-07-11 12:01
* @Description: 商品接口
*/
@RestController
@RequestMapping("product")
public class ProductController {
@GetMapping("/list")
public List<String> getProductList(){
List<String> result = new ArrayList<>(3);
result.add("苹果");
result.add("西瓜");
result.add("冰淇淋");
return result;
}
}
我们启动项目就可以看到商品服务已经注册到eureka上了。
按照同样的方式我们在新建订单服务feign-order,订单服务与商品服务的配置是一样的。新建一个ClientController类,我们来看看RestTemplate的第一种使用方式,代码如下:
package com.joklermqc.feignorder.controller;
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;
import java.util.List;
/**
* @Auther: maoqichuan
* @Date: 2019-07-11 14:24
* @Description: feign调用
*/
@RestController
@RequestMapping("/order")
public class ClientController {
/**
*
* 功能描述: .第一种方式,直接使用。缺点:需要指定url地址,不灵活,也无法适应多个地址
* @auther: maoqichuan
* @date: 2019-07-11 14:26
*/
@GetMapping("/list")
public List listInfo(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject("http://localhost:8080/product/list", List.class);
}
}
写完后启动项目,可以看到order服务也注册到eureka上:
我们访问一下接口:http://localhost:8764/order/list可以看到已经访问得到了商品服务的接口并且放回得到了数据。
这种方式的弊端大家一看就可以看出来,就是我们需要拼接url(参数传递的时候),并且把url硬编码了,这样子后期维护是一件非常痛苦的事情,我们来看一下第二种方式:
/**
*
* 功能描述: 第二种方式,借助LoadBalancerClient获取服务实例,缺点:需要拼接url依旧不灵活
* @auther: maoqichuan
* @date: 2019-07-11 14:44
*/
@GetMapping("/list2")
public List getListInfo(){
RestTemplate restTemplate = new RestTemplate();
//loadBalancerClient参数传的是服务注册的id
ServiceInstance instance = balancerClient.choose("FEIGN-PRODUCT");
String url = String.format("http://%s:%s", instance.getHost(), instance.getPort() + "/product/list");
return restTemplate.getForObject(url,List.class);
}
我们访问一下:http://localhost:8764/order/list2
可以看到我们也可以获取得到商品服务的信息,不过我们的弊端还没有解决掉,就是还是需要拼接url,这时候我们来看下restTemplate的第三种使用方式。这一种方式我们需要一个配置类。然后再我们的controller注入我们配置的Bean就可以使用了。
package com.joklermqc.feignorder.controller;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.util.List;
/**
* @Auther: maoqichuan
* @Date: 2019-07-11 14:24
* @Description: feign调用
*/
@RestController
@RequestMapping("/order")
public class ClientController {
@Autowired
private LoadBalancerClient balancerClient;
@Autowired
private RestTemplate restTemplate;
/**
*
* 功能描述: .第三种方式,与第二种方式一致
* @auther: maoqichuan
* @date: 2019-07-11 14:26
*/
@GetMapping("/list")
public List listInfo(){
// RestTemplate restTemplate = new RestTemplate();
//
// return restTemplate.getForObject("http://localhost:8763/product/list", List.class);
return restTemplate.getForObject("http://FEIGN-PRODUCT/product/list", List.class);
}
// /**
// *
// * 功能描述: 第二种方式,借助LoadBalancerClient获取服务实例,缺点:需要拼接url依旧不灵活
// * @auther: maoqichuan
// * @date: 2019-07-11 14:44
// */
// @GetMapping("/list2")
// public List getListInfo(){
// RestTemplate restTemplate = new RestTemplate();
//
// //loadBalancerClient参数传的是服务注册的id
// ServiceInstance instance = balancerClient.choose("FEIGN-PRODUCT");
// String url = String.format("http://%s:%s", instance.getHost(), instance.getPort() + "/product/list");
//
// return restTemplate.getForObject(url,List.class);
// }
}
然后访问对应的接口:http://localhost:8764/order/list可以看到也是可以正常的访问到数据,不过这种方式与第二种方式是一致的。
二、ribbon简介
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。Feign默认集成了ribbon。而我们的eureka是一个客户端发现机制,所谓的客户端发现机制其实总的来说就是在客户端做负载均衡。就是所谓的软负载。如果我们的商品服务部署在多个节点上的话,当使用Feign进行服务调用的时候,默认会使用Ribbon来做负载均衡。当然使用RestTemplate的时候也是可以结合Ribbon做负载均衡的,比如说我们上面演示到的订单服务获取商品服务的商品信息的接口的第二、三种使用RestTemplate的方式就是结合了Ribbon。
我们重新新建一个spring-boot工程,取名为:service-ribbon; 在它的pom.xml文件分别引入起步依赖spring-cloud-starter-eureka、spring-cloud-starter-ribbon、spring-boot-starter-web,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jokermqc</groupId>
<artifactId>service-ribbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-ribbon</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在工程的配置文件指定服务的注册中心地址为http://localhost:8761/eureka/,程序名称为 service-ribbon,程序端口为8765。配置文件application.yml如下:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8765
spring:
application:
name: service-ribbon
在工程的启动类中,通过@EnableDiscoveryClient向服务中心注册;并且向程序的ioc注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。
package com;
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 ServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRibbonApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
有兴趣的你们可以,在给商品服务进行小集群部署,然后再service-ribbon服务中调用商品服务的接口,在浏览器上多次访问,就可以看到ribbon的轮询访问了。此时项目的架构为:
- 一个服务注册中心,eureka server,端口为8761。
- feign-product工程跑了两个实例,端口分别为8762,8763,分别向服务注册中心注册
- sercvice-ribbon端口为8762,向服务注册中心注册
- 当sercvice-ribbon通过restTemplate调用eign-product的hi接口时,因为用ribbon进行了负载均衡,会轮流的调用service-hi:8762和8763 两个端口的hi接口;
对于ribbon更多的配置,比如说轮询的策略配置或者自定义轮询算法,大家可以去进一步学习ribbon,这里我们就介绍完毕了,下一节,我们将介绍Feign组件的使用。