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

news2024/11/24 14:29:21

前面我们讲了一下 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 中有如下引用:

<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 通信配置。

spring:

  application:

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

  cloud:

    nacos:

      discovery:

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

        username: nacos #用户名密码

        password: nacos

server:

  port: 80

3、创建库存实体类

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

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

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 来查看服务返回的数据:

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 中引入如下包:

<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 声明式通信。

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。

spring:

  application:

    name: order-service

  cloud:

    nacos:

      discovery:

        server-addr: 106.14.221.171:8848

        username: nacos

        password: nacos

server:

  port: 80

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

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 请求,进而保证通信高可用.

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 接口进行注入,像调用本地方法一样调用即可。

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 编号,则会出现以下返回:

http://192.168.3.3/create_order?skuId=1101&salesQuantity=1

{

code: "SUCCESS",

skuId: 1101,

message: "订单创建成功"

}

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

http://192.168.3.3/create_order?skuId=1102&salesQuantity=1

{

code: "NOT_ENOUGH_STOCK",

skuId: 1102,

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

}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1583217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打开Visual Studio后出现Visual Assist报错弹窗

安装了新的VA插件后发现无论如何清理打开VS都会报这个旧版VA报错弹窗&#xff0c;修复VS、重装VA都解决不了 后来进到VS安装目录&#xff0c;删掉一个可疑文件后弹窗再也不出现了

设计模式之迭代器模式(上)

迭代器模式 1&#xff09;概述 1.概念 存储多个成员对象&#xff08;元素&#xff09;的类叫聚合类(Aggregate Classes)&#xff0c;对应的对象称为聚合对象。 聚合对象有两个职责&#xff0c;一是存储数据&#xff0c;二是遍历数据。 2.概述 迭代器模式(Iterator Patter…

微信朋友圈定时神器必须拥有!随时随地轻松发圈!

在微信朋友圈这个社交平台上&#xff0c;很多人都希望通过发布内容来进行个人推广或商业营销。但是&#xff0c;在忙碌或是节假日时&#xff0c;想要随意发布内容也会有些麻烦。 今天我要向大家分享的是一个微信朋友圈定时发布的神器&#xff0c;让您随时随地轻松发圈&#xf…

ES11-12

1-ES11-Promise.allSettled Promise.allSettled0)方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。 简单来说不管成功失败都会调用.then()&#xff0c;然后处理成功和失败的结果 const promises [ …

嵌入式学习第三十二天!(队列)

1. 队列的定义&#xff1a; 队列&#xff1a;是只允许一端进行数据插入&#xff0c;而另一端进行数据删除的线性表。&#xff08;先进先出FIFO&#xff09;&#xff0c;如下图所示。 队列的应用&#xff1a;缓冲区&#xff0c;即解决高速设备和低速设备数据交互的时候&#xff…

K8S容器空间不足问题分析和解决

如上图&#xff0c;今天测试环境的K8S平台出现了一个问题&#xff0c;其中的一个容器报错&#xff1a;Free disk space below threshold. Available: 3223552 bytes (threshold: 10485760B)&#xff0c;意思服务器硬盘空间不够了。这个问题怎么产生的&#xff0c;又怎么解决的呢…

“人工智能+数字人”,让数字技术赋能多领域智能化管理、数字化服务

AI数字人结合了语音合成、语音识别、语义理解、图像处理、虚拟形象驱动等多项AI核心技术&#xff0c;可以实现导览服务、信息播报、互动交流、业务咨询等智能化功能。 如今&#xff0c;AI数字人逐渐被政务、文旅、展馆展厅、博物馆、数字会议、金融、校园等等领域多元化应用&am…

RuoYi-Vue若依框架-vue前端给对象添加字段

处理两个字段的时候有需求都要显示在下拉框的同一行&#xff0c;这里有两种解决方案&#xff0c;一是后端在实体类添加一个对象&#xff0c;加注解数据库忽略处理&#xff0c;在接口处拼接并传给前端&#xff0c;二是在前端获取的数据数组内为每个对象都添加一个字段&#xff0…

Qt | QObject 类中的成员函数存取属性值与动态属性、用反射机制获取属性的信息

1、注册自定义类型与 QMetaType 类 ①、QMetaType 类用于管理元对象系统中命名的类型,该类用于帮助 QVariant 中的类型以及队列中信号和槽的连接。它将类型名称与类型关联,以便在运行时动态创建和销毁该名称。 ②、QMetaType::Type 枚举类型定义了 QMetaType 支持的类型。其…

Golang | Leetcode Golang题解之第11题盛最多水的容器

题目&#xff1a; 题解&#xff1a; func maxArea(height []int) int {res : 0L : 0R : len(height) - 1for L < R {tmp : math.Min(float64(height[L]), float64(height[R]))res int(math.Max(float64(res), tmp * float64((R - L))))if height[L] < height[R] {L} el…

Severt基本使用

severt是让我们自己写一些类,然后把这些类给加载Tomcat中&#xff0c;后续Tomcat收到HTTP请求(来自于浏览器)&#xff0c;就会执行到咱们上面写的代码.从而通过这些代码,完成一定的业务逻辑. 创建项目 此处创建的是一种新的项目的形式称为Maven项目,Maven是Java 中的一个的构建…

微服务项目sc2024第一个子项目

1. 第一个子项目 2.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.apa…

【机器学习300问】66、ReLU激活函数相对于Sigmoid和Tanh激活函数的优点是什么?ReLU它有局限性吗?如何改进?

一、ReLU相对于Sigmoid和Tanh的优点 &#xff08;1&#xff09;计算效率高 ReLU函数数学形式简单&#xff0c;仅需要对输入进行阈值操作&#xff0c;大于0则保留&#xff0c;小于0则置为0。Sigmoid和Tanh需要指数运算但ReLU不需要。所以相比之下它会更快&#xff0c;降低了神经…

MySQL innoDB存储引擎多事务场景下的事务执行情况

一、背景 在日常开发中&#xff0c;对不同事务之间的隔离情况等理解如果不够清晰&#xff0c;很容易导致代码的效果和预期不符。因而在这对一些存在疑问的场景进行模拟。 下面的例子全部基于innoDB存储引擎。 二、场景&#xff1a; 2.1、两个事务修改同一行记录 正常来说&…

每日一题 — 最大连续 1 的个数III

解法一&#xff1a;暴力枚举 先定义left和right双指针&#xff0c;left先固定在起始位置&#xff0c;遍历right当值等于1的时候&#xff0c;直接跳过&#xff0c;等于0的时候&#xff0c;zero计数器加一当zero等于k的时候&#xff0c;就开始记录此时最大长度是多少然后left加一…

urwid,一个好用的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个好用的 Python 库 - urwid。 Github地址&#xff1a;https://github.com/urwid/urwid Urwid 是一个功能强大的 Python 库&#xff0c;用于创建基于文本的用户界面&#xf…

Linux入门攻坚——18、SELinux、Bash脚本编程续

SELinux——Secure Enhanced Linux&#xff08;安全加强的Linux&#xff09;&#xff0c;工作于Linux内核中。 SELinux 主要作用就是最大限度地减小系统中服务进程可访问的资源&#xff08;最小权限原则&#xff09;。采用委任式存取控制&#xff0c;是在进行程序、文件等细节权…

蓝桥杯2023A-05-互质数(Java)

5.互质数 题目描述 给定 a, b&#xff0c;求 1 ≤ x < a^b 中有多少个 x 与 a^b 互质。由于答案可能很大&#xff0c;你只需要输出答案对 998244353 取模的结果。 输入格式 输入一行包含两个整数分别表示 a, b&#xff0c;用一个空格分隔。 输出格式 输出一行包含一个…

【MPI并行程序】完美解决Attempting to use an MPI routine before initializing MPI

文章目录 错误原因解决方案 最近在写并行程序&#xff0c;犯了一个小错误&#xff0c;记录一下&#xff0c;以防止以后再犯。 Attempting to use an MPI routine before initializing MPI&#xff08;在初始化 MPI 之前尝试使用 MPI 例程&#xff09; 错误原因 这个错误通常是因…

编写Markdown时如何爽爽地渲染树?

在使用VitePress/Dumi等静态网站生成时&#xff0c;一般均支持直接在Markdown中渲染显示Vue/React组件&#xff0c;这给个网站非常丰富极致的表现力&#xff0c;我们在创建静态网站时开心的使用各种Vue/React组件&#xff0c;但是在输出树结构时&#xff0c;实际场景中存在几个…