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…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
