京东通天塔有效果吗 京东通天塔有什么用处( 二 )


如上图所示,优化前的设计,我们人为根据外部数据的依赖关系 , 将RPC调用拆分为多个并行阶段,也是权衡了通天塔早期的活动复杂度、流量以及系统实现复杂度等因素的设计 。这种设计的问题就是,第二阶段的整体开始查询时间依赖于第一阶段最慢的那个查询节点的结束时间,即使第二阶段的查询节点完全不依赖于第一阶段最慢的那个查询节点 。通过调用链分析工具分析RPC调用的延迟和时间轴上的关系,也证实了我们这一观点 。因此我们进行了RPC调度的设计优化 , 基于Sirector并行任务编排组件(公司内部组件,可以理解为类似于JDK的Fork/Join组件),根据页面的楼层配置,动态构建出RPC查询任务依赖的DAG图,RPC调用自然地沿着DAG图的方向执行 , 消除了人为的阶段划分 。
图6.1.1.c:优化后外部数据查询示意图
优化编排后对接口响应的均值和TP999都有很大的贡献 。
6.1.2 单任务RPC查询并行优化
通过调用链分析工具 , 我们也发现部分RPC查询任务内部存在串行查询的情况 。以商品促销信息查询为例,入参是一箩筐商品sku,分批串行查询促销信息 。优化过程如下图所示,在上游接口提供方可接受的并行度范围内进行并行查询 。
图6.1.2.a:RPC查询并行优化示意图
6.1.3 虚拟机减压 , 冷数据优化
通天塔是一个高并发高流量的系统,使用缓存技术来缓存活动配置等内部数据 。对于大流量的系统,使用缓存技术就无需多言了 。但是 , 随着业务的不断增长,缓存的量也越来越大,这给JVM堆内存造成了很大的压力 。那么是否真的所有数据都要缓存?是否可以进行冷热数据的拆分呢?
其实,为了满足用户个性化的需求,对于特定楼层(如小院feed流),通天塔预定义了一部分活动作为个性化楼层的数据源 。这类活动不会单独对外投放,因此可以作为冷数据从在线活动池中拆分出去,通过RPC查询接口按需查询这部分数据 。
图6.1.3.a:冷数据优化示意图
如上图所示,优化后减轻了虚拟机的内存压力 。
6.1.4 尾延迟优化
尾延迟优化主要参考了Jeff Dean的论文《The Tail At Scale》,采用发送hedged-request备份请求的方式,来解决扇出依赖较多,因集群规模扩大后 , 上游小概率的长尾传导至下游后放大产生长尾概率的问题 , 平滑尾延迟 。只适用于上游长尾特征明显的情况 。为了对上游不造成太大影响 , 我们也启用了限流策略,控制备份请求的发送量 。
由于我们的RPC框架不支持请求取消,而且实现要注意的细节问题较多,目前处于小流量实验阶段 。
图6.1.4.a:尾延迟优化示意图
2 优化组件
组件层面的优化,主要做了一些组件的替换升级、参数调优 , 变更代价小 , 有较高的性价比 。
6.2.1 JVM优化
JVM优化主要是进行了从JDK1.6到JDK1.8的升级 , 默认的Parallel垃圾回收器吞吐优先,不太适合我们的应用场景 , 因此改成了响应优先的G1;
另外由于JVM的sychronized(即使业务代码没用到,很多框架也有用到)锁优化在偏向锁升级过程中需要进入全局安全点,会有很短的STW 。考虑到通天塔应用的高并发场景,我们也关闭了偏向锁来提高吞吐;
从部署环境的角度考虑,当前我们使用的JVM实现无法感知到自己处于Docker容器环境,获取的CPU核数是物理机的核数 , 远超分配给我们的容器核数 。这导致了一些依赖于CPU资源限制推导的虚拟机默认参数会非常不合理 。比如 , JVM默认的GC线程数会偏大 , 在GC纯本地计算阶段会有大量无意义的线程切换影响吞吐,因此我们手动设置了GC线程数,向容器核数对齐 。
6.2.2 日志框架优化
结合线程栈和磁盘IO分析,发现之前的日志框架(log4j/logback)都使用了同步模式 。并发量大时锁竞争激烈 , 上下文切换造成额外开销;另外,同步模式下,磁盘较低的IOPS影响应用的响应时间 。因此我们切换到了log4j2框架上来,并结合LMAX Disruptor,开启了全异步的日志模式 , 同时log4j2的对象复用机制减少了框架本身产生的内存垃圾 , 降低日志框架造成尾延迟的概率,将日志对应用的影响降到最低 。
图6.2.2.a:日志框架优化示意图
6.2.3 线程池优化
原先线程池设置有一些不合理的地方 。比如:线程数设置过大,导致CPU切换开销大,占用内存资源;核心线程数与最大线程数之间差值较大、线程池没有预热 , 造成突发流量增大时,同时申请线程资源时响应时长产生明显的抖动 。

推荐阅读