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连接池配置

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

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

相关文章

增加软件投入的重要性:提升自动化程度与用户界面设计的价值

一、引言 在许多项目中&#xff0c;硬件系统通常占据了大量预算&#xff0c;而对软件的投入相对较少。这种不平衡往往导致软件自动化程度低、操作不便、界面简陋&#xff0c;过多的人工干预不仅降低了工作效率&#xff0c;还影响了用户体验。特别是对于一些国家项目&#xff0…

「C系列」C 结构体

文章目录 一、C 结构体1. 定义结构体2. 声明结构体变量3. 初始化结构体变量4. 访问结构体成员5. 结构体数组6. 结构体指针7. 结构体嵌套 二、C 如何使用结构体1. 定义结构体类型2. 声明结构体变量3. 初始化结构体变量4. 访问结构体成员5. 结构体指针6. 在函数中使用结构体7. 注…

安全相关的一些基础知识(持续更新)

目录 1. TRNG真随机数生成 2. 对称加密和非对称加密及其区别 3. Hash算法&#xff08;摘要算法&#xff09; 4. HTTPS、TLS、SSL、HTTP区别和关系 HTTPS的基本原理 5. PSS 1. TRNG真随机数生成 True Random Number Generator 在真随机数的生成里&#xff0c;把随机数的生…

undetected_chromedriver驱动浏览器结束报错OSError: [WinError 6] 句柄无效

undetected_chromedriver驱动浏览器结束报错OSError: [WinError 6] 句柄无效 问题背景 使用undetected_chromedriver包驱动浏览器结束后报错句柄无效 Exception ignored in: <function Chrome.del at 0x000001DD50F07A60> Traceback (most recent call last): File “D:…

大数据实训项目(小麦种子)-01、VirtualBox安装与Centos7系统安装

文章目录 前言项目介绍项目任务目标一、VirtualBox安装1.1、认识VirtualBox1.2、VirtualBox的下载安装 二、VirtualBox安装Centos7系统2.1、VirtualBox安装Centos72.2、Centos7配置静态IP地址2.3、Centos7环境基础配置 三、Windows安装FinalShell及连接Centos73.1、FinalShell下…

父亲节马上到了-和我一起用Python写父亲节的祝福吧

前言 让我们一起用Python写一段父亲节的祝福吧 &#x1f4dd;个人主页→数据挖掘博主ZTLJQ的主页 个人推荐python学习系列&#xff1a; ☄️爬虫JS逆向系列专栏 - 爬虫逆向教学 ☄️python系列专栏 - 从零开始学python 话不多说先上代码 import tkinter as tk from doctest imp…

设计模式-享元模式Flyweight(结构型)

享元模式(Flyweight) 享元模式是一种结构型模式&#xff0c;它主要用于减少创建对象的数量&#xff0c;减少内存占用。通过重用现有对象的方式&#xff0c;如果未找到匹配对象则新建对象。线程池、数据库连接池、常量池等池化的思想就是享元模式的一种应用。 图解 角色 享元工…

centos中安装并设置vsftpd

vsftpd是一个可安装在linux上的ftp服务器软件。 一、安装 安装前保证服务器能上互联网。如果不能上网&#xff0c;看看能不能设法利用局域网代理上网。 sudo yum -y install vsftpd二、配置 1、修改配置文件 cd /etc/vsftpd #修改之前记得备份&#xff01;&#xff01;&am…

远程桌面失败:你的凭据不工作

远程桌面失败&#xff1a;你的凭据不工作 远程桌面失败&#xff1a;你的凭据不工作_您的凭据不工作-CSDN博客https://blog.csdn.net/weixin_38004638/article/details/82290796

LoginGUI.java

LoginGUI.java 完成效果如下图&#xff1a; CODE Summary: This code sets up a login GUI using Swing. It defines a LoginGUI class extending JFrame. The constructor initializes the GUI components and sets up event listeners. The event_login method handles…

MacOS安装redis

文章目录 前言一、介绍二、下载三、安装四、启动五、配置六、Redis 可视化工具下载七、配置详解八、常用命令总结 前言 Redis因其高性能和低延迟而成为现代应用程序的理想选择&#xff0c;尤其适合需要快速读写操作的场景。随着技术的不断发展&#xff0c;Redis继续在性能、功…

电机控制安全:PWM 直通

在 H 桥中使用互补 PWM 时的一个主要考虑因素是短路的可能性&#xff0c;也称为“击穿”。 如图 5 所示&#xff0c;如果同一支路上的两个开关同时打开&#xff0c;H 桥配置可能会导致电源和接地之间发生直接短路。 如果同一条腿上的两个开关同时打开&#xff0c;则可能会发生…

ArcGIS 10.2软件安装包下载及安装教程!

今日资源&#xff1a;ArcGIS 适用系统&#xff1a;WINDOWS 软件介绍&#xff1a; ArcGIS是一款专业的电子地图信息编辑和开发软件&#xff0c;提供一种快速并且使用简单的方式浏览地理信息&#xff0c;无论是2D还是3D的信息。软件内置多种编辑工具&#xff0c;可以轻松的完成…

区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测

区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现BP-ABKDE的BP神经网络自适应带…

基于Matlab的人脸表情识别系统(GUI界面)【W4】

简介&#xff1a; 该系统是一个基于Matlab开发的人脸表情识别应用程序&#xff0c;旨在识别输入图像中的人脸表情&#xff0c;并通过直观的图形用户界面&#xff08;GUI&#xff09;向用户展示识别结果。系统结合了图像处理、机器学习和用户交互技术&#xff0c;使用户能够轻松…

攻防世界-fakebook题目__详解

1.打开题目先用dirsearch工具扫描一波&#xff0c;扫出来了robots.php目录&#xff0c;然后访问robots.txt 目录&#xff0c;发现了有一个备份文件 &#xff0c;访问备份文件&#xff0c;下载内容 文件的大致内容如下 里面有一个curl_exec这个函数容易造成ssrf攻击的漏洞 我…

基于微信小程序的童书购买系统的设计与实现

基于微信小程序的童书购买系统的设计与实现 摘 要 《“十三五”规划》第一次把“保障妇女、未成年人、残疾人的基本权利”作为重要内容&#xff0c;充分体现了党和国家对广大人民群众的关心&#xff0c;为广大人民群众营造了良好的学习氛围&#xff0c;并出台多项文件及政策&…

斯坦福ALOHA机器人团队最新论文-HumanPlus: 从人类学习的人形机器人动作模仿和自主操作

斯坦福ALOHA机器人团队最新论文-HumanPlus&#xff0c;继续推进了机器人技术的前沿进展&#xff0c;我进行了部分翻译和解读&#xff1a; HumanPlus人形机器人系统技术解读 1 简介 本教程将介绍一个名为HumanPlus的全栈式人形机器人系统。该系统能够让机器人从人类数据中学习…

【MySQL】(基础篇十二) —— 子查询

分组数据 本文介绍什么是子查询以及如何使用它们。 SQL允许我们创建子查询&#xff08;subquery&#xff09;&#xff0c;即嵌套在其他查询中的查询。这样可以实现更复杂的查询&#xff0c;理解这个概念的最好方法是考察几个例子。 利用子查询进行过滤 需求&#xff1a;查询…

Python设计模式 - 简单工厂模式

定义 简单工厂模式是一种创建型设计模式&#xff0c;它通过一个工厂类来创建对象&#xff0c;而不是通过客户端直接实例化对象。 结构 工厂类&#xff08;Factory&#xff09;&#xff1a;负责创建对象的实例。工厂类通常包含一个方法&#xff0c;根据输入参数的不同创建并返…