微应用之间的服务调用服务调用示例以商品下单为例,比如将业务拆分为商品服务和订单服务,订单服务会调用商品服务的库存扣减。单个微服务工程,统一按以下目录编排:-product–product-common 商品服务公用对象–product-client 商品服务客户端,以jar包方式被订单服务依赖–product-server 商品服务,要注册到Eureka Server,外部通过product-client来调用我们的微服务工程之间,依赖关系如下:我们从商品微服务工程开始外层product初始pom.xml:<modelVersion>4.0.0</modelVersion><parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> <relativePath/></parent><groupId>com.hicoview</groupId><artifactId>product</artifactId><version>0.0.1-SNAPSHOT</version><name>product</name><description>Demo project for Spring Boot</description><modules> <module>product-common</module> <module>product-client</module> <module>product-server</module></modules><properties> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <product-common.version>0.0.1-SNAPSHOT</product-common.version></properties><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> <dependency> <groupId>com.hicoview</groupId> <artifactId>product-common</artifactId> <version>${product-common.version}</version> </dependency> </dependencies></dependencyManagement>product-common初始pom.xml:<modelVersion>4.0.0</modelVersion><parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version></parent><artifactId>product-common</artifactId><dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency></dependencies>product-client初始pom.xml:<modelVersion>4.0.0</modelVersion><parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version></parent><artifactId>product-client</artifactId><dependencies> <dependency> <groupId>com.hicoview</groupId> <artifactId>product-common</artifactId> </dependency></dependencies>product-server的初始pom.xml:<modelVersion>4.0.0</modelVersion><parent> <groupId>com.hicoview</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version></parent><artifactId>product-server</artifactId><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.hicoview</groupId> <artifactId>product-common</artifactId> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>接下来我们调整product-server工程商品服务需注册到Eureka Server,添加Eureka Client相关依赖:<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-web</artifactId></dependency>启动类加注解@EnableDiscoveryClient:package com.hicoview.product;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication@EnableDiscoveryClientpublic class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); }}配置application.yml:eureka: client: service-url: defaultZone: http://localhost:8761/eureka/spring: application: name: productserver: port: 8080启动product-server,访问注册中心http://localhost:8761,PRODUCT注册上去了,再继续往下看。我们写个简单的服务调用示例一下,订单服务下单逻辑调用商品服务进行扣减库存。继续修改product-server工程package com.hicoview.product.service;import java.util.List;public interface ProductService { // 扣减库存 void decreaseStock();}package com.hicoview.product.service.impl;import com.hicoview.product.service.ProductService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;@Service@Slf4jpublic class ProductServiceImpl implements ProductService { @Override public void decreaseStock() { log.info("——扣减库存—–"); }}spring cloud的RPC服务是使用HTTP方式调用的,所以还要创建ProductController:package com.hicoview.product.controller;import com.hicoview.product.service.ProductService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/product")public class ProductController { @Autowired private ProductService productService; @PostMapping("/decreaseStock") public void decreaseStock() { productService.decreaseStock(); }}接下来修改product-client工程pom.xml增加Feign依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>创建ProductClient:package com.hicoview.product.client;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;@FeignClient(name=“product”)public interface ProductClient { // 通过Feign来代理对PRODUCT服务的HTTP请求 @PostMapping("/product/decreaseStock") void decreaseStock();}再次启动product-server,没问题的话,把product上传到本地maven仓库:mvn -Dmaven.test.skip=true -U clean install然后初始化订单微服务工程后继续。修改order-server工程order-server的服务可能会被User等其他服务调用,也是个Eureka client,添加依赖:<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-web</artifactId></dependency>启动类加注解@EnableDiscoveryClientpackage com.hicoview.product;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication@EnableDiscoveryClientpublic class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); }}配置application.yml:eureka: client: service-url: defaultZone: http://localhost:8761/eureka/spring: application: name: orderserver: port: 8081启动order-server,访问注册中心http://localhost:8761/:ORDER服务注册成功后,继续调整order-server工程由于订单服务要调用商品服务,需添加对product-client依。修改pom.xml:<dependency> <groupId>com.hicoview</groupId> <artifactId>product-client</artifactId></dependency>对版本的管理统一交给上层,修改上层order的pom.xml,增加以下配置:<properties> … <product-client.version>0.0.1-SNAPSHOT</product-client.version></properties><dependencyManagement> <dependencies> … <dependency> <groupId>com.hicoview</groupId> <artifactId>product-client</artifactId> <version>${product-client.version}</version> </dependency> </dependencies></dependencyManagement>回到order-server,修改启动类,添加Feign扫描路径:package com.hicoview.order;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication@EnableDiscoveryClient@EnableFeignClients(basePackages = “com.hicoview.product.client”)public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); }}然后按顺序创建Controller、Service:package com.hicoview.order.controller;import com.hicoview.order.service.OrderService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/order")public class OrderController { @Autowired private OrderService orderService; // 创建订单 @PostMapping("/create") public void create() { orderService.createOrder(); }}public interface OrderService { void createOrder();}@Service@Slf4jpublic class OrderServiceImpl implements OrderService { @Autowired private ProductClient productClient; @Override public void createOrder() { log.info("——创建订单—–"); // 调用商品扣减服务 productClient.decreaseStock(); }}启动order-server,发起下单请求:curl -X POST http://localhost:8081/order/create服务调用成功,order-server和product-server输出:2019-02-26 11:09:58.408 INFO 3021 — [nio-8081-exec-3] c.h.order.service.impl.OrderServiceImpl : ——创建订单—–2019-02-26 11:09:58.430 INFO 3015 — [nio-8080-exec-3] c.h.p.service.impl.ProductServiceImpl : ——扣减库存—–为了示例的极简,上面服务调用没有涉及到入参和返回值。如果需要定义参数或返回值,考虑到内外部都会用到,需将参数bean定义在product-common中作为公共bean。Feign和RestTemplateRest服务的调用,可以使用Feign或RestTemplate来完成,上面示例我们使用了Feign。Feign(推荐)Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。Feign会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件。RestTemplateRestTemplate提供了多种便捷访问远程Http服务的方法,可以了解下。第一种方式,直接使用RestTemplate,URL写死:// 1. RestTemplate restTemplate = new RestTemplate();restTemplate.getForObject(“http://localhost:8080/product/decreaseStock”, String.class);这种方式直接使用目标的IP,而线上部署的IP地址可能会切换,同一个服务还会启多个进程,所以有弊端。第二种方式,利用LoadBalancerClient,通过应用名获取URL,然后再使用RestTemplate:@Autowiredprivate LoadBalancerClient loadBalancerClient;// 1. 第二种方式,通过应用名字拿到其中任意一个host和portServiceInstance serviceInstance = loadBalancerClient.choose(“PRODUCT”);String url = String.format(“http://%s:%s”, serviceInstance.getHost(), serviceInstance.getPort() + “/product/decreaseStock”);RestTemplate restTemplate = new RestTemplate();restTemplate.getForObject(url, String.class);第三种方式,写一个config把RestTemplate作为一个bean配置上去:@Componentpublic class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }}@Autowiredprivate RestTemplate restTemplate;// 使用时url里直接用应用名PRODUCT即可restTemplate.getForObject(“http://PRODUCT/product/decreaseStock”, String.class);Ribbon客户端负载均衡器Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。在Spring Cloud中,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。还有微服务之间的调用,通过Feign或RestTemplate找到一个目标服务。以及API网关Zuul的请求转发等内容,实际上都是通过Ribbon来实现的。