Pinpoint是一个分析大型分布式系统的平台,提供解决方案来处理海量跟踪数据。2012年七月开始开发,2015年1月9日做为开源项目启动。html
本文将介绍Pinpoint: 什么促使咱们开始搭建它, 用了什么技术, 还有Pinpoint agent是如何优化的。java
和现在相比, 过去的因特网的用户数量相对较小,而因特网服务的架构也没那么复杂。web服务一般使用两层(web 服务器和数据库)或三层(web服务器,应用服务器和数据库)架构。然而在现在,随着互联网的成长,须要支持大量的并发链接,而且须要将功能和服务有机结合,致使更加复杂的软件栈组合。更确切地说,比三层层次更多的n层架构变得更加广泛。SOA或者微服务架构成为现实。git
系统的复杂度所以提高。系统越复杂,越难解决问题,例如系统失败或者性能问题。在三层架构中找到解决方案还不是太难,仅仅须要分析3个组件好比web服务器,应用服务器和数据库,而服务器数量也很少。可是,若是问题发生在n层架构中,就须要调查大量的组件和服务器。另外一个问题是仅仅分析单个组件很难看到大局;当发生一个低可见度的问题时,系统复杂度越高,就须要更长的时间来查找缘由。最糟糕的是,某些状况下咱们甚至可能没法查找出来。github
这样的问题也发生在NAVER的系统中。使用了大量工具如应用性能管理(APM)可是还不足以有效处理问题。所以咱们最终决定为n层架构开发新的跟踪平台,为n层架构的系统提供解决方案。web
Pinpoint, 2012年七月开始开发,在2015年1月做为一个开源项目启动, 是一个为大型分布式系统服务的n层架构跟踪平台。 Pinpoint的特色以下:spring
本文将讲述Pinpoint的技术,例如事务跟踪和字节码加强。还会解释应用在pinpoint agent中的优化方法,agent修改字节码并记录性能数据。数据库
pinpoint跟踪单个事务中的分布式请求,基于google Dapper。apache
当一个消息从Node1发送到Node2(见图1)时,分布式追踪系统的核心是在分布式系统中识别在Node1中处理的消息和在Node2中出的消息之间的关系。bootstrap
图1. 分布式系统中的消息关系tomcat
问题在于没法在消息之间识别关系。例如,咱们没法识别从Node1发送的第N个消息和Node2接收到的N'消息之间的关系。换句话说,当Node1发送完第X个消息时,是没法在Node2接收到的N的消息里面识别出第X个消息的。有一种方式试图在TCP或者操做系统层面追踪消息。可是,实现很复杂并且性能低下,并且须要为每一个协议单独实现。另外,很难精确追踪消息。
不过,Google dapper实现了一个简单的解决方案来解决这个问题。这个解决方案经过在发送消息时添加应用级别的标签做为消息之间的关联。例如,在HTTP请求中的HTTP header中为消息添加一个标签信息并使用这个标签跟踪消息。
Google's Dapper
关于Google Dapper的更多信息, 请见 "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure."
Pinpoint基于google dapper的跟踪技术,可是已经修改成在调用的header中添加应用级别标签数据以便在远程调用中跟踪分布式事务。标签数据由多个key组成,定义为TraceId。
Pinpoint中,核心数据结构由Span, Trace, 和 TraceId组成。
Google Dapper 和 NAVER Pinpoint在术语上的不一样
Pinpoint中的术语"TransactionId"和google dapper中的术语"TraceId"有相同的含义。而Pinpoint中的术语"TraceId"引用到多个key的集合。
下图描述TraceId的行为,在4个节点之间执行了3次的RPC调用:
图2: TraceId行为示例
在图2中,TransactionId (TxId) 体现了三次不一样的RPC做为单个事务被相互关联。可是,TransactionId 自己不能精确描述PRC之间的关系。为了识别PRC之间的关系,须要SpanId 和 ParentSpanId (pSpanId). 假设一个节点是Tomcat,能够将SpanId想象为处理HTTP请求的线程,ParentSpanId表明发起这个RPC调用的SpanId.
使用TransactionId,Pinpoint能够发现关联的n个Span,并使用SpanId和ParentSpanId将这n个span排列为继承树结构。
SpanId 和 ParentSpanId 是 64位长度的整型。可能发生冲突,由于这个数字是任意生成的,可是考虑到值的范围能够从-9223372036854775808到9223372036854775807,不太可能发生冲突. 若是key之间出现冲突,Pinpoint和Google Dapper系统,会让开发人员知道发生了什么,而不是解决冲突。
TransactionId 由 AgentIds, JVM (java虚拟机)启动时间, 和 SequenceNumbers/序列号组成.
Dapper 和 Zipkin, Twitter的一个分布式系统跟踪平台, 生成随机TraceIds (Pinpoint是TransactionIds) 并将冲突状况视为正常。然而, 在Pinpiont中咱们想避免冲突的可能,所以实现了上面描述的系统。有两种选择:一是数据量小可是冲突的可能性高,二是数据量大可是冲突的可能性低。咱们选择了第二种。
可能有更好的方式来处理transaction。咱们起先有一个想法,经过中央key服务器来生成key。若是实现这个模式,可能致使性能问题和网络错误。所以,大量生成key被考虑做为备选。后面这个方法可能被开发。如今采用简单方法。在pinpoint中,TransactionId被当成可变数据来对待。
前面咱们解释了分布式事务跟踪。实现的方法之一是开发人员本身修改代码。当发生RPC调用时允许开发人员添加标签信息。可是,修改代码会成为包袱,即便这样的功能对开发人员很是有用。
Twitter的 Zipkin 使用修改过的类库和它本身的容器(Finagle)来提供分布式事务跟踪的功能。可是,它要求在须要时修改代码。咱们指望功能能够不修改代码就工做并但愿获得代码级别的可见性。为了解决这个问题,pinpoint中使用了字节码加强技术。Pinpoint agent干预发起RPC的代码以此来自动处理标签信息。
字节码加强术语在手工和自动方法之间的自动方法(逻辑不通啊。。。)
下面是每一个方法的优势和缺点:
Table1 每一个方法的优缺点
优势 | 缺点 | |
---|---|---|
手工跟踪 | 1. 要求更少开发资源 2. API能够更简单并最终减小bug的数量 | 1. 开发人员必须修改代码 2. 跟踪级别低 |
自动跟踪 | 1. 开发人员不须要修改代码 2. 能够收集到更多精确的数据由于有字节码中的更多信息 | 1. 在开发pinpoint时,和实现一个手工方法相比,须要10倍开销来实现一个自动方法 2. 须要更高能力的开发人员,能够当即识别须要跟踪的类库代码并决定跟踪点 3. 增长bug发生的可能性,由于使用了如字节码加强这样的高级开发技巧 |
字节码加强是一种高难度和高风险的技术。可是,使用这种技术有不少好处,考虑开发资源和难度级别。(补充:这段话翻译的好难受。。。)
虽然它须要大量的开发资源,在开发服务上它须要不多的资源。例如,下面展现了使用字节码加强的自动方法和使用类库的手工方法(在这里的上下文中,开销是为澄清而假设的随机数)之间的开销。
自动方法: 总共 100
手工方法: 总共 30
上面的数据告诉咱们手工方法比自动方法有更合算。可是,不适用于咱们的在NAVER的环境。在NAVER咱们有几千个服务,所以在上面的数据中须要修改用于服务实施的开销。若是咱们有10个服务须要修改,总开销计算以下:
Pinpoint开发开销 20 + 服务实施开销 10 x 10 = 120
基于这个结果,自动方法是一个更合算的方式。
咱们很幸运的在pinpoint团队中拥有不少高能力而专一于Java的开发人员。所以,咱们相信克服pinpoint开发中的技术难题只是个时间问题。
咱们选择字节码加强的理由,除了前面描述的那些外,还有下面的强有力的观点:
一旦API被暴露给开发人员使用,咱们做为API的提供者,就不能随意的修改API。这样的限制会给咱们增长压力。
咱们可能修改API来纠正错误设计或者添加新的功能。可是,若是作这些受到限制,对咱们来讲很难改进API。解决这个问题的最好的答案是一个可升级的系统设计,而每一个人都知道这不是一个容易的选择。若是咱们不能掌控将来,就不可能建立完美的API设计。
而使用字节码加强技术,咱们就没必要担忧暴露跟踪API而能够持续改进设计,不用考虑依赖关系。对于那些计划使用pinpoint开发应用的人,换一句话说,这表明对于pinpoint开发人员,API是可变的。如今,咱们将保留隐藏API的想法,由于改进性能和设计是咱们的第一优先级。
使用字节码加强的缺点是当Pinpoint自身类库的采样代码出现问题时可能影响应用。不过,能够经过启用或者禁用pinpoint来解决问题,很简单,由于不须要修改代码。
经过增长下面三行到JVM启动脚本中就能够轻易的为应用启用pinpoint:
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar -Dpinpoint.agentId=<Agent's UniqueId> -Dpinpoint.applicationName=<The name indicating a same service (AgentId collection)>
若是由于pinpoint发生问题,只须要在JVM启动脚本中删除这些配置数据。
因为字节码加强技术处理java字节码, 有增长开发风险的趋势,同时会下降效率。另外,开发人员更容易犯错。在pinpoint,咱们经过抽象出拦截器(interceptor)来改进效率和可达性(accessibility)。pinpoint在类装载时经过介入应用代码为分布式事务和性能信息注入必要的跟踪代码。这会提高性能,由于代码注入是在应用代码中直接实施的。
图3: 字节码加强行为
在pinpoint中,拦截器API在性能数据被记录的地方分开(separated)。为了跟踪,咱们添加拦截器到目标方法使得before()方法和after()方法被调用,并在before()方法和after()方法中实现了部分性能数据的记录。使用字节码加强,pinpoint agent能够记录须要方法的数据,只有这样采样数据的大小才能变小。
最后,咱们描述用于pinpoint agent的性能优化的方式。
经过使用二进制格式(thrift)能够提升编码速度,虽然它使用和调试要难一些。也有利于减小网络使用,由于生成的数据比较小。
若是将一个长整型转换为固定长度的字符串, 数据大小通常是8个字节。然而,若是你用变长编码,数据大小能够是从1到10个字符,取决于给定数字的大小。为了减少数据大小,pinpoint使用thrift的CompactProtocol协议(压缩协议)来编码数据,由于变长字符串和记录数据能够为编码格式作优化。pinpoint agent经过基于跟踪的根方法的时间开始来转换其余的时间来减小数据大小。
图4 说明了上面章节描述的想法:
图4: 固定长度编码和可变长度编码的对比
为了获得关于三个不一样方法(见图4)被调用时间的数据,不得不在6个不一样的点上测量时间,用固定长度编码这须要48个字节(6 * 8)。
以此同时,pinpoint agent 使用可变长度编码并根据对应的格式记录数据。而后在其余时间点经过和参考点比较来计算时间值(在vector中),根方法的起点被确认为参考点。这只须要占用少许的字节,由于vector使用小数字。图4中消耗了13个字节。
若是执行方法花费了更多时间,即便使用可变长度编码也会增长字节数量。可是,依然比固定长度编码更有效率。
咱们但愿pinpoint能开启代码级别的跟踪。然而,存在增大数据大小的问题。每次高精度的数据被发送到服务器将增大数据大小,致使增长网络使用。
为了解决这个问题,咱们使用了在远程HBase中建立常量表的策略。例如,每次调用"Method A"的信息被发送到pinpoint collector, 数据大小将很大。pinpoint agent 用一个ID替换"method A",在HBase中做为一个常量表保存ID和"method A"的信息,而后用ID生成跟踪数据。而后当用户在网站上获取跟踪数据时,pinpoint web在常量表中搜索对应ID的方法信息并组合他们。使用一样的方式来减小SQL或者频繁使用的字符串的数据大小。
咱们在线门户服务有海量请求。单个服务天天处理超过200亿请求。容易跟踪这样的请求:方法是添加足够多的网络设施和服务器来跟踪请求并扩展服务器来收集数据。而后,这不是处理这种场景的合算的方法,仅仅是浪费金钱和资源。
在Pinpoint,能够收集采样资料而没必要跟踪每一个请求。在开发环境中请求量很小,每一个数据都收集。而在产品环境请求量巨大,收集小比率的数据如1~5%,足够检查整个应用的状态。有采样后,能够最小化应用的网络开销并下降诸如网络和服务器的设施费用。
pinpoint采样方法
Pinpoint 支持计数采样,若是设置为10则只采样10分之一的请求。咱们计划增长新的采样器来更有效率的收集数据。
注:对应的配置项在agent下的pinpoint.config文件中,默认"profiler.sampling.rate=1"表示所有
pinpoint不阻塞应用线程,由于编码后的数据或者远程消息被其余线程异步传输。
和gogole dapper不一样,pinpoint经过网络传输数据来确保数据速度。做为一个服务间使用的通用设施,当数据通信持续突发时网络会成为问题。在这种状况下,pinpoint agent使用UDP协议来给服务让出网络链接优先级。
注意
数据传输API能够被替换,由于它是接口分离的。能够修改实现为用其余方式存储数据,好比本地文件。
这里给出一个例子关于如何从应用获取数据,这样就能够全面的理解前面讲述的内容。
图5 展现了当在 TomcatA 和 TomcatB 中安装pinpoint的数据。能够把单个节点的跟踪数据当作single traction,提现分布式事务跟踪的流程。
图5:示例1:pinpoint应用
下面阐述pinpoint为每一个方法作了什么:
当请求到达TomcatA时, Pinpoint agent 产生一个 TraceId.
从spring MVC 控制器中记录数据
插入HttpClient.execute()方法的调用并在HTTPGet中配置TraceId
建立一个子TraceId
在HTTP header中配置子 TraceId
传输打好tag的请求到TomcatB.
TomcatB 检查传输过来的请求的header
HttpServletRequest.getHeader(PINPOINT_TX_ID)
TomcatB 做为子节点工做由于它识别了header中的TraceId
从spring mvc控制器中记录数据并完成请求
图6 示例2:pinpoint应用
当从tomcatB回来的请求完成时,pinpoint agent发送跟踪数据到pinpoint collector就此存储在HBase中
pinpoint是和应用一块儿运行的另外的应用。使用字节码加强使得pinpoint看上去不须要代码修改。一般,字节码加强技术让应用容易形成风险。若是问题发生在pinpoint中,它会影响应用。目前,咱们专一于改进pinpoint的性能和设计,而不是移除这样的威胁,由于咱们任务这些让pinpoint更加有价值。所以你须要决定是否使用pinpoint。