3
.3 同步通信和异步通信
前一节讨论了微服务间通信是如何触发的。在编制模式中,有一个服务控制所有其他微服务,在编排模式中,一个微服务在其需要与其他微服务通信时发出声音即可。不管最终选择编制模式还是编排模式,总有另外一个问题需要回答:通信过程是同步的还是异步的?也可以用另外一种方式来看待通信是如何完成的:同步方式是一个微服务需要通过提供条件来查询另外一个微服务的数据,并且等待返回结果;异步方式则只需要广播一条消息,告诉所有其他微服务当前我的任务已经完成,后续工作请从我完成的点开始执行。下面我们会继续探讨这两种通信方式。
3.3.1 同步通信
顾名思义,同步通信需要及时的响应。消费者服务器会等待或者阻塞直到接收到远程被调用服务器的响应为止。在同步通信中,接收方当时就知道本次调用是成功还是失败,也可以根据调用状态做出相应的决策。同步通信实现起来较为简单。基于同步通信的这种请求/响应的架构,最佳选择就是REST协议。虽然微服务开发人员将基于HTTP和JSON格式的通信方式视为“一等公民”,但是SOAP、XMLRPC以及socket
编程等方案也可以用来做同步通信。为了加深理解,下面我们以一个库存管理系统作为例子来进行讲解。
假设太阳镜的网站http://myeyewear.com/的墨镜非常流行。再假设该网站要在圣诞节期间搞促销活动,活动期间会有很多用户会将同一个商品抢到自己的购物车中。在此种情况下,该公司需要在用户添加到购物车的时候检查该商品是否能被添加到购物车。公司可能还想把这个商品的库存冻结起来直到用户成功购买或者时间过期为止。处理这种情况有多种方式,这里为了示例的需要,假设微服务之间是以REST的方式通信的,那么整个过程应该由下面这几步组成,如图3-4所示。
(1)客户端/消费者来到API网关来添加产品到其购物车中。
(2)API网关带着商品ID来到购物车管理服务将商品添加到该用户的购物车。
(3)购物车管理服务通知库存服务冻结给定产品ID的产品的库存。
(4)此时购物车管理服务会持续等待库存服务的响应,与此同时库存服务会检查该商品是否可以购买,如果在库存中找到有该商品,那么它会冻结其中一个然后返回给购物车管理服务。
(5)基于收到的返回值,购物车管理服务将该商品添加到用户的购物车中,然后将添加信息返回给其消费者。
在这次通信过程中,购物车服务需要知道库存服务的存在,因此这二者之间其实是有一些依赖关系的。与此同时在等待库存服务响应期间,购物车服务的请求是阻塞的。这其实是一个紧耦合的例子。此时调用方的服务需要处理被调用服务可能出现的多种类型的错误,或许整个被调用服务都挂掉。尽管我们可以用超时来处理这种特殊情况,但是仍然浪费了一些系统资源。而且某些情况下,即便最后调用的那几个服务没有从被调用服务那里得到任何结果,但整个请求还是要等到超时才能返回,这样不仅浪费了系统资源,而且也延长了响应时间。为了处理这种调用服务时出错或者没有响应的情况,可以引入一种断路器模式。在之前的章节中已经简单介绍过断路器,现在让我们来深入探讨一下。断路器模式会根据某些定义好的规则和阈值来识别错误。然后断路器会介入,在一段时间内阻止继续调用这个有问题的服务,或者启用一些备用方法来计算返回值。在断路器模式中,线路或调用有3种状态:
关闭状态;
开启状态;
半开启状态。
断路器在关闭状态时,调用执行正常,调用服务从被调用服务处得到正常结果。断路器在开启状态时,表示被调用服务无法响应,错误数量超过了阈值。断路器开启时,在一个配置的断路时间内,后续请求不再发往被调用服务方,而是直接执行备用方法。断路器最后一种状态是半开启状态,在断路器开启超过了配置时间后,进入这种半开启状态。半开启的时候,请求偶尔会被发往被调用服务,用来检查被调用服务是否已经修复好了,如果线路已经正常,断路器随后会切换为关闭状态,否则就继续恢复到开启状态。
要实现断路器模式,需要在调用方服务中使用一些拦截器。这个拦截器会随时注意发出去的请求和收到的响应。如果有请求得到的失败响应超过了预定义的阈值,拦截器就终止发送后续请求,直接用预先定义的响应或方法来响应,同时会启动定时器来确保一段时间内断路器处于开启状态。
Spring中的Netflix Hystrix就是作断路器的。只需要添加几个注解就可以用这个现成的断路器了。假设有一个在线预定电影票的网站,如果某一个电影订票失败或者超时(如负载太高)后,会推荐另外一个同一时间的场次给用户以便用户能订票成功。基于以上需求,我们需要创建一个订票应用,和一个调用应用。调用应用会访问订票应用公开的URLhttp://<应用IP:端口号>/bookingapplication/{userId}/{movieId}来完成订票。如果这个URL访问失败,可以通过一些简单的Hystrix注解配置一个备用方法,这其实还需要不少代码:
package com.practicalMicroservice.booking;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
@SpringBootApplication
@EnableCircuitBreaker
public class ConsumingApplication {
@Autowired
private ConsumingService consumingService;
@RequestMapping(method = RequestMethod.POST,value =
"/book/{movieId}",produces = "application/json")
public String book ticket(@PathVariable("userId") String
userId,@PathVariable("movieId") String movieId) {
return consumingService.bookAndRespond(userId,movieId);
}
public static void main(String[] args) {
SpringApplication.run( ConsumingApplication .class, args);
}
}
这里的@EnableCircuitBreaker启用了Spring Boot应用中的断路器模式。为了在应用中使用Hystrix作为断路器模式,还需要另外一个注解@HystrixCommand。但是这个注解只能在@service或者@component
注解出现的类中使用。因此,接下来需要创建一个服务类。
@Service
public class ConsumingService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getAnotherCurentlyShowingMovie")
public String bookAndRespond() {
URI uri = URI.create("http://<applicationIp:port>/bookingapplication/
{userId}/{movieId}");
return this.restTemplate.getForObject(uri, String.class);
}
public String getAnotherCurentlyShwoingMovie() {
return "We are experiencing heavy load on booking of your movie. There
are some other movies are running on same time, please check if you are
interested in any one. " + getSameShowtimeMovieList() ;
}
public String getSameShowtimeMovieList() {
return "fast and Furious 8, The Wolverine3";
}
}
在这个服务类中,只要线路出现问题,断路器介入之后,它就不会继续调用有问题的服务了,取而代之的是,会开始调用另外一个名为getAnotherCurrentlyShowingMovie的方法来告诉用户同一时间上映的其他精彩电影。断路器非常适合于处理同步通信中的失败场景,但是它解决不了同步通信中的其他问题。知道其他服务的存在加大了服务之间的耦合,而如果耦合太强,微服务其实就跟单体应用没有本质差别了。
原文网址:https://www.epubit.com/book/detail/27566
内容来源:异步社区;版权属【人民邮电出版社 异步社区】所有,转载已获得授权;未经授权,不得以任何方式复制和传播本书内容,如需转载请联系异步社区。