Motan框架 - 轻量级 RPC 框架
vclhfrpql
8年前
<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>