doc[zfoo]: update document

This commit is contained in:
godotg committed 2022-12-02 20:39:13 +08:00
1 parent 2f75896907
commit 44004d5f93
14 files changed
+965 -265

No files matched your search

+115 -73
View File
@@ -1,117 +1,149 @@
### Ⅰ. 简介
English | [简体中文](./README_CN.md)
### Ⅰ. Introduction
- [zfoo protocol](https://github.com/zfoo-project/zfoo/blob/main/protocol/README.md)
是目前的Java二进制序列化和反序列化速度最快的框架,并且为序列化字节最少的框架
- 协议目前原生支持 **C++ Java Javascript C# Go Lua GDScript**,可以轻易实现跨平台
- 协议可以自定义私有协议格式,让你的协议更加安全,支持增加字段和兼容前后版本协议
- 兼容protobuf,支持生成protobuf协议文件,提供从pojo到proto的生成方式
- proto文件到生成pojo的方式已经有不少jetbrains插件可以做到,可以在idea的plugin中搜索proto
It is currently the fastest framework for binary serialization and deserialization in Java, and has the fewest
serialization bytes
- The protocol is currently natively supported **C++ Java Javascript C# Go Lua GDScript**,It's easy to do cross-platform
- The protocol can customize the private protocol format to make your protocol more secure, and supports adding fields
and being compatible with previous and subsequent protocols
- Compatible with Protobuf, it supports the generation of Protobuf protocol files, and provides a generation method from
POJO to PROTO
- There are already quite a few JetBrains plugins that can do it in the way to generate POJOs, and you can search for
Proto in the plugin of Idea
### Ⅱ. 快速使用
- 环境要求 **JDK 11+**,可以在 **OpenJDK** 和 **Oracle JDK** 无缝切换
- Environmental requirements **JDK 11+**
- protocol是独立项目,不依赖其它项目,可以直接打开,本地install到自己的本地maven仓库,即可单独使用
- Protocol is an independent project, does not depend on other projects, can be directly opened, locally installed to
its own local maven repository, can be used alone
```
// zfoo协议注册,只能初始化一次
// The zfoo protocol is registered and can only be initialized once
ProtocolManager.initProtocol(Set.of(ComplexObject.class, ObjectA.class, ObjectB.class));
// 序列化
// serialization
ProtocolManager.write(byteBuf, complexObject);
// 反序列化
// deserialization
var packet = ProtocolManager.read(buffer);
```
### Ⅲ. 性能测试
### Ⅲ. Performance testing
- 单线程环境,在没有任何JVM参数调优的情况下速度比Protobuf快50%,比Kryo快100%,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java)
- 线程安全,zfoo和Protobuf的性能不受任何影响,kryo因为线程不安全性能会有所损失,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java)
- Single-threaded environment, 50% faster than Protobuf and 100% faster than Kryo without any JVM parameter
tuning,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java)
- Thread safety, the performance of zfoo and Protobuf is not affected in any way, kryo will lose some performance
because of thread insecurity,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java)
- 测试环境
- Test the environment
```
操作系统:win10
system:win10
cpu: i9900k
内存:64g
```
- 单线程测试,横坐标为序列化和反序列化的对象数量,纵坐标为花费的时间(单位毫秒)
- Single-threaded test with abscissa as the number of objects serialized and deserialized and time spent in milliseconds
on the ordinate
![Image text](../doc/image/protocol/simple_object.png)
![Image text](../doc/image/protocol/normal_object.png)
![Image text](../doc/image/protocol/complex_object.png)
### Ⅳ. 为什么快
### Ⅳ. Why fast
- 使用Javassist字节码增强动态生成顺序执行的序列化和反序列化函数,顺序化的函数可以轻易的被JIT编译以达到极致的性能
- 原生集成netty的高性能ByteBuf
- 使用primitive type collection,没有装箱和拆箱,避免了无效GC,性能也足够快
- 天生线程安全并且无锁化;kryo强制要求每条线程都有自己的一个Kryo实例,这是一个比较重的设计,特别是线程比较多的场景
- 没有反射,没有unsafe操作;对比kryo中使用objenesis导致大量unsafe,而且在Java11中运行会出现警告
- 扁平化了方法栈的调用深度,数据结构嵌套没有任何性能损失,如List<Set<Map<>>>;对比kryo和protobuf数据结构嵌套会出现性能损失
- 无漏洞注入风险,只有初始化时会进行字节码增强,后期不会再进行任何字节码的操作
- Use Javassist bytecode to enhance the dynamic generation of serialization and deserialization functions for sequential
execution, and sequential functions can be easily JIT compiled to achieve extreme performance
- Natively integrated with netty's high-performance Byte Buf
- With primitive type collection, there is no boxing and unboxing, invalid GCs are avoided, and the performance is fast
enough
- Inherently thread-safe and lock-free; kryo forces each thread to have its own instance of Kryo, which is a heavy
design, especially in scenarios with many threads
- No reflections, no unsafe operation; The use of objenesis in Kryo resulted in a lot of unsafe, and warnings when
running in Java 11
- Flattening the call depth of the method stack, and there is no performance penalty for nesting data structures, such
as List<Set<Map<>>>; Comparing kryo and protobuf data structure nesting results in a performance penalty
- There is no risk of vulnerability injection, only bytecode enhancement will be performed during initialization, and no
bytecode operations will be performed in the later stage
```
数据压缩体积小,压缩体积比kryo和protobuf都要小;比kryo小是因为kryo需要写入每个对象的注册号
智能语法分析,错误的协议定义将无法启动程序并给出错误警告
提升开发效率,完全支持POJO方式开发,使用非常简单
The data compression volume is small, and the compression volume is smaller than that of Kryo and Protobuf; Smaller than kryo because kryo needs to write the registration number of each object
Intelligent syntax, incorrect protocol definitions will fail to start the program and give an error warning
Improve development efficiency, fully support POJO development, very easy to use
```
### Ⅴ. 为什么小
### Ⅴ. Why small
- 轻量级实现,核心序列化和反序列化代码一千行左右
- 优化了int和long的zigzag和varint编码的算法,避免了一些多余的方法调用和位操作
- 数据压缩体积小,压缩体积比kryo和protobuf都要小;比kryo小是因为kryo需要写入每个对象的注册号
- 智能语法分析,错误的协议定义将无法启动程序并给出错误警告
- 提升开发效率,完全支持POJO方式开发,使用非常简单
- Lightweight implementation, core serialization and deserialization code of about a thousand lines
- Optimized the zigzag and varint encoding algorithms for int and long, avoiding some redundant method calls and bit
operations
- The data compression volume is small, and the compression volume is smaller than that of Kryo and Protobuf; Smaller
than kryo because kryo needs to write the registration number of each object
- Intelligent syntax, incorrect protocol definitions will fail to start the program and give an error warning
- Improve development efficiency, fully support POJO development, very easy to use
```
目前的序列化过后对象的大小如下:
简单对象,zfoo包体大小8,kryo包体大小5,protobuf包体大小8
常规对象,zfoo包体大小430,kryo包体大小483,protobuf包体大小793
复杂对象,zfoo包体大小2216,kryo包体大小2528,protobuf包体大小5091
The current size of the serialized object is as follows:
Simple objects, zfoo package size 8, kryo package size 5, protobuf package size 8
Regular objects, ZFOO package size 430, KRYO package size 483, Protobuf package size 793
For complex objects, ZFOO package size 2216, KRYO package size 2528, and Protobuf package size 5091
```
### Ⅵ. 数据类型
### Ⅵ. Data type
- 默认的数据格式支持,无需用户手动注册,[参考类定义](src/test/java/com/zfoo/protocol/packet/ComplexObject.java)
- Default data format support eliminates the need for users to register
manually.[参考类定义](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<Integer>,必须指定泛型类,如果发送的是[1,1,null,1],接收到的是[1,1,0,1]
- List<XXXClass>,如果发送的是[obj,obj,null,obj],接收到的是[obj,obj,null,obj],即引用类型序列化之前为null,序列化之后同样为null
- Boolean,Byte,Short,Integer,Long,Float,Double,Character,If it is null during serialization, a default value of 0
will be given (the default value of Character is Character.MIN VALUE)
- int[],Integer[],If it is null, it is parsed as an array of length 0
- Native generic List, Set, Map, deserialization return type Hash Set, Array List, Hash Map, and null pointer
safe (returns a collection of size 0)
- List<Integer>,You must specify a generic class if [1,1,null,1] is sent and [1,1,0,1] is received
- List<XXXClass>,If [obj,obj,null,obj] is sent, [obj,obj,null,obj] is received, that is, the reference type is
null before serialization, and it is also null after serialization
- 不支持的数据格式,因为zfoo会自动识别不支持的类型并且给出错误警告,所以用户不必太关心
- int[][],二维以上数组,考虑到不是所有语言都支持多维数组
- List<Integer>[],Map<Integer, Integer>[],Java语言本身就没有支持泛型类数组
- List<int[]>,Map<Integer, Integer[]>,泛型里面套数组,这种写法看起来比较奇怪,实际使用的地方很少
- 枚举类,考虑到很多其他语言不支持枚举类,可以用int或者string在代码层面做替换
- 自定义泛型类XXXClass<T>,泛型类在很多框架中都极易出现性能上和解析上的问题,而且并不是所有语言都支持
- 循环引用,虽然底层支持循环引用,但是考虑到循环引用带来语义上难以理解,容易出现错误,所以就屏蔽了
- Unsupported data formats, because ZFOO automatically recognizes unsupported types and gives error warnings, so users
don't have to care too much
- int[][],Arrays above two dimensions, considering that not all languages support multidimensional arrays
- List<Integer>[],Map<Integer, Integer>[],The Java language itself does not support generic class arrays
- List<int[]>,Map<Integer, Integer[]>,Generics are set in arrays, which looks strange and has few actual uses
- Enumerated classes, considering that many other languages do not support enumerated classes, can be replaced at
the code level with int or string
- Custom generic class XXX Class, <T>generic classes are prone to performance and parsing problems in many
frameworks, and are not supported in all languages
- Circular references, although the underlying support circular references, but considering that circular references
bring semantically difficult to understand and prone to errors, so they are blocked
### Ⅶ. 协议规范
### Ⅶ. Protocol specifications
- 协议类必须是简单的javabean,不能继承任何其它的类,但是可以继承接口
- The protocol class must be a simple javabean, not inheriting from any other class, but can inherit an interface
- 为了防止代码里Object满天飞,避免协议层和po层混用对象造成一些潜在的并发问题,zfoo强制要求协议类必须实现IPacket接口
- In order to prevent objects in the code and avoid some potential concurrency problems caused by the mixing of objects
in the protocol layer and the po layer, zfoo mandates that protocol classes must implement the IPacket interface
```
现在IPacket的接口只是一个标识接口,继承IPacket的设计主要是为了让代码更优雅,容易理解一点,改为只继承Object也并没有很大工作量
Now the interface of IPacket is just an identification interface, inheriting the design of IPacket is mainly to make
the code more elegant and easier to understand, and it is not a lot of work to inherit only Object
继承IPacket的设计还有跨语言层面上的考虑,极大的简化了实现其它语言的序列化和反序列化难度,统一了其它语言的代码实现
The design of inheriting IPacket also has cross-language considerations, which greatly simplifies the difficulty
of serialization and deserialization in other languages and unifies the code implementation of other languages
```
- 协议号定义为short类型是为了减少包体积和内存大小,一个包可以减少2个byte,每个协议的在应用内存也可以降低6byte(protocols + IProtocolRegistration + protocolIdMap)
- The protocol number is defined as a short type to reduce the packet size and memory size, a packet can be reduced by 2
bytes, and the application memory of each protocol can also be reduced by 6 byte(protocols + IProtocolRegistration +
protocolIdMap)
```
一个项目的协议体类很难超过3w,会有工具自动把你的协议号打包的紧凑一点,这样你的协议号就不会超过3w了
It is difficult for a project's protocol body class to exceed 3 w, and there will be tools that automatically package
your protocol number a little more compactly, so that your protocol number will not exceed 3 w
```
- 协议类必须标注协议号,有两种方式
- 第一种使用注解:@Protocol(id = protocolId)
- There are two ways to indicate that the protocol class must be labeled with the protocol number
- The first uses annotations: @Protocol(id = protocolId)
```
@Protocol(id = 104)
public class SimpleObject implements IPacket {
@@ -122,7 +154,8 @@ cpu: i9900k
}
```
- 第二种使用静态常量+接口:这个协议号的值必须和IPacket接口返回的值一样,这样的方式可以微弱的提升一点性能
- The second uses static constants + interfaces: the value of this protocol number must be the same as the value
returned by the IPacket interface, which can slightly improve performance
```
public class SimpleObject implements IPacket {
@@ -140,7 +173,8 @@ cpu: i9900k
}
```
- 第三种使用:通过ProtocolManager.initProtocolAuto()去注册协议就可以不写协议号
- The third use: Register the agreement through Protocol Manager.initProtocolAuto() without writing the protocol
number
```
public class SimpleObject implements IPacket {
@@ -151,21 +185,29 @@ cpu: i9900k
}
```
- 如果为了版本兼容,增加字段,需要加上Compatible注解,order需要自然增大,这样就可以保证新老协议可以互相兼容
- 正式环境为了版本兼容,避免修改字段名称,内部默认使用字段的名称按照字符串的自然顺序来依次读写的(也可以自定义),所以会导致序列化出现异常
- 正式环境为了版本兼容,避免减少字段,没必要一定要删除一个不需要的字段,所以不考虑支持这种减少字段兼容协议的情况
- 设计模式六大原则中的开闭原则是对扩展开放,对修改关闭。协议的设计涉及到功能应该也要遵守这个原则,优先增加新的协议而不是修改现有协议
- If you add a field for version compatibility, you need to add a Compatible annotation, and the order needs to be
naturally increased, so as to ensure that the old and new protocols can be compatible with each other
- In order to be compatible with versions and avoid modifying field names, default uses field names to read and write
in the natural order of strings (can also be customized), so it will cause exceptions in serialization
- The official environment does not necessarily have to delete an unwanted field in order to be version compatible and
avoid reducing fields
- Among the six principles of design patterns, the principle of opening and closing is open to expansion and closed to
modification. The design of the protocol should also adhere to this principle when it comes to functionality,
prioritizing the addition of new protocols over modifying existing ones
### Ⅷ. 在zfoo中使用Protobuf
### Ⅷ. Use Protobuf in zfoo
- zfoo只提供了jprotobuf那种pojo的方式使用protobuf,但是可以通过pojo对象生成proto文件给客户端使用
- zfoo only provides protobuf in the way of pojo like jprotobuf, but it can generate proto files for clients to use
through pojo objects
- [jprotobuf](https://github.com/jhunters/jprotobuf) 针对Java程序开发一套简易类库,可以直接使用简单的pojo对象无需再去了解proto文件操作与语法
- [jprotobuf](https://github.com/jhunters/jprotobuf) you can directly use simple pojo objects without having to
understand proto file operations and syntax
- 通过pojo对象生成proto文件给客户端使用,[生成proto配置](src/test/resources/protobuf.xml)
- Generate a proto file for the client to use through the pojo
object, [Generate a proto configuration](src/test/resources/protobuf.xml)
- 也可以自定义自己的生成方式,
[使用代码自定义生成proto](https://github.com/zfoo-project/tank-game-server/tree/main/common/src/main/java/com/zfoo/tank/common/generate)
- You can also customize your own build method,
[Use code custom generation to proto](https://github.com/zfoo-project/tank-game-server/tree/main/common/src/main/java/com/zfoo/tank/common/generate)
+176
View File
@@ -0,0 +1,176 @@
简体中文 | [English](./README.md)
### Ⅰ. 简介
- [zfoo protocol](https://github.com/zfoo-project/zfoo/blob/main/protocol/README.md)
是目前的Java二进制序列化和反序列化速度最快的框架,并且为序列化字节最少的框架
- 协议目前原生支持 **C++ Java Javascript C# Go Lua GDScript**,可以轻易实现跨平台
- 协议可以自定义私有协议格式,让你的协议更加安全,支持增加字段和兼容前后版本协议
- 兼容protobuf,支持生成protobuf协议文件,提供从pojo到proto的生成方式
- proto文件到生成pojo的方式已经有不少jetbrains插件可以做到,可以在idea的plugin中搜索proto
### Ⅱ. 快速使用
- 环境要求 **JDK 11+**,可以在 **OpenJDK** 和 **Oracle JDK** 无缝切换
- protocol是独立项目,不依赖其它项目,可以直接打开,本地install到自己的本地maven仓库,即可单独使用
```
// zfoo协议注册,只能初始化一次
ProtocolManager.initProtocol(Set.of(ComplexObject.class, ObjectA.class, ObjectB.class));
// 序列化
ProtocolManager.write(byteBuf, complexObject);
// 反序列化
var packet = ProtocolManager.read(buffer);
```
### Ⅲ. 性能测试
-
单线程环境,在没有任何JVM参数调优的情况下速度比Protobuf快50%,比Kryo快100%,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java)
-
线程安全,zfoo和Protobuf的性能不受任何影响,kryo因为线程不安全性能会有所损失,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java)
- 测试环境
```
操作系统:win10
cpu: i9900k
内存:64g
```
- 单线程测试,横坐标为序列化和反序列化的对象数量,纵坐标为花费的时间(单位毫秒)
![Image text](../doc/image/protocol/simple_object.png)
![Image text](../doc/image/protocol/normal_object.png)
![Image text](../doc/image/protocol/complex_object.png)
### Ⅳ. 为什么快
- 使用Javassist字节码增强动态生成顺序执行的序列化和反序列化函数,顺序化的函数可以轻易的被JIT编译以达到极致的性能
- 原生集成netty的高性能ByteBuf
- 使用primitive type collection,没有装箱和拆箱,避免了无效GC,性能也足够快
- 天生线程安全并且无锁化;kryo强制要求每条线程都有自己的一个Kryo实例,这是一个比较重的设计,特别是线程比较多的场景
- 没有反射,没有unsafe操作;对比kryo中使用objenesis导致大量unsafe,而且在Java11中运行会出现警告
- 扁平化了方法栈的调用深度,数据结构嵌套没有任何性能损失,如List<Set<Map<>>>;对比kryo和protobuf数据结构嵌套会出现性能损失
- 无漏洞注入风险,只有初始化时会进行字节码增强,后期不会再进行任何字节码的操作
```
数据压缩体积小,压缩体积比kryo和protobuf都要小;比kryo小是因为kryo需要写入每个对象的注册号
智能语法分析,错误的协议定义将无法启动程序并给出错误警告
提升开发效率,完全支持POJO方式开发,使用非常简单
```
### Ⅴ. 为什么小
- 轻量级实现,核心序列化和反序列化代码一千行左右
- 优化了int和long的zigzag和varint编码的算法,避免了一些多余的方法调用和位操作
- 数据压缩体积小,压缩体积比kryo和protobuf都要小;比kryo小是因为kryo需要写入每个对象的注册号
- 智能语法分析,错误的协议定义将无法启动程序并给出错误警告
- 提升开发效率,完全支持POJO方式开发,使用非常简单
```
目前的序列化过后对象的大小如下:
简单对象,zfoo包体大小8,kryo包体大小5,protobuf包体大小8
常规对象,zfoo包体大小430,kryo包体大小483,protobuf包体大小793
复杂对象,zfoo包体大小2216,kryo包体大小2528,protobuf包体大小5091
```
### Ⅵ. 数据类型
- 默认的数据格式支持,无需用户手动注册,[参考类定义](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<Integer>,必须指定泛型类,如果发送的是[1,1,null,1],接收到的是[1,1,0,1]
- List<XXXClass>,如果发送的是[obj,obj,null,obj],接收到的是[obj,obj,null,obj],即引用类型序列化之前为null,序列化之后同样为null
- 不支持的数据格式,因为zfoo会自动识别不支持的类型并且给出错误警告,所以用户不必太关心
- int[][],二维以上数组,考虑到不是所有语言都支持多维数组
- List<Integer>[],Map<Integer, Integer>[],Java语言本身就没有支持泛型类数组
- List<int[]>,Map<Integer, Integer[]>,泛型里面套数组,这种写法看起来比较奇怪,实际使用的地方很少
- 枚举类,考虑到很多其他语言不支持枚举类,可以用int或者string在代码层面做替换
- 自定义泛型类XXXClass<T>,泛型类在很多框架中都极易出现性能上和解析上的问题,而且并不是所有语言都支持
- 循环引用,虽然底层支持循环引用,但是考虑到循环引用带来语义上难以理解,容易出现错误,所以就屏蔽了
### Ⅶ. 协议规范
- 协议类必须是简单的javabean,不能继承任何其它的类,但是可以继承接口
- 为了防止代码里Object满天飞,避免协议层和po层混用对象造成一些潜在的并发问题,zfoo强制要求协议类必须实现IPacket接口
```
现在IPacket的接口只是一个标识接口,继承IPacket的设计主要是为了让代码更优雅,容易理解一点,改为只继承Object也并没有很大工作量
继承IPacket的设计还有跨语言层面上的考虑,极大的简化了实现其它语言的序列化和反序列化难度,统一了其它语言的代码实现
```
- 协议号定义为short类型是为了减少包体积和内存大小,一个包可以减少2个byte,每个协议的在应用内存也可以降低6byte(protocols +
IProtocolRegistration + protocolIdMap)
```
一个项目的协议体类很难超过3w,会有工具自动把你的协议号打包的紧凑一点,这样你的协议号就不会超过3w了
```
- 协议类必须标注协议号,有两种方式
- 第一种使用注解:@Protocol(id = protocolId)
```
@Protocol(id = 104)
public class SimpleObject implements IPacket {
public int c;
public boolean g;
}
```
- 第二种使用静态常量+接口:这个协议号的值必须和IPacket接口返回的值一样,这样的方式可以微弱的提升一点性能
```
public class SimpleObject implements IPacket {
public static final transient short PROTOCOL_ID = 104;
public int c;
public boolean g;
@Override
public short protocolId() {
return PROTOCOL_ID;
}
}
```
- 第三种使用:通过ProtocolManager.initProtocolAuto()去注册协议就可以不写协议号
```
public class SimpleObject implements IPacket {
public int c;
public boolean g;
}
```
- 如果为了版本兼容,增加字段,需要加上Compatible注解,order需要自然增大,这样就可以保证新老协议可以互相兼容
- 正式环境为了版本兼容,避免修改字段名称,内部默认使用字段的名称按照字符串的自然顺序来依次读写的(也可以自定义),所以会导致序列化出现异常
- 正式环境为了版本兼容,避免减少字段,没必要一定要删除一个不需要的字段,所以不考虑支持这种减少字段兼容协议的情况
- 设计模式六大原则中的开闭原则是对扩展开放,对修改关闭。协议的设计涉及到功能应该也要遵守这个原则,优先增加新的协议而不是修改现有协议
### Ⅷ. 在zfoo中使用Protobuf
- zfoo只提供了jprotobuf那种pojo的方式使用protobuf,但是可以通过pojo对象生成proto文件给客户端使用
- [jprotobuf](https://github.com/jhunters/jprotobuf) 针对Java程序开发一套简易类库,可以直接使用简单的pojo对象无需再去了解proto文件操作与语法
- 通过pojo对象生成proto文件给客户端使用,[生成proto配置](src/test/resources/protobuf.xml)
- 也可以自定义自己的生成方式,
[使用代码自定义生成proto](https://github.com/zfoo-project/tank-game-server/tree/main/common/src/main/java/com/zfoo/tank/common/generate)