优化 1:元数据共享

hessian 序列化会将两种信息写到输出流:

  1. 元数据:即类全名,字段名
  2. 值数据:即各个字段对应值(如果字段是复杂类型,则会递归传递该复杂类型

    的元数据和内部字段的值数据)

    在 hessian1 协议里,每次写出 Class  A 的实例时,都会写出 Class  A 的元

    数据和值数据,就是会重复传输相同的元数据。针对这点,hessian2 协议做了一个

    优化就是:在“同一次序列化上下文”里,如果存在 Class  A 的多个实例,只会对

    Class  A 的元数据传输一次。该元数据会在对端被缓存起来重复使用,下次再序列化

    Class  A 的对象时,只需要先写出对元数据的一个引用句柄(缓存中的 index,用一

    个 int 表示),然后直接写出值数据即可。接受方通过元数据句柄即可知道后面的值数

    据对应的类型。

    这是一个极大的提升。因为编码字段名字(就是字符串)所需的字节数很可能比

    它对应的值(可能只是一个 byte)更多。

    不过在官方的 hessian 里,这个优化有两个限制:
  3. 序列化过程中类型对应的 Class 结构不能改变
  4. 元数据引用只能在“同一个序列化上下文”,这里的“上下文”就是指同一

    个 HessianOutput 和 HessianInput。因为元数据的 id 分配和缓存分别是在

    HessianOutput 和 HessianInput 里进行的

    限制 1 我们可以接受,一般 DO 不会再运行时改变。但是限制 2 不太友好,因

    为针对每次请求的序列化和反序列化,HSF 都需要使用全新构造的 HessianOutput

    和 HessianInput。这就导致每次请求都需要重新发送上次请求已经发送过的元数据。

    针对限制 2,HSF 实现了跨请求元数据共享,这样只要发送过一次元数据,以

    后就再也不用发送了,进一步减少传输的数据量。实现机制如下:
  5. 修改 hessian 代码,将元数据 id 分配和缓存的数据结构从 HessianOutput

    和 HessianInput 剥离出来。
  6. 修改 HSF 代码,将上述剥离出来的数据结构作为连接级别的上下文保存起来。
  7. 每次构造 HessianOutput 和 HessianInput 时将其作为参数传入。这样就达

    了跨请求复用元数据的目的。

    该优化的效果取决于业务对象中,元数据所占的比例,如果“精心”构造对象,

    使得元数据所占比例很大,那么测试表现会很好,不过这没有意义。我们还是选取线

    上核心应用的真实业务对象来测试。从线上 tcp dump 了一个真实业务对象,测试同

    学以此编写测试用例得到测试数据如下:
  8. 新版本比老版本 CPU 利用率下降 10% 左右
  9. 新版本的网络流量相比老版本减少约 17%

    线上核心应用压测结果显示数据流量下降一般在 15%~20% 之间。

优化 2:UTF8 解码优化

hessian 传输的字符串都是 utf8 编码的,反序列化时需要进行解码。hessian

现行的解码方式是逐个字符进行。代码如下:

    private int parseUTF8Char() throws IOException {
int ch = _offset < _length
? (_buffer[_offset++] & 0xff) : read();
if (ch < 0x80)
return ch;
else if ((ch & 0xe0) == 0xc0) {
int ch1 = read();
int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
return v;
} else if ((ch & 0xf0) == 0xe0) {
int ch1 = read();
int ch2 = read();
int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
return v;
} else
throw error("bad utf-8 encoding at " + codeName(ch));
}

UTF8 是变长编码,有三种格式:

1  byte  format:  0xxxxxxx

2  byte  format:  110xxxxx  10xxxxxx

3  byte  format:  1110xxxx  10xxxxxx  10xxxxxx

上面的代码是对每个字节,通过位运算判断属于哪一种格式,然后分别解析。

优化方式是:通过 unsafe 将 8 个字节作为一个 long 读取,然后通过一次位运

算判断这 8 个字节是否都是“1  byte  format”,如果是(很大概率是,因为常用的

ASCII 都是“1  byte  format”),则可以将 8 个字节直接解码返回。以前 8 次位运

算,现在只需要一次了。如果判断失败,则按老的方式,逐个字节进行解码。主要代

码如下:

private boolean parseUTF8Char_improved() throws IOException {
while (_chunkLength > 0) {
if (_offset >= _length && !readBuffer()) {
return false;
}
int sizeOfBufferedBytes = _length - _offset;
int toRead =
sizeOfBufferedBytes <= _chunkLength ? sizeOfBufferedBytes : _chunkLength;
// fast path for ASCII
int numLongs = toRead >> 3;
for (int i = 0; i < numLongs; i++) {
long currentOffset = baseOffset + _offset;
long test =
unsafe.getLong(_buffer, currentOffset);
if ((test & 0x8080808080808080L) == 0L) {
_chunkLength -=
8;
toRead -= 8;
for (int j = 0; j < 8; j++) {
_sbuf.append((char) (_
buffer[_offset++]));
}
} else {
break;
}
for (int i = 0; i < toRead; i++) {
_chunkLength--;
int ch = (_buffer[_offset++] & 0xff);
if (ch < 0x80) {
_sbuf.append((char) ch);
} else if ((ch & 0xe0) == 0xc0) {
int ch1 = read();
int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
_sbuf.append((char) v);
} else if ((ch & 0xf0) == 0xe0) {
int ch1 = read();
int ch2 = read();
int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
_sbuf.append((char) v);
} else
throw error("bad utf-8 encoding at " + codeName(ch));
}
}
return true;
}
}

同样使用线上 dump 的业务对象进行对比,测试结果显示该优化带来了 17% 的

性能提升:

time: 981

improved utf8 decode time: 810

(981-810)/981 = 0.1743119266055046

优化 4: map 操作数组化

大型系统里多个模块间经常通过 Map 来交互信息,互相只需要耦合 String 类型

的 key。常见代码如下:

public static final String key = "mykey";
Map<String,Object> attributeMap = new HashMap<String,Object>();
Object value = attributeMap.get(key);

大量的 Map 操作也是性能的一大消耗点。HSF 今年尝试将 Map 操作进行了优

化,改进为数组操作,避免了 Map 操作消耗。新的范例代码如下:

public static final AttributeNamespace ns = AttributeNamespace.
createNamespace("mynamespace");
public static final AttributeKey key = new AttributeKey(ns, "mykey");
DefaultAttributeMap attributeMap = new DefaultAttributeMap(ns, 8);
Object value = attributeMap.get(key);

工作机制简单说明如下:

  1. key 类型由 String 改为自定义的 AttributeKey,AttributeKey 会在初始化阶

    段就去 AttributeNamespace 申请一个固定 id

    2.map 类 型 由 HashMap 改 为 自 定 义 的 DefaultAttributeMap,DefaultAttributeMap

    内部使用数组存放数据
  2. 操作 DefaultAttributeMap 直接使用 AttributeKey 里存放的 id 作为 index 访

    问数组即可,避免了 hash 计算等一系列操作。核心就是将之前的字符串 key

    和一个固定的 id 对应起来,作为访问数组的 index

    对比 HashMap 和 DefaultAttributeMap,性能提升约 30%。

HashMap put time(ms) : 262

ArrayMap put time(ms) : 185

HashMap get time(ms) : 184

ArrayMap get time(ms) : 126

摘自阿里双11技术文档

RPC性能优化的更多相关文章

  1. Go RPC 框架 KiteX 性能优化实践 原创 基础架构团队 字节跳动技术团队 2021-01-18

    Go RPC 框架 KiteX 性能优化实践 原创 基础架构团队 字节跳动技术团队 2021-01-18

  2. Netty实现高性能RPC服务器优化篇之消息序列化

    在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路.设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器).在文章的最后 ...

  3. SQL性能优化

    引言: 以前在面试的过程中,总有面试官问道:你做过sql性能优化吗?对此,我的答复是没有.一次没有不是自己的错误,两次也不是,但如果是多次呢?今天痛下决心,把有关sql性能优化的相关知识总结一下,以便 ...

  4. Tair LDB基于Prefixkey中期范围内查找性能优化项目总结

    "Tair LDB基于Prefixkey该范围内查找性能优化"该项目是仅一个月.这个月主要是熟悉项目..以下从几个方面总结下个人在该项目上所做的工作及自己的个人所得所感. 项目工作 ...

  5. 15套java架构师、集群、高可用、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩展. ...

  6. 15套java互联网架构师、高并发、集群、负载均衡、高可用、数据库设计、缓存、性能优化、大型分布式 项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...

  7. java架构师负载均衡、高并发、nginx优化、tomcat集群、异步性能优化、Dubbo分布式、Redis持久化、ActiveMQ中间件、Netty互联网、spring大型分布式项目实战视频教程百度网盘

    15套Java架构师详情 * { font-family: "Microsoft YaHei" !important } h1 { background-color: #006; ...

  8. 15套java架构师、集群、高可用、高可扩 展、高性能、高并发、性能优化Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...

  9. 常见性能优化策略的总结 good

    阅读目录 代码 数据库 缓存 异步 NoSQL JVM调优 多线程与分布式 度量系统(监控.报警.服务依赖管理) 案例一:商家与控制区关系的刷新job 案例二:POI缓存设计与实现 案例三:业务运营后 ...

随机推荐

  1. JVM源码分析之JDK8下的僵尸(无法回收)类加载器[z]

    [z]http://lovestblog.cn/blog/2016/04/24/classloader-unload/ 概述 这篇文章基于最近在排查的一个问题,花了我们团队不少时间来排查这个问题,现象 ...

  2. python数据挖掘决策树算法

    决策树是一个非参数的监督式学习方法,主要用于分类和回归.算法的目标是通过推断数据特征,学习决策规则从而创建一个预测目标变量的模型.如下如所示,决策树通过一系列if-then-else 决策规则 近似估 ...

  3. 014-数据结构-树形结构-基数树、Patricia树、默克尔树、梅克尔帕特里夏树( Merkle Patricia Tree, MPT)

    一.基数树 Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构.与Trie不同的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩.同样的,Radi ...

  4. R语言与概率统计(六) 主成分分析 因子分析

    超高维度分析,N*P的矩阵,N为样本个数,P为指标,N<<P PCA:抓住对y对重要的影响因素 主要有三种:PCA,因子分析,回归方程+惩罚函数(如LASSO) 为了降维,用更少的变量解决 ...

  5. linux定时脚本:删除linux/HDFS上过期文件

    一.定时删除linux上定时的文件 显示20分钟前的文件 -exec ls -l {} \; 删除20分钟前的文件 -exec rm {} \; 显示20天前的文件 -exec ls -l {} \; ...

  6. iptable规则的执行顺序

    众所周知,iptable的中包含了各种各样的table和规则链条.这篇博文对规则链的执行顺序做一个简单的介绍. Chain OUTPUT (policy ACCEPT)target prot opt ...

  7. 树莓派实现摄像头监控(使用motion和mjpg-streamer)

    购买raspBerryCarmen,大概20元, 启动树莓派,安装: `sudo apt install motion` 配置/etc/motion/motion.conf, `sudo vim /e ...

  8. Python 面向对象(上)

    一. 什么是面向对象? 1. 在了解面向对象之前,首先我们需要知道两个概念:(1)什么是函数?函数是对功能或动作的一种封装.函数的语法结构如下: def func(arg1): '''函数的内部有函数 ...

  9. IDEA里的git的使用

    1.将代码交由git管理 VCS  ——>  Enable Version Control Integration... 选择要使用的版本控制系统,选择Git  ——>  OK 2.将代码 ...

  10. 【Python】【demo实验32】【回文数的确认】

    原题: 我的代码: #!/usr/bin/python # encoding=utf-8 # -*- coding: UTF-8 -*- #判断一个数字是否为回文数 即 12345654321 x = ...