Motan框架 - 轻量级 RPC 框架

vclhfrpql 3年前
   <h2>基本介绍</h2>    <p>Motan是一套基于java开发的RPC框架,除了常规的点对点调用外,motan还提供服务治理功能,包括服务节点的自动发现、摘除、高可用和负载均衡等。Motan具有良好的扩展性,主要模块都提供了多种不同的实现,例如支持多种注册中心,支持多种rpc协议等。</p>    <h2>架构概述</h2>    <p>Motan中分为服务提供方(RPC Server),服务调用方(RPC Client)和服务注册中心(Registry)三个角色。</p>    <ul>     <li>Server提供服务,向Registry注册自身服务,并向注册中心定期发送心跳汇报状态;</li>     <li>Client使用服务,需要向注册中心订阅RPC服务,Client根据Registry返回的服务列表,与具体的Sever建立连接,并进行RPC调用。</li>     <li>当Server发生变更时,Registry会同步变更,Client感知后会对本地的服务列表作相应调整。</li>    </ul>    <p>三者的交互关系如下图:</p>    <p><img alt="Motan框架 - 轻量级 RPC 框架 " src="https://simg.open-open.com/show/21eb9648331d97b8f534acb7ec25ce3d.jpg"></p>    <h2>模块概述</h2>    <p>Motan框架中主要有register、transport、serialize、protocol几个功能模块,各个功能模块都支持通过SPI进行扩展,各模块的交互如下图所示:</p>    <p><img alt="Motan框架 - 轻量级 RPC 框架 " src="https://simg.open-open.com/show/9f83900fcaa957721dc19f0c9fb30637.jpg"></p>    <p>register</p>    <p>用来和注册中心进行交互,包括注册服务、订阅服务、服务变更通知、服务心跳发送等功能;Server端会在系统初始化时通过register模块注册服务,Client端在系统初始化时会通过register模块订阅到具体提供服务的Server列表,当Server 列表发生变更时也由register模块通知Client。</p>    <p>protocol</p>    <p>用来进行RPC服务的描述和RPC服务的配置管理,这一层还可以添加不同功能的filter用来完成统计、并发限制等功能。</p>    <p>serialize</p>    <p>将RPC请求中的参数、结果等对象进行序列化与反序列化,即进行对象与字节流的互相转换;默认使用对java更友好的hessian2进行序列化。</p>    <p>transport</p>    <p>用来进行远程通信,默认使用Netty nio的TCP长链接方式。</p>    <p>cluster</p>    <p>Client端使用的模块,cluster是一组可用的Server在逻辑上的封装,包含若干可以提供RPC服务的Server,实际请求时会根据不同的高可用与负载均衡策略选择一个可用的Server发起远程调用。</p>    <p>在进行RPC请求时,Client通过代理机制调用cluster模块,cluster根据配置的HA和LoadBalance选出一个可用的Server,通过serialize模块把RPC请求转换为字节流,然后通过transport模块发送到Server端。</p>    <h2>配置概述</h2>    <p>Motan框架中将功能模块抽象为四个可配置的元素,分别为:</p>    <ul>     <li> <p>protocol:服务通信协议。服务提供方与消费方进行远程调用的协议,默认为motan协议,使用hessian2进行序列化,netty作为Endpoint以及使用motan自定义的协议编码方式。</p> </li>     <li> <p>registry:注册中心。服务提供方将服务信息(包含ip、端口、服务策略等信息)注册到注册中心,服务消费方通过注册中心发现服务。当服务发生变更,注册中心负责通知各个消费方。</p> </li>     <li> <p>service:服务提供方提供的服务。使用方将核心业务抽取出来,作为独立的服务。通过暴露服务并将服务注册至注册中心,从而使调用方调用。</p> </li>     <li> <p>referer:服务消费方对服务的引用,即服务调用方。</p> </li>    </ul>    <p>Motan推荐使用spring配置rpc服务,目前Motan扩展了6个自定义Spring xml标签:</p>    <ul>     <li>motan:protocol</li>     <li>motan:registry</li>     <li>motan:basicService</li>     <li>motan:service</li>     <li>motan:basicReferer</li>     <li>motan:referer</li>    </ul>    <p>每种标签的详细含义请参考后文<a href="/misc/goto?guid=4959672504150526426">配置说明</a>部分。全部参数清单请参考<a href="/misc/goto?guid=4959672504245800053">配置清单</a>。</p>    <p>使用Motan</p>    <p>Motan主要使用Spring进行配置,业务代码无需修改。关于在项目中使用Motan框架的具体步骤,请参考:<a href="/misc/goto?guid=4959672504328809989">快速入门</a>。</p>    <p>在使用Motan框架时,除了配置之外还需要注意工程依赖及Motan框架本身的异常处理。</p>    <h2>工程依赖</h2>    <p>Motan框架采用模块化设计,使用时可以按需依赖。目前的模块有:</p>    <ul>     <li>motan-core<br> Motan核心框架</li>     <li>motan-transport-netty<br> 基于Netty协议的长连接传输协议</li>     <li>motan-registry-consul<br> Consul服务发现组件</li>     <li>motan-registry-zookeeper<br> Zookeeper服务发现组件</li>     <li>motan-springsupport<br> Spring标签解析相关功能</li>    </ul>    <h2>处理调用异常</h2>    <ul>     <li> <p>业务代码异常<br> 当调用的远程服务出现异常时,Motan会把Server业务中的异常对象抛出到Client代码中,与本地调用逻辑一致。</p>      <blockquote>       <p>注意:如果业务代码中抛出的异常类型为Error而非Exception(如OutOfMemoryError),Motan框架不会直接抛出Error,而是抛出包装了Error的MotanServiceException异常。</p>      </blockquote> </li>     <li> <p>MotanServiceException<br> 使用Motan框架将一个本地调用改为RPC调用后,如果出现网络问题或服务端集群异常等情况,Motan会在Client调用远程服务时抛出MotanServiceException异常,业务方需要自行决定后续处理逻辑。</p> </li>     <li> <p>MotanFrameworkException<br> 框架异常,比如系统启动、关闭、服务暴露、服务注册等非请求情况下出现问题,Motan会抛出此类异常。</p> </li>    </ul>    <p>配置说明</p>    <h2>协议与连接(motan:protocol)</h2>    <h3>介绍</h3>    <p>Protocol用来配置Motan服务的协议。不同的服务适用不同的协议进行传输,可以自行扩展协议。</p>    <h3>motan协议</h3>    <pre>  <motan:protocol name="motan" /></pre>    <p>Motan默认的rpc协议为motan协议,使用tcp长连接模式,基于netty通信。</p>    <p>负载均衡</p>    <p>Motan 在集群负载均衡时,提供了多种方案,缺省为 ActiveWeight,并支持自定义扩展。 负载均衡策略在Client端生效,因此需在Client端添加配置</p>    <p>目前支持的负载均衡策略有:</p>    <ul>     <li> <p>ActiveWeight(缺省)</p> <pre>  <code><motan:protocol ... loadbalance="activeWeight"/>  </code></pre> <p>低并发度优先: referer 的某时刻的 call 数越小优先级越高<br> 由于 Referer List 可能很多,比如上百台,如果每次都要从这上百个 Referer 或者最低并发的几个,性能有些损耗,因此 random.nextInt(list.size()) 获取一个起始的 index,然后获取最多不超过 MAX_REFERER_COUNT 的状态是 isAvailable 的 referer 进行判断 activeCount.</p> </li>     <li> <p>Random</p> <pre>  <code><motan:protocol ... loadbalance="random"/>  </code></pre> <p>随机,按权重设置随机概率。<br> 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。</p> </li>     <li> <p>RoundRobin</p> <pre>  <code><motan:protocol ... loadbalance="roundrobin"/>  </code></pre> <p>轮循,按公约后的权重设置轮循比率</p> </li>     <li> <p>LocalFirst</p> <pre>  <code><motan:protocol ... loadbalance="localFirst"/>  </code></pre> <p>本地服务优先获取策略,对referers根据ip顺序查找本地服务,多存在多个本地服务,获取Active最小的本地服务进行服务。<br> 当不存在本地服务,但是存在远程RPC服务,则根据ActivWeight获取远程RPC服务<br> 当两者都存在,所有本地服务都应优先于远程服务,本地RPC服务与远程RPC服务内部则根据ActiveWeight进行</p> </li>     <li> <p>Consistent</p> <pre>  <code><motan:protocol ... loadbalance="consistent"/>  </code></pre> <p>一致性 Hash,相同参数的请求总是发到同一提供者</p> </li>     <li> <p>ConfigurableWeight</p> <pre>  <code><motan:protocol ... loadbalance="configurableWeight"/>  </code></pre> <p>权重可配置的负载均衡策略</p> </li>    </ul>    <p>容错策略</p>    <p>Motan 在集群调用失败时,提供了两种容错方案,并支持自定义扩展。 高可用集群容错策略在Client端生效,因此需在Client端添加配置 目前支持的集群容错策略有:</p>    <ul>     <li> <p>Failover 失效切换(缺省)</p> <pre>  <code><motan:protocol ... haStrategy="failover"/>  </code></pre> <p>失败自动切换,当出现失败,重试其它服务器。</p> </li>     <li> <p>Failfast 快速失败</p> <pre>  <code><motan:protocol ... haStrategy="failfast"/>  </code></pre> <p>快速失败,只发起一次调用,失败立即报错。</p> </li>    </ul>    <p>连接控制</p>    <ul>     <li> <p>限制服务端连接池工作线程数</p> <pre>  <code><motan:protocol id="demoMotan" name="motan" maxWorkerThread="800" minWorkerThread="20"/>  </code></pre> </li>     <li> <p>限制客户端对每个服务建立的连接数</p> <pre>  <code><motan:protocol name="motan" maxClientConnection="10" minClientConnection="2"/>  </code></pre> </li>    </ul>    <h3>本地调用</h3>    <pre>  <code><motan:protocol name="injvm" />  </code></pre>    <p>Injvm 协议是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Motan 的 Filter 链。</p>    <h2>注册中心与服务发现(motan:registry)</h2>    <h3>介绍</h3>    <p>注册中心配置。用于配置注册中心的注册协议、地址端口、超时时间等。motan:registry包含以下常用属性:</p>    <ul>     <li>name:标识配置名称</li>     <li>regProtocol:标识注册中心协议</li>     <li>address:标识注册中心地址</li>    </ul>    <p>Motan支持使用多种Registry模块,使用不同注册中心需要依赖对应jar包。</p>    <h3>使用Consul作为注册中心</h3>    <pre>  <motan:registry regProtocol="consul" name="my_consul" address="consul_port:port"/></pre>    <h3>使用Zookeeper作为注册中心</h3>    <p>zookeeper为单节点</p>    <pre>  <code>```xml  <motan:registry regProtocol="zookeeper" name="my_zookeeper" address="zookeeper_ip1:port"/>  ```    zookeeper多节点集群      ```xml  <motan:registry regProtocol="zookeeper" name="my_zookeeper" address="zookeeper_ip1:port1,zookeeper_ip2:port2,zookeeper_ip3:port"/>  ```  </code></pre>    <h3>不使用注册中心</h3>    <p>在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要 点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,需要在配置<strong>motan:referer</strong>时定义directUrl属性:</p>    <pre>  <motan:referer id="xxxService" interface="com.motan.xxx.XxxService" directUrl="server_ip:server_port" /></pre>    <h2>服务提供方(motan:service)</h2>    <h3>介绍</h3>    <p>定义提供给外部调用的接口,motan:service包含以下常用属性:</p>    <ul>     <li>interface:标识服务的接口类名</li>     <li>ref:标识服务的实现类,引用具体的spring业务实现对象</li>     <li>export:标识服务的暴露方式,格式为“protocolId:port”(使用的协议及对外提供的端口号),其中protocolId:应与motan:protocol中的name一致</li>     <li>group:标识服务的分组</li>     <li>module:标识模块信息</li>     <li>protocol:标识service使用的协议,与motan:protocol中的name对应,默认为motan协议</li>     <li>basicService:标识使用的基本配置,引用motan:basicService对象</li>    </ul>    <p>Motan在注册中心的服务是以group的形式保存的,一般推荐一个分组以机房+业务线进行命名,如yf-user-rpc。一个分组中包含若干的Service,一个Service即是java中的一个接口类名,每个Service下有一组能够提供对应服务的Server。</p>    <h3>使用basicService简化配置</h3>    <pre>  <motan:basicService .../></pre>    <p>rpc服务的通用配置,用于配置所有服务接口的公共配置,减少配置冗余。basicService包含以下常用属性:</p>    <ul>     <li>id:标识配置项</li>     <li>export:标识服务的暴露方式,格式为“protocolId:port”(使用的协议及对外提供的端口号),其中protocolId:应与motan:protocol中的name对应</li>     <li>group:标识服务的分组</li>     <li>module:标识模块信息</li>     <li>protocol:标识service使用的协议,与motan:protocol中的name对应,默认为motan协议</li>     <li>registry:标识service使用的注册中心,与motan:registry中的name对应</li>    </ul>    <p>motan:service可以通过以下方式引用基本配置。</p>    <pre>  <code><!-- 通用配置,多个rpc服务使用相同的基础配置. group和module定义具体的服务池。export格式为“protocol id:提供服务的端口” -->  <motan:basicService id="serviceBasicConfig" export="demoMotan:8002" group="motan-demo-rpc" module="motan-demo-rpc" registry="registry" protocol="motan"/>  <!-- 通用配置,多个rpc服务使用相同的基础配置. group和module定义具体的服务池。export格式为“protocol id:提供服务的端口” -->  <motan:service interface="com.weibo.motan.demo.service.MotanDemoService"                     ref="demoServiceImpl" basicService="serviceBasicConfig"/>  </code></pre>    <p>motan:service中的basicService属性用来标识引用哪个motan:basicService对象,对于basicService中已定义的内容,service不必重复配置。</p>    <h2>服务调用方(motan:referer)</h2>    <h3>介绍</h3>    <p>调用方对象,motan:referer包含以下常用属性:</p>    <ul>     <li>id:标识配置项</li>     <li>group:标识服务的分组</li>     <li>module:标识模块信息</li>     <li>protocol:标识referer使用的协议,与motan:protocol中的name对应,默认为motan协议</li>     <li>registry:标识referer使用的注册中心,与motan:registry中的name对应</li>     <li>basicReferer:标识使用的基本配置,引用motan:basicReferer对象</li>    </ul>    <p>Client端订阅Service后,会从Registry中得到能够提供对应Service的一组Server,Client把这一组Server看作一个提供服务的cluster。当cluster中的Server发生变更时,Client端的register模块会通知Client进行更新。</p>    <p><img alt="Motan框架 - 轻量级 RPC 框架 " src="https://simg.open-open.com/show/11fbd2c1046898e2b70818b24212e7f7.jpg"></p>    <h3>使用basicReferer简化配置</h3>    <p>调用方基础配置。用于配置所有服务代理的公共属性。</p>    <ul>     <li>id:标识配置项</li>     <li>group:标识服务的分组</li>     <li>module:标识模块信息</li>     <li>protocol:标识referer使用的协议,与motan:protocol中的name对应,默认为motan协议</li>     <li>registry:标识referer使用的注册中心,与motan:registry中的name对应</li>    </ul>    <p>motan:referer可以通过以下方式引用基本配置。</p>    <pre>  <code><!-- 通用referer基础配置 -->  <motan:basicReferer id="clientBasicConfig" group="motan-demo-rpc" module="motan-demo-rpc"  registry="registry" protocol="motan"/>    <!-- 具体referer配置。使用方通过beanid使用服务接口类 -->  <motan:referer id="demoReferer" interface="com.weibo.motan.demo.service.MotanDemoService"  basicReferer="clientBasicConfig"/>  </code></pre>    <p>motan:referer中的basicService属性用来标识引用哪个motan:basicReferer对象,对于basicReferer中已定义的内容,service不必重复配置。</p>    <h2>配置清单</h2>    <p>详细内容请参考<a href="/misc/goto?guid=4959672504245800053">配置清单</a></p>    <p>运维及监控</p>    <h2>优雅的停止服务</h2>    <p>Motan支持在Consul集群环境下优雅的关闭节点,当需要关闭或重启节点时,可以先将待上线节点从集群中摘除,避免直接关闭影响正常请求。</p>    <p>待关闭节点需要调用以下代码,建议通过servlet或业务的管理模块进行该调用。</p>    <pre>  MotanSwitcherUtil.setSwitcher(ConsulConstants.NAMING_PROCESS_HEARTBEAT_SWITCHER, false)</pre>    <blockquote>     <p>注意:Zookeeper模块此功能正在开发。</p>    </blockquote>    <h2>管理后台</h2>    <p>管理后台主要包括RPC服务查询、流量切换、Motan指令设置等功能,需使用ZooKeeper作为注册中心</p>    <p>管理后台独立于Motan其他部分,可单独部署</p>    <h3>管理后台安装</h3>    <ol>     <li> <p>配置:</p> <p>修改配置文件config.properties,配置ZooKeeper的registry地址,默认不使用数据库</p> <p>默认的登录用户及权限如下: 管理员:用户名admin 密码admin 访客:用户名guest 密码guest</p> <p>若需使用历史操作查询功能,则需配置数据库: 数据库表结构位于motan-manager.sql,可直接导入 数据库配置地址位于config.properties 在WEB-INF/web.xml的contextConfigLocation中添加classpath*:spring-mybaits.xml</p> </li>     <li> <p>启动</p> <p>在motan-open/motan-manager/下执行mvn install 将motan-open/motan-manager/target/motan-manager.war部署到任意web容器中(如:tomcat的webapps目录下),运行web容器即可</p> </li>    </ol>    <h3>管理后台使用</h3>    <p>Coming Soon...</p>    <h2>日志说明</h2>    <p>Motan会打印两种类型的日志,帮助运维人员监控系统状态。</p>    <h3>请求类日志</h3>    <p>通过motan:service或motan:referer的accessLog属性来配置,基本格式如下:</p>    <pre>  <code>"accesslog" - date - side - local_application_module - localip - interface - method_name - parameter_name - to_ip - remote_application_module - result - request_id - process_time_mills (分隔符为"|")  </code></pre>    <h3>统计类日志</h3>    <p>所有请求的统计:</p>    <pre>  <code>[motan-totalAccessStatistic] total_count: 32565 slow_count: 26 biz_excp: 0 other_excp: 2 avg_time: 1.93ms biz_time: 0.94ms avg_tps: 1085  total_count: 30s 内总请求数  slow_count:30s 内慢请求数(超过 50ms 算 slow)  biz_excp: 30s 内业务处理异常的总数  other_excp: 30s 其他异常的总数  avg_time: 所有接口的平均响应时间(网络传输+序列化+service 端处理)  biz_time: 所有接口的 service 端的业务处理时间(不包含序列化和网络传输)  avg_tps:平均 tps  注:上面是基于 client 端为维度的统计,service 端也有,其中 avg_time 便是业务处理时间,biz_time 为 0。  </code></pre>    <p>单方法的统计:</p>    <pre>  <code>[motan-accessStatistic] item: injvm://cn.sina.api.data.service.GroupService.getGroupMemberCounters(long,long) total_count: 0 slow_count: 0 biz_excp: 0 other_excp: 0 avg_time: 0.00ms biz_time: 0.00ms avg_tps: 0 max_tps: 0 min_tps: 0  total_count: 30s 该接口的请求数,  slow_count: 30s 内该接口的慢请求数 (超过 50ms 的算 slow) ,  biz_excp: 30s 内该接口业务处理异常的总数,  other_excp: 30s 该接口其他异常的总数,  avg_time: 平均响应时间(网络传输+序列化+service 端处理),  biz_time: service 端的业务处理时间(不包含序列化和网络传输) ,  avg_tps:平均 tps,  max_tps: 最大的 TPS,  min_tps: 最小的 TPS  </code></pre>    <p>内存统计:</p>    <pre>  <code>[motan-memoryStatistic] 1954.67MB of 7987.25 MB (24.5%) used  </code></pre>    <p>性能测试</p>    <p>Motan源码中提供了性能测试框架,便于使用者进行性能评估,源码请参考<a href="/misc/goto?guid=4959672504422697621">https://github.com/weibocom/motan/tree/master/motan-benchmark</a>。</p>    <p>以下是我们测试的结果:</p>    <h3>测试环境</h3>    <p>硬件配置</p>    <pre>  <code> Server端:   CPU:model name:Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz,cache size: 15360 KB,processor_count : 24   内存:16G   网络:千兆网卡   硬盘:300GB     Client端:   CPU:model name: Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz,cache size:15360 KB,processor_count : 24   内存:16G   网络:千兆网卡   硬盘:300GB  </code></pre>    <p>软件配置</p>    <pre>  <code> JDK版本:   java version "1.7.0_75"   OpenJDK Runtime Environment (rhel-2.5.4.2.el7_0-x86_64 u75-b13)   OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)     JVM参数:   java -Djava.net.preferIPv4Stack=true -server -Xms1g -Xmx1g -XX:PermSize=128m  </code></pre>    <h3>测试脚本</h3>    <p>Server测试场景:</p>    <pre>  <code>并发多个Client,连接数50,并发数100,测试Server极限性能  </code></pre>    <p>Client测试场景:</p>    <pre>  <code>单客户端,10连接,在并发数分别为1,10,20,50的情况下,分别进行如下场景测试:  - 传入空包,不做任何处理,原样返回  - 传入Pojo嵌套对象,不做任何处理,原样返回  - 传入1kString,不做任何处理,原样返回  - 传入5kString,不做任何处理,原样返回  - 传入10kString,不做任何处理,原样返回  - 传入20kString,不做任何处理,原样返回  - 传入30kString,不做任何处理,原样返回  - 传入50kString,不做任何处理,原样返回。  </code></pre>    <h3>测试结果</h3>    <p>Server测试结果:</p>    <pre>  <code>请求空包:单Server极限TPS:18W  请求1KString:单Server极限TPS:8.4W  请求5KString:单Server极限TPS:2W  </code></pre>    <p>Client测试结果:</p>    <p>对比图:</p>    <p><img alt="Motan框架 - 轻量级 RPC 框架 " src="https://simg.open-open.com/show/c4e489108ec19d17d1a71de4a4cdf611.jpg"></p>    <p>原始数据:</p>    <table>     <thead>      <tr>       <th>并发数</th>       <th>测试场景</th>       <th>平均TPS</th>       <th>平均响应时间(ms)</th>      </tr>     </thead>     <tbody>      <tr>       <td>1</td>       <td>Empty</td>       <td>5601</td>       <td>0.178</td>      </tr>      <tr>       <td>1</td>       <td>Pojo</td>       <td>3556</td>       <td>0.281</td>      </tr>      <tr>       <td>1</td>       <td>1KString</td>       <td>2657</td>       <td>0.376</td>      </tr>      <tr>       <td>1</td>       <td>5KString</td>       <td>1100</td>       <td>0.908</td>      </tr>      <tr>       <td>1</td>       <td>10KString</td>       <td>949</td>       <td>1.052</td>      </tr>      <tr>       <td>1</td>       <td>20KString</td>       <td>600</td>       <td>1.664</td>      </tr>      <tr>       <td>1</td>       <td>30KString</td>       <td>512</td>       <td>1.95</td>      </tr>      <tr>       <td>1</td>       <td>50KString</td>       <td>253</td>       <td>3.939</td>      </tr>      <tr>       <td>10</td>       <td>Empty</td>       <td>39181</td>       <td>0.255</td>      </tr>      <tr>       <td>10</td>       <td>Pojo</td>       <td>27314</td>       <td>0.365</td>      </tr>      <tr>       <td>10</td>       <td>1KString</td>       <td>19968</td>       <td>0.5</td>      </tr>      <tr>       <td>10</td>       <td>5KString</td>       <td>11236</td>       <td>0.889</td>      </tr>      <tr>       <td>10</td>       <td>10KString</td>       <td>5875</td>       <td>1.701</td>      </tr>      <tr>       <td>10</td>       <td>20KString</td>       <td>4493</td>       <td>2.224</td>      </tr>      <tr>       <td>10</td>       <td>30KString</td>       <td>3387</td>       <td>2.951</td>      </tr>      <tr>       <td>10</td>       <td>50KString</td>       <td>1499</td>       <td>6.668</td>      </tr>      <tr>       <td>20</td>       <td>Empty</td>       <td>69061</td>       <td>0.289</td>      </tr>      <tr>       <td>20</td>       <td>Pojo</td>       <td>47226</td>       <td>0.423</td>      </tr>      <tr>       <td>20</td>       <td>1KString</td>       <td>34754</td>       <td>0.575</td>      </tr>      <tr>       <td>20</td>       <td>5KString</td>       <td>18883</td>       <td>1.058</td>      </tr>      <tr>       <td>20</td>       <td>10KString</td>       <td>9032</td>       <td>2.214</td>      </tr>      <tr>       <td>20</td>       <td>20KString</td>       <td>5471</td>       <td>3.654</td>      </tr>      <tr>       <td>20</td>       <td>30KString</td>       <td>3724</td>       <td>5.368</td>      </tr>      <tr>       <td>20</td>       <td>50KString</td>       <td>1973</td>       <td>10.133</td>      </tr>      <tr>       <td>50</td>       <td>Empty</td>       <td>69474</td>       <td>0.719</td>      </tr>      <tr>       <td>50</td>       <td>Pojo</td>       <td>64022</td>       <td>0.78</td>      </tr>      <tr>       <td>50</td>       <td>1KString</td>       <td>58937</td>       <td>0.848</td>      </tr>      <tr>       <td>50</td>       <td>5KString</td>       <td>20703</td>       <td>2.414</td>      </tr>      <tr>       <td>50</td>       <td>10KString</td>       <td>10761</td>       <td>4.645</td>      </tr>      <tr>       <td>50</td>       <td>20KString</td>       <td>5614</td>       <td>8.904</td>      </tr>      <tr>       <td>50</td>       <td>30KString</td>       <td>3782</td>       <td>13.214</td>      </tr>      <tr>       <td>50</td>       <td>50KString</td>       <td>2285</td>       <td>21.869</td>      </tr>     </tbody>    </table>