手写简单的RPC框架(一)

news2024/10/7 5:21:11

一、RPC简介

1、什么是RPC

RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是想调用本地方法一样调用远程的过程。

2、为什么会出现RPC

RPC的概念与技术早在1981年由Nelson提出。1984年,Birrell和Nelson把其用于支持异构型分布式系统间的通讯。Birrell的RPC 模型引入存根进程( stub) 作为远程的本地代理,调用RPC运行时库来传输网络中的调用。Stub和RPC runtime屏蔽了网络调用所涉及的许多细节,特别是,参数的编码/译码及网络通讯是由stub和RPC runtime完成的,因此这一模式被各类RPC所采用。由于分布式系统的异构性及分布式计算模式与计算任务的多样性,RPC作为网络通讯与委托计算的实现机制,在方法、协议、语义、实现上不断发展,种类繁多,其中SUN公司和开放软件基金会在其分布式产品中所建立和实用的RPC较为典型。
在SUN公司的网络文件系统NFS及开放网络计算环境ONC中,RPC是基本实现技术。OSF酝酿和发展的另一个重要的分布式计算软件环境DCE也是基于RPC的。在这两个系统中,RPC既是其自身的实现机制,又是提供给用户设计分布式应用程序的高级工具。由于对分布式计算的广泛需求,ONC和DCE成为Client/Server模式分布式计算环境的主流产品,而RPC也成为实现分布式计算的事实标准之一。
摘抄自:百度文库https://baike.baidu.com/item/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8/7854346?fromtitle=RPC&fromid=609861&fr=aladdin

同时微服务的出现也进一步促进了RPC的发展,我们知道在微服务当道的今天。众多个微服务之间需要合作才能完成业务,例如“订单服务”需要调用“用户服务”的某个接口,这个场景就非常适合RPC(当然了用Http请求也是可以的)

二、RPC需要解决的问题

1、 通信协议?

所谓的协议,可以认为是一种约定,即服务端和客户端定义好数据是如何解析的。由于在网络传输的过程都是比特流(01010101),所以双方需要约定好如何解读这些数据。

2、服务提供方和调用方如何进行通信

通常RPC框架,双方的通信是基于TCP协议的。

3、调用方如何知道服务提供方

方法有很多,
1、比如最简单的服务的提供方将服务信息写入数据库,调用每次去查询。当然这个方案显然是不可能的,抛开性能问题不谈,绝大多数场景这两者并不能使用同一个数据库。
2、利用一些中间件,比如ZK就非常适合。在ZK中存储服务端暴露的信息,同时客户端可以通过添加监听器来感知服务端信息的变化。

4、如何高效的序列化和反序列化

这里引用一下其他文章:https://zhuanlan.zhihu.com/p/367295821

三、手写一个简单的PRC框架

在这里插入图片描述

上图是一个简单的RPC调用架构图,当然了实际上会更复杂。结合小结说的RPC要解决的问题,我们罗列一些一个PRC框架所需要的技术。
1、我们希望服务端(生产者)的信息不要写死,客户端(消费者)可以从某个地方动态的获取到服务端的消息,同时服务端如果宕机了,客户端可以感知到,从而不再去调用宕机的服务端接口。当然可以实现这种功能的技术有很多,不过首先想到的就是Zookeeper。所以我们第一个技术选型将Zookeeper作为注册中心。
2、既然PRC是远程调用,那么肯定离不开网络。比如我们可以用Http去实现我们的远程调用,不过相对来说性能会差一些。所以考虑到性能方面,我们可以自己写一个网络模块。提到网络通信,我们很自然的想到了Netty,Netty作为一个使用简单的NIO的高性能框架,可以快速编写服务端程序。
3、我们知道网络通信的过程中,我们的数据都是二进制,0101010的形式,但是在咱们业务上都是以具体的实体类来使用。所以我们需要有一些列的编解码器,根据一定的协议(规范)来解析网络的数据流,当我们根据协议拿到了数据流后,在业务上我们是不能直接使用的所以需要进行返序列化。提到返序列化,第一个想到的就是JDK自带的序列化,JDK自带的序列化有一定的局限性:1、效率相对较低;2、不支持跨平台。所以不使用JDK自带的序列化工具,这里我们使用protobuf 这个框架来实现序列化和反序列化。
4、由于当下多是Spring或者Springboot的项目,所以我们也使用Sping作为项目容器。

小结:

至此手写一个简单的RPC框架所需要的技术点已经够了,话不多说让我们开始coding吧。

四、搭建项目框架

1、项目分层

在PRC中有服务端(生产者)和客户端(消费者)这两种角色,所以基于这个考虑,我们把项目进行拆分。总共分为3个模块:

  • Server模块:主要负责将暴露的接口信息上报到注册中心中供消费者调用。
  • Core模块:PRC核心功能、包括网络IO、编解码器、缓存等等一些列功能。
  • Client模块:负责生成代理,调用实际接口,并处理响应等。

整体结构如下
image.png

2、POM文件

TIPS:一开始依赖并非完整的,随着项目的开发逐步完善。
1、父工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.cmxy</groupId>
  <artifactId>yrpc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>yrpc-client</module>
    <module>yrpc-core</module>
    <module>yrpc-server</module>
  </modules>
  <packaging>pom</packaging>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring.version>5.1.4.RELEASE</spring.version>
    <cglib.version>3.1</cglib.version>
    <netty.version>4.1.42.Final</netty.version>
    <zkclient.version>0.1</zkclient.version>
    <objenesis.version>2.6</objenesis.version>
    <protostuff.version>1.6.0</protostuff.version>
    <slf4j.log4j.version>1.7.25</slf4j.log4j.version>
    <guava.version>19.0</guava.version>
    <reflections.version>0.9.10</reflections.version>
    <beanutils.version>1.9.3</beanutils.version>
    <commons.lang3.version>3.6</commons.lang3.version>
    <commons.collections.version>3.2.2</commons.collections.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <!-- zookeeper客户端组件依赖 -->
      <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>${zkclient.version}</version>
      </dependency>
      <!-- Netty 组件依赖 -->
      <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>${netty.version}</version>
      </dependency>
      <!-- 实例化组件依赖 -->
      <dependency>
        <groupId>org.objenesis</groupId>
        <artifactId>objenesis</artifactId>
        <version>${objenesis.version}</version>
      </dependency>
      <!-- protostuff 核心依赖 -->
      <!--基于google protobuf的工具类 protostuff-->
      <dependency>
        <groupId>io.protostuff</groupId>
        <artifactId>protostuff-core</artifactId>
        <version>${protostuff.version}</version>
      </dependency>
      <dependency>
        <groupId>io.protostuff</groupId>
        <artifactId>protostuff-runtime</artifactId>
        <version>${protostuff.version}</version>
      </dependency>
      <!-- spring 上下文组件依赖 -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
      </dependency>
      <!-- 日志组件依赖 -->
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j.log4j.version}</version>
      </dependency>
      <!-- Google Guava 核心扩展库-->
      <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>${guava.version}</version>
      </dependency>
      <!-- Apache 集合 扩展依赖 -->
      <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>${commons.collections.version}</version>
      </dependency>
      <!-- Apache lang 包扩展依赖 -->
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>${commons.lang3.version}</version>
      </dependency>
      <!-- Apache BeanUtils 辅助工具依赖 -->
      <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>${beanutils.version}</version>
      </dependency>
      <!-- cglib动态代理依赖-->
      <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>${cglib.version}</version>
      </dependency>
      <!-- Java元数据分析反射依赖-->
      <dependency>
        <groupId>org.reflections</groupId>
        <artifactId>reflections</artifactId>
        <version>${reflections.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>


  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.22</version>
      <optional>true</optional>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <encoding>UTF-8</encoding>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

2、Core模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>yrpc</artifactId>
    <groupId>com.cmxy</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>yrpc-core</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- spring 上下文组件依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
    </dependency>
    <!-- Netty 通讯依赖-->
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
    </dependency>
    <!-- zookeeper客户端依赖 -->
    <dependency>
      <groupId>com.github.sgroschupf</groupId>
      <artifactId>zkclient</artifactId>
    </dependency>
    <!--基于google protobuf的工具类 protostuff-->
    <dependency>
      <groupId>io.protostuff</groupId>
      <artifactId>protostuff-core</artifactId>
    </dependency>
    <dependency>
      <groupId>io.protostuff</groupId>
      <artifactId>protostuff-runtime</artifactId>
    </dependency>
    <!-- Apache 集合 扩展依赖 -->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
    </dependency>
    <!-- Apache lang 包扩展依赖 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
    <!-- Apache BeanUtils 辅助工具依赖 -->
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
    </dependency>
    <!-- Google Guava 核心扩展库-->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
    </dependency>
    <!-- 日志组件依赖 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </dependency>
  </dependencies>

</project>

3、Server和Client模块只需要引入Core模块即可(目前是这样)

3、开发Server模块

首先我们要明确各个模块的作用,然后从由简到难的开发。相比之下Server端会比较简单。理由如下:
服务端只需暴露接口,处理接受请求处理响应基本上就可以了。但是作为客户端来说,需要处理的就比较多了,维护服务端提暴露的服务列表(本地缓存)、负载均衡(简单的来说就是服务发现)、生成代理类、失败重试等等一系列,所以我们先开发服务端。
在开发之前我们需要罗列出服务端要做的事情:

  1. 扫描需要暴露的接口
  2. 将暴露的接口保存到注册中心
  3. 处理网络连接,收到请求然后处理响应。

简单的来说RPC 服务端最基本的功能就是这几个,接下来我们逐一实现。

通常情况下我们会自定义一个注解,有该注解的接口我们认为是需要提供给外部使用的。所以我们在Core模块中定义一个最简单的注解,名字就叫YRpcService

package com.cmxy.rpc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2023/5/24 10:40
 */

@Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YRpcService {

    /**
     * 等同于@Component的value
     * @return
     */
    @AliasFor(annotation = Component.class)
    String value() default "";

    /**
     * 服务接口Class
     * @return
     */
    Class<?> interfaceClass() default void.class;

    /**
     * 服务接口名称
     * @return
     */
    String interfaceName() default "";

    /**
     * 服务版本号
     * @return
     */
    String version() default "";

    /**
     * 服务分组
     * @return
     */
    String group() default "";

}

由于当下基本上都是Spring环境,所以我们也利用Spring的特性。将该注解也认定是Spring的一个Component。接下来我们编写一个服务端的初始化类(Server模块下)

在这里插入图片描述

package com.cmxy.rpc.server.registry.zk;

import com.cmxy.rpc.annotation.YRpcService;
import com.cmxy.rpc.server.config.zk.RpcServerConfiguration;
import com.cmxy.rpc.server.registry.Registry;
import com.cmxy.rpc.util.IpUtil;
import com.cmxy.rpc.util.SpringApplicationUtil;
import java.util.Map;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2023/5/24 10:50
 */
@Slf4j
public class ZkRegister implements Registry {

    @Resource
    private ServerZKit serverZKit;
    @Resource
    private RpcServerConfiguration rpcServerConfiguration;
    @Resource
    private SpringApplicationUtil springApplicationUtil;


    /**
     * 基于ZK实现的服务注册: 1、扫描所有需要暴露的接口:即携带了YRpcService的接口 2、将接口信息注册到ZK中
     */
    @Override
    public void register() {

        //1、扫描出携带了YRpcService注解的类
        final Map<String, Object> serviceMap = SpringApplicationUtil.getBeanListByAnnotationClass(
            YRpcService.class);
        if (serviceMap.isEmpty()) {
            log.info("暂无需要暴露的接口,结束注册");
            return;
        }
        //创建根目录
        serverZKit.createRootNode();
        //2、将接口信息写入ZK:path:ServiceBean的名称 data:IP+端口号
        //注意这里创建的是临时节点:以确保当前节点不可用的时候ZK上自动删除当前的节点信息
        serviceMap.forEach((beanName, serviceBean) -> {
            //获取当前Bean上的注解,通过注解
            final YRpcService yRpcService = serviceBean.getClass().getAnnotation(YRpcService.class);
            //获取接口
            final Class<?> serviceClass = yRpcService.interfaceClass();
            //创建服务层节点:例如 com.example.service.impl.testImpl
            String serviceName = serviceClass.getName();
            serverZKit.createPersistentNode(serviceName);
            //获取服务器IP
            String ip = IpUtil.getRealIp();
            //获取端口号:注意这里是RPC端端口号,不是服务端的端口号(因为通信是RPC框架)
            Integer port = rpcServerConfiguration.getRpcPort();
            String path = serviceClass.getName();
            //创建临时节点
            serverZKit.createEphemeralNode(serviceName + "/" + ip + ":" + port);
            log.info("服务:{} 注册成功,ip:{} 端口:{}", serviceName, ip, port);
        });
    }
}

代码解释:上述代码是为了将暴露的接口保存到注册中心,步骤如下

  1. 首先在Spring容器中查询出含有YPrcService注解的的类
  2. 创建根节点(根据配置)
  3. 拿到Service后,根据注解上配置的“接口属性”在ZK中创建节点
  4. 拿到当前的IP和端口号,在点不创建的节点下 创建临时节点(为什么是临时节点,上面注释中有)
  5. 完成注册

4、工具类代码

1、ServerZKit

package com.cmxy.rpc.server.registry.zk;

import com.cmxy.rpc.server.config.zk.RpcServerConfiguration;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Zookeeper连接操作接口
 */
@Component
public class ServerZKit {

    @Autowired
    private ZkClient zkClient;

    @Autowired
    private RpcServerConfiguration rpcServerConfiguration;

    /***
     * 根节点创建
     */
    public void createRootNode() {
        boolean exists = zkClient.exists(rpcServerConfiguration.getZkRoot());
        if (!exists) {
            zkClient.createPersistent(rpcServerConfiguration.getZkRoot());
        }
    }

    /***
     * 创建其他节点
     * @param path
     */
    public void createPersistentNode(String path) {
        String pathName = rpcServerConfiguration.getZkRoot() + "/" + path;
        boolean exists = zkClient.exists(pathName);
        if (!exists) {
            zkClient.createPersistent(pathName);
        }
    }

    /***
     * 创建临时节点
     * @param path
     */
    public void createEphemeralNode(String path) {
        String pathName = rpcServerConfiguration.getZkRoot() + "/" + path;
        boolean exists = zkClient.exists(pathName);
        if (!exists) {
            zkClient.createEphemeral(pathName);
        }
    }
}

2、SpringFactory工具

package com.cmxy.rpc.util;

import java.lang.annotation.Annotation;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2023/5/24 10:42
 */
@Component
public class SpringApplicationUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
        SpringApplicationUtil.applicationContext = applicationContext;
    }

    public static Object getBean(String className) {
        return applicationContext.getBean(className);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    /***
     * 获取有指定注解的对象
     * @param annotationClass
     * @return
     */
    public static Map<String, Object> getBeanListByAnnotationClass(Class<? extends Annotation> annotationClass) {
        return applicationContext.getBeansWithAnnotation(annotationClass);
    }


}

3、配置类

package com.cmxy.rpc.server.config.zk;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component
public class RpcServerConfiguration {

    /**
     * ZK根节点名称
     */
    @Value("${rpc.server.zk.root}")
    private String zkRoot;

    /**
     * ZK地址信息
     */
    @Value("${rpc.server.zk.addr}")
    private String zkAddr;


    /**
     * RPC通讯端口
     */
    @Value("${rpc.network.port}")
    private int rpcPort;

    /**
     * Spring Boot 服务端口
     */
    @Value("${server.port}")
    private int serverPort;

    /**
     * ZK连接超时时间配置
     */
    @Value("${rpc.server.zk.timeout:10000}")
    private int connectTimeout;
}

五、小结

本文我们讲述了什么是RPC,以及RPC所解决的问题、需要的技术点。最后我们准备做一个简单的RPC框架,开发了服务端的一部分内容。接下里的我们不断完善这个框架。希望对你有所帮助,未完待续。。。。

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

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

相关文章

【P33】JMeter 临界部分控制器(Critical Section Controller)

文章目录 一、临界部分控制器&#xff08;Critical Section Controller&#xff09;参数说明二、测试计划设计 一、临界部分控制器&#xff08;Critical Section Controller&#xff09;参数说明 可以对指定代码块增加同步锁&#xff0c;确保此代码块由单线程执行&#xff1b;…

【C++】初入C++

认识C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(object orient…

Gap业绩逆转,宝尊电商是如何当好“全球品牌数字商业伙伴”的?

电商永不眠。技术、消费趋势、供应链&#xff0c;任何一个因素都可以引起商业格局的巨变。一些看似普通的事件落到一个品牌身上&#xff0c;往往会带来改变命运的巨大变化。就像今年2月&#xff0c;宝尊官宣已完成对Gap大中华区的收购&#xff0c;到现在&#xff0c;Gap便已在宝…

探究javascript对象和数组的异同,及函数变量缓存技巧

javascript中最经典也最受非议的一句话就是&#xff1a;javascript中一切皆是对象。这篇重点要提到的&#xff0c;就是任何jser都不陌生的Object和Array。 有段时间曾经很诧异&#xff0c;到底两种数据类型用来存储数据有什么不同。于是&#xff0c;我打算探究探究。 一、掌握三…

9. python的if语句

文章目录 一、if结构1.1 比较符号1.1.1 使用比较两个数据是否相等&#xff1a;1.1.2 使用!号比较数据是否不相等1.1.3 使用<号比较数字大小关系1.1.4 使用<号比较数字大小关系1.1.5 使用>号比较数字大小关系1.1.6 使用>号比较数字大小关系 1.2 关键字1.2.1 and关键…

计算机系统漫游

重点理解部分&#xff1a; 系统硬件&#xff1a;对硬件如处理器、存储器、I/O设备有一个基本的认识&#xff0c;理解它们的基本工作原理以及它们是如何协同工作的。Hello&#xff0c;World程序运行的过程&#xff1a;了解一个C程序如何从源代码到最终在计算机上运行的全过程。…

智慧农业大数据平台的“智慧”体现在哪些方面?

看到农业两个字&#xff0c;我们先想起来的是什么&#xff1f;是耕种呢&#xff0c;还是灌溉&#xff1f; 其实&#xff0c;种植业只是狭义上的农业&#xff0c;从广义上讲&#xff0c;农业指包括种植业、林业、畜牧业、渔业、副业五种产业形式。所以&#xff0c;山东仁科智慧…

电脑如何查找重复文件?轻松揪出它!

电脑如何查找重复文件&#xff1f;小编每天要接触各种文档、图片等资料&#xff0c;很多时候下载了一些图片后&#xff0c;我根本记不住&#xff0c;下次看到不错的图片&#xff0c;我又会下载下来&#xff0c;结果就是和之前下载的图片是一样的内容。下载的重复文件多了&#…

如何处理SAP错误:不可能为条目1000 UMB 1000 MR1 2004确立帐户 (附:ChatGPT 如何处理这个问题的?)

SAP财务用户在MR22 进行修改物料价格时&#xff0c;出现了一个报错。报错如下&#xff1a;“不可能为条目1000 UMB 1000 MR1 2004确立帐户”。 Account determination for entry 1000 UMB 1000 MR2 2004 not possible Message No. M8147 Diagnosis The system did not find …

Web安全:代码执行漏洞 测试(防止 黑客利用此漏洞.)

Web安全&#xff1a;代码执行漏洞 测试 攻击者可以通过构造恶意输入来欺骗应用程序执行恶意代码。这种漏洞通常出现在应用程序中使用动态语言(如 PHP、Python、Ruby 等)编写的代码中&#xff0c;因为这些语言允许在运行时动态执行代码。攻击者可以通过构造特定的输入来欺骗应用…

利用canvas画一个时钟

利用canvas画一个时钟 详细步骤 画中心圆点和刻度线 画时针 画分针 画秒针 下面是整体代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body&g…

【亲测有效】idea部署jrebel插件

idea部署jrebel插件 1.背景 最近在维护tomcat项目&#xff0c;工程修改代码后需要rebuild才能更新class文件&#xff0c;进而运行生效。 同事介绍jrebel可以实现热部署&#xff0c;于是接入使用。 2.简介 JRebel是一套JavaEE开发工具。 Jrebel 可快速实现热部署&#xff0c…

尚硅谷Docker实战教程-笔记02【安装docker、镜像加速器配置】

尚硅谷大数据技术-教程-学习路线-笔记汇总表【课程资料下载】视频地址&#xff1a;尚硅谷Docker实战教程&#xff08;docker教程天花板&#xff09;_哔哩哔哩_bilibili 尚硅谷Docker实战教程-笔记01【理念简介、官网介绍、平台入门图解、平台架构图解】尚硅谷Docker实战教程-笔…

c++学习——构造函数和析构函数

当对象产生时&#xff0c;必须初始化成员变量&#xff0c;当对象销毁前&#xff0c;必须清理对象. 初始化用构造函数&#xff0c;清理用析构函数&#xff0c;这两个函数是编译器调用.初始化的作用和析构函数的作用 构造函数点和析构函数的注意 构造函数和析构函数的权限必须是公…

Redis基本介绍 五大数据类型

Redis基本介绍 redis-benchmark性能测试工具 测试如&#xff1a; redis-benchmark -h localhost -p 6379 -c 100 -n 10000000 redis默认有16个数据库 切换数据库和查看数据库大小 &#xff1a; 设置值和取值&#xff1a; >set name chunling >get name >keys…

python+django网上美食菜品订餐系统的设计与实现vue

随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;好吃网线上订餐系统当然也不能排除在外&#xff0c;从美食类型、美食信息的统计和分析&#xff0c;在过程中会产生大量的、各种…

与创新者同行,共享数字时代创新红利-通付盾城市沙龙圆满举行!

5月25日&#xff0c;通付盾城市沙龙在深圳圆满举行。通付盾与多家企业分享了数字安全创新实践-“WAAP解决方案、GPT助力APP合规开发解决方案、UIAM解决方案”&#xff0c;与合作伙伴共话生态&#xff0c;共同起航&#xff0c;共创未来&#xff01; 会上&#xff0c;通付盾面向各…

基于java的篮球论坛系统的设计与实现

背景 过网上调查和搜集数据,我们可以发现篮球论坛管理方面的系统在中并不是相当普及,在篮球论坛管理方面的可以有许多改进。实际上如今信息化成为一个未来的趋势或者可以说在当前现代化的城市典范中,信息化已经成为主流,开发一个篮球论坛系统一方面的可能会更合乎时宜,另一方面…

springboot+vue财务管理系统(java项目源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的财务管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&a…

opencv_c++学习(二十四)

一、积分图像 积分图像是对原图像进行积分操作的算法。如上图左所示&#xff0c;不同颜色代表不同区域。当我们想求取一个像素点的积分值时&#xff0c;我们需要求取该点左上方区域的数据之和&#xff0c;如P0的积分值是浅蓝色区域的数据之和。 P1的积分值为蓝色和橙色区域的数…