【grpc】 10.链路追踪
# 什么是链路追踪
分布式链路追踪(Distributed Tracing),也叫 分布式链路跟踪,分布式跟踪,分布式追踪 等等。
链路追踪是分布式系统下的一个概念,它的目的就是要解决上面所提出的问题,也就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如,各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
# 技术选型
zipkin | jaeger | skywalking | |
---|---|---|---|
OpenTracing兼容 | 是 | 是 | 是 |
客户端支持语言 | java,c#,go,php,python等 | java,c#,go,php,python等 | Java, .NET Core, NodeJS ,PHP,python,go |
存储 | ES,mysql,Cassandra,内存 | ES,kafka,Cassandra,内存 | ES,H2,mysql,TIDB,sharding sphere |
传输协议支持 | http,MQ | udp/http | gRPC |
ui丰富程度 | 低 | 中 | 中 |
实现方式-代码侵入性 | 拦截请求,侵入 | 拦截请求,侵入 | 字节码注入,无侵入 |
扩展性 | 高 | 高 | 中 |
trace查询 | 支持 | 支持 | 支持 |
性能损失 | 中 | 中 | 低 |
# jaeger
# 安装
github : https://github.com/jaegertracing/jaeger/tree/master/examples/hotrod
Docker 简单安装
docker run \ --rm \ --name jaeger \ -p6831:6831/udp \ -p16686:16686 \ jaegertracing/all-in-one:latest
1
2
3
4
5
6
# 架构
Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 jaeger-agent。
Agent - 它是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。它被设计成一个基础组件,部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。
Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector。
Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandra、elastic search。
Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。
分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点,数据存储、查询展示
# OpenTracing
- 这是正式的OpenTracing语义标准。OpenTracing是一个跨编程语言的标准,此文档会避免具有语言特性的概念。比如,我们在文档中使用"interface",因为所有的语言都包含"interface"这种概念。
# 1. 版本命名策略
- OpenTracing标准使用
Major.Minor
版本命名策略(即:大版本.小版本),但不包含.Patch
版本(即:补丁版本)。如果标准做出不向前兼容的改变,则使用“主版本”号提升。如果是向前兼容的改进,则进行小版本号提升,例如加入新的标准tag, log和SpanContext引用类型。(如果你想知道更多关于制定此版本政策的原因,可参考specification#2 (opens new window))
# 2. OpenTracing数据模型
Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。
Span,可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span.在此译本中,为了便于理解,Span和其他标准内声明的词汇,全部不做名词翻译。
例如:下面的示例Trace就是由8个Span组成:
单个Trace中,span间的因果关系 [Span A] ←←←(the root span) | +------+------+ | | [Span B] [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf) | | [Span D] +---+-------+ | | [Span E] [Span F] >>> [Span G] >>> [Span H] ↑ ↑ ↑ (Span G 在 Span F 后被调用, FollowsFrom)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
有些时候,使用下面这种,基于时间轴的时序图可以更好的展现Trace(调用链):
单个Trace中,span间的时间关系 ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time [Span A···················································] [Span B··············································] [Span D··········································] [Span C········································] [Span E·······] [Span F··] [Span G··] [Span H··]
1
2
3
4
5
6
7
8
9每个Span包含以下的状态:(译者注:由于这些状态会反映在OpenTracing API中,所以会保留部分英文说明)
An operation name,操作名称
A start timestamp,起始时间
A finish timestamp,结束时间
Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型。
Span Log,一组span的日志集合。 每次log操作包含一个键值对,以及一个时间戳。 键值对中,键必须为string,值可以是任意类型。 但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型。
SpanContext,Span上下文对象 (下面会详细说明)
References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)
每一个SpanContext包含以下状态:
任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输
Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输
# 2.1 Span间关系
一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:
ChildOf
(父子) 和FollowsFrom
(跟随)。这两种关系明确的给出了两个父子关系的Span的因果模型。 将来,OpenTracing可能提供非因果关系的span间关系。(例如:span被批量处理,span被阻塞在同一个队列中,等等)。ChildOf
引用: 一个span可能是一个父级span的孩子,即"ChildOf"关系。在"ChildOf"引用关系下,父级span某种程度上取决于子span。下面这些情况会构成"ChildOf"关系:一个RPC调用的服务端的span,和RPC服务客户端的span构成ChildOf关系
一个sql insert操作的span,和ORM的save方法的span构成ChildOf关系
很多span可以并行工作(或者分布式工作)都可能是一个父级的span的子项,他会合并所有子span的执行结果,并在指定期限内返回
FollowsFrom
引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子span和父span之间是"FollowsFrom"的因果关系。"FollowsFrom"关系可以被分为很多不同的子类型,未来版本的OpenTracing中将正式的区分这些类型
# 3. OpenTracing API
OpenTracing标准中有三个重要的相互关联的类型,分别是
Tracer
,Span
和SpanContext
。下面,我们分别描述每种类型的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法。当我们讨论“可选”参数时,需要强调的是,不同的语言针对可选参数有不同理解,概念和实现方式 。例如,在Go中,我们习惯使用"functional Options",而在Java中,我们可能使用builder模式。
# 3.1 Tracer
Tracer
接口用来创建Span
,以及处理如何处理Inject
(serialize) 和Extract
(deserialize),用于跨进程边界传递。它具有如下官方能力:
# 创建一个新
Span
必填参数
operation name, 操作名, 一个具有可读性的字符串,代表这个span所做的工作(例如:RPC方法名,方法名,或者一个大型计算中的某个阶段或子任务)。操作名应该是一个抽象、通用,明确、具有统计意义的名称。因此,
"get_user"
作为操作名,比"get_user/314159"
更好。例如,假设一个获取账户信息的span会有如下可能的名称:
操作名 指导意见 get
太抽象 get_account/792
太明确 get_account
正确的操作名,关于 account_id=792
的信息应该使用Tag (opens new window)操作可选参数
零个或者多个关联(references)的
SpanContext
,如果可能,同时快速指定关系类型,ChildOf
还是FollowsFrom
。一个可选的显性传递的开始时间;如果忽略,当前时间被用作开始时间。
零个或者多个tag。
返回值,返回一个已经启动
Span
实例(已启动,但未结束。译者注:英语上started和finished理解容易混淆)
# 将
SpanContext
上下文Inject(注入)到 carrier
必填参数
**
SpanContext
**实例format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知
Tracer
实现,如何对SpanContext
进行编码放入到carrier中。carrier,根据format确定。
Tracer
实现根据format声明的格式,将SpanContext
序列化到carrier对象中。
# 将
SpanContext
上下文从carrier中Extract(提取)
必填参数
format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知
Tracer
实现,如何从carrier中解码SpanContext
。carrier,根据format确定。
Tracer
实现根据format声明的格式,从carrier中解码SpanContext
。
返回值,返回一个
SpanContext
实例,可以使用这个SpanContext
实例,通过Tracer
创建新的Span
。
注意,对于Inject(注入)和Extract(提取),format是必须的。
Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中
SpanContext
是如何编码的。所有的Tracer实现,都必须支持下面的format。Text Map: 基于字符串:字符串的map,对于key和value不约束字符集。
HTTP Headers: 适合作为HTTP头信息的,基于字符串:字符串的map。(RFC 7230 (opens new window).在工程实践中,如何处理HTTP头具有多样性,强烈建议tracer的使用者谨慎使用HTTP头的键值空间和转义符)
Binary: 一个简单的二进制大对象,记录
SpanContext
的信息。
# 3.2 Span
- 当
Span
结束后(span.finish()
),除了通过Span
获取SpanContext
外,下列其他所有方法都不允许被调用。
# 除了通过
Span
获取SpanContext
不需要任何参数。
返回值,
Span
构建时传入的SpanContext
。这个返回值在Span
结束后(span.finish()
),依然可以使用。
# 复写操作名(operation name)
- 必填参数
- 新的操作名operation name,覆盖构建
Span
时,传入的操作名。
- 新的操作名operation name,覆盖构建
# 结束
Span
可选参数
- 一个明确的完成时间;如果省略此参数,使用当前时间作为完成时间。
# 为
Span
设置tag
必填参数
- tag key,必须是string类型
- tag value,类型为字符串,布尔或者数字
注意,OpenTracing标准包含**"standard tags,标准Tag" (opens new window)**,此文档中定义了Tag的标准含义。
# Log结构化数据
必填参数
- 一个或者多个键值对,其中键必须是字符串类型,值可以是任意类型。某些OpenTracing实现,可能支持更多的log值类型。
可选参数
- 一个明确的时间戳。如果指定时间戳,那么它必须在span的开始和结束时间之内。
注意,OpenTracing标准包含**"standard log keys,标准log的键" (opens new window)**,此文档中定义了这些键的标准含义。
# 设置一个baggage(随行数据)元素
Baggage元素是一个键值对集合,将这些值设置给给定的Span
,Span
的SpanContext
,以及所有和此Span
有直接或者间接关系的本地Span
。 也就是说,baggage元素随trace一起保持在带内传递。(译者注:带内传递,在这里指,随应用程序调用过程一起传递)
Baggage元素为OpenTracing的实现全栈集成,提供了强大的功能 (例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统。由于它如此强大的功能,他也会产生巨大的开销,请小心使用此特性。
再次强调,请谨慎使用此特性。每一个键值都会被拷贝到每一个本地和远程的下级相关的span中,因此,总体上,他会有明显的网络和CPU开销。
必填参数
- baggage key, 字符串类型
- baggage value, 字符串类型
# 获取一个baggage元素
必填参数
- baggage key, 字符串类型
返回值,相应的baggage value,或者可以标识元素值不存在的返回值(译者注:如Null)。
# 3.3 SpanContext
相对于OpenTracing中其他的功能,
SpanContext
更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。 OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContext
和references (opens new window),OpenTracing要求,
SpanContext
是不可变的,目的是防止由于Span
的结束和相互关系,造成的复杂生命周期问题。
# 遍历所有的baggage元素
- 遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的
SpanContext
实例,高效的遍历所有的baggage元素
# 3.4 NoopTracer
所有的OpenTracing API实现,必须提供某种方式的NoopTracer
实现。NoopTracer
可以被用作控制或者测试时,进行无害的inject注入(等等)。例如,在 OpenTracing-Java实现中,NoopTracer
在他自己的模块中。
# 3.5 可选 API 元素
有些语言的OpenTracing实现,为了在串行处理中,传递活跃的Span
或SpanContext
,提供了一些工具类。例如,opentracing-go
中,通过context.Context
机制,可以设置和获取活跃的Span
。
# Go 链路追踪
- 一库: https://github.com/jaegertracing/jaeger-client-go
# 1. 普通多级关联调用
/*
* @date: 2021/11/18
* @desc: ...
*/
package main
import (
"context"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
jaegerlog "github.com/uber/jaeger-client-go/log"
"log"
"time"
)
func main() {
cfg := jaegercfg.Configuration{
ServiceName: "goJaegerTest2",
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "127.0.0.1:6831",
},
}
// Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
// and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
// frameworks.
jLogger := jaegerlog.StdLogger
//jMetricsFactory := metrics.NullFactory
// Initialize tracer with a logger and a metrics factory
tracer, closer, err := cfg.NewTracer(
jaegercfg.Logger(jLogger),
//jaegercfg.Metrics(jMetricsFactory),
)
if err != nil {
log.Printf("Could not initialize jaeger tracer: %s", err.Error())
return
}
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
span := opentracing.StartSpan("gpSpanTest3")
ctx := opentracing.ContextWithSpan(context.Background(),span)
span2,_ := opentracing.StartSpanFromContext(ctx,"span2")
time.Sleep(2 * time.Second)
defer span2.Finish()
defer span.Finish()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 2. grpc 配置jaeger
一库: https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc
server
package main import ( "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" jaegerlog "github.com/uber/jaeger-client-go/log" "golang.org/x/net/context" "google.golang.org/grpc" "log" "net" "picturePro/grpcTest/proto" ) type Server struct { } func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) { return &proto.HelloReply{ Message: "Hello " + request.Name, }, nil } func main() { cfg := jaegercfg.Configuration{ ServiceName: "goJaegerTest-http-server", Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, LocalAgentHostPort: "127.0.0.1:6831", }, } jLogger := jaegerlog.StdLogger //jMetricsFactory := metrics.NullFactory tracer, closer, err := cfg.NewTracer( jaegercfg.Logger(jLogger), //jaegercfg.Metrics(jMetricsFactory), ) if err != nil { log.Printf("Could not initialize jaeger tracer: %s", err.Error()) return } defer closer.Close() opentracing.SetGlobalTracer(tracer) var serverOptions []grpc.ServerOption serverOptions = append(serverOptions, grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer))) g := grpc.NewServer(serverOptions...) proto.RegisterGreeterServer(g, &Server{}) listen, err := net.Listen("tcp", ":50051") if err != nil { panic(err.Error()) } err = g.Serve(listen) if err != nil { panic(err.Error()) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63client
package main import ( "context" "fmt" "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" jaegerlog "github.com/uber/jaeger-client-go/log" "google.golang.org/grpc" "log" "picturePro/grpcTest/proto" ) func main() { cfg := jaegercfg.Configuration{ ServiceName: "goJaegerTest-http", Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, LocalAgentHostPort: "127.0.0.1:6831", }, } // Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log // and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics // frameworks. jLogger := jaegerlog.StdLogger //jMetricsFactory := metrics.NullFactory // Initialize tracer with a logger and a metrics factory tracer, closer, err := cfg.NewTracer( jaegercfg.Logger(jLogger), //jaegercfg.Metrics(jMetricsFactory), ) if err != nil { log.Printf("Could not initialize jaeger tracer: %s", err.Error()) return } defer closer.Close() opentracing.SetGlobalTracer(tracer) conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure(), grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), ) if err != nil { panic(err) } defer conn.Close() c := proto.NewGreeterClient(conn) r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"}) if err != nil { panic(err) } fmt.Println(r.Message) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 3. gin框架封装jaeger
进一步封装修改 otgrpc 源码, 负载均衡模式下 方便同一个业务块中多个子业务模块的链路区分
创建拦截器
tracing middleware
tracing.go
package middlewares import ( "fmt" "github.com/gin-gonic/gin" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" jaegerlog "github.com/uber/jaeger-client-go/log" "mxshop-api/goods-web/global" ) func Trace() gin.HandlerFunc { return func(ctx *gin.Context) { cfg := jaegercfg.Configuration{ ServiceName: global.ServerConfig.JaegerInfo.Name, Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.JaegerInfo.Host, global.ServerConfig.JaegerInfo.Port), }, } logger := jaegerlog.StdLogger tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(logger)) if err != nil { panic(err) } opentracing.SetGlobalTracer(tracer) defer closer.Close() startSpan := tracer.StartSpan(ctx.Request.URL.Path) defer startSpan.Finish() ctx.Set("tracer", tracer) ctx.Set("parentSpan", startSpan) ctx.Next() } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
在初始化负载均衡的srv连接器
userConn
时添加 注入opentrace
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), // 注入链路追踪
srvConn.go
package initialize import ( "fmt" "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" _ "github.com/mbobakov/grpc-consul-resolver" // It's important "github.com/opentracing/opentracing-go" "go.uber.org/zap" "google.golang.org/grpc" "mxshop-api/goods-web/global" "mxshop-api/goods-web/proto" ) func InitSrvConnWithLoadBLance() { // 从注册中心获取user-srv用户服务信息 - 负载均衡版 consulInfo := global.ServerConfig.ConsulInfo userConn, err := grpc.Dial( fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name), grpc.WithInsecure(), grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), /* 注入链路追踪 问题: 全局使用同一个GlobalTracer, 多个子业务板块的链路混乱 */ grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), // 注入链路追踪 ) if err != nil { zap.S().Fatalf("[InitSrvConnWithLoadBLance] 连接失败:%s", err.Error()) } goodsSrvClient := proto.NewGoodsClient(userConn) global.GoodsSrvClient = goodsSrvClient }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
更改
otgrpc
包源码,实现子业务trance 隔离文件路径
~/otgrpc/client.go
,在OpenTracingClientInterceptor
方法的返回func中,加入以下注释包含的代码块:... var err error var parentCtx opentracing.SpanContext if parent := opentracing.SpanFromContext(ctx); parent != nil { parentCtx = parent.Context() } /* start 判断ctx 是否 使用户自己传的(ginContext) , 如果是, 就使用自己的tracer和parentCtx 负载均衡模式下 方便同一个业务块中多个子业务模块的链路区分 */ ginContext := ctx.Value("ginContext") switch ginContext.(type) { case *gin.Context: if itracer, ok := ginContext.(*gin.Context).Get("tracer");ok{ tracer = itracer.(opentracing.Tracer) } if parentSpan, ok := ginContext.(*gin.Context).Get("parentSpan");ok{ parentCtx = parentSpan.(*jaegerClient.Span).Context() } } /*end*/ if otgrpcOpts.inclusionFunc != nil && ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24修改完的部分代码
在子业务的
handler
发送rpc请求时, 将context中设置值ginContext
customCtx := context.WithValue(context.Background(), "ginContext", ctx) r, err := global.GoodsSrvClient.GoodsList(customCtx, request)
goods.go
... func List(ctx *gin.Context) { fmt.Println("商品列表") ////商品的列表 pmin=abc, spring cloud, go-micro request := &proto.GoodsFilterRequest{} priceMin := ctx.DefaultQuery("pmin", "0") priceMinInt, _ := strconv.Atoi(priceMin) request.PriceMin = int32(priceMinInt) priceMax := ctx.DefaultQuery("pmax", "0") priceMaxInt, _ := strconv.Atoi(priceMax) request.PriceMax = int32(priceMaxInt) isHot := ctx.DefaultQuery("ih", "0") if isHot == "1" { request.IsHot = true } isNew := ctx.DefaultQuery("in", "0") if isNew == "1" { request.IsNew = true } isTab := ctx.DefaultQuery("it", "0") if isTab == "1" { request.IsTab = true } categoryId := ctx.DefaultQuery("c", "0") categoryIdInt, _ := strconv.Atoi(categoryId) request.TopCategory = int32(categoryIdInt) pages := ctx.DefaultQuery("p", "0") pagesInt, _ := strconv.Atoi(pages) request.Pages = int32(pagesInt) perNums := ctx.DefaultQuery("pnum", "0") perNumsInt, _ := strconv.Atoi(perNums) request.PagePerNums = int32(perNumsInt) keywords := ctx.DefaultQuery("q", "") request.KeyWords = keywords brandId := ctx.DefaultQuery("b", "0") brandIdInt, _ := strconv.Atoi(brandId) request.Brand = int32(brandIdInt) // 请求商品service服务 // 负载均衡使用 jaeger 的span关联 //parentSpan,_:= ctx.Get("parentSpan") //opentracing.ContextWithSpan(context.Background(), parentSpan.(opentracing.Span)) customCtx := context.WithValue(context.Background(), "ginContext", ctx) r, err := global.GoodsSrvClient.GoodsList(customCtx, request) if err != nil { zap.S().Errorw("[list 查询商品列表失败]") api.HandleGrpcErrorToHttp(ctx, err) return } reMap := map[string]interface{}{ "total": r.Total, } goodsList := make([]interface{}, 0) for _, value := range r.Data { goodsList = append(goodsList, map[string]interface{}{ "id": value.Id, "name": value.Name, "goods_brief": value.GoodsBrief, "desc": value.GoodsDesc, "ship_free": value.ShipFree, "images": value.Images, "desc_images": value.DescImages, "front_image": value.GoodsFrontImage, "shop_price": value.ShopPrice, "ctegory": map[string]interface{}{ "id": value.Category.Id, "name": value.Category.Name, }, "brand": map[string]interface{}{ "id": value.Brand.Id, "name": value.Brand.Name, "logo": value.Brand.Logo, }, "is_hot": value.IsHot, "is_new": value.IsNew, "on_sale": value.OnSale, }) } reMap["data"] = goodsList ctx.JSON(http.StatusOK, reMap) } ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91