SpringCloud跨服务远程调用
随着项目的使用者越来越多,项目承担的压力也会越来越大,为了让我们的项目能服务更多的使用者,我们不得不需要把我们的单体项目拆分成多个微服务,就比如把一个商城系统拆分成用户系统,商品系统,订单系统,购物车系统,支付系统等等,每个微服务都对应着自己的数据库,别的服务不能访问,别的微服务需要通过本微服务的service层方法才能获取。把这些微服务分配到多个Tomcat上,使其能承担更多的压力。
但问题也来了,在原来的单体架构中,我们把所有系统的mapper写在同一个包中,service写在同一个包中,还有po、controller都各写在一个包中,这样某个微服务想获取别的微服务的数据库中的数据直接引入对应的service层方法就可以了。但现在各个微服务分开了,写成了多个module或project,不能通过直接导入service层方法获取其他微服务的数据库数据了。
我就拿查询购物车列表时,购物车微服务还需要调用商品微服务的service方法查询购物车中每个商品的详细信息。(购物车微服务的数据库中只会存商品的基本信息)
这是单体架构时的代码:
@Service
@RequiredArgsConstructor //只对加上final的必须初始化的成员变量写入构造函数,不想写入构造函数的不写final就行
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {private final IItemService itemService;@Overridepublic List<CartVO> queryMyCarts() {// 1.查询我的购物车列表List<Cart> carts = lambdaQuery().eq(Cart::getUserId, 1L/* UserContext.getUser()*/).list();if (CollUtils.isEmpty(carts)) {return CollUtils.emptyList();}// 2.转换VOList<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);// 3.处理VO中的商品信息handleCartItems(vos);// 4.返回return vos;}private void handleCartItems(List<CartVO> vos) {
// 1.获取商品idSet<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());// 2.查询商品List<ItemDTO> items = itemService.queryItemByIds(itemIds);if (CollUtils.isEmpty(items)) {return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (CartVO v : vos) {ItemDTO item = itemMap.get(v.getItemId());if (item == null) {continue;}v.setNewPrice(item.getPrice());v.setStatus(item.getStatus());v.setStock(item.getStock());}
原代码中只有handleCartItems()方法中有对itemService的引用,所以咱们只需要该handleCartItems方法中的itemService业务就可以了。
所以这时就需要微服务直接的远程调用,远程调用有3种方法:
方法一:利用restTemplate(不推荐)
首先需要在启动类中声明resttemplate的bean
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartServiceApplication {public static void main(String[] args) {SpringApplication.run(CartServiceApplication.class, args);}@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}
然后在需要改写的方法所在的类中,也就是CartServiceImpl类中注入该bean,我用的构造器注入。
@Service
@RequiredArgsConstructor //只对加上final的必须初始化的成员变量写入构造函数,不想写入构造函数的不写final就行
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {//private final IItemService itemService;private final RestTemplate restTemplate;}
然后改写handleCartItems方法
private void handleCartItems(List<CartVO> vos) {// 1.获取商品idSet<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());// 2.查询商品//List<ItemDTO> items = itemService.queryItemByIds(itemIds);//2.1 利用restTemplate发送http请求HashMap<String,Object> map=new HashMap<>();map.put("ids", itemIds.stream().map(String::valueOf).collect(Collectors.joining(",")));ResponseEntity<List<ItemDTO>> response = restTemplate.exchange("http://localhost:8081/items?ids={ids}", //请求路径HttpMethod.GET, //请求方式null, //请求体,可以为空//ItemDTO.class, //返回体的类型,应该返回ItemDTO的集合,字节码不能使用泛型,所以不能直接传list集合new ParameterizedTypeReference<List<ItemDTO>>() {}, //这里传的是个对象,可以用泛型//Map.of("ids",CollUtils.join(itemIds,","))map);//2.2 解析响应if (!response.getStatusCode().is2xxSuccessful()) {//查询失败return;}List<ItemDTO> items = response.getBody();if (CollUtils.isEmpty(items)) {return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (CartVO v : vos) {ItemDTO item = itemMap.get(v.getItemId());if (item == null) {continue;}v.setNewPrice(item.getPrice());v.setStatus(item.getStatus());v.setStock(item.getStock());}
注意,restTemplate.exchange()方法中的最后一个map参数,如果你用的是jdk11及以上,就可以直接写Map.of()。我用的jdk8,没有Map.of方法所以我提前声明一个hashMap,并用stream流把itemIds转换成String形式并用逗号隔开。
由代码可知,这中方式访问其他微服务的路径是写死的,所以又有问题了,我们在开发微服务时,会有生产环境、测试环境等等,而且微服务一般都是集群部署,一个微服务会部署很多个端口后,然后有nginx负载均衡转发给其中的一个 ,并且如果其中一个端口号对应的服务宕机了,应该是不需要重启服务,就自动有本集群中其他端口号的服务立刻顶上,由这些原因可见,restTemplate是很不方便的。
方法二:nacos
购物车微服务的name为cart-service,商品微服务的name为item-service
nacos是个注册中心,我简单说一下nacos在远程调用过程中起到的作用:每个微服务在启动时都会向nacos注册本微服务的信息(包括端口号,本服务的名字等等),然后当购物车微服务想查询商品详细信息时,会根据微服务名字(item-service)从nacos中拉取商品微服务的相关信息(包括端口号等等),当商品微服务在nacos中注册了多个端口号的服务时,nacos会根据负载均衡原则帮你选出一个来给你提高服务。
首先需要引入nacos的依赖
<!-- nacos--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
然后在application.yaml文件中写入nacos的地址(地址看你启动nacos的IP): 别忘了开启nacos
spring:cloud:nacos:server-addr: 127.0.0.1:8848
然后代码
@Service
@RequiredArgsConstructor //只对加上final的必须初始化的成员变量写入构造函数,不想写入构造函数的不写final就行
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {//private final IItemService itemService;private final RestTemplate restTemplate;private final DiscoveryClient discoveryClient;private void handleCartItems(List<CartVO> vos) {//1.获取商品idSet<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());// 2.查询商品//2.1根据服务名称获取服务的实例列表List<ServiceInstance> instances = discoveryClient.getInstances("item-service");if (CollUtil.isEmpty(instances)){return;}//2.2手写负载均衡,从实例列表中挑一个ServiceInstance serviceInstance = instances.get(RandomUtil.randomInt(instances.size()));//2.3 利用restTemplate发送http请求HashMap<String,Object> map=new HashMap<>();map.put("ids", itemIds.stream().map(String::valueOf).collect(Collectors.joining(",")));ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(serviceInstance.getUri()+"/items?ids={ids}", //请求路径HttpMethod.GET, //请求方式null, //请求体,可以为空//ItemDTO.class, //返回体的类型,应该返回ItemDTO的集合,字节码不能使用泛型,所以不能直接传list集合new ParameterizedTypeReference<List<ItemDTO>>() {}, //这里传的是个对象,可以用泛型//Map.of("ids",CollUtils.join(itemIds,","))map);//2.4 解析响应if (!response.getStatusCode().is2xxSuccessful()) {//查询失败return;}List<ItemDTO> items = response.getBody();if (CollUtils.isEmpty(items)) {return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (CartVO v : vos) {ItemDTO item = itemMap.get(v.getItemId());if (item == null) {continue;}v.setNewPrice(item.getPrice());v.setStatus(item.getStatus());v.setStock(item.getStock());}
}
这样我们的路径就不是写死的了,但是原来本是
List<ItemDTO> items = itemService.queryItemByIds(itemIds); 这一行代码的事,现在呢,写了10多行代码才解决。如果不同微服务之间都需要互相调用,那岂不是每个方法都需要这么写,繁琐死,所以我们有了简明一点的第三种方法
第三种方法:openFeign
feign是一个声明式的http客户端,其作用是帮助我们优雅的实现http请求的发送。
声明式,意思是我们只需要把发请求所需要的信息声明好,剩下的我们不需要去管,全交给Feign去做。
步骤:
首先我们需要创建一个与购物车系统和商品系统同层次的系统,名字随便命名,我用的是hm-api。
然后在pom文件中引入相关依赖:
<!-- openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!-- 引入okhttp连接池,提高feign的性能--><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>1.6.6</version></dependency>
然后在hm-api项目下创建client,dto,config包。client包中写的是远程调用时调用的方法,dto是远程调用方法需要的实体类,config包是对Feign的配置。
我们查看需要远程调用的代码部分
List<ItemDTO> items = itemService.queryItemByIds(itemIds);
需要用到itemService.queryItemByIds方法,我们先去找它的controller中对应的方法
@RestController
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {private final IItemService itemService;@GetMappingpublic Collection<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids){return itemService.queryItemByIds(ids);}
}
然后把该controller中对应的方法抽取出来写入hm-api中的client包中
package com.hmall.hmapi.client;import com.hmall.hmapi.dto.ItemDTO;
import com.hmall.hmapi.dto.OrderDetailDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;import java.util.Collection;
import java.util.List;@FeignClient("item-service")
public interface ItemClient {@GetMapping("/items")List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);}
@FeignClient()注解中写要调用的微服务的name。
然后把代码中用到的ItemDto实体类拷贝到hm-api中的dto包中。
然后代码
@Service
@RequiredArgsConstructor //只对加上final的必须初始化的成员变量写入构造函数,不想写入构造函数的不写final就行
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {//private final IItemService itemService;//private final RestTemplate restTemplate;//private final DiscoveryClient discoveryClient;private final ItemClient itemClient;private void handleCartItems(List<CartVO> vos) {//openFeignSet<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());List<ItemDTO> items = itemClient.queryItemByIds(itemIds);if (CollUtils.isEmpty(items)) {return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (CartVO v : vos) {ItemDTO item = itemMap.get(v.getItemId());if (item == null) {continue;}v.setNewPrice(item.getPrice());v.setStatus(item.getStatus());v.setStock(item.getStock());}}
}
现在代码就简短很多了。
因为每次远程调用访问数据库,都需要建立一次连接,损耗比较大,所以我们建议引入一个连接池。连接池的依赖我们前面已经引入了,现在需要做的只有开启连接池了,现在让我们去调用远程调用的application.yaml文件中,也就是cart-service服务中的application.yaml中,写上以下几行配置就可以了。
feign:okhttp:enabled: true # 打开feign连接池配置
相关文章:

SpringCloud跨服务远程调用
随着项目的使用者越来越多,项目承担的压力也会越来越大,为了让我们的项目能服务更多的使用者,我们不得不需要把我们的单体项目拆分成多个微服务,就比如把一个商城系统拆分成用户系统,商品系统,订单系统&…...
postgres常用查询
一.字符串截取 left: 从左往右截取字符 right: 从右往左截取字符 如截取4个字符: SELECT left( column_name, 4 ) from table SELECT right( column_name, 4 ) from table 二.条件统计 COUNT(CASE WHEN column_name ‘value’ THEN 1 END) AS count_name 如截统计值1,值2的…...
JavaFX应用
JavaFX案例:集成进度条与后台任务 在这个示例中,我们将向JavaFX应用中集成一个进度条,用来展示一个模拟的后台任务的完成进度。这将涉及JavaFX的并发特性,特别是Task类和如何在UI线程安全地更新UI组件。 假设我们想要实现一个简…...

axios打通fastapi和vue,实现前后端分类项目开发
axios axios是一个前后端交互的工具,负责在前端代码,调用后端接口,将后端的数据请求到本地以后进行解析,然后传递给前端进行处理。 比如,我们用fastapi写了一个接口,这个接口返回了一条信息: …...

【最新鸿蒙应用开发】——ArkWeb1——arkts加载h5页面
1. Web组件概述 Web组件用于在应用程序中显示Web页面内容,为开发者提供页面加载、页面交互、页面调试等能力。 页面加载:Web组件提供基础的前端页面加载的能力,包括:加载网络页面、本地页面、html格式文本数据。 页面交互&#…...
【设计模式】结构型设计模式之 享元模式
文章目录 介绍关键概念 应用举例象棋游戏共享棋子对象文本编辑器中文字格式设计成享元模式 享元模式在 Java 中的应用享元模式在包装类缓存中的应用享元模式在 String 中的应用 对比享元模式和单例模式的区别享元模式与缓存的区别 总结优点缺点 介绍 享元模式,”享…...
嵌入式操作系统_5.存储管理
1.存储管理 存储管理是嵌入式操作系统的基本功能之一。其管理的对象是主存,也称内存。它的主要功能包括分配和回收主存空间、提高主存利用率、扩充主存、对主存信息实现有效保护。存储器管理的目的就是提供一个有价值的内存抽象,其目标包括:…...
HTML DOM 事件
HTML DOM 事件 HTML DOM(文档对象模型)事件是当网页中的某些操作发生时,浏览器会自动触发或通过脚本代码手动触发的动作。这些事件可以是对用户操作的响应,如点击按钮,也可以是浏览器自身的动作,如页面加载完成。理解和掌握DOM事件对于前端开发至关重要,因为它们是实现…...

有没有硅基生命?AGI在哪里?
摘要 随着科技的飞速发展,人工智能(AI)和生命科学的探索逐渐成为人们关注的焦点。其中,关于硅基生命的可能性与AGI(Artificial General Intelligence,即人工通用智能)的实现,更是引…...

HAL库开发--串口
知不足而奋进 望远山而前行 目录 文章目录 前言 学习目标 学习内容 开发流程 串口功能配置 串口功能开启 串口中断配置 串口参数配置 查询配置结果 发送功能测试 中断接收功能测试 printf配置 DMA收发 配置 DMA发送 DMA接收(方式1) DMA接收(方式2) 总结 前言…...
Web前端设计毕业论文:深度探索与未来展望
Web前端设计毕业论文:深度探索与未来展望 在数字化时代,Web前端设计作为互联网应用的重要组成部分,其重要性和复杂性日益凸显。本论文旨在深度探索Web前端设计的关键要素、发展趋势以及面临的挑战,为未来的研究和实践提供有价值的…...
JAVA 字节运算 取低5位 获取低位第一位
1、JAVA 取低5位 什么是取低5位 在计算机中,每个数字都是以二进制形式存储的。一个二进制数字可以由多个位组成,每一位都可以是 0 或者 1。取低5位即表示只取二进制数字的最后5位(从右向左数)。 取低5位的方法 在 JAVA 中&#…...

全网首发:教你如何直接用4090玩转最新开源的stablediffusion3.0
1.stablediffusion的概述: Stable Diffusion(简称SD)近期的动态确实不多,但最新的发展无疑令人瞩目。StableCascade、Playground V2.5和Stableforge虽然带来了一些更新,但它们在SD3面前似乎略显黯然。就在昨晚&#x…...

智慧监狱技术解决方案
1. **建设背景**:介绍了智慧监狱建设的战略部署,包括司法部提出的“数字法治、智慧司法”信息化体系建设,以及智慧监狱建设的总体目标、重点任务和实施步骤。 2. **建设需求**:分析了当前监狱系统存在的问题,如子系统…...
QT——事件
一、什么是事件 在QT中,事件(Event)是指由特定对象发生的动作或状态变化,通常用于响应用户的操作。事件可以是鼠标点击、键盘输入、窗口移动等用户操作,也可以是系统发出的信号,比如定时器超时、网络数据到达等。在QT中,可以通过连接信号与槽(Signals and Slots)的方…...

【SpringBoot】Spring Boot 中高级特性详解
文章目录 1. 异步处理1.1 什么是异步处理?1.2 实现异步处理1.2.1 启用异步支持1.2.2 使用 Async 注解1.2.3 调用异步方法 2. 安全管理2.1 Spring Security 集成2.2 基础安全配置2.2.1 添加依赖2.2.2 默认配置2.2.3 自定义用户认证 3. 监控和调试3.1 Spring Boot Act…...
MQTT TCP HTTP 协议对比
目录 1. 类型与用途 2. 通信模式与特性 3. 优缺点 4. 使用场景 MQTT、TCP和HTTP在类型、用途、通信模式、特性以及使用场景等方面存在显著的区别,以下是详细的阐述: 1. 类型与用途 MQTT:MQTT是一种消息传输协议,主要适用于物…...

C++面向对象程序设计 - 函数库
C语言程序中各种功能基本上都是由函数来实现的,在C语言的发展过程中建立了功能丰富的函数库,C从C语言继承了些函数功能。如果要用函数库中的函数,就必须在程序文件中包含文件中有关的头文件,在不同的头文件中,包含了不…...
computeIfAbsent是Java 8引入的Map接口中的一个方法
computeIfAbsent是Java 8引入的Map接口中的一个方法,它提供了一种更高效且线程安全的方式来 conditionally compute or retrieve a value for a given key in a map. 当你想要为一个键计算一个值(如果该键尚不存在对应的映射关系),…...
HTML实现进度条/加载框模版
HTML加载 一、环形加载 1二、环形加载 2三、波形加载四、百分比环形五、进度条 一、环形加载 1 <div class"loader"></div>.loader {border: 16px solid #f3f3f3;border-radius: 50%;border-top: 16px solid #3498db;width: 120px;height: 120px;-webki…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...

客户案例 | 短视频点播企业海外视频加速与成本优化:MediaPackage+Cloudfront 技术重构实践
01技术背景与业务挑战 某短视频点播企业深耕国内用户市场,但其后台应用系统部署于东南亚印尼 IDC 机房。 随着业务规模扩大,传统架构已较难满足当前企业发展的需求,企业面临着三重挑战: ① 业务:国内用户访问海外服…...

echarts使用graphic强行给图增加一个边框(边框根据自己的图形大小设置)- 适用于无法使用dom的样式
pdf-lib https://blog.csdn.net/Shi_haoliu/article/details/148157624?spm1001.2014.3001.5501 为了完成在pdf中导出echarts图,如果边框加在dom上面,pdf-lib导出svg的时候并不会导出边框,所以只能在echarts图上面加边框 grid的边框是在图里…...

大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...