Go 事件驱动 Saga:跨服务事务编排

背景 这类问题在真实项目里很常见:高并发、复杂依赖、发布频繁、团队协作面广。只有把边界条件提前定义清楚,系统才会在压力下保持稳定。 实践要点 先定义目标:可用性、延迟、成本哪个优先。 把关键路径显式化:超时、重试、降级、回滚。 把策略写进代码和流程,而不是只停留在文档。 代码片段 ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() err := client.Call(ctx) if err != nil { return err } 总结 工程实践最怕“看起来正确”。把策略做成可观测、可验证、可回滚的闭环,才能在生产环境里真正稳定运行。 稳定性不是某个技巧,而是持续的系统化约束。

2026年5月26日 · 1 分钟 · BvBeJ

Go API 版本管理:平滑演进而不破坏旧客户端

背景 这类问题在真实项目里很常见:高并发、复杂依赖、发布频繁、团队协作面广。只有把边界条件提前定义清楚,系统才会在压力下保持稳定。 实践要点 先定义目标:可用性、延迟、成本哪个优先。 把关键路径显式化:超时、重试、降级、回滚。 把策略写进代码和流程,而不是只停留在文档。 代码片段 ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() err := client.Call(ctx) if err != nil { return err } 总结 工程实践最怕“看起来正确”。把策略做成可观测、可验证、可回滚的闭环,才能在生产环境里真正稳定运行。 稳定性不是某个技巧,而是持续的系统化约束。

2026年5月17日 · 1 分钟 · BvBeJ

Go Context 传递清单:避免超时与取消失控

背景 这类问题在真实项目里很常见:高并发、复杂依赖、发布频繁、团队协作面广。只有把边界条件提前定义清楚,系统才会在压力下保持稳定。 实践要点 先定义目标:可用性、延迟、成本哪个优先。 把关键路径显式化:超时、重试、降级、回滚。 把策略写进代码和流程,而不是只停留在文档。 代码片段 ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() err := client.Call(ctx) if err != nil { return err } 总结 工程实践最怕“看起来正确”。把策略做成可观测、可验证、可回滚的闭环,才能在生产环境里真正稳定运行。 稳定性不是某个技巧,而是持续的系统化约束。

2026年5月11日 · 1 分钟 · BvBeJ

Go 服务降级手册:高峰期先保核心链路

背景 这类问题在真实项目里很常见:高并发、复杂依赖、发布频繁、团队协作面广。只有把边界条件提前定义清楚,系统才会在压力下保持稳定。 实践要点 先定义目标:可用性、延迟、成本哪个优先。 把关键路径显式化:超时、重试、降级、回滚。 把策略写进代码和流程,而不是只停留在文档。 代码片段 ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() err := client.Call(ctx) if err != nil { return err } 总结 工程实践最怕“看起来正确”。把策略做成可观测、可验证、可回滚的闭环,才能在生产环境里真正稳定运行。 稳定性不是某个技巧,而是持续的系统化约束。

2026年5月8日 · 1 分钟 · BvBeJ

Go Kafka Consumer:重平衡期间的可用性设计

背景 扩缩容、实例重启、网络波动都会触发 rebalance。处理不好就会出现消费停顿、重复处理和延迟暴涨。 实践要点 处理逻辑幂等化 offset 提交时机明确 重平衡回调里做好 flush for msg := range claim.Messages() { if err := handle(msg); err == nil { session.MarkMessage(msg, "") } } 总结 消费者稳定性的上限,取决于你对 rebalance 的设计,而不是对“正常流量”的设计。 消息系统里,异常路径才是主路径。

2026年5月4日 · 1 分钟 · BvBeJ

Go 服务发现容错:注册中心抖动时怎么保服务

背景 微服务调用里,服务发现经常被当成理所当然的基础设施。但注册中心一旦抖动,调用链就会被放大影响。 实用策略 本地缓存上次可用实例列表 失败时指数退避刷新 查询失败时优先用“最近成功快照” type Resolver interface { Resolve(ctx context.Context, service string) ([]string, error) } type SnapshotCache struct { mu sync.RWMutex data map[string][]string } 总结 服务发现的核心目标是“可用优先”,而不是“每次都拿最新”。 基础组件会失败,容错设计要把失败当常态。

2026年5月1日 · 1 分钟 · BvBeJ

Go 幂等设计:防重试、防重复提交、防重复消费

背景 在微服务里,重试是常态;没有幂等,重试就会制造脏数据。 常见重复来源: 客户端重复点击 网关超时后自动重试 消息队列重复投递 一种常见实现 以幂等 key 做唯一约束,先查后写或直接 UPSERT。 type IdempotencyRecord struct { Key string Status string ResultRef string } func (s *OrderService) CreateOrder(ctx context.Context, key string, req *CreateReq) (*Order, error) { if rec, ok := s.repo.FindByKey(ctx, key); ok { return s.repo.FindOrder(ctx, rec.ResultRef) } order, err := s.repo.CreateOrderWithKey(ctx, key, req) if err != nil { return nil, err } return order, nil } 总结 幂等设计本质是在失败重试下维持业务语义稳定。 关键是“唯一键 + 状态机 + 可重放结果”。 高可用系统默认会重试,幂等就是重试的安全带。

2026年4月29日 · 1 分钟 · BvBeJ

Go 缓存一致性:更新策略与失效控制

背景 缓存能提升性能,但也最容易制造隐性 bug。 线上常见问题: 数据库已更新,缓存还是旧值 热点 key 失效瞬间把数据库打穿 多服务写入同一份数据,更新顺序错乱 在 Go 服务里,缓存一致性通常不是“技术选型”问题,而是“写路径设计”问题。 常见策略 Cache Aside 最常见模型:读先查缓存,未命中再查库并回填;写时先写库,再删缓存。 func (s *UserService) GetUser(ctx context.Context, id int64) (*User, error) { key := fmt.Sprintf("user:%d", id) if val, ok := s.cache.Get(ctx, key); ok { return decodeUser(val) } user, err := s.repo.FindByID(ctx, id) if err != nil { return nil, err } _ = s.cache.Set(ctx, key, encodeUser(user), 5*time.Minute) return user, nil } func (s *UserService) UpdateUser(ctx context.Context, user *User) error { if err := s.repo.Update(ctx, user); err != nil { return err } key := fmt.Sprintf("user:%d", user.ID) _ = s.cache.Delete(ctx, key) return nil } 这个模型简单、可靠,适合大多数业务系统。 双删策略的取舍 有些场景会用“先删缓存,再写库,延迟再删一次”。 它能降低极端并发下的脏读概率,但不是银弹。更关键的还是: 写操作是否集中在一条服务链路 有没有事件通知机制统一刷新 key 的 TTL 是否合理 避免缓存雪崩 两个实用点: TTL 加随机抖动 热点 key 做单飞保护 var g singleflight.Group func (s *UserService) GetUserWithSingleflight(ctx context.Context, id int64) (*User, error) { key := fmt.Sprintf("user:%d", id) v, err, _ := g.Do(key, func() (interface{}, error) { return s.GetUser(ctx, id) }) if err != nil { return nil, err } return v.(*User), nil } 总结 缓存一致性最重要的不是某个技巧,而是明确一致性目标: ...

2026年4月24日 · 1 分钟 · BvBeJ

Go gRPC 服务治理:超时、重试、熔断怎么配合

背景 很多团队从 REST 切到 gRPC 后,第一感受通常都不错: 接口定义清晰 代码生成省心 性能和序列化效率更好 但线上跑久了会发现,真正决定服务质量的不是 protobuf 文件写得多漂亮,而是这些问题处理得怎么样: 超时怎么设 失败要不要重试 下游抖动时怎么自保 连接和并发要怎么控 这些内容不处理好,gRPC 只是让调用更快地失败而已。 超时必须从调用入口就带上 Go 里最好的习惯之一,就是把超时放进 context.Context。 func (s *OrderService) GetUser(ctx context.Context, userID string) (*pb.User, error) { callCtx, cancel := context.WithTimeout(ctx, 300*time.Millisecond) defer cancel() return s.userClient.GetUser(callCtx, &pb.GetUserRequest{ UserId: userID, }) } 为什么一定要带超时? 因为不带超时的 RPC,本质上就是把失败时间交给网络、内核和对端服务决定。你无法控制,也无法稳定预期。 线上更糟的是,请求可能层层调用: API -> Order Service -> User Service -> Profile Service 如果每一层都没有明确 deadline,慢请求会像雪球一样越滚越大。 重试不是默认开启就完事 很多人一看到失败就想自动重试,但重试最危险的地方在于:如果失败原因是过载,重试可能会让故障更严重。 适合重试的场景通常是: 短暂网络抖动 连接瞬时中断 明显的临时性错误 不适合盲目重试的场景: 已经超时很久的请求 非幂等写操作 下游明显处于过载状态 一个更稳的客户端封装通常像这样: func callWithRetry(ctx context.Context, fn func(context.Context) error) error { var lastErr error backoffs := []time.Duration{50 * time.Millisecond, 100 * time.Millisecond, 200 * time.Millisecond} for _, backoff := range backoffs { if err := fn(ctx); err == nil { return nil } else { lastErr = err } select { case <-time.After(backoff): case <-ctx.Done(): return ctx.Err() } } return lastErr } 这里最重要的不是代码本身,而是策略: ...

2026年4月16日 · 2 分钟 · BvBeJ

Go 服务限流:用令牌桶保护你的 API

背景 服务刚上线时,大家最担心的是没流量。等真有流量了,新的问题马上出现: 某个接口被脚本打爆 下游数据库扛不住瞬时峰值 重试风暴把正常请求一起拖死 这时候如果没有限流,服务再快也会被拖垮。 Go 很适合写高并发服务,限流逻辑做成中间件也很自然。今天聊一个最常见、也最实用的方案:令牌桶。 为什么是令牌桶 令牌桶的规则很简单: 系统按固定速率往桶里放令牌 请求到来时先拿一个令牌 拿到就放行,拿不到就拒绝或排队 它的好处是同时兼顾两件事: 控制平均速率 允许短时间突发 比如接口平时稳定在每秒 100 个请求,但偶尔瞬间冲到 150,只要桶里有积累的令牌,就不一定要马上拒绝。 单机场景:HTTP 中间件 Go 官方扩展库里已经有现成实现:golang.org/x/time/rate。 package main import ( "net/http" "time" "golang.org/x/time/rate" ) func RateLimitMiddleware(limiter *rate.Limiter, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !limiter.Allow() { w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte("rate limit exceeded")) return } next.ServeHTTP(w, r) }) } func main() { mux := http.NewServeMux() mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("ok")) }) limiter := rate.NewLimiter(rate.Every(10*time.Millisecond), 20) server := &http.Server{ Addr: ":8080", Handler: RateLimitMiddleware(limiter, mux), } _ = server.ListenAndServe() } 这段配置的含义: ...

2026年4月14日 · 2 分钟 · BvBeJ