博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【RPC】一步一步实现基于netty+zookeeper的RPC框架(四)
阅读量:4079 次
发布时间:2019-05-25

本文共 5615 字,大约阅读时间需要 18 分钟。

上一篇实现了服务的负载均衡,本篇带来链路追踪。

关于链路追踪,大部分都是参考了谷歌的dapper论文:https://bigbully.github.io/Dapper-translation/

    通过论文总结,其中span的核心元素为:traceId,name,spanId,parentSpanId,其他则根据自身业务需要来定义即可。

    链路追踪核心原理为通过一个全局的traceId作为依据,在调用服务时为每个服务分配spanId,并记录操作名name,在调用RPC服务时将带有这些属性的span随同请求一起传递,并在关键节点将span数据通过尽量小影响原服务性能的方式传递给我们自己的trace采集服务,采集服务存入数据并通过traceId以及spanId和parentSpanId的关系梳理出调用链并做图形化展示。这里给服务器传递span有很多中模式,其中包括:直接通过RPC服务调用,写入本地磁盘通过另外的进程读取磁盘数据写入远程服务器,写入缓存传输,发送消息等等方式,可以根据自己的需要选择,原则上是尽量少的影响服务本身性能。

    本篇只带来客户端采集span的过程,trace服务器采集和分析展示链路的过程这里省略。

这里还是贴出github代码地址,想直接看代码的可以直接下载运行:https://github.com/whiteBX/wrpc

首先来看我这里的span定义:

public class Span {
/** * 全局唯一id */ String traceId; /** * 操作名--此处取方法名 */ String operationName; /** * 当前spanId */ String spanId; /** * 调用方spanId */ String parentSpanId; /** * appCode */ String appCode; /** * 当前机器ip */ String localIp; /** * 目标机器ip */ String remoteIp; /** * 时间戳,用于记录访问时间 */ long timestamp;}

上面是一些我定义的span属性,当然各位可以加一些自己需要用到的,比如exception记录等等,不过原则上这里span要尽量小,如果定义的过大会影响每次请求的传输数据量,对我们的服务性能造成影响。

在我们的comsumer中修改动态代理类,在发起远程调用之前,处理span相关内容:

public 
T getBean(final Class
clazz, final String appCode) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] {
clazz }, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取服务器地址 String serverHost = getServer(appCode); Span span = SpanBuilder.buildNewSpan(SpanHolder.get(), method.getName(), serverHost, appCode); TODO: 2018/10/25 新启线程发起rpc调用远程链路追踪服务记录追踪日志 此处打日志代替 System.out.println("链路追踪,调用远程服务:" + JSON.toJSONString(span)); BaseRequestBO baseRequestBO = buildBaseBO(span, clazz.getName(), method, JSON.toJSONString(args[0])); return JSON.parseObject(call(serverHost, JSON.toJSONString(baseRequestBO)), method.getReturnType()); } });}

这里注释写的开启新线程调用rpc的方式传输数据,各位可以看需要自行修改,比如写入磁盘通过其他进程读取传输的性能往往会高于这种方式。

这里来看一下上面代码用到的SpanBuilder:

public class SpanBuilder {
/** * 构造span * @param parentSpan * @return * @throws UnknownHostException */ public static Span buildNewSpan(Span parentSpan, String operationName, String serverIp, String appCode) throws UnknownHostException {
Span span = new Span(); span.setLocalIp(InetAddress.getLocalHost().getHostAddress()); if (parentSpan == null) {
span.setTraceId(ShortUUIDUtils.nextId()); span.setParentSpanId("0"); } else {
span.setTraceId(parentSpan.getTraceId()); span.setParentSpanId(parentSpan.getSpanId()); } span.setTimestamp(System.currentTimeMillis()); span.setOperationName(operationName); span.setRemoteIp(serverIp); span.setAppCode(appCode); span.setSpanId(ShortUUIDUtils.nextId()); return span; } /** * 构建新的appCpde的Span * @param span * @param appCode * @return */ public static Span rebuildSpan(Span span, String appCode) {
Span newSpan = copy(span); newSpan.setAppCode(appCode); return newSpan; } /** * 拷贝 * @param source * @return */ public static Span copy(Span source) {
if (source == null) {
return null; } Span span = new Span(); span.setTraceId(source.getTraceId()); span.setOperationName(source.getOperationName()); span.setSpanId(source.getSpanId()); span.setParentSpanId(source.getParentSpanId()); span.setAppCode(source.getAppCode()); span.setLocalIp(source.getLocalIp()); span.setRemoteIp(source.getRemoteIp()); span.setTimestamp(source.getTimestamp()); return span; }}

这里其实就是简单的构造span,其中主要是traceId和spanId的生成,我这里用到了一个短码的生成器,这里就不贴代码了,可以自行去github上拉代码来看,或者直接用uuid也是可以的,这里只是需要保证不重复的基础上尽量短一点。

接下来是改造provider端的接收数据处理方法:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("服务端收到请求:" + msg); try {
// 解析出 类名+方法名+请求参数类型(方法签名) BaseRequestBO baseRequestBO = JSON.parseObject(msg.toString(), BaseRequestBO.class); // 放入span SpanHolder.put(baseRequestBO.getSpan()); // 获取注册的服务 Object object = ProviderBeanHolder.getBean(baseRequestBO.getClazzName()); if (object == null) {
System.out.println("服务类未注册:" + baseRequestBO.getClazzName()); } // 通过反射调用服务 Class paramType = Class.forName(baseRequestBO.getParamTypeName()); Method method = object.getClass().getDeclaredMethod(baseRequestBO.getMethodName(), paramType); Object response = method.invoke(object, JSON.parseObject(baseRequestBO.getData(), paramType)); // 请求响应 ctx.writeAndFlush(JSON.toJSONString(response)); Span span = SpanBuilder.rebuildSpan(baseRequestBO.getSpan(), ProviderConstant.APP_CODE); TODO: 2018/10/25 新启线程发起rpc调用远程链路追踪服务记录追踪日志 此处打日志代替 System.out.println("链路追踪,远程服务响应:" + JSON.toJSONString(span)); } catch (Exception e) {
System.out.println("服务异常" + e); }}

这里获取到传递来的span,之后放入本地线程变量中记录,在这个服务处理中继续调用别的provider时,comsumer代码中可以取到这一个span,再生成新的span时,这个span的traceId会被沿用,spanId则会被设置成为下一个span的parentSpanId,这样一级一级的传递就形成了调用链。

到这里主要的代码其实就完成了,大家可以直接去github拉代码来运行。这里补充几点:

  1. 由于span在服务内部通过本地线程变量传递,会造成服务中起新线程时链路会丢失,这里可以通过其他方式来处理,比如存入第三方缓存等其他方式来解决.
  2. span的采集节点,这里采用了在consumer发起调用前和provider处理完成后的两个节点进行采集,是综合考虑请求成功/超时/异常后的一种方案。各位也可以在别的节点进行采集,比如consumer收到响应、provider收到请求等等节点,或者都收集,然后在trace服务端自行分化处理。

转载地址:http://mhsni.baihongyu.com/

你可能感兴趣的文章
现在明白为什么无名飞控的STM32工程里面有个DSP文件夹了
查看>>
确实是读一个硕士机会平台高些
查看>>
20道嵌入式工程师面试题(附答案)
查看>>
面试积累——嵌入式软件工程师面试题(非常经典)
查看>>
这些网站有一些嵌入式面试题合集
查看>>
我觉得刷题是有必要的,不然小心实际被问的时候懵逼,我觉得你需要刷个50份面试题。跟考研数学疯狂刷卷子一样!
查看>>
我觉得嵌入式面试三要素:基础吃透+项目+大量刷题,缺一不可。不刷题是不行的。而且得是大量刷,刷出感觉套路,别人做题都做得是固定题型套路条件反射了,你还在那慢慢理解慢慢推是不行的,也是考研的教训。
查看>>
嵌入式面试题错题集(我自己做的)
查看>>
ACfly飞控用STlink下载的接口,接线,并用STlink(SWD)下载程序
查看>>
死锁的四个必要条件
查看>>
我有个设想既然T265是全局定位,我能不能直接给定一个轨迹函数(可以是比较复杂曲线),然后它按照这种路线去飞行。
查看>>
嵌入式-操作系统方面常见的面试题
查看>>
7000字干货 | 嵌入式必备技能之Git的使用
查看>>
git我到现在也没学会,可能我更喜欢视频教程,不喜欢文字教程,看到那一堆关系就乱了。
查看>>
我现在发觉github真的是一个好东西,可以保存你每次对工程的修改,不用再像以前一样每个版本的工程都单独保存到一个文件夹。。。
查看>>
我还是想真正做出摄像头+VINS的无人机定点飞
查看>>
Ubuntu16.04+RealsenseT265跑通VINS-Fusion
查看>>
小觅这里有直接教你怎么整合它的SDK和vins一起运行的,搞不好用小觅更快点?
查看>>
使用小觅相机标准入门版运行vins
查看>>
干货 | 一起快速上手 VINS-Fusion
查看>>