第一章:Go语言在BAT高可用基建中的战略定位
在百度、阿里、腾讯等头部互联网企业的核心基础设施演进中,Go语言已从“边缘工具语言”跃升为高可用系统构建的战略级基础设施语言。其轻量协程模型、原生并发支持、静态链接与快速启动特性,精准匹配微服务治理、API网关、分布式中间件、可观测性采集器等关键组件对低延迟、高吞吐、强稳定性的严苛要求。
核心优势与工程价值对齐
- 极致可运维性:单二进制分发免依赖,规避C/C++动态链接风险,大幅降低容器镜像体积(典型gRPC服务镜像可压缩至15MB以内);
- 确定性性能表现:无STW的GC(v1.21+优化至亚毫秒级停顿)、无虚拟机层开销,支撑秒级扩缩容与P99
- 生态协同能力:与Kubernetes原生深度集成(client-go为官方SDK),etcd、Prometheus、TIDB、TiKV等核心云原生组件均以Go实现,形成统一技术栈闭环。
典型落地场景验证
以某大型电商实时风控网关为例,其Go重构路径体现战略价值:
- 将Java旧网关(QPS 8k,P99=120ms)迁移至Go+Gin+ent框架;
- 引入
pprof持续分析CPU热点,通过sync.Pool复用HTTP header map与JSON decoder实例; - 最终达成QPS 24k,P99降至22ms,节点资源占用下降60%。
// 示例:高性能JSON解析池化实践(避免每次alloc)
var jsonPool = sync.Pool{
New: func() interface{} {
return &json.Decoder{}
},
}
func parseRequest(r io.Reader) error {
dec := jsonPool.Get().(*json.Decoder)
defer jsonPool.Put(dec)
dec.Reset(r) // 复用decoder,跳过new分配
return dec.Decode(&payload)
}
企业级技术选型共识
| 维度 | Go语言表现 | 对比Java/C++ |
|---|---|---|
| 启动耗时 | Java:~300ms(JVM预热) | |
| 内存常驻开销 | ~3MB(空服务) | C++:~8MB(含标准库符号) |
| 故障隔离粒度 | goroutine级崩溃不扩散 | Java线程崩溃易拖垮JVM进程 |
这种深度契合使Go不再仅是“一种选择”,而是BAT在云原生时代保障业务连续性、加速技术迭代、统一基础设施工具链的关键支点。
第二章:服务注册与发现组件的Go实现
2.1 基于etcd v3的分布式服务注册理论与gRPC-Resolver实践
etcd v3 的 Watch 机制与原子性事务(Txn)为服务注册/发现提供了强一致基础。gRPC Resolver 需将 etcd 的键值变更实时映射为 []*resolver.Address,避免轮询开销。
数据同步机制
gRPC Resolver 启动时建立长连接 Watch /services/{service-name}/ 前缀,监听 PUT/DELETE 事件:
watchCh := cli.Watch(ctx, "/services/user/", clientv3.WithPrefix())
for wresp := range watchCh {
for _, ev := range wresp.Events {
addr := parseEndpointFromKey(ev.Kv.Key) // 如 /services/user/10.0.1.5:8080
if ev.Type == mvccpb.PUT {
resolver.State = append(resolver.State, &resolver.Address{Addr: addr})
}
}
}
逻辑分析:
WithPrefix()确保捕获所有实例节点;ev.Kv.Key包含完整路径,需提取 IP:Port;resolver.State是 gRPC 内部维护的地址列表,变更后需调用resolver.ClientConn.UpdateState()触发负载均衡器刷新。
关键参数对照表
| 参数 | etcd v3 语义 | gRPC Resolver 作用 |
|---|---|---|
WithPrefix() |
匹配前缀路径下的所有 key | 实现服务名维度批量监听 |
WithRev(rev) |
从指定 revision 恢复监听 | 避免 Watch 断连导致的事件丢失 |
resolver.BuildInfo{} |
— | 封装解析器元数据,供 gRPC 调用链追踪 |
服务发现流程(mermaid)
graph TD
A[gRPC Dial] --> B[Resolver.ResolveNow]
B --> C[Watch /services/user/]
C --> D{etcd event?}
D -->|PUT| E[Parse addr → Address]
D -->|DELETE| F[Remove from State]
E & F --> G[UpdateState → LB refresh]
2.2 多集群跨AZ服务发现的一致性模型与Go泛型路由调度器
跨可用区(AZ)多集群服务发现需在分区容忍性与强一致性间权衡。我们采用最终一致性+版本向量(Vector Clock) 模型,各集群本地缓存服务实例,并通过轻量 gossip 协议同步增量变更。
数据同步机制
- 每个服务注册携带
(clusterID, version, timestamp)三元组 - 冲突时优先保留高版本号;同版本则按 timestamp 裁决
- 同步消息压缩为 delta patch,降低带宽开销
Go泛型调度器核心设计
type Router[T any] struct {
routes map[string]func(T) (string, error)
}
func (r *Router[T]) Register(name string, h func(T) (string, error)) {
r.routes[name] = h // T 在编译期绑定具体类型(如 ServiceInstance)
}
此泛型
Router[T]支持统一调度逻辑复用:T可为ServiceInstance、EndpointSlice或自定义上下文结构体。func(T)签名确保策略可插拔,避免运行时类型断言开销。
| 一致性维度 | CAP倾向 | 延迟上限 | 适用场景 |
|---|---|---|---|
| 强一致 | CP | >500ms | 金融事务路由 |
| 最终一致 | AP | Web API 发现 |
graph TD
A[Client请求] --> B{Router[ServiceInstance]}
B --> C[AZ1本地缓存]
B --> D[AZ2gossip同步]
C --> E[返回最近有效实例]
D --> E
2.3 服务健康探针的轻量级HTTP/GRPC双模检测框架设计
为统一治理混合微服务架构中的健康检查语义,我们设计了零依赖、可插拔的双模探针框架,支持自动协议协商与上下文透传。
核心能力矩阵
| 特性 | HTTP 模式 | gRPC 模式 |
|---|---|---|
| 探测端点 | /healthz |
Health/Check RPC |
| 超时控制 | timeout_ms: 3000 |
deadline: 3s |
| 响应解析 | JSON status.code | HealthCheckResponse |
协议自适应流程
graph TD
A[Probe Init] --> B{Protocol Hint?}
B -->|HTTP| C[Send GET /healthz]
B -->|gRPC| D[Invoke Health/Check]
B -->|Auto| E[DNS SRV + ALPN sniff]
C --> F[Parse JSON status]
D --> F
F --> G[Return Ready/NotReady]
探针配置示例(YAML)
probe:
endpoint: "svc.example.com:8080"
mode: auto # http | grpc | auto
timeout_ms: 2500
tls_enabled: true
headers: { "X-Cluster-ID": "prod-us-east" }
该配置通过 mode: auto 触发 ALPN 协商,timeout_ms 控制全链路探测上限,headers 仅在 HTTP 模式生效,gRPC 模式自动转换为 metadata。TLS 启用后,HTTP 使用 HTTPS,gRPC 复用同一证书链。
2.4 流量染色与元数据透传:Go中间件链中Context.Value的工程化约束实践
在高并发微服务链路中,context.Context 是传递请求生命周期元数据的事实标准。但直接滥用 Context.Value 易引发类型不安全、键冲突与内存泄漏。
染色键的全局唯一性保障
采用强类型键(而非字符串)避免冲突:
type traceKey struct{} // 非导出空结构体,确保唯一地址
func WithTraceID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, traceKey{}, id)
}
traceKey{} 实例无字段,零内存开销;其地址唯一,杜绝字符串键 "trace_id" 的跨包污染风险。
元数据透传的中间件契约
| 中间件职责 | 是否允许覆盖 | 安全边界 |
|---|---|---|
| 入口网关 | ✅ | 校验染色头并注入 |
| 业务服务层 | ❌ | 仅读取,禁止 WithValue |
| 日志/监控中间件 | ⚠️ | 只读 + 追加 log.Fields |
上下文透传流程
graph TD
A[HTTP Header X-Trace-ID] --> B[Gateway Middleware]
B --> C[WithTraceID ctx]
C --> D[Service Handler]
D --> E[DB/Cache Client]
E --> F[Log middleware reads ctx.Value]
2.5 注册中心降级策略:本地缓存FUSE+内存快照回滚的Go原子切换机制
当注册中心不可用时,服务发现需无缝退化至本地可信数据源。本方案采用双层降级:FUSE挂载只读本地缓存(/etc/service-registry)提供POSIX兼容访问;同时维护内存中带版本号的快照链表,支持毫秒级回滚。
数据同步机制
主流程通过 sync.RWMutex 保护快照切换,配合 atomic.Value 实现无锁读取:
var currentSnapshot atomic.Value // 存储 *ServiceSnapshot
func switchToSnapshot(snap *ServiceSnapshot) {
snap.Version = atomic.AddUint64(&versionCounter, 1)
currentSnapshot.Store(snap) // 原子写入,零拷贝切换
}
atomic.Value确保Store()/Load()对任意结构体指针的线程安全;Version用于幂等校验与快照生命周期管理。
降级触发条件
- 注册中心心跳超时 ≥3 次
- HTTP 503 或连接拒绝连续发生
- FUSE 缓存校验和匹配且 mtime 未过期
快照状态迁移表
| 状态 | 触发动作 | 切换耗时(均值) |
|---|---|---|
| Healthy | 定期拉取并生成新快照 | 12ms |
| Degraded | 加载最新FUSE缓存 | 0.8ms |
| SnapshotRollback | 回滚至上一有效快照 | 0.3ms |
graph TD
A[注册中心健康] -->|心跳失败| B[触发降级]
B --> C[挂载FUSE缓存]
C --> D[加载内存快照]
D --> E[atomic.Value.Store]
第三章:分布式配置中心的Go内核演进
3.1 配置变更的事件驱动模型:基于Go Channel的Watch-Broadcast高性能管道
传统轮询式配置监听存在延迟高、资源浪费等问题。事件驱动模型通过 watch-broadcast 管道解耦生产者(配置中心变更通知)与消费者(服务组件),实现毫秒级响应。
数据同步机制
核心是双向通道抽象:watcher 注册接收专属 chan ConfigEvent,广播器统一向 broadcastCh 发送事件,再由分发协程扇出至各 watcher 通道。
type Broadcaster struct {
broadcastCh chan ConfigEvent
watchers map[uint64]chan ConfigEvent
mu sync.RWMutex
}
func (b *Broadcaster) Watch() <-chan ConfigEvent {
ch := make(chan ConfigEvent, 16) // 缓冲防阻塞
b.mu.Lock()
id := uint64(atomic.AddUint64(&b.nextID, 1))
b.watchers[id] = ch
b.mu.Unlock()
return ch
}
chan ConfigEvent容量为16:平衡内存占用与突发事件丢包风险;atomic.AddUint64保证 watcher ID 全局唯一且无锁递增;RWMutex支持高并发读(Watch调用频次远高于注册/注销)。
性能对比(万级 watcher 场景)
| 模型 | 平均延迟 | CPU 占用 | 内存增长 |
|---|---|---|---|
| 轮询(500ms) | 250ms | 18% | 线性 |
| Channel 广播 | 3.2ms | 6.1% | 对数 |
graph TD
A[Config Center] -->|Webhook/ETCD Watch| B(Broadcaster)
B --> C[Watcher-1]
B --> D[Watcher-2]
B --> E[...]
3.2 多环境/多租户配置隔离:Go struct tag驱动的动态Schema校验引擎
传统硬编码校验难以应对租户差异化规则。本方案通过自定义 struct tag(如 env:"prod,stage"、tenant:"t1,t2,all")声明配置字段的生效范围,实现零代码变更的多维策略隔离。
核心校验逻辑
type DatabaseConfig struct {
Host string `env:"prod,stage" tenant:"t1,t2"`
Port int `env:"prod" tenant:"all"`
}
envtag 指定运行环境白名单,校验时匹配os.Getenv("ENV");tenanttag 支持"all"通配或显式租户ID列表,结合上下文tenantID动态裁剪字段可见性。
运行时校验流程
graph TD
A[加载配置实例] --> B{解析struct tag}
B --> C[匹配当前env & tenant]
C -->|匹配失败| D[跳过该字段校验]
C -->|匹配成功| E[执行validator链]
支持的 tag 类型对比
| Tag | 示例值 | 语义说明 |
|---|---|---|
env |
"prod,stage" |
仅在指定环境生效 |
tenant |
"t1,all" |
租户白名单,all全局生效 |
required |
"true" |
环境/租户维度级必填约束 |
3.3 配置热加载的内存安全边界:sync.Map+atomic.Pointer实现零GC热替换
核心设计思想
传统配置热更新常依赖互斥锁 + 指针原子赋值,但存在竞态与 GC 压力;sync.Map 适合读多写少场景,但不支持原子性整体替换。结合 atomic.Pointer 可实现配置结构体的无锁、零分配热切换。
数据同步机制
type Config struct {
Timeout int
Retries int
}
var configPtr atomic.Pointer[Config]
// 热替换(无GC分配)
func UpdateConfig(newCfg *Config) {
configPtr.Store(newCfg) // 原子写入,旧对象由GC异步回收
}
func GetConfig() *Config {
return configPtr.Load() // 原子读取,无锁、无拷贝
}
atomic.Pointer[Config]底层使用unsafe.Pointer+runtime/internal/atomic指令保证线性一致性;Store不触发新内存分配,仅更新指针值;Load返回当前快照,避免读写冲突。
对比方案性能维度
| 方案 | GC压力 | 读性能 | 写安全性 | 替换原子性 |
|---|---|---|---|---|
sync.RWMutex + *Config |
中 | 低(锁竞争) | 高 | 否(需临界区) |
sync.Map |
高(value 拷贝) | 中 | 中 | 否 |
atomic.Pointer |
零 | 极高 | 高 | 是 |
graph TD
A[配置变更事件] --> B[构造新Config实例]
B --> C[atomic.Pointer.Store]
C --> D[所有goroutine立即看到新配置]
D --> E[旧Config对象等待GC回收]
第四章:可观测性基建三件套的Go原生构建
4.1 OpenTelemetry Go SDK深度定制:Span上下文在微服务链路中的无侵入注入
OpenTelemetry Go SDK 提供了 otelhttp 和 otelgrpc 等自动注入中间件,但真实微服务场景常需跨异步任务、消息队列或自定义协议传递 Span 上下文。
无侵入注入的核心机制
依赖 context.Context 的透传能力,结合 propagation.TextMapPropagator 实现跨进程上下文注入:
// 从 HTTP 请求中提取父 SpanContext
prop := otel.GetTextMapPropagator()
ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
// 创建子 Span 并继承 trace_id / span_id
span := trace.SpanFromContext(ctx)
_, span = tracer.Start(
trace.ContextWithSpan(ctx, span),
"process-order",
trace.WithSpanKind(trace.SpanKindServer),
)
prop.Extract()解析traceparent(W3C 标准)或b3等格式头;trace.ContextWithSpan()将 Span 显式绑定到新 Context,确保后续tracer.Start()自动关联父子关系。
关键传播字段对照表
| 字段名 | 用途 | 示例值 |
|---|---|---|
traceparent |
W3C 标准 trace ID + span ID | 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 |
tracestate |
跨厂商状态扩展 | rojo=00f067aa0ba902b7,congo=t61rcWkgMzE |
异步任务上下文延续流程
graph TD
A[HTTP Handler] -->|prop.Inject| B[Message Broker Header]
B --> C[Worker Goroutine]
C -->|prop.Extract| D[Child Span]
4.2 Prometheus Exporter高并发采集:基于Goroutine池与ring buffer的指标缓冲架构
在高吞吐场景下,直接为每次指标采集启动 Goroutine 易引发调度风暴。我们采用 worker pool + ring buffer 双层缓冲架构:
核心组件协同流程
graph TD
A[HTTP Handler] -->|批量推入| B[Ring Buffer]
B --> C{Buffer非空?}
C -->|是| D[Worker Pool取值]
D --> E[序列化为Prometheus文本格式]
E --> F[响应Write]
Ring Buffer 实现关键片段
type RingBuffer struct {
data []*MetricPoint
head uint64 // 写指针
tail uint64 // 读指针
mask uint64 // 容量-1(2的幂)
}
func (rb *RingBuffer) Push(mp *MetricPoint) bool {
next := atomic.AddUint64(&rb.head, 1) - 1
idx := next & rb.mask
if atomic.LoadUint64(&rb.tail) <= next &&
atomic.LoadUint64(&rb.tail)+uint64(len(rb.data)) <= next {
return false // 已满
}
rb.data[idx] = mp
return true
}
mask保证 O(1) 索引计算;atomic操作避免锁竞争;head/tail分离读写边界,实现无锁生产者。
性能对比(10K/s 指标写入)
| 方案 | CPU占用 | GC压力 | 吞吐稳定性 |
|---|---|---|---|
| 直接goroutine | 82% | 高 | 波动±35% |
| Worker Pool+Ring | 31% | 低 | ±3% |
4.3 日志结构化与采样:Zap Logger Hook扩展与动态采样率调控Go控制器
Zap 默认不支持运行时采样率调整,需通过自定义 zapcore.Core Hook 实现动态调控。
自定义采样 Hook 核心逻辑
type DynamicSampler struct {
sampler *zapcore.ConstSampler
mu sync.RWMutex
}
func (ds *DynamicSampler) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
ds.mu.RLock()
defer ds.mu.RUnlock()
if ds.sampler != nil && ds.sampler.Sample(ent) {
return ce.Add(ds.sampler)
}
return ce
}
// 动态更新采样率(如从 1% 调至 5%)
func (ds *DynamicSampler) UpdateRate(rate float64) {
ds.mu.Lock()
defer ds.mu.Unlock()
ds.sampler = zapcore.NewSamplingPrioritySampler(
zapcore.DebugLevel, rate, 100, // 每秒最多 100 条调试日志
)
}
该 Hook 封装 ConstSampler 并支持并发安全的采样策略热更新;rate 表示每秒允许通过的日志比例,100 是突发窗口内最大条数。
控制器集成方式
- 通过 HTTP 接口接收
PATCH /api/v1/log/sampling请求; - 解析 JSON body 中的
rate字段并调用UpdateRate(); - 返回当前生效采样率及最近 10 秒采样统计。
| 指标 | 示例值 | 说明 |
|---|---|---|
| 当前采样率 | 0.03 | 3% 的日志被保留 |
| 窗口内总日志量 | 1247 | 过去 10 秒原始日志总数 |
| 实际输出日志量 | 38 | 经采样后写入日志系统的数量 |
graph TD
A[HTTP PATCH] --> B{解析 rate}
B --> C[调用 UpdateRate]
C --> D[刷新 Sampler 实例]
D --> E[后续日志按新策略采样]
4.4 分布式追踪熔断:Jaeger UI后端Go服务的TraceQL查询优化与索引预热策略
Jaeger UI 的 Go 后端(jaeger-query)在高并发 TraceQL 查询下易触发熔断,核心瓶颈在于 Elasticsearch 中 trace 数据的动态字段未预建索引、查询时需 runtime field 解析。
索引预热策略
- 启动时异步加载高频 TraceQL 模式(如
service.name == "auth" and duration > 100ms)对应的时间范围索引别名; - 预热
trace_id,service.name,duration,start_time四个字段的 keyword + date 类型映射。
TraceQL 查询优化示例
// 在 spanstore/es/reader.go 中增强 QueryBuilder
q := es.MustMatchQuery("service.name.keyword", svcName).
Filter(es.RangeQuery("start_time").Gte(startTime).Lte(endTime)).
Filter(es.RangeQuery("duration").Gt(100_000_000)) // 单位:纳秒
此写法绕过 TraceQL 解析器默认的
script_score动态计算,直接命中 ES 倒排索引;100_000_000表示 100ms,避免浮点转换开销。
| 字段 | 类型 | 是否参与排序 | 是否启用 keyword |
|---|---|---|---|
| service.name | text | 否 | ✅ |
| duration | long | ✅ | ❌(原生数值) |
| start_time | date | ✅ | ❌ |
graph TD
A[TraceQL 请求] --> B{是否含高频模式?}
B -->|是| C[路由至预热索引别名]
B -->|否| D[回退至全量索引+runtime field]
C --> E[ES 快速倒排检索]
D --> F[触发熔断阈值风险↑]
第五章:从P6到P9——Go工程师的基建认知跃迁
基建不是“搭架子”,而是定义系统演进的契约
某电商中台团队在P6阶段主导了订单服务的Go微服务化改造,使用标准gin+gorm栈,QPS稳定在3k。但当大促流量峰值突破12k时,数据库连接池耗尽、慢查询激增、熔断策略失效——根本原因并非代码性能差,而是基建层缺失可观测性契约:日志无统一trace_id注入、指标未按SLI维度(如order_create_p99<800ms)暴露、链路追踪采样率硬编码为1%。P7工程师推动接入OpenTelemetry SDK并定制otelgin中间件,强制所有HTTP handler注入context-aware trace;同时将Prometheus指标命名规范化为service_order_create_latency_seconds_bucket{status="success",region="sh"},使SRE能基于真实业务标签下钻分析。
从“能跑”到“可治理”的配置范式升级
早期服务配置散落在config.yaml、环境变量、K8s ConfigMap中,导致灰度发布时出现timeout=3s(测试环境)与timeout=30s(生产)混用。P8阶段重构配置体系,引入Viper分层加载机制,并强制实施配置Schema校验:
type Config struct {
Database struct {
TimeoutSeconds int `mapstructure:"timeout_seconds" validate:"min=5,max=60"`
}
Cache struct {
TTLSeconds int `mapstructure:"ttl_seconds" validate:"required,gte=60"`
}
}
配合CI流水线中的viper.Unmarshal(&cfg)校验步骤,阻断非法配置提交。某次误将cache.ttl_seconds: 0合并至主干,被预检脚本捕获并报错:validation failed: cache.ttl_seconds: 0 must be >= 60。
构建面向故障的基建韧性
某支付网关在P9主导设计“降级金丝雀”机制:当核心依赖user-service的错误率连续3分钟>5%,自动触发两级动作:
- 将
/pay接口的user_profile字段降级为null(业务无损) - 向内部告警群推送带
runbook_link的卡片,并同步调用curl -X POST https://ops-api/rollback?service=payment&version=v2.3.1回滚至已知稳定版本
该机制通过Envoy Filter注入Go服务Sidecar,其决策逻辑完全独立于业务代码,实现故障响应的秒级闭环。
| 能力层级 | P6典型行为 | P9关键实践 |
|---|---|---|
| 部署 | 手动执行kubectl apply |
GitOps驱动,ArgoCD监听infra-prod分支变更 |
| 安全 | 使用默认TLS证书 | 自动轮换SPIFFE证书,mTLS双向认证强制开启 |
| 成本 | 按节点规格粗粒度估算 | 基于eBPF采集容器级CPU/内存热力图,动态缩容闲置实例 |
基建即产品:为内部开发者提供SDK而非文档
团队将通用能力封装为go-sdk-infra模块,例如分布式锁不再要求开发者手写Redis Lua脚本:
// P9提供的声明式API
lock, err := infra.NewDistributedLock(
infra.WithResource("inventory_sku_12345"),
infra.WithTTL(30*time.Second),
infra.WithRetryPolicy(infra.RetryExponentialBackoff{MaxRetries: 3}),
)
if err != nil { /* 处理获取失败 */ }
defer lock.Release() // 自动续期与清理
该SDK内置熔断器、上下文超时继承、结构化错误码(infra.ErrLockTimeout),且所有方法均支持context.Context传递trace信息。
技术决策必须附带反脆弱验证方案
每次基建升级需同步交付验证用例:
- 引入新的gRPC网关后,必须提供
chaos-test.sh脚本,模拟iptables DROP30%请求包,验证重试与降级是否生效 - 升级etcd集群版本前,运行
./validate-leader-election --duration=10m,持续制造网络分区并观测Leader切换延迟
某次etcd 3.5升级因未覆盖watch连接闪断场景,在灰度期间导致配置中心短暂失联,该事故直接催生了watch-stress-test工具链的强制集成。
基建演进的物理约束永远优先于技术选型
在东南亚多IDC架构中,P9团队放弃强一致的分布式事务方案,转而采用Saga模式+本地消息表,因跨地域RTT稳定在180ms,任何两阶段提交协议在此延迟下必然违反SLA。他们用Go编写轻量级SagaOrchestrator,状态机定义嵌入代码注释,经go:generate自动生成状态流转图(mermaid):
stateDiagram-v2
[*] --> ReserveInventory
ReserveInventory --> ConfirmPayment: success
ReserveInventory --> CompensateInventory: timeout
ConfirmPayment --> ShipOrder: success
ConfirmPayment --> CompensatePayment: failure
ShipOrder --> [*]
CompensateInventory --> [*]
CompensatePayment --> [*] 