SpringCloud跨服务远程调用

news2024/10/7 11:27:31

随着项目的使用者越来越多,项目承担的压力也会越来越大,为了让我们的项目能服务更多的使用者,我们不得不需要把我们的单体项目拆分成多个微服务,就比如把一个商城系统拆分成用户系统,商品系统,订单系统,购物车系统,支付系统等等,每个微服务都对应着自己的数据库,别的服务不能访问,别的微服务需要通过本微服务的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;
    
@Override
    public List<CartVO> queryMyCarts() {
        // 1.查询我的购物车列表
        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, 1L/* UserContext.getUser()*/).list();
        if (CollUtils.isEmpty(carts)) {
            return CollUtils.emptyList();
        }

        // 2.转换VO
        List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);

        // 3.处理VO中的商品信息
        handleCartItems(vos);

        // 4.返回
        return vos;
    }

    private void handleCartItems(List<CartVO> vos) {
// 1.获取商品id
        Set<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的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (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);
    }


    @Bean
    public 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.获取商品id
        Set<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的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (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.获取商品id
        Set<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的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (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;

    @GetMapping
    public 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) {
        //openFeign
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
        if (CollUtils.isEmpty(items)) {
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (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.coloradmin.cn/o/1826529.html

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

相关文章

Type-C接口显示器:C口高效连接与无限可能 LDR

Type-C显示器C接口的未来&#xff1a;高效连接与无限可能 随着科技的飞速发展&#xff0c;我们的日常生活和工作中对于高效、便捷的连接方式的需求日益增加。在这样的背景下&#xff0c;Type-C接口显示器凭借其卓越的性能和广泛的兼容性&#xff0c;正逐渐崭露头角&#xff0c…

RIP路由协议汇总(华为)

#交换设备 RIP路由协议汇总 一、原理概述 当网络中路由器的路由条目非常多时&#xff0c;可以通过路由汇总&#xff08;又称路由汇聚或路由聚合&#xff09;来减少路由条目数&#xff0c;加快路由收敛时间和增强网络稳定性。路由汇总的原理是&#xff0c;同一个自然网段内的不…

C++11默认成员函数控制

默认成员函数有 如果自己不显示声明&#xff0c;那么默认编译器会自己生成一个 如果有一个构造函数被声明了&#xff0c;那其他的默认函数编译器就不会再生成 这样的有时又不生成&#xff0c;容易造成混乱&#xff0c;于是C11让程序员可以控制是否需要编译器生成。 显式缺省函…

MEMS:Lecture 16 Gyros

陀螺仪原理 A classic spinning gyroscope measures the rotation rate by utilizing the conservation of angular momentum. 经典旋转陀螺仪通过利用角动量守恒来测量旋转速率。 Coriolis Effect and Coriolis Force 科里奥利效应是一种出现在旋转参考系中的现象。它描述了…

多线程中run()和start()的区别

我们知道&#xff0c;在多线程中 Thread thread new Thread(runnable); thread.start();以及 thread.run();都可以执行runnable中run方法下的代码&#xff0c;但是二者又有所不同 下面给出一段代码用以体现二者的区别&#xff1a; 以下代码中&#xff0c;通过thread.start()启…

后端中缓存的作用以及基于Spring框架演示实现缓存

缓存的作用及演示 现在我们使用的程序都是通过去数据库里拿数据然后展示的 长期对数据库进行数据访问 这样数据库的压力会越来越大 数据库扛不住了 创建了一个新的区域 程序访问去缓存 缓存区数据库 缓存里放数据 有效降低数据访问的压力 我们首先进行一个演示 为了演示…

2024全新仿麻豆视频苹果cms源码v10影视模板

下载地址&#xff1a;2024全新仿麻豆视频苹果cms源码v10影视模板 高端大气的设计&#xff0c;适合做电影、连续剧、综艺、动漫、微电影、纪录片、海外剧等视频网站

Leetcode419. 甲板上的战舰

Every day a Leetcode 题目来源&#xff1a;419. 甲板上的战舰 解法1&#xff1a;一次遍历 战舰的个数&#xff0c;等于战舰「头部」的个数。 具体来说&#xff0c;如果位于 (i, j) 的格子是战舰的头部&#xff0c;那么左边和上边的相邻格子不能是 X。 代码&#xff1a; …

OrangePi AIpro 机器人仿真与人工智能应用测评

系列文章目录 前言 本篇文章分为2个部分&#xff0c;第一部分主要搭建了机器人的仿真环境&#xff08;ROS2 MuJoCo等&#xff09;&#xff0c;运行了机械臂及移动机器人相关示例程序&#xff1b;第二部分运行了OrangePi AIpro系统自带的示例程序及昇腾社区官方的示例程序&#…

MySQL 使用 MyFlash 快速恢复误删除、误修改数据

一、MyFlash MyFlash 是由美团点评公司技术工程部开发并维护的一个开源工具&#xff0c;主要用于MySQL数据库的DML操作的回滚。这个工具通过解析binlog日志&#xff0c;帮助用户高效、方便地进行数据恢复。MyFlash的优势在于它提供了更多的过滤选项&#xff0c;使得回滚操作变…

一文了解Spark引擎的优势及应用场景

Spark引擎诞生的背景 Spark的发展历程可以追溯到2009年&#xff0c;由加州大学伯克利分校的AMPLab研究团队发起。成为Apache软件基金会的孵化项目后&#xff0c;于2012年发布了第一个稳定版本。 以下是Spark的主要发展里程碑&#xff1a; 初始版本发布&#xff1a;2010年开发…

mysql报错Access denied for user ‘root‘,navicat可以连接mysql,spring不能连mysql

首先修改配置文件跳过验证&#xff0c;编辑你自己挂载的配置文件的位置 #查找my.cnf位置 sudo find / -name "my.cnf"编辑mysql配置文件 vim /opt/soft/mysql/conf/my.cnf #在[mysqld]下面添加 skip_grant_tables#重启mysql docker restart mysql#进入容器 docke…

完美的移动端 UI 风格让客户无可挑剔

完美的移动端 UI 风格让客户无可挑剔

快速数据处理:软件功能简介及下载

目录 1 功能介绍 1.1 封面 1.2 可定制功能 1.3 支持的操作系统和CPU 1.4 数据上报 1.5 数据接收 1.5 附带的测试数据 1.6 关于内置python的说明 2 软件下载 3 待开发功能 发布这个程序的原因是&#xff0c;前面写的这个专题的几篇文章&#xff0c;我原以为一点用也没…

代码随想录算法训练营第三十六天| 452. 用最少数量的箭引爆气球, 435. 无重叠区间 ,763.划分字母区间

452. 用最少数量的箭引爆气球 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int findMinArrowShots(int[][] points) {if (points.length < 1) {return points.length;}Arrays.sort(points, (o1, o2) -> {if (o1[0] ! o2[0]) {return o1[0] < o2[0…

局域网监控软件有哪些:五款好用的网络监控神器分享(收藏篇)

在日益复杂的企业网络环境中&#xff0c;有效地监控局域网内的活动对于确保网络安全、提高工作效率和维护企业资产至关重要。 为此&#xff0c;精选了五款市场上广受好评的局域网监控软件&#xff0c;它们各自具备独特的功能和优势&#xff0c;能够满足不同规模企业的需求&…

下载使用nginx发布html自定义页面

在浏览器搜索nginx.org&#xff0c;然后点击download&#xff0c;接着点击 stable and mainline 选择自己所使用系统对应的信息后点击&#xff08;我用的是CentOS&#xff0c;所以需要点击RHEL and derivatives&#xff09; vim /etc/yum.repos.d/nginx.repo [nginx-stable] n…

大模型应用:LangChain-Golang核心模块使用

1.简介 LangChain是一个开源的框架&#xff0c;它提供了构建基于大模型的AI应用所需的模块和工具。它可以帮助开发者轻松地与大型语言模型(LLM)集成&#xff0c;实现文本生成、问答、翻译、对话等任务。LangChain的出现大大降低了AI应用开发的门槛&#xff0c;使得任何人都可以…

Vue引入element-plus-04

我们这次开发是使用vue的脚手架来进行开发,前面我们已经使用过最原生的方式去编写我们的vue的语法,从今天开始就使用vue的脚手架,但是前提是你需要用于node的环境 在我们开始之前&#xff0c;我们至少需要有node npm是什么&#xff1f; npm是一个强大的包管理工具&#xff0c;它…

监控神器vnStat初探

文章目录 一、概述二、官方docker部署1. vnStat守护进程和HTTP服务器在同一容器中运行2. 双容器运行&#xff0c;vnstat容器收集数据&#xff0c;vnstati容器提供web服务 三、修改后的编排文件四、运行结果五、停止监控不感兴趣的网卡 一、概述 vnStat是一款网络流量监测工具&…