在现代微服务架构中,服务之间的调用频繁而复杂。一个下游服务响应慢或者出现故障,可能会导致整个系统链路被拖垮,甚至引发雪崩式失败。今天我们来讲一个非常关键的主题:请求超时与熔断策略,并用 Go + Gin + Sony Gobreaker 搭建可跑通的微服务示例,让你直接实践。
创建两个微服务:
-
user-service (下游服务,可能慢或失败)
-
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")
}
四、测试流程
- 启动user-service
sql
cd user-service
go run .
User Service running at :8081
- 启动order-service
sql
cd order-service
go run .
Order Service running at :8080
- 测试调用:
apache
curl http://localhost:8080/order/1
如果下游延迟或失败,order-service 会返回503
控制台可看到熔断器状态变化日志
并发访问可观察熔断器自动保护效果

- 并发访问
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
五、人生哲理
-
微服务教我们 稳定与韧性
-
人生教我们 节奏与界限
-
当你把技术的智慧套用到生活,你会发现,生活也可以像高可用系统一样,既高效又有韧性。
*源码地址*
需要的私信.
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!