每日一Go-52、Go微服务--请求超时与熔断策略实战

在现代微服务架构中,服务之间的调用频繁而复杂。一个下游服务响应慢或者出现故障,可能会导致整个系统链路被拖垮,甚至引发雪崩式失败。今天我们来讲一个非常关键的主题:请求超时与熔断策略,并用 Go + Gin + Sony Gobreaker 搭建可跑通的微服务示例,让你直接实践。

创建两个微服务:

  1. user-service (下游服务,可能慢或失败)

  2. order-service(上游服务,调用user-service)

并使用Sony Gobreaker实现熔断器。

一、依赖准备

swift 复制代码
go get github.com/gin-gonic/gin
go get github.com/sony/gobreaker

二、下游服务 user-service

go 复制代码
package main
import (
    "fmt"
    "math/rand"
    "net/http"
    "time"
    "github.com/gin-gonic/gin"
)
// main 函数是程序的入口点
func main() {
    // 创建Gin引擎实例
    r := gin.Default()
    // Go 1.20+ 无需手动设置随机数种子,默认已自动初始化
    // 定义获取用户信息的API端点
    r.GET("/user/:id", func(c *gin.Context) {
        // 获取用户ID参数
        id := c.Param("id")
        // 模拟随机延迟和失败
        delay := rand.Intn(3) // 生成0~2秒的随机延迟
        time.Sleep(time.Duration(delay) * time.Second)
        // 30%概率模拟服务失败
        if rand.Float32() < 0.3 {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "user service failed"})
            return
        }
        // 返回成功响应,包含用户ID和用户名
        c.JSON(http.StatusOK, gin.H{"id": id, "name": fmt.Sprintf("User%s", id)})
    })
    // 启动用户服务,监听8081端口
    fmt.Println("User Service running at :8081")
    r.Run(":8081")
}

三、上游服务 order-service

go 复制代码
package main
import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "math/rand"
    "net/http"
    "time"
    "github.com/gin-gonic/gin"
    "github.com/sony/gobreaker"
)
// client 是用于发送 HTTP 请求的客户端
var client = &http.Client{}
// cb 是用于调用用户服务的断路器实例
var cb *gobreaker.CircuitBreaker
// init 函数在程序启动时初始化断路器
func init() {
    // 配置断路器参数
    settings := gobreaker.Settings{
        Name:        "UserService",   // 断路器名称
        MaxRequests: 3,               // 半开状态下允许的最大请求数
        Interval:    5 * time.Second, // 统计周期
        Timeout:     5 * time.Second, // 断路器从打开到半开的超时时间
        ReadyToTrip: func(counts gobreaker.Counts) bool {
            // 当连续失败次数达到3次时,断路器打开
            return counts.ConsecutiveFailures >= 3
        },
        OnStateChange: func(name string, from, to gobreaker.State) {
            // 记录断路器状态变化
            log.Printf("Circuit breaker state changed: %s -> %s\n", from.String(), to.String())
        },
    }
    // 创建断路器实例
    cb = gobreaker.NewCircuitBreaker(settings)
}
// callUserService 通过断路器调用用户服务获取用户信息
// id: 用户ID
// 返回用户信息的字节数组和可能的错误
func callUserService(id string) ([]byte, error) {
    // 创建带有2秒超时的上下文
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    // 构建用户服务的请求URL
    url := fmt.Sprintf("http://localhost:8081/user/%s", id)
    req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    // 使用断路器执行请求
    body, err := cb.Execute(func() (interface{}, error) {
        resp, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        return io.ReadAll(resp.Body)
    })
    if err != nil {
        return nil, err
    }
    return body.([]byte), nil
}
// main 函数是程序的入口点
func main() {
    // 初始化随机数种子
    rand.Seed(time.Now().UnixNano())
    // 创建Gin引擎实例
    r := gin.Default()
    // 定义获取订单的API端点
    r.GET("/order/:id", func(c *gin.Context) {
        // 获取订单ID参数
        id := c.Param("id")
        // 调用用户服务获取用户信息
        data, err := callUserService(id)
        if err != nil {
            // 如果调用失败,返回服务不可用状态
            c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
            return
        }
        // 解析用户信息JSON
        var user map[string]interface{}
        json.Unmarshal(data, &user)
        // 构造订单响应
        response := gin.H{
            "order_id": fmt.Sprintf("order-%d", rand.Intn(1000)), // 生成随机订单ID
            "user":     user,                                     // 包含用户信息
        }
        // 返回成功响应
        c.JSON(http.StatusOK, response)
    })
    // 启动订单服务,监听8080端口
    fmt.Println("Order Service running at :8080")
    r.Run(":8080")
}

四、测试流程

  1. 启动user-service
sql 复制代码
cd user-service
go run .
User Service running at :8081
  1. 启动order-service
sql 复制代码
cd order-service
go run .
Order Service running at :8080
  1. 测试调用:
apache 复制代码
curl http://localhost:8080/order/1

如果下游延迟或失败,order-service 会返回503

控制台可看到熔断器状态变化日志

并发访问可观察熔断器自动保护效果

  1. 并发访问
go 复制代码
package main
import (
    "fmt"
    "io"
    "net/http"
    "sync"
    "time"
)
func main() {
    const (
        url         = "http://localhost:8080/order/1"
        concurrency = 20  // 并发数
        requests    = 100 // 总请求数
    )
    var wg sync.WaitGroup
    var mu sync.Mutex
    var success, fail int
    client := &http.Client{
        Timeout: 3 * time.Second,
    }
    start := time.Now()
    for i := 0; i < concurrency; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < requests/concurrency; j++ {
                resp, err := client.Get(url)
                if err != nil {
                    mu.Lock()
                    fail++
                    mu.Unlock()
                    continue
                }
                _, err = io.ReadAll(resp.Body)
                resp.Body.Close()
                mu.Lock()
                if err != nil || resp.StatusCode != 200 {
                    fail++
                } else {
                    success++
                }
                mu.Unlock()
            }
        }()
    }
    wg.Wait()
    duration := time.Since(start)
    fmt.Println("=== Stress Test Result ===")
    fmt.Printf("Total Requests: %d\n", requests)
    fmt.Printf("Success: %d\n", success)
    fmt.Printf("Fail (timeout / circuit open): %d\n", fail)
    fmt.Printf("Total Duration: %v\n", duration)
}

运行压测

sql 复制代码
$ go run .
=== Stress Test Result ===
Total Requests: 100
Success: 32
Fail (timeout / circuit open): 68
Total Duration: 3.051756s

五、人生哲理

  • 微服务教我们 稳定与韧性

  • 人生教我们 节奏与界限

  • 当你把技术的智慧套用到生活,你会发现,生活也可以像高可用系统一样,既高效又有韧性。

*源码地址*

需要的私信.


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!

相关推荐
亚历克斯神3 小时前
Java 23 虚拟线程进阶:深度探索与实战
java·spring·微服务
codeejun4 小时前
每日一Go-53、Go微服务--限流与降级
开发语言·微服务·golang
NotFound4864 小时前
Go语言中的图形界面开发实战解析:从GUI到WebAssembly
开发语言·golang·wasm
平淡风云5 小时前
Copying shared cache symbols from xxx iPhone
ios·iphone·xcode
2401_832635585 小时前
小白分享如何Go 语言中的图形界面开发:从 GUI 到 WebAssembly
microsoft·golang·wasm
SL-staff5 小时前
2026企业文档选型白皮书:功能、技术栈、私有化部署与采购建议
spring cloud·docker·微服务·kubernetes·开源·私有化部署·企业文档
aXin_ya6 小时前
微服务 第三天
java·微服务·架构
Wild API7 小时前
按任务轻重做模型分流的实战思路
分布式·微服务·架构
XMYX-07 小时前
15 - Go 泛型(Generics):从入门到实战
开发语言·golang