经历400多天打磨,HSF的架构和性能有哪些新突破?
ccwgpt 2024-10-13 01:35 22 浏览 0 评论
摘要: 从HSF2.1到HSF2.2的目标是提升HSF框架的扩展性、易用性和性能,相当于将原有HSF推倒了重新来过,因此涉及到的内容众多,这里不做展开,我们仅从架构升级、性能优化和运维提升三个方面来聊一下HSF2.2做了什么,有什么能帮到大家,以及如何解决了双十一的一些问题。
2017年的双十一圆满结束了,1682亿的成交额再一次刷新了记录,而HSF(High Speed Framework,分布式服务框架)当天调用量也突破了3.5万亿次,调用量是2016年双十一的三倍多。为了这不平凡的一天中,HSF经历了一年多的磨练,从架构升级到性能优化,从可用性提升到运维提升,完成了从2.1到2.2的全面升级,截止到双十一开始的一刻,HSF2.2在全网机器占比超过75%,并成功接受了大促考验。
从HSF2.1到HSF2.2的目标是提升HSF框架的扩展性、易用性和性能,相当于将原有HSF推倒了重新来过,因此涉及到的内容众多,这里不做展开,我们仅从架构升级、性能优化和运维提升三个方面来聊一下HSF2.2做了什么,有什么能帮到大家,以及如何解决了双十一的一些问题。
架构升级
HSF2.1主要被用户抱怨的一点就是扩展性不强,比如:不少用户想拦截调用请求但无法方便做到,只好退而求其次,使用代理来包装HSF的客户端或者服务端,这样做一来显得繁琐,二来出现问题不易排查。不光是用户的扩展支持的不好,框架自身对不同的开源组件适配也显得力不从心,比如:支持ZooKeeper作为注册中心的方案一直难以实施,原因在于框架内部与ConfigServer耦合过于紧密。无论是用户还是框架的维护者,都受限于现有架构,这是HSF需要改变的地方。
HSF2.2的架构与原有版本有了显著变化,首先是层与层之间的边界清晰,其次组件之间的关系职责明确,最后是扩展可替换的思想贯穿其中,下面我们先看一下新的架构:
可以看到HSF2.2从宏观上分为了三个域:框架、应用和服务,其中框架是对上层多个应用做支撑的基础,而服务属于具体的某个应用,从这种划分可以看出,HSF2.2是天然支持多应用(或多模块)部署的。每个域中,都有各自的功能组件,比如:服务域中的InvocationHandler就是对服务调用逻辑的抽象,通过扩展其中的ClientFilter或者ServerFilter完成对客户端或者服务端的调用处理工作,这里只是罗列了主要的功能组件和扩展点,以下是对它们的说明:
服务 ,对应于一个接口,它可以提供服务或者消费服务
组件名 | 扩展点 | 说明 |
---|---|---|
InvocationHandler | ClientFilter | 客户端调用拦截器,扩展它实现调用端的请求拦截处理 |
InvocationHandler | ServerFilter | 服务端调用拦截器,扩展它实现服务端的请求拦截处理 |
Router | AbstractMultiTargetRouter | 客户端调用多节点选择路由器,扩展它实现客户端调用多节点的路由 |
Router | AbstractSingleTargetRouter | 客户端调用单节点选择路由器,扩展它实现客户端调用单节点的路由 |
应用 ,对应于一个应用或者一个模块,它管理着一组消费或者发布的服务
组件名 | 扩展点 | 说明 |
---|---|---|
Registry | AddressListener | 注册中心地址监听器,扩展它可以获取到注册中心推送的地址列表 |
Registry | AddressListenerInteceptor | 注册中心地址拦截器,扩展它可以监听到注册中心推送地址并加以控制 |
Protocol | ProtocolInterceptor | 订阅发布流程,扩展它可以捕获应用发布或者消费的具体服务 |
框架 , 对应于框架底层支持,它统一管理着线程、链接等资源
组件名 | 扩展点 | 说明 |
---|---|---|
Serialize | Serializer | 序列化和反序列化器,需要实现新的序列化方式可以扩展它 |
Packet | PacketFactory | 协议工厂, 适配不同的RPC框架协议需要扩展它 |
Stream | StreamLifecycleListener | 长连接生命周期管理监听器,扩展它可以监听到长连接的建立和销毁等生命周期 |
Stream | StreamMessageListener | 长连接事件监听器,扩展它可以监听到每条连接上跑的数据 |
分类介绍了HSF2.2的主要组件后,我们来看看架构的改变,扩展性的提升,能够为业务方,为他们更好的支持双十一带来什么。
纯异步调用拦截器
HSF异步future调用被业务方越来越多的使用,但是eagleeye对future调用的耗时统计上一直不准,因为future调用返回的是默认值,所以业务看到的是一次飞快的调用返回,它真实的耗时该如何度量?信息平台承载了集团的众多内部服务业务,一直苦于没有办法全面的监控HSF的调用记录,也无法做到异常报警,难道还要让所有的使用方配置代理?
HSF2.2设计的InvocationHandler组件以及扩展,完美的解决了这个问题,我们近距离观察一下InvocationHandler以及其扩展,以客户端调用场景为例:一次调用是如何穿过用户扩展的调用拦截器的。
可以看到,HSF2.2定义的调用拦截器不是一个简单的正向invoke过程,它还具备响应回来时的逆向onResponse过程,这样一来一回很好的诠释了HSF同步调用异步执行的调用模型。用户可以通过在一次调用中计算invoke和onResponse之间的时差来获得调用的正确耗时,而不用关心HSF调用是同步还是异步,用户也可以扩展一个调用拦截器穿插在其中,用于监控HSF的调用内容,而不用让应用方做任何代码上的改造。
多应用支持
多模块部署一直是业务方同学探索的热点,从合并部署开始,HSF就有限的支持了多应用部署,但是一直没有将其融入到架构中。在HSF2.2中,应用不仅仅是作为托管服务的容器而是成为核心领域,多应用序列化的难题,就在应用层解决。
(模块在HSF2.2中也会被认为是一个应用,以下提到模块或者应用可以认为是一个概念。)
HSF2.2支持多个应用部署的结构如下图所示:
可以看到,HSF2.2框架域处于最底层,它向上为应用提供了线程、连接等多种资源的支持,而应用能够发布或者订阅多个服务,这些服务能够被应用统一管理,也支持更加精细化的配置,层与层的司职明确让HSF具有了良好的适应性。
性能优化
RPC流程中,序列化是开销很大的环节。尤其现在业务对象都很复杂,序列化开销更不容小觑。
下面先介绍下今年HSF针对Hessian的几个优化点。
优化1:元数据共享
hessian序列化会将两种信息写到输出流:
元数据:即类全名,字段名
值数据:即各个字段对应值(如果字段是复杂类型,则会递归传递该复杂类型的元数据和内部字段的值数据)
在hessian1协议里,每次写出Class A的实例时,都会写出Class A的元数据和值数据,就是会重复传输相同的元数据。针对这点,hessian2协议做了一个优化就是:在“同一次序列化上下文”里,如果存在Class A的多个实例,只会对Class A的元数据传输一次。该元数据会在对端被缓存起来重复使用,下次再序列化Class A的对象时,只需要先写出对元数据的一个引用句柄(缓存中的index,用一个int表示),然后直接写出值数据即可。接受方通过元数据句柄即可知道后面的值数据对应的类型。
这是一个极大的提升。因为编码字段名字(就是字符串)所需的字节数很可能比它对应的值(可能只是一个byte)更多。
不过在官方的hessian里,这个优化有两个限制:
序列化过程中类型对应的Class结构不能改变
元数据引用只能在“同一个序列化上下文”,这里的“上下文”就是指同一个HessianOutput和HessianInput。因为元数据的id分配和缓存分别是在HessianOutput和HessianInput里进行的
限制1我们可以接受,一般DO不会再运行时改变。但是限制2不太友好,因为针对每次请求的序列化和反序列化,HSF都需要使用全新构造的HessianOutput和HessianInput。这就导致每次请求都需要重新发送上次请求已经发送过的元数据。
针对限制2,HSF实现了跨请求元数据共享,这样只要发送过一次元数据,以后就再也不用发送了,进一步减少传输的数据量。实现机制如下:
修改hessian代码,将元数据id分配和缓存的数据结构从HessianOutput和HessianInput剥离出来。
修改HSF代码,将上述剥离出来的数据结构作为连接级别的上下文保存起来。
每次构造HessianOutput和HessianInput时将其作为参数传入。这样就达了跨请求复用元数据的目的。
该优化的效果取决于业务对象中,元数据所占的比例,如果“精心”构造对象,使得元数据所占比例很大,那么测试表现会很好,不过这没有意义。我们还是选取线上核心应用的真实业务对象来测试。从线上tcp dump了一个真实业务对象,测试同学以此编写测试用例得到测试数据如下:
新版本比老版本CPU利用率下降10%左右
新版本的网络流量相比老版本减少约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
优化3:异常流程改进
hessian在一些异常场景下,虽然业务不一定报错,但是性能却很差。今年我们针对两个异常场景做了优化。
UnmodifiableSet构造异常
某核心应用压测发现大量如下错误:
java.util.Collections$UnmodifiableSet
========================================================================================
java.lang.Throwable.(Throwable.java:264)
java.lang.Exception.(Exception.java:66)
java.lang.ReflectiveOperationException.(ReflectiveOperationException.java:56)
java.lang.InstantiationException.(InstantiationException.java:63)
java.lang.Class.newInstance(Class.java:427)
com.taobao.hsf.com.caucho.hessian.io.CollectionDeserializer.createList(CollectionDeserializer.java:107)
com.taobao.hsf.com.caucho.hessian.io.CollectionDeserializer.readLengthList(CollectionDeserializer.java:88)
com.taobao.hsf.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1731)
com.taobao.hsf.com.caucho.hessian.io.UnsafeDeserializer$ObjectFieldDeserializer.deserialize(UnsafeDeserializer.java:387)
这是Hessian对UnmodifiableSet反序列化的一个问题,因为UnmodifiableSet没有默认构造函数,所以Class.newInstance会抛出,出错之后Hessian会使用HashSet进行反序列化,所以业务不会报错,但是应用同学反馈每次都处理异常,对性能影响比较大。
重复加载缺失类型
某核心应用压测发现大量线程block在ClassLoader.loadClass方法
"HSFBizProcessor-DEFAULT-7-thread-1107" #3049 daemon prio=10 os_prio=0 tid=0x00007fd127cad000 nid=0xc29 waiting for monitor entry [0x00007fd0da2c9000] java.lang.Thread.State: BLOCKED (on object monitor) at com.taobao.pandora.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:133) - waiting to lock <0x0000000741ec9870> (a java.lang.Object) atjava.lang.ClassLoader.loadClass(ClassLoader.java:380) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at com.taobao.hsf.com.caucho.hessian.io.SerializerFactory.getDeserializer(SerializerFactory.java:681)
排查确认是业务二方包升级,服务端客户端二方包版本不一致,导致老版本的一边反复尝试加载对应的新增类型。虽然该问题并没有导致业务错误,但是确严重影响了性能,按应用同学的反馈,如果每次都尝试加载缺失类型,性能会下降一倍。
针对这种情况,修改hessian代码,将确认加载不到的类型缓存起来,以后就不再尝试加载了。
优化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);
工作机制简单说明如下:
key类型由String改为自定义的AttributeKey,AttributeKey会在初始化阶段就去AttributeNamespace申请一个固定id
map类型由HashMap改为自定义的DefaultAttributeMap,DefaultAttributeMap 内部使用数组存放数据
操作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
在刚刚过去的 2017 年双 11 中,HSF服务治理(HSFOPS)上线了诸多提升应用安全、运维效率的新功能,如:
服务鉴权
单机运维
连接预热
其中,在今年双11中发挥最大价值的,当属 HSF 连接预热功能。
HSF 连接预热功能,可以帮助应用提前建立起和 Providers 之间的连接,从而避免首次调用时因连接建立造成的 rt 损耗。
HSF服务治理平台的连接预热功能,支持指定应用、机器分组以及单元环境的预热,并且可以指定预热执行计划,并支持按预热批次暂停、恢复预热。在 2017 双 11 预热完成后,目标应用集合整体连接数增长大于50%,部分应用增长达 40 倍,符合连接预热的预期。
小结
架构升级使HSF能够从容的应对未来的挑战,性能优化让我们在调用上追求极致,运维提升更是将眼光拔高到全局去思考问题,这些改变共同的组成了HSF2.1到HSF2.2的变革,而这次变革不仅仅是稳定的支撑2017年的双十一,而是开启了服务框架下一个十年。
相关推荐
- 定时任务工具,《此刻我要...》软件体验
-
之前果核给大家介绍过一款小众但实用的软件——小说规则下载器,可以把网页里的小说章节按照规则下载到本地,非常适合喜欢阅读小说的朋友。有意思的是,软件作者当时看到果核写的体验内容后,给反推荐到他的帖子里去...
- 前端定时任务的神库:Node-cron,让你的项目更高效!
-
在前端开发中,定时任务是一个常见的需求。无论是定时刷新数据、轮询接口,还是发送提醒,都需要一个可靠且灵活的定时任务解决方案。今天,我要向大家介绍一个强大的工具——Node-cron,它不仅能解决定时任...
- Shutter Pro!一款多功能定时执行任务工具
-
这是一款可以在电脑上定时执行多种任务的小工具,使用它可以根据时间,电量等来设定一些定时任务,像定时打开程序、打开文件,定时关机重启,以及定时弹窗提醒等都可以轻松做到。这是个即开即用的小工具,无需安装,...
- 深度解析 Redis 缓存击穿及解决方案
-
在当今互联网大厂的后端开发体系中,Redis缓存占据着极为关键的地位。其凭借高性能、丰富的数据类型以及原子性操作等显著优势,助力众多高并发系统从容应对海量用户的访问冲击,已然成为后端开发从业者不可或...
- 从零搭建体育比分网站完整步骤(比较好的体育比分软件)
-
搭建一个体育比分网站是一个涉及前端、后端、数据源、部署和维护的完整项目。以下是从零开始搭建的详细流程:一、明确项目需求1.功能需求:实时比分展示(如足球、篮球、网球等)支持多个联赛和赛事历史数据查询比...
- 告别复杂命令行:GoCron 图形界面让定时任务触手可及
-
如果你是运维人员或者经常接触一些定时任务的配置,那么你一定希望有一款图形界面来帮助你方便的轻松配置定时任务,而GoCron就是这样一款软件,让你的配置可视化。什么是GoCron从名字你就可以大概猜到,...
- Java任务管理框架核心技术解析与分布式高并发实战指南
-
在当今数字化时代,Java任务管理框架在众多应用场景中发挥着关键作用。随着业务规模的不断扩大,面对分布式高并发的复杂环境,掌握其核心技术并进行实战显得尤为重要。Java任务管理框架的核心技术涵盖多个方...
- 链表和结构体实现:MCU软件定时器(链表在单片机中的应用)
-
在一般的嵌入式产品设计中,介于成本、功耗等,所选型的MCU基本都是资源受限的,而里面的定时器的数量更是有限。在我们软件设计中往往有多种定时需求,例如脉冲输出、按键检测、LCD切屏延时等等,我们不可能...
- SpringBoot定时任务(springboot定时任务每小时执行一次)
-
前言在我们开发中,经常碰到在某个时间点去执行某些操作,而我们不能人为的干预执行,这个时候就需要我们使用定时任务去完成该任务,下面我们来介绍下载springBoot中定时任务实现的方式。定时任务实现方式...
- 定时任务新玩法!systemd timer 完整实战详解
-
原文链接:「链接」Hello,大家好啊!今天给大家带来一篇使用systemdtimer实现定时任务调度的详细实战文章。相比传统的crontab,systemdtimer更加现代化、结构清晰...
- Celery与Django:打造高效DevOps的定时任务与异步处理神器
-
本文详细介绍了Celery这一强大的异步任务队列系统,以及如何在Django框架中应用它来实现定时任务和异步处理,从而提高运维开发(DevOps)的效率和应用性能。下面我们先认识一下Cele...
- 订单超时自动取消的7种方案,我用这种!
-
前言在电商、外卖、票务等系统中,订单超时未支付自动取消是一个常见的需求。这个功能乍一看很简单,甚至很多初学者会觉得:"不就是加个定时器么?"但真到了实际工作中,细节的复杂程度往往会超...
- 裸机下多任务框架设计与实现(gd32裸机配置lwip 网络ping不通)
-
在嵌入式系统中,特别是在没有操作系统支持的裸机环境下,实现多任务执行是一个常见的挑战。本文将详细介绍一种基于定时器的多任务框架设计,通过全局时钟和状态机机制,实现任务的非阻塞调度,确保任务执行中不会出...
- 亿级高性能通知系统构建,小白也能拿来即用
-
作者介绍赵培龙,采货侠JAVA开发工程师分享概要一、服务划分二、系统设计1、首次消息发送2、重试消息发送三、稳定性的保障1、流量突增2、问题服务的资源隔离3、第三方服务的保护4、中间件的容错5、完善...
- 运维实战:深度拆解Systemd定时任务原理,90%的人不知道的玩法
-
运维实战:深度拆解Systemd定时任务原理,90%的人不知道的高效玩法一、Systemd定时任务的核心原理Systemd定时任务是Linux系统中替代传统cron的现代化解决方案,通过...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- bootstrap框架 (43)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- express框架 (43)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)