java高可用grpc_GRPC java 分布式调用链跟踪实践

Opentracing基本模型

image.png

如图,在跟踪链中有以下几个比较重要的数据结构和概念:

span:标识一次分布式调用,其自身包含了id,parentId(指向上级Span的id), traceIds,服务名称等重要属性,其应尽量保持精简;

trace:标识整个请求链,即一些列Span的组合。其自身的ID将贯穿整个调用链,其中的每个Span都必须携带这个traceId,因此traceId将在整个调用链中传递;

cs:客户端发起请求,标志Span的开始;

sr:服务端接收到请求,并开始处理内部事务,其中sr - cs则为网络延迟和时钟抖动;

ss:服务端处理完请求,返回响应内容,其中ss - sr则为服务端处理请求耗时;

cr:客户端接收到服务端响应内容,标志着Span的结束,其中cr - ss则为网络延迟和时钟抖动。

客户端调用时间=cr-cs

服务端处理时间=sr-ss

分布式系统调用跟踪的基本架构要求

低侵入性,高性能,高可用容错,低丢失率等。

基于GRPC的分布式系统调用跟踪实践

创建TraceContext

TraceContext通过Threadlocal对span进行保存,并且将traceid和spanid向底层服务传递,zebra对线程上下文传递进行了封装,具体参照GRPC如何实现公共参数与业务参数分离传递下面是TraceContext具体代码

public class TraceContext{

private static final String SPAN_LIST_KEY = "spanList";

public static final String TRACE_ID_KEY = "traceId";

public static final String SPAN_ID_KEY = "spanId";

public static final String ANNO_CS = "cs";

public static final String ANNO_CR = "cr";

public static final String ANNO_SR = "sr";

public static final String ANNO_SS = "ss";

private TraceContext(){}

public static void setTraceId(String traceId) {

RpcContext.getContext().set(TRACE_ID_KEY, traceId);

}

public static String getTraceId() {

return (String) RpcContext.getContext().get(TRACE_ID_KEY);

}

public static String getSpanId() {

return (String) RpcContext.getContext().get(SPAN_ID_KEY);

}

public static void setSpanId(String spanId) {

RpcContext.getContext().set(SPAN_ID_KEY, spanId);

}

@SuppressWarnings("unchecked")

public static void addSpan(Span span){

((List)RpcContext.getContext().get(SPAN_LIST_KEY)).add(span);

}

@SuppressWarnings("unchecked")

public static List getSpans(){

return (List) RpcContext.getContext().get(SPAN_LIST_KEY);

}

public static void clear(){

RpcContext.getContext().remove(TRACE_ID_KEY);

RpcContext.getContext().remove(SPAN_ID_KEY);

RpcContext.getContext().remove(SPAN_LIST_KEY);

}

public static void start(){

clear();

RpcContext.getContext().set(SPAN_LIST_KEY, new ArrayList());

}

}

创建TraceAgent

TraceAgent将span信息上传至kafka,代码如下:

public class TraceAgent {

private GrpcProperties grpcProperties;

private KafkaSender sender;

private AsyncReporter report;

public TraceAgent() {

grpcProperties = SpringContextUtils.getBean(GrpcProperties.class);

sender = KafkaSender.newBuilder().bootstrapServers(grpcProperties.getCallChainUpdAddr()).topic("zipkin").encoding(Encoding.JSON).build();

report = AsyncReporter.builder(sender).build();

}

public void send(final List spans){

spans.forEach(item ->{

report.report(item);

});

}

}

创建ZebraClientTracing

ZebraClientTracing用于记录调用端的span信息,具体代码如下:

@Component

public class ZebraClientTracing {

public Span startTrace(String method) {

String id = IdUtils.get() + "";

String traceId = null;

if (null == TraceContext.getTraceId()) {

TraceContext.start();

traceId = id;

} else {

traceId = TraceContext.getTraceId();

}

long timestamp = System.currentTimeMillis() * 1000;

// 注册本地信息

Endpoint endpoint = Endpoint.newBuilder().ip(NetUtils.getLocalHost()).serviceName(EtcdRegistry.serviceName)

.port(50003).build();

// 初始化span

Span consumerSpan = Span.newBuilder().localEndpoint(endpoint).id(id).traceId(traceId)

.parentId(TraceContext.getSpanId() + "").name(EtcdRegistry.serviceName).timestamp(timestamp)

.addAnnotation(timestamp, TraceContext.ANNO_CS).putTag("method", method)

.putTag("pkgId", RpcContext.getContext().getAttachment("pkg")).build();

// 将tracing id和spanid放到上下文

RpcContext.getContext().get().put(TraceContext.TRACE_ID_KEY, consumerSpan.traceId());

RpcContext.getContext().get().put(TraceContext.SPAN_ID_KEY, String.valueOf(consumerSpan.id()));

return consumerSpan;

}

public void endTrace(Span span, Stopwatch watch,int code) {

span = span.toBuilder().addAnnotation(System.currentTimeMillis() * 1000, TraceContext.ANNO_CR)

.duration(watch.stop().elapsed(TimeUnit.MICROSECONDS)).putTag("code", code+"").build();

TraceAgent traceAgent = new TraceAgent();

traceAgent.send(TraceContext.getSpans());

}

}

创建ZebraServerTracing

ZebraServerTracing用于记录服务端的span信息,具体代码如下:

@Component

public class ZebraServerTracing {

public Span startTrace(String method) {

String traceId = (String) RpcContext.getContext().get(TraceContext.TRACE_ID_KEY);

String parentSpanId = (String) RpcContext.getContext().get(TraceContext.SPAN_ID_KEY);

String id = IdUtils.get() + "";

TraceContext.start();

TraceContext.setTraceId(traceId);

TraceContext.setSpanId(parentSpanId);

long timestamp = System.currentTimeMillis() * 1000;

Endpoint endpoint = Endpoint.newBuilder().ip(NetUtils.getLocalHost()).serviceName(EtcdRegistry.serviceName)

.port(50003).build();

Span providerSpan = Span.newBuilder().id(id).parentId(parentSpanId).traceId(traceId)

.name(EtcdRegistry.serviceName).timestamp(timestamp).localEndpoint(endpoint)

.addAnnotation(timestamp, TraceContext.ANNO_SR).putTag("method", method)

.putTag("pkgId", RpcContext.getContext().getAttachment("pkg"))

.build();

TraceContext.addSpan(providerSpan);

return providerSpan;

}

public void endTrace(Span span, Stopwatch watch,int code) {

span = span.toBuilder().addAnnotation(System.currentTimeMillis() * 1000, TraceContext.ANNO_SS)

.duration(watch.stop().elapsed(TimeUnit.MICROSECONDS)).putTag("code", code+"").build();

TraceAgent traceAgent = new TraceAgent();

traceAgent.send(TraceContext.getSpans());

}

}

创建grpc client拦截器

public class HeaderClientInterceptor implements ClientInterceptor {

private static final Logger log = LogManager.getLogger(HeaderClientInterceptor.class);

private final ZebraClientTracing clientTracing;

public static ClientInterceptor instance() {

return new HeaderClientInterceptor();

}

private HeaderClientInterceptor() {

clientTracing = SpringContextUtils.getBean(ZebraClientTracing.class);

}

@Override

public ClientCall interceptCall(MethodDescriptor method,

CallOptions callOptions, Channel next) {

return new SimpleForwardingClientCall(next.newCall(method, callOptions)) {

//判断API网关是否要打开调用链

boolean isGatewayTracing = "1".equals(RpcContext.getContext().getAttachment(ZebraConstants.ZEBRA_OPEN_TRACING))?true:false;

boolean isSubTracing = RpcContext.getContext().get(TraceContext.TRACE_ID_KEY)!=null?true:false;

Stopwatch watch =null;

Span span =null;

@Override

public void start(Listener responseListener, Metadata headers) {

if(isSubTracing||isGatewayTracing){

span =clientTracing.startTrace(method.getFullMethodName());

watch = Stopwatch.createStarted();

}

copyThreadLocalToMetadata(headers);

super.start(new SimpleForwardingClientCallListener(responseListener) {

@Override

public void onHeaders(Metadata headers) {

super.onHeaders(headers);

}

@Override

public void onClose(Status status, Metadata trailers) {

super.onClose(status, trailers);

if(isSubTracing||isGatewayTracing)

clientTracing.endTrace(span, watch,status.getCode().value());

}

}, headers);

}

};

}

private void copyThreadLocalToMetadata(Metadata headers) {

Map attachments = RpcContext.getContext().getAttachments();

Map values = RpcContext.getContext().get();

try {

if (!attachments.isEmpty()) {

headers.put(GrpcUtil.GRPC_CONTEXT_ATTACHMENTS, SerializerUtil.toJson(attachments));

}

if (!values.isEmpty()) {

headers.put(GrpcUtil.GRPC_CONTEXT_VALUES, SerializerUtil.toJson(values));

}

} catch (Throwable e) {

log.error(e.getMessage(), e);

}

}

}

创建grpc server拦截器

public class HeaderServerInterceptor implements ServerInterceptor {

private static final Logger log = LogManager.getLogger(HeaderServerInterceptor.class);

private final ZebraServerTracing serverTracing;

public static ServerInterceptor instance() {

return new HeaderServerInterceptor();

}

private HeaderServerInterceptor() {

serverTracing = SpringContextUtils.getBean(ZebraServerTracing.class);

}

@Override

public Listener interceptCall(ServerCall call, final Metadata headers,

ServerCallHandler next) {

return next.startCall(new SimpleForwardingServerCall(call) {

boolean isSubTracing = RpcContext.getContext().get(TraceContext.TRACE_ID_KEY) != null ? true : false;

Stopwatch watch = null;

Span span = null;

@Override

public void request(int numMessages) {

if (isSubTracing) {

span = serverTracing.startTrace(call.getMethodDescriptor().getFullMethodName());

watch = Stopwatch.createStarted();

}

InetSocketAddress remoteAddress = (InetSocketAddress) call.getAttributes()

.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);

RpcContext.getContext().setAttachment(ZebraConstants.REMOTE_ADDRESS, remoteAddress.getHostString());

copyMetadataToThreadLocal(headers);

log.debug("FullMethodName:{},RemoteAddress={},attachments={},context={}",

call.getMethodDescriptor().getFullMethodName(), remoteAddress.getHostString(),

headers.get(GrpcUtil.GRPC_CONTEXT_ATTACHMENTS), headers.get(GrpcUtil.GRPC_CONTEXT_VALUES));

super.request(numMessages);

}

@Override

public void close(Status status, Metadata trailers) {

delegate().close(status, trailers);

if(isSubTracing)

serverTracing.endTrace(span, watch,status.getCode().value());

}

}, headers);

}

private void copyMetadataToThreadLocal(Metadata headers) {

String attachments = headers.get(GrpcUtil.GRPC_CONTEXT_ATTACHMENTS);

String values = headers.get(GrpcUtil.GRPC_CONTEXT_VALUES);

try {

if (attachments != null) {

Map attachmentsMap = SerializerUtil.fromJson(attachments,

new TypeToken>() {

}.getType());

RpcContext.getContext().setAttachments(attachmentsMap);

}

if (values != null) {

Map valuesMap = SerializerUtil.fromJson(values, new TypeToken>() {

}.getType());

for (Map.Entry entry : valuesMap.entrySet()) {

RpcContext.getContext().set(entry.getKey(), entry.getValue());

}

}

} catch (Throwable e) {

log.error(e.getMessage(), e);

}

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/333806.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jrebel gradle_JRebel适用于Gradle Spring Boot应用程序

jrebel gradle关于如何将JRebel添加到使用Gradle作为构建工具的Spring Boot应用程序中,有一些文档 。 它是基本的,但是效果很好。 您所要做的就是在build.gradle中添加几行: if (project.hasProperty(rebelAgent)) {bootRun.jvmArgs rebel…

vim 的寄存器/剪贴板

文章目录 一、查看寄存器查看所有的寄存器查看指定的寄存器二、各种寄存器介绍无名寄存器(默认寄存器)命名寄存器复制专用寄存器逐级临时缓存寄存器。行内删除寄存器黑洞寄存器表达式寄存器文件名寄存器搜索模式寄存器命令行寄存器插入寄存器轮换缓冲区寄存器系统剪贴板选择和…

Python中的lambda表达式

lambda表达式被用于创建匿名函数。匿名函数就是没有名字的函数。 返回类型是函数类型。 作用:一行就可以表示一个函数,使代码简洁。很适合只使用一次的函数。 语法 lambda 参数:表达式等价于 def 函数名(参数):return 表达式举例 无参函数…

华为的型号命名规则_华为Mate 40 Pro+ 外观曝光 正面双挖孔 背部奥利奥

近日关于华为Mate 40 Pro的消息,又有了新进展。有媒体曝光出了一组Mate 40 Pro 的外观渲染图。华为Mate 40系列将会有三种型号,分别是华为Mate 40,Mate 40 Pro 和Mate 40 Pro ,这与之前发布的华为 P40 系列是相同的型号命名规则。…

drools 执行函数_Drools可执行模型还活着

drools 执行函数总览 可执行模型的目的是提供规则集的纯基于Java的表示,以及方便的Java DSL以编程方式创建这种模型。 该模型是低级的,旨在为用户提供所需的所有信息,例如用于索引评估的lambda。 这样可以使其保持快速运行,并避免…

vim 底行命令模式的正则表达式(匹配模式)

文章目录一、vim 替换表达式的语法格式二、vim 关于 range 的表达式三、vim 关于 flags 的元字符四、vim 关于数量的元字符五、环视和固化分组六、vim 替换表达式的示例(一)结合 copy 命令使用(二)结合 move 命令使用(…

navicat模型显示注释_RetinaNet模型构建面罩检测器

字幕组双语原文:如何使用RetinaNet模型构建面罩检测器英语原文:How to build a Face Mask Detector using RetinaNet Model!翻译:雷锋字幕组(李珺毅)介绍目标检测是计算机视觉中一个非常重要的领域,对于自动…

python中parse.add_argument()简单用法

argparse模块简单使用流程以及与命令行的交互1.导入模块2.创建解析器3.添加参数4.解析参数5使用参数完整代码与命令行的交互argparse 模块是 Python 内置的一个用于命令项选项与参数解析的模块。下面以例子来简单记下模块使用流程。1.导入模块 import argparse2.创建解析器 该…

java最好学习的方法_学习Java的最佳方法

java最好学习的方法Java是有些人可能说很难学习的语言之一,而其他人则认为它与其他语言具有相同的学习曲线。 两种观察都是正确的。 但是,由于Java具有平台无关性,因此在语言方面具有相当大的优势。 Java是一种低级语言,它以一种…

什么是整数

整数(integer)是正整数、零、负整数的集合。 整数的全体构成整数集,整数集是一个数环。在整数系中,零和正整数统称为自然数。-1、-2、-3、…、-n、…(n为非零自然数)为负整数。则正整数、零与负整数构成整数…

镜像电流源特点_模电中的电流源电路

在模电中,电流源电路也称恒流源电路,经常作为放大电路中的偏置电路(即为放大电路提供合适的静态工作点的电路)、有源负载以及电压-电流转换电路等。在一些名牌大学的期末考试或研究生入学考试中,也经常出一道和恒流源相关的题。不同学校的教材…

java改变变量编码方式_Java 10将如何改变您的编码方式

java改变变量编码方式突出显示Java 10中新的Java局部变量类型推断功能 早在2016年,Java社区就掀起了新的JDK增强提案(JEP): JEP 286 。 现在,两年后,局部变量类型推断可以说是Java 10中最值得注意的功能。…

vim 批量注释技巧

文章目录一、块选择模式批量注释取消注释二、替换命令批量注释取消注释一、块选择模式 批量注释 Ctrl v 进入块选择模式,然后移动光标选中你要注释的行,再按大写的 I 进入行首插入模式输入注释符号如 // 或 #,输入完毕之后,按 …

深度学习Pytorch--梯度与反向传播笔记

Pytorch梯度与反向传播相关概念导数偏导数方向导数梯度梯度下降法自动求梯度概念Tensor反向传播求梯度相关概念 先来理解一下从导数到梯度的相关概念。 导数 一元函数中导数就是该函数所代表的曲线在这一点上的切线斜率。 多元函数的导数可以称为全导数,可以得到…

工业机器人打磨抛光编程员工资_一种工业机器人打磨抛光工作平台的制作方法...

本实用新型涉及工业机器人领域,具体是一种工业机器人打磨抛光工作平台。背景技术:随着手机的使用量逐年增加,人们对手机外观的要求越来越高,手机壳的打磨和抛光工艺变得越来越严格。由于在拉伸和其他冲压工序中,容易使…

jdk10 换成jdk8_JDK 10的摘要Javadoc标签

jdk10 换成jdk8JDK 10通过发行JDK-8173425 引入了Javadoc标签{summary} (“ Javadoc需要一个新标签来指定摘要。”)。 这个新标签允许开发人员显式指定Javadoc注释的哪一部分出现在“摘要”中,而不是依靠Javadoc的默认处理来寻找一段时间和空…

深度学习pytorch--线性回归(一)

线性回归线性回归案例提出问题模型定义模型训练(1) 训练数据(2) 损失函数(3) 优化算法模型预测线性回归的表示方法神经网络图矢量计算小结线性回归案例 线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常见,如预测房屋价格、气温、销…

关于 vim 的 magic 设置

文章目录一、magic 介绍二、设置 magic(一)长效设置(二)临时设置(三)magic 示例1. 匹配由 3 个以字母 a 开头,以字母 c 结尾,中间是任意一个字符的子串组成的字符串(1&am…

语言逻辑运算符两侧运算对象_5.3 C语言逻辑运算符与表达式

01优先次序 1、有3种逻辑运算符:与(AND),或(OR),非(NOT)。在basic和Pascal等语言可以在程序中直接用and,or,not作为逻辑运算符。在C语言中不能再程…

rabbitmq 传递文件_使用RabbitMQ进行消息传递

rabbitmq 传递文件RabbitMQ是一个强大的消息代理,可用于实现不同的消息传递模式。 即使有出色的教程 (使用不同的语言和框架),也很难理解这些概念。 在这篇文章中,我想展示一些可以用RabbitMQ实现的不同范例&#xff0…