Spring Cloud笔记(4)构建Spring Cloud Demo

通过前几篇文章的积累,我们现在可以来动手搭建一个完整的Spring Cloud Demo项目了。为了更清楚的说明Spring Cloud的结构特点,我们的demo项目还是遵循由浅入深的原则,一开始只加入一些基本的特性,后面再来逐步完善。

业务背景

本来演示技术点的demo,弄一些sayHello的方法出来也无可厚非。但Spring Cloud的很多特性都是与业务的实际需求紧密结合的,脱离业务谈技术难免显得有些空洞,所以我们也需要为demo弄一个简单的业务背景。就用常见的订单管理和仓储管理来举例子吧,一个基本的业务流程就是客户创建订单 ,如下图:


客户下单流程.png

为此我们需要创建两个微服务模块:订单服务和仓储服务,订单服务提供创建订单的接口,创建订单的同时需要调用仓储服务提供的接口来对库存进行更新。

组件选择和环境搭建

之前我们介绍过,每一种Spring Cloud的特性的实现都有好几种不同的框架和工具进行选择,根据官方的推荐和技术的流行度,demo主要采用了以下的框架和工具:

  • Consul:用于服务的注册和发现
  • OpenFeign:声明式HTTP客户端,服务间调用
  • Spring Cloud Loadbalancer:客户端负载均衡
  • Hystrix:断路保护
  • Spring Cloud Gateway:智能路由,服务网关

这里面需要提前安装配置的组件只有一个Consul,可以参考我之前的文章 Consul的架构和配置

新建Maven项目

我们首先为所有的服务模块创建一个Parent项目,用于管理各种公共的依赖包,pom.xml如下,需要注意的地方请查看注释:



    4.0.0
    com.github.davidfantasy
    spring-cloud-demo
    1.0.0
    
        
        order-service
        
        storage-service
    
    spring-cloud-demo
    Demo project for Spring Cloud
    pom
    
        1.8
        Hoxton.SR3
        2.2.6.RELEASE
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.cloud
            spring-cloud-starter-consul-discovery
            
            
                
                    org.springframework.cloud
                    spring-cloud-starter-netflix-ribbon
                
            
        
        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
            
            
                
                    spring-cloud-netflix-ribbon
                    org.springframework.cloud
                
            
        
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
            
            
                org.springframework.boot
                spring-boot-dependencies
                ${spring-boot.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


然后新建两个子模块:order-service和storage-service,子模块目前并不需要引入什么额外的依赖。接下来需要为每个模块创建一个启动类和添加一个系统配置文件,这部分两个模块其实都大同小异,以order-service为例:

服务启动类
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {

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

}
配置文件:application.yml
server:
  port: 9001
spring:
  application:
    name: order-service
  cloud:
    consul:
      host: 192.168.1.220
      port: 8500
      discovery:
        #使用IP地址而不是HOSTNAME作为服务的访问地址
        prefer-ip-address: true
feign:
  hystrix:
    #启用hystrix短路保护
    enabled: true
  okhttp:
    #feign使用okhttp来作为http客户端
    enabled: true

其实到这里,一个服务就已经基本搭建完成了,只需要填充具体的业务逻辑。这也体现了Spring Boot机制的强大之处,通过简单的配置加上各种starter,就能快速的将各种功能特性整合到项目中来。我们先来验证一下现在的配置,运行两个启动类,顺利的话,就能够在Consul的控制台http://localhost:8500/ui 看到服务成功注册的信息:

服务注册界面.png

服务注册的过程

service启动时,spring-cloud-starter-consul-discovery组件会根据配置信息生成一段服务注册的json报文,然后调用Consul Server的REST接口进行服务注册,比如order-service的注册信息是这样的(控制台有相应的输出信息):

{id='order-service-9001', name='order-service', tags=[secure=false], address='192.168.1.252', meta=null, port=9001, enableTagOverride=null, check=Check{script='null', interval='10s', ttl='null', http='http://192.168.1.252:9001/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null'}, checks=null}

从输出信息可以看出Consul对服务进行健康检查的回调地址是
http://192.168.1.252:9001/actuator/health,间隔时间是10s,如果需要修改默认的健康检查信息,可以通过设置相应的参数,请查看 这里。我们可以通过引入spring-boot-starter-actuator组件自动实现健康检查的回调,否则就需要自行定义了。

添加业务逻辑

首先在storage-service中添加一个API,用于模拟库存的变更,核心代码如下:

@RestController
@RequestMapping("/api/storage")
public class Controller {
    @Autowired
    private StorageService storageService;

    @PostMapping("/change-inventory")
    public Integer changeInventory(@RequestBody InventoryChangeDTO req) {
        return storageService.changeInventory(req);
    }
}

storageService.changeInventory方法模拟对库存的扣减操作,并返回一个Integer表示当前剩余的库存数(默认返回98)。然后在order-service中添加一个Feign Client接口,用于声明需要远程调用的storage-service的相关接口:

@FeignClient(name = "storage-service", fallback = StorageServiceFallback.class)
public interface StorageService {

    @PostMapping("/api/storage/change-inventory")
    Integer updateInventoryOfGood(InventoryChangeDTO inventoryChangeDTO);

}

这个接口类相当于是order-service访问storage-service中API的一个代理,@FeignClient的name字段中指定的名称需要与storage-service注册的服务名称保持一致,这样feign会通过服务名查询Consul中已注册的服务,并自动获取order-service的访问地址。如果order-service部署了多个实例,Feign会使用Spring Cloud Loadbalancer进行相应的负载均衡(这个不需要额外的配置,只需要在依赖包中引入相应的starter即可)。fallback = StorageServiceFallback.class 声明了如果storage-service中相应的接口不可用的时候,需要进行相应的降级处理,这个是利用到了Hystrix的熔断保护特性,需要在配置文件中声明:

feign.hystrix.enabled=true

然后我们来构造order-service的createOrder接口,用于用户创建订单:

@RestController
@RequestMapping("/api/order")
@Slf4j
public class Controller {

    @Autowired
    private OrderService orderService;

    @Autowired
    private StorageService storageService;

    @PostMapping("/create-order")
    public String createOrder(@RequestBody OrderDTO order) {
        //创建新订单
        orderService.createNewOrder(order);
        InventoryChangeDTO req = new InventoryChangeDTO();
        req.setGoodCode(order.getGoodCode());
        req.setQuantity(order.getQuantity());
        //调用仓储服务变更库存
        Integer remainQuantity = storageService.updateInventoryOfGood(req);
        log.info("剩余数量:" + remainQuantity);
        return "ok";
    }

}

这样基本的业务流程已经完成了,我们来编写一个单元测试来测试一下订单创建的接口:

    @Test
    public void testCreateOrder() throws Exception {
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setCustomerCode("cus001");
        orderDTO.setGoodCode("tc-1");
        orderDTO.setQuantity(100);
        this.mvc.perform(post("/api/order/create-order").content(JsonUtil.obj2json(orderDTO))
                .contentType("application/json"))
                .andExpect(status().isOk())
                .andExpect(content().string("ok"));
    }

运行之后,一切正常的话,就可以在控制台看到输出的剩余库存日志信息:

2020-04-16 10:03:58.846  INFO 8276 --- [           main] c.g.d.s.o.controller.Controller          : 剩余数量:98

熔断降级

如果当远程服务不可用的时候,需要做一些额外的处理,比如加入重试队列后期进行重试,记录错误日志等等,就需要用到Hystrix提供的熔断保护特性了。之前说到的StorageServiceFallback就是用于这个目的的,我们来看一下StorageServiceFallback的代码:

@Component
@Slf4j
public class StorageServiceFallback implements StorageService {

    public Integer updateInventoryOfGood(InventoryChangeDTO inventoryChangeDTO) {
        log.error("StorageServiceFallback.updateInventoryOfGood暂不可用");
        return -1;
    }

}

这里的业务处理很简单,只是返回一个默认数字-1,并打印了一行错误日志。现在先把StorageServiceApplication停止,然后再执行testCreateOrder方法,就会看到如下的输出日志:

2020-04-16 10:55:09.245 ERROR 6432 --- [ HystrixTimer-1] c.g.d.s.o.remote.StorageServiceFallback  : StorageServiceFallback.updateInventoryOfGood暂不可用
2020-04-16 10:55:09.245  INFO 6432 --- [           main] c.g.d.s.o.controller.Controller          : 剩余数量:-1

从日志上就可以看出降级服务已经生效了。熔断降级在一些中小型的系统中可能意义不太大,但还是可以利用这个机制来做一些其它的应用,比如对单个服务进行单元测试时,其依赖的远程服务都需要打桩,通过降级机制我们就可以很容易的生成远程服务的MOCK接口,定制自己的测试逻辑了。

本文的相关代码可以查看这里 spring-cloud-demo

你可能感兴趣的:(Spring Cloud笔记(4)构建Spring Cloud Demo)