smartbuf-springcloud
是一个基于 smartbuf
的 spring-cloud
序列化插件。
smartbuf
是一种新颖、高效、智能、易用的跨语言序列化框架,它既拥有不亚于 protobuf
的高性能,也拥有与 json
相仿的通用性、可扩展性、可调试性等。
在 Java
语言生态中,它通过以下插件支持多种 RPC
框架:
smartbuf-dubbo
: 为 dubbo
提供了 stream
模式的序列化扩展插件 smartbuf-springcloud
: 为 spring-cloud
提供了 packet
模式的序列化扩展插件 以下为 smartbuf-springcloud
插件的具体介绍。
smartbuf-springcloud
介绍 此插件内部封装了 smartbuf
序列化框架的 packet
模式,通过自定义的 SmartbufMessageConverter
向 spring
容器中暴露了一个名为 application/x-smartbuf
的 HTTP
消息编码解码器。
这个新增的 application/x-smartbuf
编码器在复杂对象的数据传输过程中,可以提供优于 protobuf
的高性能。
application/x-smartbuf
编码 此插件在配置文件 META-INFO/spring.factories
中声明了一个名为 SmartbufAutoConfiguration
的自动注解配置,即自定义 Auto-Configuration
。
spring-boot
初始化时会主动扫描并注册它,因此不需要你做额外的配置。更多资料请参考 SpringBoot文档 。
SmartbufAutoConfiguration
会向 spring
容器中增加一个新的 SmartbufMessageConverter
对象,它是一个自定义的 HttpMessageConverter
,其 MediaType
为 application/x-smartbuf
。
spring-mvc
启动后会把这个新增的 HttpMessageConverter
加入 messageConverters
中,如果后续 http
请求的头信息中包括 accept: application/x-smartbuf
或 content-type: application/x-smartbuf
,则 spring-mvc
会将该请求的 OutputStream
编码或 InputStream
解码委托给 SmartbufMessageConverter
处理。
整个过程和 application/json
的实现原理类似,不同之处在于 application/x-smartbuf
底层采用了另外一种序列化方案: SmartBuf
总结:你不需要做任何额外的配置,此插件会自动向 spring-mvc
中注册一个名为 application/x-smartbuf
的数据编码解码器,它对于正常的 http
请求没有任何影响,只有头信息中的 accept
或 content-type
匹配到它时才会被激活。
本章节通过一个简单的实例,介绍如何将 smartbuf-springcloud
引入自己的工程中,以及如何在代码中使用它。
你可以通过以下 maven
坐标添加 smartbuf-springcloud
的依赖:
<dependency> <groupId>com.github.smartbuf</groupId> <artifactId>smartbuf-springcloud</artifactId> <version>1.0.0</version> </dependency>
application/json
spring-cloud
底层使用 http
与服务端的 spring-mvc
进行通信。例如服务端的 Controller
可能类似这样:
@RestController public class DemoController { @PostMapping("/hello") public String sayHello(String name) { return "hello " + name; } }
调用方可以声明这样的 FeignClient
与服务端通信:
@FeignClient(name = "demo") public interface DemoClient { @PostMapping(value = "/hello", consumes = "application/json", produces = "application/json") String hello(@RequestParam("name") String name); }
调用方通过 DemoClient
请求服务端的 DemoController
时, feign
会根据接口中声明的 consumes
和 produces
,向服务端发送类似于这样的请求:
=== MimeHeaders ===
accept = application/json
content-type = application/json
user-agent = Java/1.8.0_191
connection = keep-alive
服务端的 spring-mvc
会根据头信息中的 accept
和 content-type
,确定使用 application/json
来执行 input
的解码和 output
的编码。
application/x-smartbuf
如前文所言, smartbuf-springcloud
会自动向 spring-mvc
中注册一个名为 application/x-smartbuf
的编码解码器,因此在引入 maven
依赖之后,不需要做任何额外的配置。
你只需要将 DemoClient
修改为这样,注意 consumes
和 produces
的变化:
@FeignClient(name = "demo") public interface DemoClient { @PostMapping(value = "/hello", consumes = "application/x-smartbuf", produces = "application/x-smartbuf") String hello(@RequestParam("name") String name); }
之后 feign
就会使用 application/x-smartbuf
与服务端 spring-mvc
进行通信,此时头信息就类似这样:
=== MimeHeaders ===
accept = application/x-smartbuf
content-type = application/x-smartbuf
user-agent = Java/1.8.0_191
connection = keep-alive
更妙的是,你可以同时创建两个接口,让 application/json
与 application/x-smartbuf
共存:
@FeignClient(name = "demo") public interface DemoClient { @PostMapping(value = "/hello", consumes = "application/json", produces = "application/json") String helloJSON(@RequestParam("name") String name); @PostMapping(value = "/hello", consumes = "application/x-smartbuf", produces = "application/x-smartbuf") String helloSmartbuf(@RequestParam("name") String name); }
客户端可以通过 helloJSON
使用 application/json
编码方式,通过 helloSmartbuf
使用 application/x-smartbuf
编码方式,而服务端会根据请求方指定的编码类型自动进行切换。
具体演示代码在此工程的 demo
子模块中,你可以直接 checkout
到本地执行。
smartbuf
的优点在于其分区序列化所带来的高压缩率,尤其是面对复杂对象、数组时,它的空间利用率远超其他序列化方案。
对于RPC而言,序列化耗时往往是纳秒级,而逻辑处理、数据传输往往是毫秒级的,因此以下测试将采用单线程测试相同接口、相同次数的调用下, json
与 smartbuf
的数据传输量和总耗时的差别。
下面我们通过三个不同类型的接口测试一下 json
与 smartbuf
的区别。
hello
测试 hello
即前文提到的 helloJson
和 helloSmartbuf
接口,输入输出参数都是 String
,接口内部代码逻辑非常简单。单线程循环调用 400,000
次总耗时分别为:
JSON
: 169秒 SmartBuf
: 170秒 网络输入( bytes_input
)输出( bytes_output
)总量分别为:
由于 smartbuf
编码中需要额外几个字节来描述完整的数据信息,因此在处理 String
这种简单数据时,它的空间利用率并不如 json
。
getUser
测试 getUser
接口的实现方式如下:
@RestController public class DemoController { private UserModel user = xxx; // initialized at somewhere else @PostMapping("/getUser") public UserModel getUser(Integer userId) { return user; } }
其中 user
是一个专门用于测试的、随机分配的对象,其具体模型可以查阅 demo
源码中的 UserModel
类。
调用方的 FeignClient
定义如下:
@FeignClient(name = "demo") public interface DemoClient { @PostMapping(value = "/getUser", consumes = "application/json", produces = "application/json") UserModel getUserJSON(@RequestParam("userId") Integer userId); @PostMapping(value = "/getUser", consumes = "application/x-smartbuf", produces = "application/x-smartbuf") UserModel getUserSmartbuf(@RequestParam("userId") Integer userId); }
单线程循环调用 300,000
次的总耗时分别为:
JSON
: 162秒 SmartBuf
: 149秒 网络输入( bytes_input
)输出( bytes_output
)总量分别为:
可以看到请求参数 userId
数据类型单一,因此 json
和 smartbuf
所使用的网络流量几乎一样。而返回结果 UserModel
是一个比较复杂的对象,因此 json
网络资源消耗量是 smartbuf
的将近 三倍 。
因为测试环境为 localhost
,网络传输耗时对接口的总耗时没有太大影响。
queryPost
测试 queryPost
接口的实现方式如下:
@RestController public class DemoController { private List<PostModel> posts = xxx; // initialized at somewhere else @PostMapping("/queryPost") public List<PostModel> queryPost(String keyword) { return posts; } }
此接口返回值 posts
是一个预先分配的、用于测试的 PostModel
数组,此数组长度为固定的 100
,其具体模型及初始化可以查阅 demo
源码。
客户端、调用方的 FeignClient
定义如下:
@FeignClient(name = "demo") public interface DemoClient { @PostMapping(value = "/queryPost", consumes = "application/json", produces = "application/json") List<PostModel> queryPostJSON(@RequestParam("keyword") String keyword); @PostMapping(value = "/queryPost", consumes = "application/x-smartbuf", produces = "application/x-smartbuf") List<PostModel> queryPostSmartbuf(@RequestParam("keyword") String keyword); }
单线程循环调用 100,000
次的总耗时分别为:
JSON
: 195秒 SmartBuf
: 155秒 网络输入( bytes_input
)输出( bytes_output
)总量分别为:
可以看到请求参数 keyword
数据类型单一,因此 json
和 smartbuf
所使用的网络流量几乎一样。而返回结果 List<PostModel>
是一个复杂对象的大数组,因此 json
网络资源消耗量是 smartbuf
的将近 十倍 。
因为测试环境为 localhost
,网络传输耗时对接口的总耗时没有太大影响。
在输入输出数据格式都非常简单的 RPC
接口调用中,此插件所提供的 application/x-smartbuf
编码没有任何性能优势。
当接口数据中存在复杂对象、数组、集合等较大数据时,使用 application/x-smartbuf
可以大幅降低 net
输入输出的字节流大小,比如在上文 queryPost
测试中,使用 application/x-smartbuf
时网络输入输出字节总量仅为 application/json
的 十分之一 。
难能可贵的是,实际应用中 application/x-smartbuf
与 application/json
即可以共存,也可以无缝切换。
比如对于某些简单接口,可以直接采用简单的 application/json
编码,而对于数据量比较大的复杂接口,可以采用高效率的 application/x-smartbuf
编码进行性能优化。
比如开发测试时直接使用 application/json
编码,上线时再切换为 application/x-smartbuf
编码。
本文同步发布于GitHub、个人主页等