Dubbo框架实现RPC远程调用

news2024/12/26 21:45:07

项目背景介绍

这个技术我是直接在项目中运用并且学习的,所以我写笔记最优先的角度就是从项目背景出发

继上一次API网关完成了这个实现用户调用一次接口之后让接口次数增多的操作之后,又迎来了新的问题。

就是我们在调用接口的时候需要对用户进行校验,对调用的接口是否存在进行验证。

从这个需求出发,我们第一反应想到的解决办法是什么,应该是在api-gateway项目中也来引入一下这个数据库配置,包括三层架构重新写一轮,包括实体类也需要引入一下

这样做的问题其实很明显,就是代码很冗余,如果以后微服务的模块开发多了,那每个模块都需要引入这个数据库巴拉巴拉一堆配置,这就很麻烦,也不易于维护

所以从实际需求出发,我们就需要用到这个微服务中的远程调用的操作(我感觉对于微服务来说这其实应该是一个很基础的操作,但是我写这个项目的时候,微服务还没学到远程调用)

首先我们可以先来简单想一下这个我们如果想在其它项目中使用其它项目的方法,我们可以怎么做

  1. 我们可以用笨办法,直接cv,然后搭环境配依赖
  2. HTTP 请求(提供一个接口,供其他项目调用)(这个方式我没有用过,看鱼皮的笔记先进行一个记录)
  3. 第三个就是RPC
  4. 第四个其实我们也肯定知道,就是打个jar包,这也有点类似于自定义start的方式

根据项目背景我们就可以想到用RPC和Dubbo框架来实现远程调用的方式了。


简单的概念介绍:

什么是RPC?

作用:像调用本地方法一样调用远程方法。

和直接 HTTP 调用的区别:

  1. 对开发者更透明,减少了很多的沟通成本。
  2. RPC 向远程服务器发送请求时,未必要使用 HTTP 协议,比如还可以用 TCP / IP,性能更高。(内部服务更适用)

什么理解这个像调用本地方法一样调用远程方法,就是比如我们在我们的项目中的三层架构,controller直接调用mapper那样方便。

远程调用的流程:

为什么可以远程调用呢,

首先我们明白调用,肯定是有一方提供方法,一方调用你提供的方法

这里就会有三个角色,提供者调用方和注册中心

提供者根据地址先将方法注册到注册中心,

注册中心进行一个存储,

然后调用方也根据地址去注册中心里面去取你注册到里面的方法

这里的注册中心我用的是后面会记录的nacos。

什么是Dubbo框架:

这个是官方的基于springboot的案例3 - 基于 Spring Boot Starter 开发微服务应用 | Apache Dubbo

这里也有点坑,官方的这个案例里面用的是nacos,如果你没有下载和开启nacos啊,直接报错

我搞这个搞了半天才发现要下载nacos 

Dubbo 是一个高性能的Java RPC(远程过程调用)框架,最初由阿里巴巴开发并开源。它主要用于构建分布式服务,可以让服务提供者和消费者之间进行高效、安全的通信。

我们简单理解为就是封装了一些方法让我们可以非常简单的使用这个RPC进行远程调用。

 nacos注册中心:

Nacos 快速开始 | Nacos 官网

官网上有详细步骤:

1:详细上来说就是下载解压压缩包

2:注册服务并用startup.cmd -m standalone命令开启,注意这里开发需要到nacos的bin目录下打开cmd输入startup.cmd -m standalone开启

 3:第三步我们就可以照着这上面的网址进行访问了

进入之后,如果我们有注册方法的话,这里就会有提示。

4:根据Dubbo框架的官方文档来使用nacos注册中心:

Nacos | Apache Dubbo

总体说起来就是、

引入对应依赖(包括dubbo的依赖和nacos的依赖)

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>3.0.9</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>2.1.0</version>
        </dependency>

在配置文件中配置

# 以下配置指定了应用的名称、使用的协议(Dubbo)、注册中心的类型(Nacos)和地址
dubbo:
  application:
    # 设置应用的名称
    name: dubbo-springboot-demo-provider
  # 指定使用 Dubbo 协议,且端口设置为 -1,表示随机分配可用端口
  protocol:
    name: dubbo
    port: -1
  registry:
    # 配置注册中心为 Nacos,使用的地址是 nacos://localhost:8848
    id: nacos-registry
    address: nacos://localhost:8848

这些东西直接复制官网就行。

注意点:

这些配置和依赖在provider引入了,在consumer也需要引入,并且需要保持一致


讲完了一些基础概念,下面上一个Demo演示流程

先用Demo会更好入门会更好一点,我刚刚自己重新看了一遍流程,我自己在项目中将需要远程调用的方法抽象成了一个公共模块,所以,感觉理解起来会有点难。

实战:

Demo案例:

在实战之前我们需要引入上面的依赖

并且在springboot的启动类上加上

@EnableDubbo
@SpringBootApplication
@MapperScan("com.yupi.project.mapper")
@EnableDubbo
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

provider和consumer的启动类都要加

首先我们从远程调用的过程入手。

我们首先需要一个provider来提供方法

这里的提供者是master这个项目

我们在这个项目中新建了一个软件包叫provider,里面很简单就是一个接口来规定调用的方法

一个实现这个接口的实现类来重写里面的方法。

import java.util.concurrent.CompletableFuture;

public interface DemoService {

    String sayHello(String name);

    String sayHello2(String name);

    default CompletableFuture<String> sayHelloAsync(String name) {
        return CompletableFuture.completedFuture(sayHello(name));
    }

}
package com.yupi.project.provider;


import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcContext;

@DubboService
public class DemoServiceImpl implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello " + name;
    }

    @Override
    public String sayHello2(String name) {
        return "ljh";
    }


}

很简单两个方法:输出hello和一个字符串ljh


根据流程下一步得到注册中心了:

看一下注册中心里面的信息:


下一步就是consumer了 

consumer就是这里的gateway项目:

首先我同样创建了一个名字一样的软件包,里面也有一个DemoService来接收这个提供者提供的方法。

package com.yupi.project.provider;

import java.util.concurrent.CompletableFuture;

public interface DemoService {

    String sayHello(String name);

    String sayHello2(String name);

    default CompletableFuture<String> sayHelloAsync(String name) {
        return CompletableFuture.completedFuture(sayHello(name));
    }

}

没有看错和上面的provider就是一模一样的。

调用该接口中的方法实在启动类中

这里放在启动类没有什么特殊含义,单纯就是懒得再开一个软件包而已

@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class})
@EnableDubbo
@Service
public class ApiSpringcloudgatewayApplication {

    @DubboReference
    private DemoService demoService;

    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(ApiSpringcloudgatewayApplication.class, args);
        ApiSpringcloudgatewayApplication application = context.getBean(ApiSpringcloudgatewayApplication.class);
        String result = application.doSayHello("world");
        String result2 = application.doSayHello2("world");
        System.out.println("result: " + result);
        System.out.println("result: " + result2);
    }

    public String doSayHello(String name) {
        return demoService.sayHello(name);
    }

    public String doSayHello2(String name) {
        return demoService.sayHello2(name);
    }

    @Bean
    public GlobalFilter customFilter() {
        return new CustomGlobalFilter();
    }
}

我们从上到下分析,首先加上@EnableDubbo这个需要记得

    @DubboReference
    private DemoService demoService;

我觉得着有点像spring的依赖注入,但可能这个是Dubbo框架提供的赋值的方式。

ConfigurableApplicationContext context = SpringApplication.run(ApiSpringcloudgatewayApplication.class, args);
        ApiSpringcloudgatewayApplication application = context.getBean(ApiSpringcloudgatewayApplication.class);

这两行就是获取spring的bean工厂,之前在研究spring的源码的时候,就有了解过,bean工厂

然后获取了bean工厂之后再从工厂里面获取对应的bean对象。

接着就是调用provider提供的方法了。

输出结果:

接下来就是项目实战了,这个项目背景就是API开发平台的项目。

项目实战:

首先我在上面介绍的时候有提到,我对这个公共模块进行了抽取。

为什么会想到这个呢

我们仔细看上面的Demo案例,

我们的DemoService是不是在provider和consumer都写了一次。

我们基于这个操作,我们是不是就可以想到我们可以将这个模块抽取出来

再者,抽取公共模块也可以更好的就是规范我们的业务流程。

所以在项目实战之前

我们先抽取一下公共模块

抽取公共模块并在provider中提供接口:
抽象公共模块的步骤

1:想清楚什么方法需要抽象,或者说什么方法是公共,还有其它模块是需要使用的
2:想清楚实体类规整到公共模块还是原来的单体架构模块
3:打包引入依赖,引入依赖之后,需要照着公共的接口创建实现改接口的实体类

根据上面的步骤(其实这个步骤并不是什么规范步骤,就是我自己在抽取的时候遇到了问题,后面复盘总结起来的步骤)

先说一下,一般微服务抽取公共项目的名称都叫什么comon,所以我这里也取名叫api-common

想一下,我们需要抽取什么?
第一个就是我们在项目中需要其它项目引用的方法:

那得结合一下我们的业务

我们在网关的过滤器中,我们需要

1:对用户进行API签名的认证,这个的具体步骤是根据用户在请求头中的accessKey从数据库中查出用户的secretKey,然后结合用户的id+secretKey计算sign,和请求头中发过来的sign进行比对校验。具体更详细步骤在API网关认证哪一章有具体讲

2:查询接口是否存在,这个就是根据接口id到数据库中进行查询

3:调用次数+1,这个就是到UserInterfaceInfo中讲totalNum+1,讲LeftNum-1即可

分析了业务之后,发现我们的需要的方法:


/**
 * 内部接口信息服务
 *
 */
public interface InnerInterfaceInfoService {

    /**
     * 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数)
     */
    InterfaceInfo getInterfaceInfo(String path, String method);
}
/**
 * 内部用户接口信息服务
 *
 *
 */
public interface InnerUserInterfaceInfoService {

    /**
     * 调用接口统计
     * @param interfaceInfoId
     * @param userId
     * @return
     */
    boolean invokeCount(long interfaceInfoId, long userId);
}
import com.yupi.model.entity.User;

/**
 * 内部用户服务
 *
 */
public interface InnerUserService {

    /**
     * 数据库中查是否已分配给用户秘钥(accessKey)
     * @param accessKey
     * @return
     */
    User getInvokeUser(String accessKey);
}
第二个就是实体类

第二个就是上面这三个接口中要用到的实体类

想清楚实体类规整到公共模块还是原来的单体架构模块

这个点就是我在抽取的时候没搞清楚的地方了,就是我有的实体类在我master项目中,

有的在common公共模块中

我后面就索性全部改到common公共模块中

并且将master项目中的mapper和mapper,xml都进行了修改

打包引入依赖,引入依赖之后,需要照着公共的接口创建实现改接口的实体类

这里的打包引入依赖就和自定义starter一样。

照着公共的接口创建实现改接口的实体类:

这里当然也可以调用原来的service的方法

不过为了规范一点可以新建一个inner软件包

@DubboService//@DubboService注解是加在你想要暴露出去的方法上的
public class InnerInterfaceInfoServiceImpl implements InnerInterfaceInfoService {

    @Resource
    private InterfaceInfoMapper interfaceInfoMapper;

    @Override
    public InterfaceInfo getInterfaceInfo(String url, String method) {
        if (StringUtils.isAnyBlank(url, method)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        LambdaQueryWrapper<InterfaceInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        LambdaQueryWrapper<InterfaceInfo> eq = lambdaQueryWrapper.eq(InterfaceInfo::getUrl, url)
                .eq(InterfaceInfo::getMethod, method);
        InterfaceInfo interfaceInfo = interfaceInfoMapper.selectOne(eq);
        return interfaceInfo;
    }

}
/**
 * 内部用户接口信息服务实现类
 *
 */
@DubboService
public class InnerUserInterfaceInfoServiceImpl implements InnerUserInterfaceInfoService {

    @Resource
    private UserInterfaceInfoService userInterfaceInfoService;

    @Override
    public boolean invokeCount(long interfaceInfoId, long userId) {
        return userInterfaceInfoService.invokecount(interfaceInfoId,userId);
    }
}
/**
 * 内部用户服务实现类
 *
 */
@DubboService
public class InnerUserServiceImpl implements InnerUserService {

    @Resource
    private UserMapper userMapper;


    @Override
    public User getInvokeUser(String accessKey) {
        if (StringUtils.isAnyBlank(accessKey)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("accessKey", accessKey);
        return userMapper.selectOne(queryWrapper);
    }
}

注意要在这些类上加上 @DubboService注解,注册到nacos注册中心中


等抽取完公共模块之后,剩下的步骤就很简单了

毕竟RPC的宣称就是像调用本地方法一样调用远程方法

 consumer调用远程接口:
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {


    @DubboReference
    private InnerUserService innerUserService;

    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;
    
    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;

    private static ArrayList<String> IP_WHITE_LIST = new ArrayList<>();

    private static final String INTERFACE_HOST = "http://localhost:8123";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1:请求日志
        //2:访问控制 -黑白名单
        //3:用户鉴权
        final HttpHeaders headers = request.getHeaders();
        final String accessKey = headers.getFirst("accessKey");
        String random = headers.getFirst("random");
        String timestamp = headers.getFirst("timestamp");
        String sign = headers.getFirst("sign");
        String body = headers.getFirst("body");
        final User invokeUser = innerUserService.getInvokeUser(accessKey);
        if(invokeUser==null){
            //user==null,说明这个accessKey根本没有被分配给用户
            throw new RuntimeException("accessKey不存在");
        }
        if(Long.parseLong(random) > 10000){
            handleNoAuth(response);
        }
        //通过时间戳判断是否过期
        long currentTimeMillis = System.currentTimeMillis()/1000;
        long differenceInSeconds = (long) (currentTimeMillis/1000 - Long.parseLong(timestamp));
        if(differenceInSeconds > 300){
            handleNoAuth(response);
        }
        final String secretKey = invokeUser.getSecretKey();
        String flag = SingUtils.getSign(body, secretKey);
        if(!flag.equals(sign)){
            handleNoAuth(response);
        }
        final Long userId = invokeUser.getId();
        //4:从数据库中查询模拟接口是否存在
        System.out.println(path);
        System.out.println(method);
        InterfaceInfo interfaceInfo = innerInterfaceInfoService.getInterfaceInfo(path,method);
        if(interfaceInfo==null){
            handleNoAuth(response);
        }
        final Long interfaceInfoId = interfaceInfo.getId();
        //5:请求转化,调用模拟接口
        final Mono<Void> filter = chain.filter(exchange);//在这个方法中调用次数+1
        return handleResponse(exchange,chain,42L,userId);
    } 
}

源代码很多

流行需要调用远程方法的部分

在调用之前,我们需要先注入一下,这三个接口

就和之前的DemoService一样 

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

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

相关文章

苍穹外卖day12(day15)---数据统计——Excel报表(项目完结)

工作台 接口设计 新建admin/WorkSpaceController /*** 工作台*/ RestController RequestMapping("/admin/workspace") Slf4j Api(tags "工作台相关接口") public class WorkSpaceController {Autowiredprivate WorkspaceService workspaceService;/*** 工…

★WIN10计算器程序员版的使用说明(详细)

主界面 拉动边框的角&#xff1a; 1.进制转换 HEX(hexadecimal)&#xff1a;显示十六进制&#xff0c;DEC(decimal)&#xff1a;显示十进制&#xff0c;OCT(octonary)&#xff1a;显示八进制&#xff0c;BIN(binary):显示二进制 例如&#xff1a; 选中HEX 0~9&#xff0c;A…

Genymotion adb shell

Genymotion 账户是 qq邮箱 参考 Ubuntu 20.04 安装 Android 模拟器 Genymotion https://www.zzzmh.cn/post/553cd96d4e47490a90b3302a76a93c0d Genymotion adb shell adb shell C:\Program Files\Genymobile\Genymotion\tools>adb shell lsusb Bus 001 Device 001: ID …

【Bug分析】Keil报错:error: #18:expected a “)“问题解决

【Bug分析】Keil报错&#xff1a;error: #18:expected a “&#xff09;”问题解决 前言bug查找bug解决方法小结 前言 keil编译时出现一个问题&#xff0c;缺少一个右括号。然后仔细查看代码&#xff0c;并没有括号缺失。 如下&#xff0c;代码括号正常。 bug查找 站内文章…

多机部署, 负载均衡-LoadBalance

目录 1.负载均衡介绍 1.1问题描述 1.2什么是负载均衡 1.3负载均衡的一些实现 服务端负载均衡 客户端负载均衡 2.Spring Cloud LoadBalancer 2.1快速上手实现负载均衡 2.2负载均衡策略 自定义负载均衡策略 3.服务部署&#xff08;Linux&#xff09; 3.1服务构建打包…

企业发展与智能化改造:从传统到现代的转型之路

引言 在当今全球化和数字化快速发展的背景下&#xff0c;企业面临着前所未有的竞争压力和市场变化。传统的商业模式已难以满足不断变化的市场需求和客户期望&#xff0c;迫使企业探索新的增长路径和创新方式。在这种情况下&#xff0c;智能化改造成为了企业发展的关键战略之一。…

springboot“云茶”新零售系统-计算机毕业设计源码25947

摘 要 科技的发展、企业的改革和管理技术的提高&#xff0c;中国很多中小型企业面临库存管理的时效性、准确性等难题。以前在网站上&#xff0c;企业的信誉难以认证、网络法律法规不健全、物流不发达等一系列的原因&#xff0c;限制了网上交易发展的步伐&#xff0c;进入21世纪…

【OpenCV C++20 学习笔记】拉普拉斯(Laplace)二阶求导-边缘检测

拉普拉斯二阶求导 原理拉普拉斯算子(Laplacian Operator) API实例 原理 在OpenCV中&#xff0c;Sobel算法可以对图片中的值求一阶导数&#xff0c;从而计算出图片中的边缘线。其原理如下面的示意图&#xff1a; 那么&#xff0c;如果再求一次导数的&#xff0c;即求二阶导数&…

软信天成:国内企业需要什么样的国产主数据管理平台?(一)

主数据管理作为政企数据治理的基石&#xff0c;承担着维护、治理关键业务实体信息&#xff08;客户、产品、供应商、员工等核心数据&#xff09;的重任&#xff0c;确保其在整个组织内的一致性、完整性和准确性。 在当下的环境中&#xff0c;企业正面临诸多考验&#xff1a;一…

AQS为什么采用双向链表?

单链表和双链表的区别 首先我们要先搞清楚单链表和双链表之间的区别&#xff1a; 单链表每个节点只包含一个指向下一个节点的指针&#xff0c;因此它的遍历只能是单向的&#xff0c;并且插入和删除需要遍历链表找到前一个节点&#xff08;比如a->b->c->d&#xff0c…

录屏新选择!Bandicam来袭,满足你所有录制需求,好用到爆!

前言 嘿&#xff0c;各位小伙伴们&#xff0c;你们的小江湖又来啦&#xff01;今天&#xff0c;我要给大家带来一个超级神秘又酷炫的软件介绍&#xff0c;保证让你们大开眼界&#xff0c;甚至可能改变你们日常记录生活、工作学习的方式哦&#xff01; 想象一下&#xff0c;有没…

硬件模拟的基本原理

具体来说&#xff0c;这种设计方法减少了集成电路 (IC) 设计和开发的设计迭代次数&#xff0c;并且广泛适用于所有电力电子设计。我详细介绍了我在快速上市 IC 开发方面的经验&#xff0c;并将该方法与其他旨在缩短产品开发时间的技术进行了对比。 产品开发流程 图 1&#xff…

【云原生】Kubernetes中如何对etcd进行备份和还原,确保k8s集群的稳定和健壮

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

互联网应用主流框架整合之Redis基础

Redis简介 在传统的Java Web项目中存储数据&#xff0c;主要是用关系型数据库&#xff0c;如MySQL、SqlServer、Oracle等等&#xff0c;这些数据库的数据持久化在磁盘上&#xff0c;而磁盘的读写速度比较慢&#xff0c;而一般的管理系统上又不存在瞬间的高并发场景&#xff0c…

英语疑惑之在树上

在树上&#xff0c;on the tree&#xff0c;我想这个这个介词到底该用in&#xff0c;on or other prep。本来我以为跟on the roof差不多&#xff0c;就是在物体表面&#xff0c;可是百度了一下&#xff0c;可以有on the tree, in the tree, by the tree, at the tree, under th…

vs+qt项目转qt creator

1、转换方法 打开vs工程&#xff0c;右键项目&#xff0c;Qt->Create Base .pro File 后面默认OK 如果工程有include和lib路径需要配置&#xff0c;则转换后的工程&#xff0c;需要修改pro文件 2.修改pro文件 例如转换后的工程如下&#xff1a; 修改后 # ------------…

掌握 R 软件在 Windows 及 Mac 上的下载安装流程

临床数据科学是一门综合利用统计学、数据挖掘、机器学习和信息技术等方法&#xff0c;对临床数据进行分析和解释的学科。它的目标是从海量的临床数据中挖掘出有价值的信息&#xff0c;以支持医疗决策&#xff0c;提高医疗质量&#xff0c;降低医疗成本&#xff0c;并促进医学研…

springboot高校无人车配送系统-计算机毕业设计源码90207

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 修改信息流程 2.2.6 删除信息流程 2.3 系统功能…

云计算专业创新人才培养体系的探索与实践

一、引言 近年来&#xff0c;云计算技术凭借其高效、灵活、可扩展等优势&#xff0c;在各行各业得到广泛应用。为满足社会对云计算人才的需求&#xff0c;职业院校纷纷开设云计算相关专业&#xff0c;并积极探索创新人才培养体系。本文基于职业院校的特点&#xff0c;构建了“…

【wsl】wsl + vscode 中使用 typora 打开 markdown 文件

vscode 连接好wsl 使用Open in External App 一个五星好评的插件Open in External App则可以在vscode中用typora打开md文件&#xff0c;不仅如此&#xff0c;还有设定其他应用打开相应的文件&#xff0c;比如chrome打开html。插件食用方法也比较简单&#xff0c;安装后&#…