diff --git a/README.md b/README.md index 506238f3..836504c2 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,13 @@
----------- +Why is zfoo protocol ? +----------- +- 目前的Java二进制序列化和反序列化速度最快的框架,并且为序列化字节最少的框架 +- 自定义协议格式的私有化序列化框架,同时能够前后兼容,更加安全,防止破解 +- 协议目前原生支持 C++ Java Javascript C# Lua GDScript,可以轻易实现跨平台 +- 单线程的Benchmark测试中,序列化和反序列化速度比Protobuf快50%,比Kryo快100% + Ⅰ. zfoo简介🚩 ----------- @@ -43,7 +50,6 @@ - 性能需求极高的项目,如网站和游戏服务器框架,单服滚服,全球服,直播聊天,IM系统,实时推送 - 节省研发成本的项目,如想节省,开发,部署,运维成本 - 适合作为 **Godot,Unity,Cocos,Webgl,H5** 的后端基础框架,网络通信协议支持 tcp udp websocket http -- 协议目前原生支持 **C++ Java Javascript C# Lua GDScript**,可以轻易实现跨平台 - 喜欢 [KISS法则](https://baike.baidu.com/item/KISS原则/3242383) 的项目 ,简单的配置,优雅的代码 Ⅲ. 详细的教程和完整的工程案例 diff --git a/doc/image/protocol/complex_object.png b/doc/image/protocol/complex_object.png index c961dc94..68ce0f7d 100644 Binary files a/doc/image/protocol/complex_object.png and b/doc/image/protocol/complex_object.png differ diff --git a/doc/image/protocol/normal_object.png b/doc/image/protocol/normal_object.png index 985e0d2d..1a21b162 100644 Binary files a/doc/image/protocol/normal_object.png and b/doc/image/protocol/normal_object.png differ diff --git a/doc/image/protocol/simple_object.png b/doc/image/protocol/simple_object.png index bb8758cc..be652c17 100644 Binary files a/doc/image/protocol/simple_object.png and b/doc/image/protocol/simple_object.png differ diff --git a/protocol/README.md b/protocol/README.md index fe317cfa..f91e7b25 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -1,9 +1,9 @@ ### Ⅰ. 简介 - [zfoo protocol](https://github.com/zfoo-project/zfoo/blob/main/protocol/README.md) - 是目前的Java二进制序列化和反序列化最快的框架,并且为序列化后字节最少的框架 + 是目前的Java二进制序列化和反序列化速度最快的框架,并且为序列化字节最少的框架 - 协议目前原生支持 **C++ Java Javascript C# Lua GDScript**,可以轻易实现跨平台 -- 使用Javassist字节码增强动态生成顺序执行的序列化和反序列化函数,顺序执行的函数可以轻易的被JIT编译以达到极致的性能 +- 协议可以自定义私有协议格式,让你的协议更加安全,支持增加字段和兼容前后版本协议 - 兼容protobuf,支持生成protobuf协议文件,提供从pojo到proto的生成方式 ### Ⅱ. 快速使用 @@ -23,7 +23,7 @@ var packet = ProtocolManager.read(buffer); ### Ⅲ. 性能测试 -- 单线程环境,在没有任何JVM参数调优的情况下速度比Protobuf快20%,比Kryo快40%,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java) +- 单线程环境,在没有任何JVM参数调优的情况下速度比Protobuf快50%,比Kryo快100%,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java) - 线程安全,zfoo和Protobuf的性能不受任何影响,kryo因为线程不安全性能会有所损失,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java) @@ -42,43 +42,33 @@ cpu: i9900k ### Ⅳ. 为什么快 -- 轻量级实现,核心序列化和反序列化代码一千行左右 +- 使用Javassist字节码增强动态生成顺序执行的序列化和反序列化函数,顺序化的函数可以轻易的被JIT编译以达到极致的性能 +- 原生集成netty的高性能ByteBuf - 没有装箱和拆箱,避免了无效GC - 天生线程安全并且无锁化;kryo强制要求每条线程都有自己的一个Kryo实例,这是一个比较重的设计,特别是线程比较多的场景 - 没有反射,没有unsafe操作;对比kryo中使用objenesis导致大量unsafe,而且在Java11中运行会出现警告 -- 优化了int和long的zigzag和varint编码的算法,避免了一些多余的方法调用和位操作 - 扁平化了方法栈的调用深度,数据结构嵌套没有任何性能损失,如List>>;对比kryo和protobuf数据结构嵌套会出现性能损失 -- 使用Javassist字节码增强动态生成顺序执行的序列化和反序列化函数,顺序化的函数可以轻易的被JIT编译以达到极致的性能 -- 其它优点 +- 无漏洞注入风险,只有初始化时会进行字节码增强,后期不会再进行任何字节码的操作 ``` -无漏洞注入风险,只有初始化时会进行字节码增强,后期不会再进行任何字节码的操作 数据压缩体积小,压缩体积比kryo和protobuf都要小;比kryo小是因为kryo需要写入每个对象的注册号 智能语法分析,错误的协议定义将无法启动程序并给出错误警告 提升开发效率,完全支持POJO方式开发,使用非常简单 ``` -### Ⅴ. 待解决的问题 +### Ⅴ. 为什么小 -- 为了代码的优雅,zfoo protocol要求全部的协议类都要继承IPacket,但是可以保证不损失性能的情况下支持不继承IPacket的设计,这个有待继续讨论。 -- 协议类修改 - - 修改字段名称过后无法解析,内部使用字段的名称按照字符串的自然顺序来依次读写的,所以修改名称会导致读写顺序变化导致出现异常 - - 减少字段无法解析,没必要一定要删除一个不需要的字段,所以不考虑这种情况 - - 增加字段无法解析,可以考虑通过版本号去控制,或者增加新的协议类去解决(组合大于继承,协议类应该对扩展开放,对修改关闭) +- 轻量级实现,核心序列化和反序列化代码一千行左右 +- 优化了int和long的zigzag和varint编码的算法,避免了一些多余的方法调用和位操作 +- 数据压缩体积小,压缩体积比kryo和protobuf都要小;比kryo小是因为kryo需要写入每个对象的注册号 +- 智能语法分析,错误的协议定义将无法启动程序并给出错误警告 +- 提升开发效率,完全支持POJO方式开发,使用非常简单 ``` -设计模式六大原则中的开闭原则是对扩展开放,对修改关闭。协议的设计涉及到功能应该也要遵守这个原则。 - 目前的序列化过后对象的大小如下: 简单对象,zfoo包体大小8,kryo包体大小5,protobuf包体大小8 -常规对象,zfoo包体大小547,kryo包体大小594,protobuf包体大小984 -复杂对象,zfoo包体大小2214,kryo包体大小2525,protobuf包体大小5091 - -如果考虑支持修改协议类字段名称,必须让字段的读写顺序可控,这就需要注解来标识字段的顺序(protostuff就是这样做的),但是感觉这样不优雅。 -如果考虑支持字段增加和减少,需要消耗5%左右的性能(预估),并且增加一倍的包体积大小(写入字段的顺序),感觉不是非常划算。 -因为可以通过协议版本号来解决这个问题,所以去支持这样的增删操作动力并不是非常的大。 - -对于服务器来说协议一般有对内和对外的协议,对外protobuf用起来还行,但是服务器的内部调用protobuf用的就少了,用zfoo就可以统一对内和对外的协议。 +常规对象,zfoo包体大小430,kryo包体大小483,protobuf包体大小793 +复杂对象,zfoo包体大小2216,kryo包体大小2528,protobuf包体大小5091 ``` ### Ⅵ. 协议规范 @@ -88,22 +78,32 @@ cpu: i9900k - 协议类必须是简单的javabean,不能继承任何其它的类,但是可以继承接口 - 默认的数据格式支持,无需用户手动注册,[参考类定义](src/test/java/com/zfoo/protocol/packet/ComplexObject.java) - - boolean,byte,short,int,long,float,double,char,String - - Boolean,Byte,Short,Integer,Long,Float,Double,Character,序列化的时候如果null,会给个默认值0(Character默认值为Character.MIN_VALUE) - - int[],Integer[],如果是null,则解析后的为一个长度为0的数组 - - 原生泛型List,Set,Map,反序列化返回类型为HashSet,ArrayList,HashMap,并且空指针安全(返回大小为0的集合) - - List,必须指定泛型类,如果发送的是[1,1,null,1],接收到的是[1,1,0,1] - - List,如果发送的是[obj,obj,null,obj],接收到的是[obj,obj,null,obj],即引用类型序列化之前为null,序列化之后同样为null + - boolean,byte,short,int,long,float,double,char,String + - Boolean,Byte,Short,Integer,Long,Float,Double,Character,序列化的时候如果null,会给个默认值0(Character默认值为Character.MIN_VALUE) + - int[],Integer[],如果是null,则解析后的为一个长度为0的数组 + - 原生泛型List,Set,Map,反序列化返回类型为HashSet,ArrayList,HashMap,并且空指针安全(返回大小为0的集合) + - List,必须指定泛型类,如果发送的是[1,1,null,1],接收到的是[1,1,0,1] + - List,如果发送的是[obj,obj,null,obj],接收到的是[obj,obj,null,obj],即引用类型序列化之前为null,序列化之后同样为null - 不支持的数据格式,因为zfoo会自动识别不支持的类型并且给出错误警告,所以用户不必太关心 - - int[][],二维以上数组,考虑到不是所有语言都支持多维数组 - - List[],Map[],Java语言本身就没有支持泛型类数组 - - List,Map,泛型里面套数组,这种写法看起来比较奇怪,实际使用的地方很少 - - 枚举类,考虑到很多其他语言不支持枚举类,可以用int或者string在代码层面做替换 - - 自定义泛型类XXXClass,泛型类在很多框架中都极易出现性能上和解析上的问题,而且并不是所有语言都支持 - - 循环引用,虽然底层支持循环引用,但是考虑到循环引用带来语义上难以理解,容易出现错误,所以就屏蔽了 + - int[][],二维以上数组,考虑到不是所有语言都支持多维数组 + - List[],Map[],Java语言本身就没有支持泛型类数组 + - List,Map,泛型里面套数组,这种写法看起来比较奇怪,实际使用的地方很少 + - 枚举类,考虑到很多其他语言不支持枚举类,可以用int或者string在代码层面做替换 + - 自定义泛型类XXXClass,泛型类在很多框架中都极易出现性能上和解析上的问题,而且并不是所有语言都支持 + - 循环引用,虽然底层支持循环引用,但是考虑到循环引用带来语义上难以理解,容易出现错误,所以就屏蔽了 -### Ⅵ. 用途 +### Ⅶ. 待解决的问题 -- 通信协议 -- 对象复制,深克隆 +- 为了代码的优雅,zfoo protocol要求全部的协议类都要继承IPacket,但是可以保证不损失性能的情况下支持不继承IPacket的设计,这个有待继续讨论。 + +- 协议类修改 + - 修改字段名称过后无法解析,内部默认使用字段的名称按照字符串的自然顺序来依次读写的(也可以自定义),所以修改名称会导致读写顺序变化导致出现异常 + - 减少字段无法解析,没必要一定要删除一个不需要的字段,所以不考虑这种情况 + - 增加字段,需要加上Compatible,order需要自然增大 + +``` +设计模式六大原则中的开闭原则是对扩展开放,对修改关闭。协议的设计涉及到功能应该也要遵守这个原则,优先增加新的协议而不是修改现有协议。 + +协议类修改,可以考虑通过版本号去控制,或者增加新的协议类去解决(组合大于继承,协议类应该对扩展开放,对修改关闭) +``` diff --git a/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java b/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java index 9a4f5c70..03f61c9d 100644 --- a/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java +++ b/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java @@ -15,10 +15,8 @@ package com.zfoo.protocol; import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.io.ByteBufferInput; -import com.esotericsoftware.kryo.io.ByteBufferOutput; -import com.esotericsoftware.kryo.io.Input; -import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.unsafe.UnsafeInput; +import com.esotericsoftware.kryo.unsafe.UnsafeOutput; import com.google.protobuf.ByteString; import com.google.protobuf.CodedInputStream; import com.google.protobuf.CodedOutputStream; @@ -33,7 +31,6 @@ import io.netty.buffer.UnpooledUnsafeHeapByteBuf; import org.junit.Ignore; import org.junit.Test; -import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -54,8 +51,8 @@ public class SpeedTest { *

* 包体大小: * 简单对象,zfoo包体大小8,kryo包体大小5,protobuf包体大小8 - * 常规对象,zfoo包体大小547,kryo包体大小594,protobuf包体大小984 - * 复杂对象,zfoo包体大小2214,kryo包体大小2525,protobuf包体大小5091 + * 常规对象,zfoo包体大小430,kryo包体大小483,protobuf包体大小793 + * 复杂对象,zfoo包体大小2216,kryo包体大小2528,protobuf包体大小5091 */ @Ignore @Test @@ -140,8 +137,8 @@ public class SpeedTest { public void kryoTest() { var kryo = kryos.get(); - var output = new Output(1024 * 8); - var input = new Input(output.getBuffer()); + var output = new UnsafeOutput(1024 * 8); + var input = new UnsafeInput(output.getBuffer()); // 序列化和反序列化简单对象 long startTime = System.currentTimeMillis();