第10讲:Go 微服务架构 —— 从单体到微服务的演进之路
·
一、微服务架构概述
1.1 单体架构 vs 微服务架构
单体架构 微服务架构
┌─────────────────────┐ ┌─────────────────────────────┐
│ 应用 │ │ ┌──────┐ ┌──────┐ ┌──────┐ │
│ ┌─────────────────┐ │ │ │用户 │ │订单 │ │支付 │ │
│ │ UI │ │ │ │服务 │ │服务 │ │服务 │ │
│ ├─────────────────┤ │ │ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ 业务逻辑 │ │ │ │ │ │ │
│ ├─────────────────┤ │ │ └─────────┼─────────┘ │
│ │ 数据访问 │ │ │ │ │
│ ├─────────────────┤ │ │ ┌─────▼─────┐ │
│ │ 数据库 │ │ │ │ API 网关 │ │
│ └─────────────────┘ │ │ └───────────┘ │
└─────────────────────┘ └─────────────────────────────┘
单体架构优缺点:
✅ 开发简单,部署方便
✅ 测试容易,调试直观
❌ 耦合严重,难以扩展
❌ 技术栈锁定,牵一发而动全身
微服务架构优缺点:
✅ 独立部署,独立扩展
✅ 技术栈灵活,容错性好
❌ 运维复杂,网络开销大
❌ 分布式事务困难,调试困难
1.2 微服务核心原则
微服务设计的 12 条原则
┌─────────────────────────────────────────────────────────────┐
│ 1. 单一职责:每个服务只做一件事 │
│ 2. 自治性:每个服务独立开发、部署、扩展 │
│ 3. 去中心化:数据存储、治理、技术栈去中心化 │
│ 4. 弹性设计:熔断、降级、重试、限流 │
│ 5. 观察性:日志、指标、链路追踪 │
│ 6. 自动化:CI/CD、自动化测试、自动化部署 │
│ 7. 接口契约:API First 设计 │
│ 8. 数据隔离:每个服务拥有自己的数据库 │
│ 9. 异步通信:优先使用消息队列 │
│ 10. 容错设计:假设网络不可靠 │
│ 11. 无状态:便于水平扩展 │
│ 12. 渐进式:逐步演进,不要一次性重构 │
└─────────────────────────────────────────────────────────────┘
二、服务拆分策略
2.1 拆分方法论
服务拆分策略
┌─────────────────────────────────────────────────────────────┐
│ 1. 按业务能力拆分 │
│ 用户服务、订单服务、支付服务、库存服务 │
│ │
│ 2. 按子域拆分 (DDD) │
│ 限界上下文 → 微服务 │
│ 例如:电商领域的商品域、订单域、支付域 │
│ │
│ 3. 按数据边界拆分 │
│ 每个服务拥有独立的数据库 │
│ 服务间通过 API 访问数据 │
│ │
│ 4. 按变更频率拆分 │
│ 高频变更的服务独立出来 │
│ 低频变更的服务可以合并 │
│ │
│ 拆分原则: │
│ - 高内聚低耦合 │
│ - 团队结构对齐 (康威定律) │
│ - 先拆分边界清晰的模块 │
│ - 逐步演进,不要一刀切 │
└─────────────────────────────────────────────────────────────┘
2.2 电商系统拆分示例
// microservice/split_example.go
package main
// 用户服务 (User Service)
type UserService struct {
repo UserRepository
}
func (s *UserService) Register(email, password string) (*User, error) { return nil, nil }
func (s *UserService) Login(email, password string) (string, error) { return "", nil }
func (s *UserService) GetProfile(userID int64) (*User, error) { return nil, nil }
// 商品服务 (Product Service)
type ProductService struct {
repo ProductRepository
}
func (s *ProductService) ListProducts(category string, page, size int) ([]*Product, error) { return nil, nil }
func (s *ProductService) GetProductDetail(productID int64) (*Product, error) { return nil, nil }
func (s *ProductService) UpdateStock(productID int64, delta int) error { return nil }
// 订单服务 (Order Service)
type OrderService struct {
productClient ProductServiceClient // 调用商品服务
paymentClient PaymentServiceClient // 调用支付服务
}
func (s *OrderService) CreateOrder(userID int64, items []*OrderItem) (*Order, error) { return nil, nil }
func (s *OrderService) CancelOrder(orderID int64) error { return nil }
func (s *OrderService) GetOrderHistory(userID int64) ([]*Order, error) { return nil, nil }
// 支付服务 (Payment Service)
type PaymentService struct {
orderClient OrderServiceClient
}
func (s *PaymentService) Pay(orderID int64, amount float64) (*PaymentResult, error) { return nil, nil }
func (s *PaymentService) Refund(orderID int64) error { return nil }
// 库存服务 (Inventory Service)
type InventoryService struct {
repo InventoryRepository
}
func (s *InventoryService) Reserve(productID int64, quantity int) error { return nil }
func (s *InventoryService) Release(productID int64, quantity int) error { return nil }
func (s *InventoryService) CheckAvailability(productID int64) (int, error) { return 0, nil }
三、服务间通信
3.1 RESTful API 通信
// communication/rest_server.go
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// 用户服务 REST API
type UserAPI struct {
service *UserService
}
func NewUserAPI(service *UserService) *UserAPI {
return &UserAPI{service: service}
}
func (api *UserAPI) RegisterRoutes(r *mux.Router) {
r.HandleFunc("/api/v1/users", api.CreateUser).Methods("POST")
r.HandleFunc("/api/v1/users/{id}", api.GetUser).Methods("GET")
r.HandleFunc("/api/v1/users/{id}", api.UpdateUser).Methods("PUT")
r.HandleFunc("/api/v1/users/{id}", api.DeleteUser).Methods("DELETE")
}
func (api *UserAPI) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user, err := api.service.Register(req.Email, req.Password)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func (api *UserAPI) GetUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 10, 64)
user, err := api.service.GetProfile(id)
if err != nil {
http.Error(w, "User not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(user)
}
// REST 客户端
type UserServiceClient struct {
baseURL string
client *http.Client
}
func NewUserServiceClient(baseURL string) *UserServiceClient {
return &UserServiceClient{
baseURL: baseURL,
client: &http.Client{Timeout: 5 * time.Second},
}
}
func (c *UserServiceClient) GetUser(id int64) (*User, error) {
url := fmt.Sprintf("%s/api/v1/users/%d", c.baseURL, id)
resp, err := c.client.Get(url)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var user User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
return &user, nil
}
3.2 gRPC 通信
// communication/grpc_communication.go
package main
import (
"context"
"log"
"net"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "path/to/proto/userpb"
)
// gRPC 服务器
type UserGRPCServer struct {
pb.UnimplementedUserServiceServer
service *UserService
}
func (s *UserGRPCServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
user, err := s.service.GetProfile(req.UserId)
if err != nil {
return nil, err
}
return &pb.UserResponse{
Id: user.ID,
Name: user.Name,
Email: user.Email,
}, nil
}
// gRPC 客户端
type UserGRPCClient struct {
client pb.UserServiceClient
conn *grpc.ClientConn
}
func NewUserGRPCClient(address string) (*UserGRPCClient, error) {
conn, err := grpc.Dial(
address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(10 * 1024 * 1024)),
)
if err != nil {
return nil, err
}
return &UserGRPCClient{
client: pb.NewUserServiceClient(conn),
conn: conn,
}, nil
}
func (c *UserGRPCClient) GetUser(ctx context.Context, userID int64) (*pb.UserResponse, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return c.client.GetUser(ctx, &pb.GetUserRequest{UserId: userID})
}
func (c *UserGRPCClient) Close() {
c.conn.Close()
}
3.3 消息队列通信
// communication/message_queue.go
package main
import (
"context"
"encoding/json"
"log"
"github.com/segmentio/kafka-go"
)
// 消息生产者
type OrderEventProducer struct {
writer *kafka.Writer
}
func NewOrderEventProducer(brokers []string) *OrderEventProducer {
return &OrderEventProducer{
writer: &kafka.Writer{
Addr: kafka.TCP(brokers...),
Topic: "order-events",
Balancer: &kafka.LeastBytes{},
},
}
}
func (p *OrderEventProducer) PublishOrderCreated(order *Order) error {
event := OrderEvent{
Type: "order.created",
OrderID: order.ID,
UserID: order.UserID,
Amount: order.TotalAmount,
Time: time.Now(),
}
data, err := json.Marshal(event)
if err != nil {
return err
}
return p.writer.WriteMessages(context.Background(),
kafka.Message{
Key: []byte(strconv.FormatInt(order.ID, 10)),
Value: data,
Headers: []kafka.Header{
{Key: "event-type", Value: []byte("order.created")},
},
},
)
}
// 消息消费者
type OrderEventConsumer struct {
reader *kafka.Reader
}
func NewOrderEventConsumer(brokers []string, groupID string) *OrderEventConsumer {
return &OrderEventConsumer{
reader: kafka.NewReader(kafka.ReaderConfig{
Brokers: brokers,
Topic: "order-events",
GroupID: groupID,
MinBytes: 10e3,
MaxBytes: 10e6,
MaxWait: 1 * time.Second,
}),
}
}
func (c *OrderEventConsumer) Consume(ctx context.Context) {
for {
msg, err := c.reader.ReadMessage(ctx)
if err != nil {
log.Printf("read message error: %v", err)
continue
}
var event OrderEvent
if err := json.Unmarshal(msg.Value, &event); err != nil {
log.Printf("unmarshal error: %v", err)
continue
}
// 处理事件
switch event.Type {
case "order.created":
c.handleOrderCreated(event)
case "order.paid":
c.handleOrderPaid(event)
case "order.cancelled":
c.handleOrderCancelled(event)
}
// 提交偏移量
if err := c.reader.CommitMessages(ctx, msg); err != nil {
log.Printf("commit error: %v", err)
}
}
}
func (c *OrderEventConsumer) handleOrderCreated(event OrderEvent) {
log.Printf("Processing order created: %d", event.OrderID)
// 更新库存、发送通知等
}
func (c *OrderEventConsumer) handleOrderPaid(event OrderEvent) {
log.Printf("Processing order paid: %d", event.OrderID)
// 发货、更新订单状态等
}
func (c *OrderEventConsumer) handleOrderCancelled(event OrderEvent) {
log.Printf("Processing order cancelled: %d", event.OrderID)
// 退款、释放库存等
}
四、服务发现与负载均衡
4.1 基于 Consul 的服务发现
// discovery/consul_discovery.go
package main
import (
"fmt"
"log"
"math/rand"
"sync"
"time"
"github.com/hashicorp/consul/api"
)
// 服务注册
type ServiceRegistry struct {
client *api.Client
mu sync.RWMutex
services map[string][]*api.ServiceEntry
}
func NewServiceRegistry(address string) (*ServiceRegistry, error) {
config := api.DefaultConfig()
config.Address = address
client, err := api.NewClient(config)
if err != nil {
return nil, err
}
return &ServiceRegistry{
client: client,
services: make(map[string][]*api.ServiceEntry),
}, nil
}
func (r *ServiceRegistry) Register(serviceName, instanceID string, port int) error {
registration := &api.AgentServiceRegistration{
ID: instanceID,
Name: serviceName,
Port: port,
Check: &api.AgentServiceCheck{
HTTP: fmt.Sprintf("http://localhost:%d/health", port),
Interval: "10s",
Timeout: "5s",
},
}
return r.client.Agent().ServiceRegister(registration)
}
func (r *ServiceRegistry) Deregister(instanceID string) error {
return r.client.Agent().ServiceDeregister(instanceID)
}
func (r *ServiceRegistry) Discover(serviceName string) ([]*api.ServiceEntry, error) {
entries, _, err := r.client.Health().Service(serviceName, "", true, nil)
if err != nil {
return nil, err
}
r.mu.Lock()
r.services[serviceName] = entries
r.mu.Unlock()
return entries, nil
}
// 负载均衡客户端
type LoadBalancer struct {
registry *ServiceRegistry
mu sync.RWMutex
cache map[string][]string // serviceName → addresses
}
func NewLoadBalancer(registry *ServiceRegistry) *LoadBalancer {
lb := &LoadBalancer{
registry: registry,
cache: make(map[string][]string),
}
// 定期刷新服务列表
go lb.refreshLoop()
return lb
}
func (lb *LoadBalancer) refreshLoop() {
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
lb.mu.Lock()
for serviceName := range lb.cache {
entries, err := lb.registry.Discover(serviceName)
if err != nil {
log.Printf("refresh service %s error: %v", serviceName, err)
continue
}
addresses := make([]string, 0, len(entries))
for _, entry := range entries {
addr := fmt.Sprintf("%s:%d", entry.Service.Address, entry.Service.Port)
addresses = append(addresses, addr)
}
lb.cache[serviceName] = addresses
}
lb.mu.Unlock()
}
}
// 轮询负载均衡
func (lb *LoadBalancer) RoundRobin(serviceName string) (string, error) {
lb.mu.RLock()
addresses, ok := lb.cache[serviceName]
lb.mu.RUnlock()
if !ok || len(addresses) == 0 {
// 从注册中心获取
entries, err := lb.registry.Discover(serviceName)
if err != nil {
return "", err
}
addresses = make([]string, 0, len(entries))
for _, entry := range entries {
addr := fmt.Sprintf("%s:%d", entry.Service.Address, entry.Service.Port)
addresses = append(addresses, addr)
}
lb.mu.Lock()
lb.cache[serviceName] = addresses
lb.mu.Unlock()
}
if len(addresses) == 0 {
return "", fmt.Errorf("no available instances for %s", serviceName)
}
// 随机选择一个
return addresses[rand.Intn(len(addresses))], nil
}
4.2 健康检查
// discovery/health_check.go
package main
import (
"encoding/json"
"log"
"net/http"
"sync"
"time"
)
type HealthStatus string
const (
StatusHealthy HealthStatus = "healthy"
StatusDegraded HealthStatus = "degraded"
StatusUnhealthy HealthStatus = "unhealthy"
)
type HealthChecker struct {
mu sync.RWMutex
checks map[string]HealthCheck
status HealthStatus
details map[string]interface{}
}
type HealthCheck struct {
Name string
Interval time.Duration
Timeout time.Duration
Check func() error
lastCheck time.Time
lastError error
}
func NewHealthChecker() *HealthChecker {
hc := &HealthChecker{
checks: make(map[string]HealthCheck),
status: StatusHealthy,
details: make(map[string]interface{}),
}
go hc.runChecks()
return hc
}
func (hc *HealthChecker) RegisterCheck(name string, interval, timeout time.Duration, check func() error) {
hc.mu.Lock()
hc.checks[name] = HealthCheck{
Name: name,
Interval: interval,
Timeout: timeout,
Check: check,
}
hc.mu.Unlock()
}
func (hc *HealthChecker) runChecks() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
hc.mu.Lock()
for name, check := range hc.checks {
if time.Since(check.lastCheck) < check.Interval {
continue
}
err := check.Check()
check.lastCheck = time.Now()
check.lastError = err
hc.checks[name] = check
if err != nil {
hc.details[name] = err.Error()
} else {
hc.details[name] = "ok"
}
}
hc.updateOverallStatus()
hc.mu.Unlock()
}
}
func (hc *HealthChecker) updateOverallStatus() {
hasErrors := false
for _, check := range hc.checks {
if check.lastError != nil {
hasErrors = true
break
}
}
if hasErrors {
hc.status = StatusDegraded
} else {
hc.status = StatusHealthy
}
}
func (hc *HealthChecker) HealthEndpoint(w http.ResponseWriter, r *http.Request) {
hc.mu.RLock()
defer hc.mu.RUnlock()
status := HealthResponse{
Status: hc.status,
Details: hc.details,
Time: time.Now(),
}
w.Header().Set("Content-Type", "application/json")
if hc.status != StatusHealthy {
w.WriteHeader(http.StatusServiceUnavailable)
}
json.NewEncoder(w).Encode(status)
}
type HealthResponse struct {
Status HealthStatus `json:"status"`
Details map[string]interface{} `json:"details"`
Time time.Time `json:"time"`
}
五、分布式事务
5.1 Saga 模式
// transaction/saga.go
package main
import (
"context"
"log"
"time"
)
// Saga 编排器
type SagaOrchestrator struct {
steps []SagaStep
}
type SagaStep struct {
Name string
Execute func(ctx context.Context) error
Compensate func(ctx context.Context) error
}
func NewSagaOrchestrator() *SagaOrchestrator {
return &SagaOrchestrator{}
}
func (s *SagaOrchestrator) AddStep(name string, execute, compensate func(ctx context.Context) error) {
s.steps = append(s.steps, SagaStep{
Name: name,
Execute: execute,
Compensate: compensate,
})
}
func (s *SagaOrchestrator) Execute(ctx context.Context) error {
var executed []SagaStep
for _, step := range s.steps {
log.Printf("Executing step: %s", step.Name)
if err := step.Execute(ctx); err != nil {
log.Printf("Step %s failed: %v, starting compensation", step.Name, err)
// 回滚已执行的步骤
for i := len(executed) - 1; i >= 0; i-- {
compStep := executed[i]
log.Printf("Compensating step: %s", compStep.Name)
if compErr := compStep.Compensate(ctx); compErr != nil {
log.Printf("Compensation failed for step %s: %v", compStep.Name, compErr)
}
}
return err
}
executed = append(executed, step)
}
log.Printf("Saga completed successfully")
return nil
}
// 订单创建 Saga 示例
func orderCreationSaga() *SagaOrchestrator {
saga := NewSagaOrchestrator()
// Step 1: 创建订单
saga.AddStep("create-order",
func(ctx context.Context) error {
// 创建订单
return nil
},
func(ctx context.Context) error {
// 取消订单
return nil
},
)
// Step 2: 预留库存
saga.AddStep("reserve-inventory",
func(ctx context.Context) error {
// 调用库存服务预留库存
return nil
},
func(ctx context.Context) error {
// 释放库存
return nil
},
)
// Step 3: 扣减余额
saga.AddStep("deduct-balance",
func(ctx context.Context) error {
// 调用账户服务扣款
return nil
},
func(ctx context.Context) error {
// 退款
return nil
},
)
return saga
}
5.2 TCC 模式
// transaction/tcc.go
package main
import (
"context"
"log"
"sync"
)
// TCC 事务接口
type TCCResource interface {
Try(ctx context.Context) error
Confirm(ctx context.Context) error
Cancel(ctx context.Context) error
}
// 库存资源 TCC 实现
type InventoryResource struct {
mu sync.Mutex
reserved map[int64]int // productID → quantity
}
func NewInventoryResource() *InventoryResource {
return &InventoryResource{
reserved: make(map[int64]int),
}
}
func (r *InventoryResource) Try(ctx context.Context, productID int64, quantity int) error {
r.mu.Lock()
defer r.mu.Unlock()
// 检查库存并预留
currentStock := getCurrentStock(productID)
if currentStock < quantity {
return fmt.Errorf("insufficient stock for product %d", productID)
}
r.reserved[productID] = quantity
log.Printf("Try: Reserved %d units of product %d", quantity, productID)
return nil
}
func (r *InventoryResource) Confirm(ctx context.Context, productID int64) error {
r.mu.Lock()
defer r.mu.Unlock()
quantity, ok := r.reserved[productID]
if !ok {
return fmt.Errorf("no reservation found for product %d", productID)
}
// 确认扣减库存
deductStock(productID, quantity)
delete(r.reserved, productID)
log.Printf("Confirm: Deducted %d units of product %d", quantity, productID)
return nil
}
func (r *InventoryResource) Cancel(ctx context.Context, productID int64) error {
r.mu.Lock()
defer r.mu.Unlock()
quantity, ok := r.reserved[productID]
if !ok {
return nil // 没有预留,无需取消
}
// 释放预留的库存
delete(r.reserved, productID)
log.Printf("Cancel: Released %d units of product %d", quantity, productID)
return nil
}
// TCC 事务管理器
type TCCTransactionManager struct {
resources []TCCResource
}
func NewTCCTransactionManager() *TCCTransactionManager {
return &TCCTransactionManager{}
}
func (tm *TCCTransactionManager) AddResource(resource TCCResource) {
tm.resources = append(tm.resources, resource)
}
func (tm *TCCTransactionManager) Execute(ctx context.Context) error {
// Phase 1: Try
for _, resource := range tm.resources {
if err := resource.Try(ctx); err != nil {
log.Printf("Try phase failed: %v, starting cancel", err)
// Cancel all previous resources
tm.cancelAll(ctx)
return err
}
}
// Phase 2: Confirm
for _, resource := range tm.resources {
if err := resource.Confirm(ctx); err != nil {
log.Printf("Confirm phase failed: %v", err)
// 记录异常,需要人工介入或定时补偿
return err
}
}
return nil
}
func (tm *TCCTransactionManager) cancelAll(ctx context.Context) {
for _, resource := range tm.resources {
if err := resource.Cancel(ctx); err != nil {
log.Printf("Cancel failed: %v", err)
}
}
}
六、链路追踪
6.1 OpenTelemetry 集成
// tracing/opentelemetry.go
package main
import (
"context"
"log"
"net/http"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
// 初始化 tracer provider
func initTracer() (*sdktrace.TracerProvider, error) {
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint("http://localhost:14268/api/traces"),
))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("order-service"),
attribute.String("environment", "production"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}
// HTTP 中间件:自动创建 span
func TracingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("http-server")
ctx, span := tracer.Start(r.Context(), r.URL.Path,
trace.WithAttributes(
semconv.HTTPMethodKey.String(r.Method),
semconv.HTTPURLKey.String(r.URL.String()),
semconv.HTTPTargetKey.String(r.URL.Path),
),
)
defer span.End()
// 将 context 传递给下游
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
// 业务代码中手动创建 span
func processOrder(ctx context.Context, orderID int64) error {
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "process-order",
trace.WithAttributes(
attribute.Int64("order.id", orderID),
),
)
defer span.End()
// 调用库存服务
if err := reserveInventory(ctx, orderID); err != nil {
span.RecordError(err)
span.SetStatus(sdktrace.StatusError, err.Error())
return err
}
// 调用支付服务
if err := processPayment(ctx, orderID); err != nil {
span.RecordError(err)
span.SetStatus(sdktrace.StatusError, err.Error())
return err
}
span.SetStatus(sdktrace.StatusOk, "")
return nil
}
func reserveInventory(ctx context.Context, orderID int64) error {
tracer := otel.Tracer("order-service")
_, span := tracer.Start(ctx, "reserve-inventory",
trace.WithAttributes(
attribute.Int64("order.id", orderID),
),
)
defer span.End()
// 模拟调用库存服务
time.Sleep(100 * time.Millisecond)
return nil
}
func processPayment(ctx context.Context, orderID int64) error {
tracer := otel.Tracer("order-service")
_, span := tracer.Start(ctx, "process-payment",
trace.WithAttributes(
attribute.Int64("order.id", orderID),
),
)
defer span.End()
// 模拟调用支付服务
time.Sleep(200 * time.Millisecond)
return nil
}
七、配置管理
7.1 集中配置中心
// config/config_center.go
package main
import (
"context"
"encoding/json"
"log"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
// 配置管理器
type ConfigManager struct {
mu sync.RWMutex
config *AppConfig
watchers []ConfigWatcher
}
type AppConfig struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Kafka KafkaConfig `mapstructure:"kafka"`
}
type ServerConfig struct {
Port int `mapstructure:"port"`
Timeout time.Duration `mapstructure:"timeout"`
}
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
}
type RedisConfig struct {
Address string `mapstructure:"address"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}
type KafkaConfig struct {
Brokers []string `mapstructure:"brokers"`
Topic string `mapstructure:"topic"`
}
type ConfigWatcher func(oldConfig, newConfig *AppConfig)
func NewConfigManager() *ConfigManager {
return &ConfigManager{}
}
func (cm *ConfigManager) Load(path string) error {
viper.SetConfigFile(path)
viper.AutomaticEnv() // 支持环境变量覆盖
if err := viper.ReadInConfig(); err != nil {
return err
}
var config AppConfig
if err := viper.Unmarshal(&config); err != nil {
return err
}
cm.mu.Lock()
cm.config = &config
cm.mu.Unlock()
// 监听配置文件变化
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Printf("Config file changed: %s", e.Name)
var newConfig AppConfig
if err := viper.Unmarshal(&newConfig); err != nil {
log.Printf("Failed to parse new config: %v", err)
return
}
cm.mu.Lock()
oldConfig := cm.config
cm.config = &newConfig
cm.mu.Unlock()
// 通知所有 watcher
for _, watcher := range cm.watchers {
watcher(oldConfig, &newConfig)
}
})
return nil
}
func (cm *ConfigManager) GetConfig() *AppConfig {
cm.mu.RLock()
defer cm.mu.RUnlock()
return cm.config
}
func (cm *ConfigManager) Watch(watcher ConfigWatcher) {
cm.mu.Lock()
cm.watchers = append(cm.watchers, watcher)
cm.mu.Unlock()
}
// 使用示例
func main() {
cm := NewConfigManager()
if err := cm.Load("config.yaml"); err != nil {
log.Fatal(err)
}
// 注册配置变更监听
cm.Watch(func(oldConfig, newConfig *AppConfig) {
if oldConfig.Server.Port != newConfig.Server.Port {
log.Printf("Server port changed from %d to %d",
oldConfig.Server.Port, newConfig.Server.Port)
// 重启 HTTP 服务器
}
})
// 获取配置
config := cm.GetConfig()
log.Printf("Server port: %d", config.Server.Port)
}
八、总结
8.1 核心要点
| 概念 | 要点 | 面试高频 |
|---|---|---|
| 服务拆分 | 业务能力/子域/数据边界 | ⭐⭐⭐⭐⭐ |
| 服务通信 | REST/gRPC/消息队列 | ⭐⭐⭐⭐⭐ |
| 服务发现 | Consul/Etcd/ZooKeeper | ⭐⭐⭐⭐ |
| 负载均衡 | 轮询/随机/一致性哈希 | ⭐⭐⭐⭐ |
| 分布式事务 | Saga/TCC/最终一致性 | ⭐⭐⭐⭐⭐ |
| 链路追踪 | OpenTelemetry/Jaeger | ⭐⭐⭐⭐ |
| 配置管理 | Viper/配置中心 | ⭐⭐⭐ |
| 健康检查 | 存活/就绪探针 | ⭐⭐⭐⭐ |
8.2 选择指南
通信方式选择:
┌─────────────────────────────────────────────────────────────┐
│ 同步调用:REST/gRPC │
│ - 实时性强,需要立即返回结果 │
│ - 适用于查询类操作 │
│ │
│ 异步调用:消息队列 │
│ - 解耦服务,削峰填谷 │
│ - 适用于不需要立即返回的操作 │
│ - 提高系统的可用性和伸缩性 │
│ │
│ 分布式事务选择: │
│ - 强一致性:TCC 模式 │
│ - 最终一致性:Saga 模式 │
│ - 简单场景:本地消息表 │
└─────────────────────────────────────────────────────────────┘
8.3 记忆口诀
微服务拆分看业务,高内聚低耦合是关键
服务通信三件套,REST gRPC 消息队列
服务发现 Consul,健康检查不能少
负载均衡轮询好,一致性哈希也不错
分布式事务难,Saga TCC 来帮忙
链路追踪 OpenTelemetry,问题定位快准狠
配置管理要集中,热更新少不了
8.4 下讲预告
第11讲:Go 云原生实战 —— Docker + Kubernetes 部署与编排
我们将深入学习:
-
Docker 镜像构建与优化
-
Kubernetes 核心概念
-
Pod/Service/Deployment 配置
-
配置管理与 Secrets
-
监控与日志收集
-
CI/CD 流水线搭建
准备好了吗?让我们在第11讲再见!
🧰 开发之余的小工具推荐
处理 Base64、JWT 解析、JSON 格式化、Crontab 计算、PDF 合并压缩这些碎片需求,我常用一个纯前端本地工具箱:zz365.top。所有计算在浏览器完成,文件不上服务器,关页即清。免费、无登录、无广告,适合开发者当常驻标签页。
更多推荐

所有评论(0)