我想知道dubbo性能压测的框架和软件,硬件的瓶颈分析?

随着互联网的发展,网站应用的规模不断扩大,常规垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进

    • 当网站流量很小时,只需一个应用,将所有功能部署在一起,以减少部署节点和成本
    • 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键
    • 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干几个应用,以提升效率
    • 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求
    • 此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键
        • 每次发布只部署部分服务器
        • 每个节点可根据不同需求伸缩扩展
        • 每个应用之间更新,部署,运行不影响
      • 停止RPC滥用垂直业务优先通过本地jar调用跨业务才采用RPC调用
      • 正确的识别业务逻辑的归属,让各个模块最大化内聚,从性能,可用性维护性减少耦合
    • 当服务越来越多,容量评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量提高集群利用率
    • 此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键

大规模服务化之前,应用可能只是通过RMI或Hessian等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过F5等硬件进行负载均衡

  • 服务越来越多时,服务URL配置管理变得非常困难F5硬件负载均衡器的单点压力越来越大
    • 此时需要一个服务注册中心动态注册发现服务,使服务的位置透明
    • 并通过在消费方获取服务提供方地址列表,实现软负载均衡Failover降低F5硬件负载均衡器依赖,也能减少部分成本
  • 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系
    • 这时,需要自动画出应用间依赖关系图,以帮助架构师理清理关系
  • 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
    • 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划参考指标
    • 其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量
    • proxy(服务代理层)
      • 服务接口透明代理,生成服务的客户端Stub服务器端Skeleton
    • 封装服务地址注册发现
    • 支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用)
    • 支持基于客户端双写的集群方式,性能高
    • 要求服务器时间同步,用于检查心跳过期脏数据
    • 去中心化,不需要安装注册中心
    • 依赖于网络拓普路由,跨机房有风险
    • Dogfooding,注册中心本身也是一个标准的RPC服务
    • 没有集群支持,可能单点故障
    • 封装多个提供者路由负载均衡,并桥接注册中心
    • 失败自动切换,当出现失败,重试其它服务器,通常用于读操作(推荐使用)
    • 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作
    • 如果有机器正在重启,可能会出现调用失败
    • 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作
    • 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作
    • 并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作
    • 广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于更新提供方本地状态
    • 速度慢,任意一台报错则报错
      • 随机,按权重设置随机概率(推荐使用)
      • 在一个截面上碰撞的概率高,重试时,可能出现瞬间压力不均
      • 轮循,按公约后的权重设置轮循比率
      • 存在慢的机器累积请求问题,极端情况可能产生雪崩
      • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少请求
      • 不支持权重,在容量规划时,不能通过权重把压力导向一台机器压测容量
      • 一致性Hash,相同参数的请求总是发到同一提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动
      • 基于条件表达式的路由规则,功能简单易用
      • 有些复杂多分支条件情况,规则很难描述
      • 基于脚本引擎的路由规则,功能强大
      • 没有运行沙箱,脚本能力过于强大,可能成为后门
    • 启动一个内嵌Jetty,用于汇报状态
    • 大量访问页面时,会影响服务器的线程和内存
    • 自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录
    • 用户不能控制log4j的配置,不灵活
    • RPC调用次数调用时间监控
      • 采用NIO复用单一长连接,并使用线程池并发处理请求减少握手加大并发效率,性能较好(推荐使用
      • 适合于小数据量大并发的服务调用,以及服务消费者机器数服务提供者机器数的情况
      • Dubbo缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低
      • Dubbo协议缺省每服务每提供者每消费者使用单一长连接,如果数据量较大,可以使用多个连接
      • 为防止被大量连接撑挂,可在服务提供方限制大接收连接数,以实现服务提供方自我保护
      • 在大文件传输时,单一连接会成为瓶颈
  • 传输方式:NIO异步传输
  • 序列化:Hessian二进制序列化
  • 适用范围:传入传出参数数据包较小(建议小于100K),消费者提供者个数,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串
  • 适用场景:常规远程服务方法调用
    • 可与原生RMI互操作,基于TCP协议
    • 偶尔会连接失败,需重建Stub
    • 可与原生Hessian互操作,基于HTTP协议
  • 可以和原生Hessian服务互操作
    • 提供者用Dubbo的Hessian协议暴露服务,消费者直接用标准Hessian接口调用
    • 或者提供方用标准Hessian暴露服务,消费方用Dubbo的Hessian协议调用
    • 基于Hessian的远程调用协议
  • 序列化:Hessian二进制序列化
  • 适用范围:传入传出参数数据包较大提供者消费者个数,提供者压力较大,可传文件
  • 适用场景:页面传输,文件传输,或与原生hessian服务互操作
  • 参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失
    • 封装请求响应模式同步转异步
    • 一次请求派发两种事件,需屏蔽无用事件
  • 待发送消息队列派发不及时,大压力下,会出现FullGC
  • 线程池不可扩展,Filter不能拦截下一Filter
      • 性能较好,多语言支持(推荐使用
    • 通过不传送POJO的类元信息,在大量POJO传输时,性能较好
    • 当参数对象增加字段时,需外部文件声明
    • 纯文本,可跨语言解析,缺省采用FastJson解析
  • 而Cluster是外围概念,所以Cluster的目的是将多个Invoker伪装成一个Invoker,这样其它人只要关注Protocol层Invoker即可,加上Cluster或者去掉Cluster对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster的。
  • Proxy封装了所有接口的透明化代理,而在其它层都以Invoker为中心,只有到了暴露给用户使用时,才用Proxy将Invoker转成接口,或将接口实现转成Invoker,也就是去掉Proxy层RPC是可以Run的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
  • RegistryMonitor实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起
    • dubbo-remoting 远程通讯模块,相当于Dubbo协议的实现,如果RPC用RMI协议则不需要使用此包。
    • dubbo-rpc 远程调用模块,抽象各种协议,以及动态代理,只包含一对一的调用不关心集群的管理
    • dubbo-cluster 集群模块,将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
    • dubbo-registry 注册中心模块,基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
    • dubbo-monitor 监控模块,统计服务调用次数,调用时间的,调用链跟踪的服务。
    • container为服务容器,用于部署运行服务,没有在层中画出。
    • protocol层和proxy层都放在rpc模块中,这两层是rpc的核心,在不需要集群时(只有一个提供者),可以只使用这两层完成rpc调用。
    • Protocol服务域,它是Invoker暴露引用主功能入口,它负责Invoker的生命周期管理
    • Invoker实体域,它是Dubbo的核心模型其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现
    • Invocation会话域,它持有调用过程中变量,比如方法名,参数
    • 采用Microkernel + Plugin模式,Microkernel只负责组将Plugin,Dubbo自身的功能也是通过扩展点实现的,也就是Dubbo的所有功能点都可被用户自定义扩展所替换
    • 采用URL作为配置信息的统一格式,所有扩展点都通过传递URL携带配置信息
  • 在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔
  • 注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并
    • 具体服务Invoker的转换
      • 它通过Spring或Dubbo或JDK来实现RMI服务,通讯细节这一块由JDK底层来实现,这就省了不少工作量
    • Invoker转为客户端需要的接口
    • 过程:首先ReferenceConfig类的init方法调用Protocolrefer方法生成Invoker实例(如上图中的红色部分),这是服务消费的关键。接下来把Invoker转换为客户端需要的接口
    • 由于Invoker是Dubbo领域模型中非常重要的一个概念,很多设计思路都是向它靠拢
    • 被封装成为一个AbstractProxyInvoker实例,并新生成一个Exporter实例。这样当网络通讯层收到一个请求后,会找到对应的Exporter实例,并调用它所对应的AbstractProxyInvoker实例,从而真正调用了服务提供者的代码
    • all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等
    • direct 所有消息都不派发到线程池,全部在IO线程上直接执行
    • message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在IO线程上执行
    • execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行
    • connection 在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池
    • fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
    • cached 缓存线程池,空闲一分钟自动删除,需要时重建
    • limited 可伸缩线程池,但池中的线程数只会增长不会收缩。(为避免收缩时突然来了大流量引起的性能问题)
  • 连接控制: 连接数控制
  • 分组聚合: 分组聚合返回值,用于菜单聚合等服务
  • 泛化引用: 泛化调用,无需业务接口类进行远程调用,用于测试平台,开放网关桥接等
  • 延迟暴露: 延迟暴露服务,用于等待应用加载warmup数据,或等待spring加载完成
  • 延迟连接: 延迟建立连接,调用时建立
      • 包中的类应该有同样的重用可能性
      • 紧密协作的类应该放在一个包
      • 对于变化因子,包中的类应全改或全不改
      • 变化应在包内终止,而不传播到其它包
      • 发布的粒度和复用度相同
      • 被依赖的包应该总是比依赖者更稳定
      • 不要让一个稳定的包依赖于不稳定包
      • 稳定的包不抽象将导致扩展性极差
      • 抽象的包不稳定将导致其依赖包跟随变化
    • 平等对待第三方 
        • 框架自己的功能也要扩展点实现
        • 甚至微核的加载方式可以扩展
        • 装配逻辑由扩展点之间互助完成
        • 杜绝硬编码桥接中间代码
        • 层叠扩展粒度,逐级细分
        • 大的扩展点加载小的扩展点
        • 只与触手可及的扩展点交互,间接转发
        • 保持行为单一输入输出明确
      • API传入参数,SPI扩展点实例
      • 尽量引用外部对象的实例,而不类元
  • 尽量使用IoC注入,减少静态工厂方法调用
    • 所有配置信息都转换成URL的参数
    • 所有的元信息传输都采用URL
    • 所有接口都可以获取到URL
    • 指产品主要功能入口,同时负责实体域会话域生命周期管理
    • 表示你要操作对象模型,不管什么产品,总有一个核心概念,大家都绕围它转。
    • 表示每次操作瞬时状态,操作前创建,操作后销毁。
    • 充血模型,实体域带行为
    • 可变与不可变状态分离,可变状态集中
    • 所有领域线程安全,不需要加锁
      • 通常服务域是无状态,或者只有启动时初始化不变状态,所以天生线程安全,只需单一实例运行
      • 通常设计为不变类,所有属性只读,或整个类引用替换,所以是线程安全
      • 保持所有可变状态,且会话域只在线程栈内使用,即每次调用都在线程栈内创建实例,调用完即销毁没有竞争,所以线程安全
  • API可配置,一定可编程
    • 命令无返回值表示命令,有副作用
    • 查询有返回值表示查询,保持幂等无副作用
      • 行为交互为主的系统是适用
      • 在以管理状态为主的系统中适用
    • Web框架请求响应流
    • 反例:IBatis2在SQL执行过程中没有设拦截点,导致添加安全日志拦截,执行前修改分页SQL等,不得不hack源代码
    • 保持截面功能单一,不易出问题
  • 确保派发失败,不影响主过程运行
    • 不可靠操作 (尽量缩小)
    • 前置断言 + 后置断言 + 不变式
  • 异常防御,但不忽略异常
  • 降低修改时的误解性,不埋雷
    • 避免基于异常类型分支流程
  • 软件质量的下降,来源于修改
  • 替换整个实现类,而不是修改其中的某行
  • 增量式 v.s.扩充式
  • 调用两次单向消息发送完成
    • 协议实现,不透明,点对点
    • 将集群中多个提供者伪装成一个
    • 透明化接口,桥接动态代理
    • 尽可能少的依赖低阶契约,用最少的抽象概念实现功能
    • 低阶切换实现时,高阶功能可以继续复用
    • 可能携带完整的上下文信息,比如出错原因,出错的机器地址,调用对方的地址,连的注册中心地址,使用Dubbo的版本等。
    • 尽量将直接原因写在最前面,所有上下文信息,在原因后用键值对显示。
    • 抛出异常的地方不用打印日志,由最终处理异常者决定打印日志的级别,吃掉异常必需打印日志
    • 打印ERROR日志表示需要报警,打印WARN日志表示可以自动恢复,打印INFO表示正常信息或完全不影响运行。
    • 建议应用方在监控中心配置ERROR日志实时报警,WARN日志每周汇总发送通知。
    • RpcException是Dubbo对外的唯一异常类型,所有内部异常,如果要抛出给用户,必须转为RpcException。
    • RpcException不能有子类型,所有类型信息用ErrorCode标识,以便保持兼容。
    • 配置对象属性首字母小写,多个单词用驼峰命名(Java约定)。
    • 配置属性全部用小写,多个单词用"-"号分隔(Spring约定)。
    • URL参数全部用小写,多个单词用"."号分隔(Dubbo约定)。
    • 尽可能用URL传参,不要自定义Map或其它上下文格式,配置信息也转成URL格式使用。
    • 尽量减少URL嵌套,保持URL的简洁性。
  • 保持单元测试用例的运行速度,不要将性能和大的集成用例放在单元测试中。
  • 减少while循环等待结果的测试用例,对定时器和网络的测试,用以将定时器中的逻辑抽为方法测试。
  • 对于容错行为的测试,比如failsafe的测试,统一用LogUtil断言日志输出。
    • 扩展点之间的组合将关系AOP完成,ExtensionLoader只负载加载扩展点,包括AOP扩展。
    • 尽量采用IoC注入扩展点之间的依赖,不要直接依赖ExtensionLoader的工厂方法。
    • 尽量采用AOP实现扩展点的通用行为,而不要用基类,比如负载均衡之前的isAvailable检查,它是独立于负载均衡之外的,不需要检查的是URL参数关闭。
    • 对多种相似类型的抽象,用基类实现,比如RMI,Hessian等第三方协议都已生成了接口代理,只需将将接口代理转成Invoker即可完成桥接,它们可以用公共基类实现此逻辑。
    • 基类也是SPI的一部分,每个扩展点都应该有方便使用的基类支持
    • 基于复用度分包,总是一起使用的放在同一包下,将接口和基类分成独立模块,大的实现也使用独立模块。
    • 所有接口都放在模块的根包下,基类放在support子包下,不同实现用放在以扩展点名字命名的子包下。
    • 尽量保持子包依赖父包,而不要反向。
  • 参考文章“【总结】Netty(RPC高性能之道)原理剖析 ”
}

版权声明:本文为博主原创文章,遵循 版权协议,转载请附上原文出处链接和本声明。

不久前参与开发了一个基于dubbo分布式框架的底层账单系统,并实现了其中的一部分业务接口,目前需对这些接口进行压测,以评估生产环境所能承受的最大吞吐量。笔者以其中一个查询接口为例来回顾此次压测的整体流程

无限次请求查询接口(保证任意时刻并发量相同),观察Error%为0,当请求平稳进行时的tps,为该接口吞吐量

2.准备完毕后,依次在不同容量线程组下启动测试计划,结果如下

结论:当线程数为200时,tps达到1700+,随着线程数增加,99%Line明显蹿升至6s,猜想部分线程请求不到资源,并且Error线程占比瞬间增多也印证了这一点。ps:如果同一组参数测试,压测效果却在递减,可尝试重启Jmeter。

当前测试结构中包含三个节点:本地测试Consumer节点—>查询接口Provider节点—>数据库节点,所以相邻两个节点间均可能产生并发瓶颈,所以需要定位具体问题发生的具体位置。由于压测仅需一个节点,所以笔者使用了jVisualVM+jmx+jstacd组合,远程监听Dubbo服务所在的那台机器。

1.jstatd:(JDK自带)基于RMI的服务程序,用于监控基于HotSpot的JVM中资源的创建及销毁。首次使用需在被监控机器中加入权限授予文件jstatd.all.policy(jdk的bin目录下)

对外默认开启1099端口

3.jmx:(JDK自带),是一个为应用程序、设备、系统等植入管理功能的框架,如管理基于tomcat的web服务,本文中管理基于SpringBoot的Dubbo服务,需在启动脚本中加入jmx的启动配置

开启压测,并观察jVisualVM中占用CPU时间非常多的热点方法,并查询远程主机cpu使用率情况

发现在正常线程数请求时,获取DriudDataSource连接池连接的方法CPU时间非常高,经查询发现,系统中连接池的配置:initialSize、minIdle、maxActive都非常低,遂进行了第一次调优:提升数据库连接数,连接池初始化连接数50,最小空闲连接数50,最高活跃连接数400。

提升后,获取连接方法的CPU时间明显降低,遂测试线程数为400时的请求环境下的支持情况,发现已经开始出现error,即一部分线程请求不到资源,99%Line也达到6s之大!

此时系统的数据库连接池配置已经达到400,瓶颈不在此处,那么会不会是远程的数据库节点存在瓶颈,于是远程登录数据库节点,发现mysql的允许连接数非常大,不存在瓶颈。既然请求线程数非常大,数据库连接池连接数非常大,数据库提供的连接数也足够,CPU、JVM均没有异常,那么造成性能瓶颈的可能在与dubbo允许提供的连接线程数不足以匹配压测产生的线程数。

定位到dubbo配置,发现并没有显式定义dubbo连接数,查阅dubbo开发文档

dubbo默认连接线程数

问题发现了:dubbo默认连接线程数为100,  而并发量400的请求线程对dubbo造成的压力过大,导致压测不久就出现部分线程请求不到资源超时的问题,遂进行了第二次调优:提升Dubbo线程池连接数,将连接数提升至1000。

那么是不是到此并发就不存在瓶颈了呢?1000请求线程+dubbo允许线程数1000+数据库大连接数支持,理论上操作是没有问题的,我们来实际跑一下,发现压测时出现了更严重的问题,刚开始请求就出现了OOM及超过一半的error线程,准备去远程机器打印一下执行日志,就连tail及ps命令都没有可用资源供执行,停掉了请求线程,又费了九牛二虎之力停掉了服务进程,开始分析原因:各系统间通信均无瓶颈,问题会出在哪里,是什么原因撑爆了JVM,已知的条件是远程服务至少有1000个线程在服务器内生存,是不是线程量太大撑爆了机器?由于JVM中,栈空间线程私有,查阅JVM参数

服务器为linux系统,那默认ThreadStackSize=1024K,那么1000个线程JVM就需要创建k即1个G的空间!这个节点部署三个服务,光一个服务的请求线程就占据1个G,内存溢出也是情理之中的了,遂进行了第三次调优:减少线程栈空间,ThreadStackSize调至256K,也是够用的,再次模拟1000线程并发,OK,无论是系统间线程调用还是内存中JVM空间都在正常情况下,并未出现线程请求不到资源的情况。

本次压测主要目的是确定单节点在生产环境所能承受的tps峰值,并借助测试数据反向分析之前开发及单元测试无法覆盖的隐藏问题,通过三次调优,我们可以发现,该环境下瓶颈主要在系统间请求发生时,以及JVM自身无法负载大数据量线程导致。当然也有可能发生在程序本身过程中,如逻辑中创造大量对象,消耗大量内存,或同步逻辑处理块设计欠缺,导致死锁、线程饿死等。笔者所描述的问题只是众多压测问题中的一小部分,分析、操作难免有疏漏,欢迎各位同学予以指正及建议。感谢华哥、林哥指导,感谢一鸣同学协助~

}

我要回帖

更多关于 dubbo负载均衡策略如何配置 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信