概述
即使是单机应用,也会有RPC服务;无论http多么普及,总还是有自定义的协议;只要不是服务于一个用户,就要考虑并发。RPC框架正是为解决这类共性问题的,延展开来问题会更多,也需要更多的能力来解决。当然对于RPC框架最小的范围应该有三个能力:理解消息、高效通讯、并发管理。
ICE是一个老牌RPC框架了,应该说要回溯到CORBA,几十年的事情了,使用C++实现,性能的确好。目标是突破语言之间的边界,将程序员从通讯、报文、并发调度上解放出来,去专注业务本身。主要特点有跨语言,协议绑定,可在几乎所有主流语言或者平台上开发和运行。具体,大家可看下官网。
以往工作中有使用C++来运用过,结合NIO特性支撑个几十万链接,处理2万个小尺寸请求轻轻松松。为了与其他的形成对比,这次体验一把Java。重点分析其上手难易程度,重点体验其对消息、通讯和并发的处理能力。给出特定环境下的(通讯、内存、磁盘、CPU的限定、服务端的参数配置等)单机工作效率,记录资源消耗、吞吐量与延迟率。供大家参考。
环境
基于gradle创建工程,然后使用vscode作为编辑器。执行gradle命令来完成测试。电脑配置为8C16G。其他如下:
ICE对于新手入门估计有几个点不够友好,具体如下:
- 下载安装程序。官网给出了github的源码,需要编译。对java来说有点超出了。需要基于既往经验找到下载点(不知道会不会过段时间又有变化):比如windows下的二进制安装包链接是 http://download.zeroc.com/Ice/3.7/Ice-3.7.4.msi
- 官网提供的示例。需要做些配置,不能直接运行通过。比如依赖的设定,等等
- ICE要求入门就得掌握基本的slice语法,用来定义协议。
Hello World
概述下这里的步骤。
- 第一步、创建工程目录,定义目录结构
- 第二步、将gradle跑起来
- 第三步、编写slice文件
- 第四步、编译slice文件,生成对应的slice接口文件(.java)
- 第五步、编写服务端程序
- 第六步、编写客户端程序
- 第七步、编译,运行服务与客户端程序
第一步:创建工程目录,定义目录结构
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服务框架,还有寻址与负载均衡,注册中心、网格管理等。适当花些功夫倒是值得的。
本文暂时没有评论,来添加一个吧(●'◡'●)