专业的编程技术博客社区

网站首页 > 博客文章 正文

RPC框架探索:ICE初体验(rpc框架实现原理)

baijin 2024-08-10 13:44:44 博客文章 11 ℃ 0 评论

概述

即使是单机应用,也会有RPC服务;无论http多么普及,总还是有自定义的协议;只要不是服务于一个用户,就要考虑并发。RPC框架正是为解决这类共性问题的,延展开来问题会更多,也需要更多的能力来解决。当然对于RPC框架最小的范围应该有三个能力:理解消息、高效通讯、并发管理。

ICE是一个老牌RPC框架了,应该说要回溯到CORBA,几十年的事情了,使用C++实现,性能的确好。目标是突破语言之间的边界,将程序员从通讯、报文、并发调度上解放出来,去专注业务本身。主要特点有跨语言,协议绑定,可在几乎所有主流语言或者平台上开发和运行。具体,大家可看下官网。


以往工作中有使用C++来运用过,结合NIO特性支撑个几十万链接,处理2万个小尺寸请求轻轻松松。为了与其他的形成对比,这次体验一把Java。重点分析其上手难易程度,重点体验其对消息、通讯和并发的处理能力。给出特定环境下的(通讯、内存、磁盘、CPU的限定、服务端的参数配置等)单机工作效率,记录资源消耗、吞吐量与延迟率。供大家参考。

环境

基于gradle创建工程,然后使用vscode作为编辑器。执行gradle命令来完成测试。电脑配置为8C16G。其他如下:


ICE对于新手入门估计有几个点不够友好,具体如下:

  1. 下载安装程序。官网给出了github的源码,需要编译。对java来说有点超出了。需要基于既往经验找到下载点(不知道会不会过段时间又有变化):比如windows下的二进制安装包链接是 http://download.zeroc.com/Ice/3.7/Ice-3.7.4.msi
  2. 官网提供的示例。需要做些配置,不能直接运行通过。比如依赖的设定,等等
  3. ICE要求入门就得掌握基本的slice语法,用来定义协议。

Hello World

概述下这里的步骤。

  1. 第一步、创建工程目录,定义目录结构
  2. 第二步、将gradle跑起来
  3. 第三步、编写slice文件
  4. 第四步、编译slice文件,生成对应的slice接口文件(.java)
  5. 第五步、编写服务端程序
  6. 第六步、编写客户端程序
  7. 第七步、编译,运行服务与客户端程序

第一步:创建工程目录,定义目录结构

printer是项目根目录,client存放客户端程序,server存放服务端程序,slice存放接口定义文件。

mkdir printer
cd printer
mkdir client
mkdir server
mkdir slice

第二步、初始化gradle

gradle init
按照提示,选择【basic】-【groovy】


编写gradle脚本,按照两个子项目server、client的方式构建,可参考官网给出的,定义如下。这里只是修改了个别定义,分别用汉语 注释说明,便于跑得更快一点。

// $root/setting.gradle
/*
 * This file was generated by the Gradle 'init' task.
 *
 * The settings file is used to specify which projects to include in your build.
 *
 * Detailed information about configuring a multi-project build in Gradle can be found
 * in the user manual at https://docs.gradle.org/6.7.1/userguide/multi_project_builds.html
 */

rootProject.name = 'printer'
include 'client'
include 'server'


// $root/build.gradle
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This is a general purpose Gradle build.
 * Learn more about Gradle by exploring our samples at https://docs.gradle.org/6.7.1/samples
 */
//
// Install the gradle Ice Builder plug-in from the plug-in portal
//
plugins {
    id 'com.zeroc.gradle.ice-builder.slice' version '1.4.7' apply false
}

  
subprojects {
    //
    // Apply Java and Ice Builder plug-ins to all sub-projects
    //
    apply plugin: 'java'
    apply plugin: 'com.zeroc.gradle.ice-builder.slice'
    

    //
    // Both Client and Server projects share the Printer.ice Slice definitions
   //  参考目录结构,grader相当于在server或client的目录运行,而slice在根目录下,所以应该如此修改 
    //
    slice {
        java {
            files = [file("../slice/Printer.ice")]
        }
    }
 
    //
    // Use Ice JAR files from maven central repository
   // gradle不加点这个东西,跑起来就是龟速
    //
    repositories {
        mavenLocal()
        maven { url "http://maven.aliyun.com/nexus/content/groups/public/"}
        mavenCentral()
        jcenter()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
        maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }  //转换pdf使用
    }
 
    //
    // Both Client and Server depend only on Ice JAR
    // 推荐开发时,使用compile好了,估计初学者都没有配置ICE到classpath环境。使用命令执行,或者类加载器有点过于啰嗦
    // 如果不修改,可参考这类启动命令:
    //  java -Xbootclasspath/a:.\ice-3.7.4.jar  -jar .\server\build\libs\server.jar 
   //  或者将ice-3.7.4.jar 拷贝到server.jar的相同目录
    
    //
    dependencies {
        //implementation  'com.zeroc:ice:3.7.4'
        compile  'com.zeroc:ice:3.7.4'
    }
 
    //
    // Create a JAR file with the appropriate Main-Class and Class-Path attributes
    // 这里改为依赖后获得的是全路径,不利于包在编译后,发布部署的情形。
    // 可改为getName
    jar {
        manifest {
            attributes(
                "Main-Class": project.name.capitalize(),
                //"Class-Path": configurations.runtime.resolve().collect { it.toURI() }.join(' ')
                 "Class-Path":  configurations.runtime.resolve().collect { it.getName() }.join(' ')
            )
        }
    }
}


第三步、编写slice文件,定义接口

这里的示例比较简单,直接写个方法就好。正常开发中,需要对数据类型有一些了解才能满足日常工作需要。

// 文件路径:$root/slice/Printer.ice
module Demo
{
    interface Printer
    {
        void printString(string s);
    }
}

第四步、编译slice文件,生成java接口文件【在本文中,可以省略,因为gradle会自动编译此文件,并放到对应项目的build/generate-src目录下】

# 可以指定输出目录,这样能够让你编写接口实现文件时,会获得更好的提示
slice2java slice\Printer.ice --output-dir .\

这里会生成三个文件,可主要关注Printer.java这个文件。


//
// Copyright (c) ZeroC, Inc. All rights reserved.
//
//
// Ice version 3.7.4
//
// <auto-generated>
//
// Generated from file `Printer.ice'
//
// Warning: do not edit this file.
//
// </auto-generated>
//

package Demo;

public interface Printer extends com.zeroc.Ice.Object
{
    void printString(String s, com.zeroc.Ice.Current current);

    /** @hidden */
    static final String[] _iceIds =
    {
        "::Demo::Printer",
        "::Ice::Object"
    };

    @Override
    default String[] ice_ids(com.zeroc.Ice.Current current)
    {
        return _iceIds;
    }

    @Override
    default String ice_id(com.zeroc.Ice.Current current)
    {
        return ice_staticId();
    }

    static String ice_staticId()
    {
        return "::Demo::Printer";
    }

    /**
     * @hidden
     * @param obj -
     * @param inS -
     * @param current -
     * @return -
    **/
    static java.util.concurrent.CompletionStage<com.zeroc.Ice.OutputStream> _iceD_printString(Printer obj, final com.zeroc.IceInternal.Incoming inS, com.zeroc.Ice.Current current)
    {
        com.zeroc.Ice.Object._iceCheckMode(null, current.mode);
        com.zeroc.Ice.InputStream istr = inS.startReadParams();
        String iceP_s;
        iceP_s = istr.readString();
        inS.endReadParams();
        obj.printString(iceP_s, current);
        return inS.setResult(inS.writeEmptyParams());
    }

    /** @hidden */
    final static String[] _iceOps =
    {
        "ice_id",
        "ice_ids",
        "ice_isA",
        "ice_ping",
        "printString"
    };

    /** @hidden */
    @Override
    default java.util.concurrent.CompletionStage<com.zeroc.Ice.OutputStream> _iceDispatch(com.zeroc.IceInternal.Incoming in, com.zeroc.Ice.Current current)
        throws com.zeroc.Ice.UserException
    {
        int pos = java.util.Arrays.binarySearch(_iceOps, current.operation);
        if(pos < 0)
        {
            throw new com.zeroc.Ice.OperationNotExistException(current.id, current.facet, current.operation);
        }

        switch(pos)
        {
            case 0:
            {
                return com.zeroc.Ice.Object._iceD_ice_id(this, in, current);
            }
            case 1:
            {
                return com.zeroc.Ice.Object._iceD_ice_ids(this, in, current);
            }
            case 2:
            {
                return com.zeroc.Ice.Object._iceD_ice_isA(this, in, current);
            }
            case 3:
            {
                return com.zeroc.Ice.Object._iceD_ice_ping(this, in, current);
            }
            case 4:
            {
                return _iceD_printString(this, in, current);
            }
        }

        assert(false);
        throw new com.zeroc.Ice.OperationNotExistException(current.id, current.facet, current.operation);
    }
}

第五步、编写服务端程序

实现Printer接口,然后编写一个主程序配置好服务即可,分别如下。

// $root/server/src/main/java/PrinterI.java
public class PrinterI implements Demo.Printer
{
    public void printString(String s, com.zeroc.Ice.Current current)
    {
        System.out.println(s);
    }
}
// $root/server/src/main/java/Server.java
public class Server
{
    public static void main(String[] args)
    {

        try(com.zeroc.Ice.Communicator communicator = com.zeroc.Ice.Util.initialize(args))
        {

            com.zeroc.Ice.ObjectAdapter adapter = communicator.createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000");

            com.zeroc.Ice.Object object = new PrinterI();

            adapter.add(object, com.zeroc.Ice.Util.stringToIdentity("SimplePrinter"));

            adapter.activate();

            communicator.waitForShutdown();

        }

    }

}

到这里,服务端程序就已经写完可以独立编译了。

第六步、编写客户端程序

客户端程序只需要实现主程序即可。

public class Client
{
    public static void main(String[] args)
    {
        try(com.zeroc.Ice.Communicator communicator = com.zeroc.Ice.Util.initialize(args))
        {
            com.zeroc.Ice.ObjectPrx base = communicator.stringToProxy("SimplePrinter:default -p 10000");
            Demo.PrinterPrx printer = Demo.PrinterPrx.checkedCast(base);
            if(printer == null)
            {
                throw new Error("Invalid proxy");
            }
            printer.printString("Hello World!");
        }
    }
}

第七步、编译并运行

注意要拷贝ice.3.7.4.jar到server.jar,或者client.jar的目录去。或者通过运行参数指定路径



// java  -Xbootclasspath/a:..\ice-experience\printer\ice-3.7.4.jar -jar server\build\libs\server.jar
// java  -Xbootclasspath/a:..\ice-experience\printer\ice-3.7.4.jar -jar client\build\libs\client.jar
#编译命令如下:
客户端
gradle :client:build
服务端
gradle :server:client

代码

https://github.com/gaussgao/ice-stuty

结束语

ICE的特点是多语言支持,其通过Slice管理协议,这个学习成本略高。另外ICE历史悠久,提供了不仅仅是RPC服务框架,还有寻址与负载均衡,注册中心、网格管理等。适当花些功夫倒是值得的。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表