深入浅出 -- 系统架构之微服务中OpenFeign最佳实践

前面我们讲了一下 Ribbon 和 RestTemplate 实现服务端通信的方法,Ribbon 提供了客户端负载均衡,而 RestTemplate 则对 http 进行封装,简化了发送请求的流程,两者互相配合,构建了服务间的高可用通信。

但在使用后也会发现,RestTemplate 只是对 HTTP 做了简单的封装,像发送请求的 URL、参数、请求头、请求体这些细节都需要我们自己处理,如此底层的操作都暴露出来肯定是不利于团队间协作的,因此就需要一种封装度更高,使用更简单的技术来屏蔽通信底层的复杂度,这里就来到了我们这篇文章介绍的重点了:OpenFeign 技术

为了便于理解,我们这里通过一个具体的案例来配合理解。

一、案例背景

在某电商平台的订单业务中,为了保证商品不超卖,我们需要在下单时查询商品库存,如有库存则创建订单,继续支付流程,如果库存为 0,则提示用户库存不足,无法下单。这里我们来定义订单服务(order-service)和仓储服务(warehouse-service)。总体流程如下:

在上述业务中,订单服务是依赖仓储服务的,那仓储服务就是服务提供者订单服务就是服务消费者,梳理清思路后,我们来使用代码还原这个场景。

二、创建服务提供者(warehouse-service)

仓储服务做为服务提供者,就是标准的 springboot 工程,我们先创建一个 springboot 工程。

1、工程创建

利用 Spring Initializr 向导创建 warehouse-service 工程(前面文章有创建步骤,不明白的可以去看一下)。确保在创建后的 pom.xml 中有如下引用:

XML 复制代码
<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

    <groupId>com.alibaba.cloud</groupId>

    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

</dependency>

2、配置注册中心

在创建好的工程中的 application.yml 文件中新增 Nacos 通信配置。

java 复制代码
spring:

  application:

    name: warehouse-service #应用/微服务名字

  cloud:

    nacos:

      discovery:

        server-addr: 106.14.221.171:8848 #nacos服务器地址

        username: nacos #用户名密码

        password: nacos

server:

  port: 80

3、创建库存实体类

创建库存实体类,保存库存信息。

java 复制代码
package com.example.warehouseservice.dto;

//库存商品对象

public class Stock {

    private Long skuId; //商品品类编号

    private String title; //商品与品类名称

    private Integer quantity; //库存数量

    private String unit; //单位

    private String description; //描述信息

    //带参构造函数

    public Stock(Long skuId, String title, Integer quantity, String unit) {

        this.skuId = skuId;

        this.title = title;

        this.quantity = quantity;

        this.unit = unit;

    }

    //getter and setter省略...

}

4、创建控制器(controller)

创建仓储服务控制器 WarehouseController,通过一个 getStock()方法传入商品编号,返回具体的库存数据。我们这里采用数据模拟 的方式,定义两个商品库存:编号为1101 的是紫色 256G iPhone15,库存 32 台 ,编号1102 的是白色 256G iPhone15,库存为 0

java 复制代码
package com.example.warehouseservice.controller;

//省略 import 部分

//仓储服务控制器

@RestController

public class WarehouseController {

    /**

     * 查询对应 skuId 的库存状况

     * @param skuId skuId

     * @return Stock 库存对象

     */

    @GetMapping("/stock")

    public Stock getStock(Long skuId){

        Map result = new HashMap();

        Stock stock = null;

        if(skuId == 1101l){

            //模拟有库存商品

            stock = new Stock(1101l, "Apple iPhone 15 128GB 紫色", 32, "台");

            stock.setDescription("Apple 11 紫色版对应商品描述");

        }else if(skuId == 1102l){

            //模拟无库存商品

            stock = new Stock(1101l, "Apple iPhone 15 256GB 白色", 0, "台");

            stock.setDescription("Apple 11 白色版对应商品描述");

        }else{

            //演示案例,暂不考虑无对应 skuId 的情况

        }

        return stock;

    }

}

5、服务启动

上述代码完成后,我们打包部署到服务器上,启动成功后,可以在 Nacos 注册中心中看到注册状态

再在浏览器中访问 url 来查看服务返回的数据:

java 复制代码
http://192.168.3.2/stock?skuId=1101

{

  skuId: 1101,

  title: "Apple iPhone 15 128GB 紫色",

  quantity : 32,

  unit: "台",

  description:"Apple 11 紫色版对应商品描述"

}

至此,我们服务提供者 warehouse-service 就开发完成了,下面我们来开发服务消费者。

三、创建服务消费者(order-service)

1、工程创建

我们还是使用 Spring initializr 创建一个 order-service 工程,并确保 pom.xml 中引入如下包:

XML 复制代码
<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

    <groupId>com.alibaba.cloud</groupId>

    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-openfeign</artifactId>

    <version>2.2.5.RELEASE</version>

</dependency>

2、启用 OpenFeign

创建完并添加好工程依赖包后,我们需要在应用入口 OrderServiceApplication 中添加@EnableFeignClients 注解,这里是为了通知 Spring 启用 OpenFeign 声明式通信。

java 复制代码
package com.example.orderservice;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication

@EnableFeignClients //启用OpenFeign

public class OrderServiceApplication {

    public static void main(String[] args) {

        SpringApplication.run(OrderServiceApplication.class, args);

    }

}

3、配置 Nacos

默认的 OpenFeign 并不需要任何的配置,我们在 application.yml 配置一下 Nacos。

java 复制代码
spring:

  application:

    name: order-service

  cloud:

    nacos:

      discovery:

        server-addr: 106.14.221.171:8848

        username: nacos

        password: nacos

server:

  port: 80

4、创建 OpenFeign 通信接口和响应对象

java 复制代码
package com.example.orderservice.feignclient;

import com.example.orderservice.dto.Stock;

import org.springframework.cloud.openfeign.FeignClient;

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

import org.springframework.web.bind.annotation.RequestParam;

@FeignClient("warehouse-service")

public interface WarehouseServiceFeignClient {

    @GetMapping("/stock")

    public Stock getStock(@RequestParam("skuId") Long skuId);

}

在 order-service 工程下,创建一个 feignclient 包用于保存通信接口。OpenFeign 通过"接口+注解"形式描述数据传输逻辑,并不需要我们编写具体实现代码便能实现服务间高可用通信。

@FeignClient 注解说明当前接口为 OpenFeign 通信客户端,参数值 warehouse-service 为服务提供者 ID,这一项必须与 Nacos 注册 ID 保持一致。在 OpenFeign 发送请求前会自动在 Nacos 查询 warehouse-service 所有可用实例信息,再通过内置的 Ribbon 负载均衡选择一个实例发起 RESTful 请求,进而保证通信高可用.

java 复制代码
package com.lagou.orderservice.dto;

//消费者端接收响应Stock对象

public class Stock {

    private Long skuId; //商品品类编号

    private String title; //商品与品类名称

    private Integer quantity; //库存数量

    private String unit; //单位

    @Override

    public String toString() {

        return "Stock{" +

                "skuId=" + skuId +

                ", title='" + title + ''' +

                ", quantity=" + quantity +

                ", unit='" + unit + ''' +

                '}';

    }

    //getter与setter省略

}

声明的方法结构,接口中定义的方法通常与服务提供者的方法定义保持一致。这里有个非常重要的细节:用于接收数据的 Stock 对象并不强制要求与提供者端 Stock 对象完全相同,消费者端的 Stock 类可以根据业务需要删减属性,但属性必须要与提供者响应的 JSON 属性保持一致。距离说明,我们在代码发现消费者端 Stock 的包名与代码与提供者都不尽相同,而且因为消费者不需要 description 属性便将其删除,其余属性只要保证与服务提供者响应 JSON 保持一致,在 OpenFeign 获取响应后便根据 JSON 属性名自动反序列化到 Stock 对象中。

5、接口注入,远程调用

在消费者 Controller 中对 FeignClient 接口进行注入,像调用本地方法一样调用即可。

java 复制代码
package com.example.orderservice.controller;

import com.example.orderservice.dto.Stock;

import com.example.orderservice.feignclient.WarehouseServiceFeignClient;

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

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

import javax.annotation.Resource;

import java.util.LinkedHashMap;

import java.util.Map;

@RestController

public class OrderController {

    //利用@Resource将IOC容器中自动实例化的实现类对象进行注入

    @Resource

    private WarehouseServiceFeignClient warehouseServiceFeignClient;

    /**

     * 创建订单业务逻辑

     * @param skuId 商品类别编号

     * @param salesQuantity 销售数量

     * @return

     */

    @GetMapping("/create_order")

    public Map createOrder(Long skuId , Long salesQuantity){

        Map result = new LinkedHashMap();

        //查询商品库存,像调用本地方法一样完成业务逻辑。

        Stock stock = warehouseServiceFeignClient.getStock(skuId);

        System.out.println(stock);

        if(salesQuantity <= stock.getQuantity()){

            //创建订单相关代码,此处省略

            //CODE=SUCCESS代表订单创建成功

            result.put("code" , "SUCCESS");

            result.put("skuId", skuId);

            result.put("message", "订单创建成功");

        }else{

            //code=NOT_ENOUGN_STOCK代表库存不足

            result.put("code", "NOT_ENOUGH_STOCK");

            result.put("skuId", skuId);

            result.put("message", "商品库存数量不足");

        }

        return result;

    }

}

6、部署测试

将消费者部署后,我们尝试调用消费者的创建订单接口,如传入 1101 编号,则会出现以下返回:

java 复制代码
http://192.168.3.3/create_order?skuId=1101&salesQuantity=1

{

code: "SUCCESS",

skuId: 1101,

message: "订单创建成功"

}

如传入 1102 编号,则会出现以下返回:

java 复制代码
http://192.168.3.3/create_order?skuId=1102&salesQuantity=1

{

code: "NOT_ENOUGH_STOCK",

skuId: 1102,

message: "商品库存数量不足"

}

这里已经基于 OpenFeign 实现了服务间通信。

到这里,我们 SpringCloud 集成 OpenFeign 的工作就完成了,大家可以按照自己的业务愉快的撸码了。

相关推荐
韩楚风4 分钟前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
王彬泽4 小时前
【微服务】组件、基础工程构建(day2)
微服务
Cikiss4 小时前
微服务实战——SpringCache 整合 Redis
java·redis·后端·微服务
Cikiss4 小时前
微服务实战——平台属性
java·数据库·后端·微服务
_.Switch5 小时前
Python机器学习:自然语言处理、计算机视觉与强化学习
python·机器学习·计算机视觉·自然语言处理·架构·tensorflow·scikit-learn
攸攸太上8 小时前
JMeter学习
java·后端·学习·jmeter·微服务
feng_xiaoshi9 小时前
【云原生】云原生架构的反模式
云原生·架构
妍妍的宝贝9 小时前
k8s 中微服务之 MetailLB 搭配 ingress-nginx 实现七层负载
nginx·微服务·kubernetes
架构师吕师傅11 小时前
性能优化实战(三):缓存为王-面向缓存的设计
后端·微服务·架构
团儿.13 小时前
解锁MySQL高可用新境界:深入探索MHA架构的无限魅力与实战部署
数据库·mysql·架构·mysql之mha架构