Go微服务架构设计与实践:从服务发现到熔断降级的完整解决方案

雨中漫步
雨中漫步 2026-01-27T10:11:01+08:00
0 0 1

引言

在现代分布式系统架构中,微服务已成为构建大规模应用的标准模式。Go语言凭借其高性能、简洁的语法和优秀的并发支持,成为构建微服务的理想选择。本文将深入探讨基于Go语言的微服务架构设计,从服务注册发现到熔断降级等核心组件的实现方案,帮助开发者构建高可用、可扩展的微服务系统。

微服务架构概述

什么是微服务架构

微服务架构是一种将单一应用程序拆分为多个小型、独立服务的架构模式。每个服务:

  • 运行在自己的进程中
  • 通过轻量级通信机制(通常是HTTP API)进行交互
  • 专注于特定的业务功能
  • 可以独立部署和扩展

Go语言在微服务中的优势

Go语言为微服务开发提供了独特的优势:

  • 高性能:编译型语言,执行效率高
  • 并发支持:goroutine和channel机制天然支持高并发
  • 简洁语法:代码可读性强,维护成本低
  • 静态编译:部署简单,运行时依赖少
  • 标准库丰富:网络、HTTP、JSON等核心功能完善

服务注册与发现

服务注册发现的重要性

在微服务架构中,服务注册发现是实现服务间通信的基础。它解决了服务实例动态变化的问题,让服务消费者能够动态找到服务提供者。

Consul实践方案

Consul是一个优秀的服务发现工具,支持健康检查、键值存储等功能。

// 使用Consul进行服务注册
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/hashicorp/consul/api"
)

type ServiceRegistry struct {
    client *api.Client
}

func NewServiceRegistry(addr string) (*ServiceRegistry, error) {
    config := api.DefaultConfig()
    config.Address = addr
    
    client, err := api.NewClient(config)
    if err != nil {
        return nil, err
    }
    
    return &ServiceRegistry{client: client}, nil
}

func (sr *ServiceRegistry) RegisterService(serviceID, serviceName, address string, port int) error {
    registration := &api.AgentServiceRegistration{
        ID:      serviceID,
        Name:    serviceName,
        Address: address,
        Port:    port,
        Check: &api.AgentServiceCheck{
            HTTP:                           fmt.Sprintf("http://%s:%d/health", address, port),
            Interval:                       "10s",
            Timeout:                        "5s",
            DeregisterCriticalServiceAfter: "30s",
        },
    }
    
    return sr.client.Agent().ServiceRegister(registration)
}

func (sr *ServiceRegistry) DeregisterService(serviceID string) error {
    return sr.client.Agent().ServiceDeregister(serviceID)
}

// 服务发现实现
func (sr *ServiceRegistry) DiscoverServices(serviceName string) ([]*api.AgentService, error) {
    services, _, err := sr.client.Health().Service(serviceName, "", true, nil)
    if err != nil {
        return nil, err
    }
    
    var result []*api.AgentService
    for _, service := range services {
        result = append(result, service.Service)
    }
    
    return result, nil
}

基于etcd的服务发现

// 使用etcd进行服务发现
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "time"

    "go.etcd.io/etcd/clientv3"
    "go.etcd.io/etcd/clientv3/concurrency"
)

type EtcdServiceDiscovery struct {
    client *clientv3.Client
    prefix string
}

func NewEtcdServiceDiscovery(endpoints []string, prefix string) (*EtcdServiceDiscovery, error) {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   endpoints,
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        return nil, err
    }
    
    return &EtcdServiceDiscovery{
        client: client,
        prefix: prefix,
    }, nil
}

// 注册服务
func (esd *EtcdServiceDiscovery) RegisterService(serviceID string, serviceInfo interface{}) error {
    data, err := json.Marshal(serviceInfo)
    if err != nil {
        return err
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    _, err = esd.client.Put(ctx, fmt.Sprintf("%s/%s", esd.prefix, serviceID), string(data))
    return err
}

// 发现服务
func (esd *EtcdServiceDiscovery) DiscoverServices() (map[string]interface{}, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    resp, err := esd.client.Get(ctx, esd.prefix, clientv3.WithPrefix())
    if err != nil {
        return nil, err
    }
    
    services := make(map[string]interface{})
    for _, kv := range resp.Kvs {
        var serviceInfo interface{}
        if err := json.Unmarshal(kv.Value, &serviceInfo); err != nil {
            log.Printf("Failed to unmarshal service info: %v", err)
            continue
        }
        key := string(kv.Key)
        services[key] = serviceInfo
    }
    
    return services, nil
}

// 服务健康检查
func (esd *EtcdServiceDiscovery) KeepAlive(serviceID string, ttl int64) (<-chan *clientv3.LeaseKeepAliveResponse, error) {
    leaseResp, err := esd.client.Grant(context.TODO(), ttl)
    if err != nil {
        return nil, err
    }
    
    _, err = esd.client.Put(context.TODO(), fmt.Sprintf("%s/%s", esd.prefix, serviceID), "", clientv3.WithLease(leaseResp.ID))
    if err != nil {
        return nil, err
    }
    
    return esd.client.KeepAlive(context.TODO(), leaseResp.ID)
}

负载均衡策略

常见负载均衡算法

在微服务架构中,负载均衡是确保系统高可用性和性能的关键组件。常见的负载均衡算法包括:

  1. 轮询(Round Robin)
  2. 加权轮询(Weighted Round Robin)
  3. 最少连接(Least Connections)
  4. 哈希一致性(Consistent Hashing)

Go中的负载均衡实现

// 负载均衡器实现
package main

import (
    "fmt"
    "math/rand"
    "net/http"
    "sync"
    "time"
)

type LoadBalancer struct {
    mutex   sync.RWMutex
    servers []*Server
}

type Server struct {
    URL     string
    Weight  int
    Active  bool
    Failure int
    LastTry time.Time
}

func NewLoadBalancer() *LoadBalancer {
    return &LoadBalancer{
        servers: make([]*Server, 0),
    }
}

// 添加服务器
func (lb *LoadBalancer) AddServer(url string, weight int) {
    lb.mutex.Lock()
    defer lb.mutex.Unlock()
    
    server := &Server{
        URL:    url,
        Weight: weight,
        Active: true,
    }
    
    lb.servers = append(lb.servers, server)
}

// 轮询算法
func (lb *LoadBalancer) RoundRobin() *Server {
    lb.mutex.RLock()
    defer lb.mutex.RUnlock()
    
    if len(lb.servers) == 0 {
        return nil
    }
    
    // 过滤活跃服务器
    activeServers := make([]*Server, 0)
    for _, server := range lb.servers {
        if server.Active {
            activeServers = append(activeServers, server)
        }
    }
    
    if len(activeServers) == 0 {
        return nil
    }
    
    // 简单轮询选择
    index := rand.Intn(len(activeServers))
    return activeServers[index]
}

// 加权轮询算法
func (lb *LoadBalancer) WeightedRoundRobin() *Server {
    lb.mutex.RLock()
    defer lb.mutex.RUnlock()
    
    if len(lb.servers) == 0 {
        return nil
    }
    
    // 过滤活跃服务器
    activeServers := make([]*Server, 0)
    for _, server := range lb.servers {
        if server.Active {
            activeServers = append(activeServers, server)
        }
    }
    
    if len(activeServers) == 0 {
        return nil
    }
    
    // 计算总权重
    totalWeight := 0
    for _, server := range activeServers {
        totalWeight += server.Weight
    }
    
    if totalWeight <= 0 {
        return activeServers[0]
    }
    
    // 随机选择
    randomWeight := rand.Intn(totalWeight)
    currentWeight := 0
    
    for _, server := range activeServers {
        currentWeight += server.Weight
        if randomWeight < currentWeight {
            return server
        }
    }
    
    return activeServers[0]
}

// 基于健康检查的负载均衡
func (lb *LoadBalancer) HealthBased() *Server {
    lb.mutex.RLock()
    defer lb.mutex.RUnlock()
    
    // 过滤活跃且健康的服务器
    healthyServers := make([]*Server, 0)
    for _, server := range lb.servers {
        if server.Active && server.Failure < 3 { // 允许最多3次失败
            healthyServers = append(healthyServers, server)
        }
    }
    
    if len(healthyServers) == 0 {
        return nil
    }
    
    // 按照健康度排序,优先选择健康度高的
    for i := range healthyServers {
        for j := i + 1; j < len(healthyServers); j++ {
            if healthyServers[i].Failure > healthyServers[j].Failure {
                healthyServers[i], healthyServers[j] = healthyServers[j], healthyServers[i]
            }
        }
    }
    
    return healthyServers[0]
}

// HTTP客户端实现
type HttpClient struct {
    lb *LoadBalancer
}

func NewHttpClient(lb *LoadBalancer) *HttpClient {
    return &HttpClient{lb: lb}
}

func (hc *HttpClient) Get(url string) (*http.Response, error) {
    server := hc.lb.HealthBased()
    if server == nil {
        return nil, fmt.Errorf("no available servers")
    }
    
    targetURL := fmt.Sprintf("%s%s", server.URL, url)
    resp, err := http.Get(targetURL)
    if err != nil {
        // 记录失败
        hc.recordFailure(server)
        return nil, err
    }
    
    // 记录成功
    hc.recordSuccess(server)
    return resp, nil
}

func (hc *HttpClient) recordFailure(server *Server) {
    server.Failure++
    server.LastTry = time.Now()
}

func (hc *HttpClient) recordSuccess(server *Server) {
    server.Failure = 0
    server.LastTry = time.Now()
}

熔断器模式实现

熔断器设计原理

熔断器模式是微服务架构中的重要容错机制,当某个服务出现故障时,熔断器会快速失败并避免雪崩效应。

// 熔断器实现
package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type CircuitBreaker struct {
    mutex          sync.RWMutex
    state          CircuitState
    failureCount   int
    successCount   int
    lastFailure    time.Time
    failureThreshold int
    timeout        time.Duration
    resetTimeout   time.Duration
    lastAttempt    time.Time
    
    // 状态转换回调
    onStateChanged func(old, new CircuitState)
}

type CircuitState int

const (
    Closed CircuitState = iota
    Open
    HalfOpen
)

func (cs CircuitState) String() string {
    switch cs {
    case Closed:
        return "CLOSED"
    case Open:
        return "OPEN"
    case HalfOpen:
        return "HALF_OPEN"
    default:
        return "UNKNOWN"
    }
}

func NewCircuitBreaker(failureThreshold int, timeout, resetTimeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        state:            Closed,
        failureThreshold: failureThreshold,
        timeout:          timeout,
        resetTimeout:     resetTimeout,
        lastAttempt:      time.Now(),
    }
}

// 执行调用
func (cb *CircuitBreaker) Execute(ctx context.Context, operation func() error) error {
    cb.mutex.Lock()
    
    // 检查是否需要状态转换
    if cb.shouldTrip() {
        cb.transitionToOpen()
    }
    
    switch cb.state {
    case Open:
        return cb.handleOpenState()
    case HalfOpen:
        return cb.handleHalfOpenState(ctx, operation)
    case Closed:
        return cb.handleClosedState(ctx, operation)
    default:
        return fmt.Errorf("unknown circuit state: %v", cb.state)
    }
}

func (cb *CircuitBreaker) shouldTrip() bool {
    if cb.state != Closed {
        return false
    }
    
    // 如果最后一次失败时间在超时时间内,且失败次数达到阈值
    if time.Since(cb.lastFailure) < cb.timeout && cb.failureCount >= cb.failureThreshold {
        return true
    }
    
    return false
}

func (cb *CircuitBreaker) transitionToOpen() {
    oldState := cb.state
    cb.state = Open
    cb.lastAttempt = time.Now()
    
    if cb.onStateChanged != nil {
        cb.onStateChanged(oldState, cb.state)
    }
}

func (cb *CircuitBreaker) handleOpenState() error {
    // 如果超时时间已过,进入半开状态
    if time.Since(cb.lastAttempt) >= cb.resetTimeout {
        cb.state = HalfOpen
        return fmt.Errorf("circuit breaker is in half-open state")
    }
    
    return fmt.Errorf("circuit breaker is open, operation rejected")
}

func (cb *CircuitBreaker) handleHalfOpenState(ctx context.Context, operation func() error) error {
    // 允许一次请求通过
    err := operation()
    if err != nil {
        cb.failureCount++
        cb.lastFailure = time.Now()
        cb.transitionToOpen()
        return err
    }
    
    // 成功则重置熔断器
    cb.successCount++
    cb.resetCircuit()
    return nil
}

func (cb *CircuitBreaker) handleClosedState(ctx context.Context, operation func() error) error {
    err := operation()
    if err != nil {
        cb.failureCount++
        cb.lastFailure = time.Now()
        return err
    }
    
    // 成功则重置失败计数器
    cb.successCount++
    cb.resetCircuit()
    return nil
}

func (cb *CircuitBreaker) resetCircuit() {
    cb.failureCount = 0
    cb.successCount = 0
    cb.state = Closed
    cb.lastAttempt = time.Now()
    
    if cb.onStateChanged != nil {
        cb.onStateChanged(Closed, cb.state)
    }
}

// 获取当前状态
func (cb *CircuitBreaker) State() CircuitState {
    cb.mutex.RLock()
    defer cb.mutex.RUnlock()
    return cb.state
}

// 获取统计信息
func (cb *CircuitBreaker) Stats() map[string]interface{} {
    cb.mutex.RLock()
    defer cb.mutex.RUnlock()
    
    return map[string]interface{}{
        "state":           cb.state.String(),
        "failure_count":   cb.failureCount,
        "success_count":   cb.successCount,
        "last_failure":    cb.lastFailure,
        "last_attempt":    cb.lastAttempt,
        "is_open":         cb.state == Open,
        "is_half_open":    cb.state == HalfOpen,
        "is_closed":       cb.state == Closed,
    }
}

// 设置状态变更回调
func (cb *CircuitBreaker) OnStateChanged(callback func(old, new CircuitState)) {
    cb.onStateChanged = callback
}

熔断器在实际应用中的使用

// 使用熔断器的示例
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"
)

type ExternalService struct {
    breaker *CircuitBreaker
}

func NewExternalService() *ExternalService {
    // 创建熔断器:失败3次后熔断,5秒后尝试恢复,超时时间10秒
    breaker := NewCircuitBreaker(3, 10*time.Second, 5*time.Second)
    
    // 设置状态变更回调
    breaker.OnStateChanged(func(old, new CircuitState) {
        log.Printf("Circuit breaker state changed from %s to %s", old, new)
    })
    
    return &ExternalService{
        breaker: breaker,
    }
}

func (es *ExternalService) CallAPI(ctx context.Context, url string) error {
    // 包装外部调用
    operation := func() error {
        client := &http.Client{Timeout: 3 * time.Second}
        req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
        if err != nil {
            return err
        }
        
        resp, err := client.Do(req)
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        
        if resp.StatusCode >= 400 {
            return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
        }
        
        return nil
    }
    
    // 使用熔断器执行调用
    return es.breaker.Execute(ctx, operation)
}

func main() {
    service := NewExternalService()
    
    // 模拟调用
    ctx := context.Background()
    
    for i := 0; i < 10; i++ {
        err := service.CallAPI(ctx, "http://httpbin.org/delay/1")
        if err != nil {
            log.Printf("Call failed: %v", err)
        } else {
            log.Printf("Call succeeded")
        }
        
        time.Sleep(100 * time.Millisecond)
    }
    
    // 打印统计信息
    stats := service.breaker.Stats()
    for key, value := range stats {
        log.Printf("%s: %v", key, value)
    }
}

链路追踪与监控

分布式链路追踪实现

链路追踪是微服务架构中重要的监控手段,能够帮助我们理解请求在系统中的流转路径。

// 链路追踪实现
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/codes"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
    "go.opentelemetry.io/otel/trace"
)

type Tracer struct {
    tracer trace.Tracer
}

func NewTracer(serviceName string) (*Tracer, error) {
    // 创建导出器
    exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
    if err != nil {
        return nil, err
    }
    
    // 创建追踪器提供者
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exporter),
        trace.WithResource(resource.NewWithAttributes(
            attribute.String("service.name", serviceName),
        )),
    )
    
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{},
        propagation.Baggage{},
    ))
    
    return &Tracer{
        tracer: otel.Tracer(serviceName),
    }, nil
}

// 创建span
func (t *Tracer) StartSpan(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, trace.Span) {
    spanCtx, span := t.tracer.Start(ctx, name, trace.WithAttributes(attrs...))
    return spanCtx, span
}

// 记录错误
func (t *Tracer) RecordError(span trace.Span, err error) {
    if err != nil {
        span.SetStatus(codes.Error, err.Error())
        span.RecordError(err)
    }
}

// 完整的HTTP中间件实现
type TracingMiddleware struct {
    tracer *Tracer
}

func NewTracingMiddleware(tracer *Tracer) *TracingMiddleware {
    return &TracingMiddleware{tracer: tracer}
}

func (tm *TracingMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 从请求中提取上下文
        ctx := r.Context()
        
        // 创建span
        spanCtx, span := tm.tracer.StartSpan(ctx, "HTTP "+r.Method+" "+r.URL.Path,
            attribute.String("http.method", r.Method),
            attribute.String("http.url", r.URL.String()),
            attribute.String("http.client_ip", r.RemoteAddr),
        )
        
        // 将span上下文添加到请求中
        r = r.WithContext(spanCtx)
        
        defer func() {
            // 记录响应状态
            span.SetAttributes(
                attribute.Int("http.status_code", 200), // 这里需要在响应后设置
            )
            
            span.End()
        }()
        
        // 处理请求
        next.ServeHTTP(w, r)
    })
}

// 跨服务调用的追踪实现
func (tm *TracingMiddleware) WithPropagation(ctx context.Context, req *http.Request) {
    otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
}

func (tm *TracingMiddleware) ExtractSpanContext(r *http.Request) context.Context {
    return otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
}

监控指标收集

// 指标收集实现
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
    "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/metric/aggregation"
    "go.opentelemetry.io/otel/sdk/metric/view"
)

type MetricsCollector struct {
    meter   metric.Meter
    counter metric.Int64Counter
    histogram metric.Float64Histogram
    gauge   metric.Float64ObservableGauge
}

func NewMetricsCollector(serviceName string) (*MetricsCollector, error) {
    // 创建指标导出器
    exporter, err := stdoutmetric.New(stdoutmetric.WithPrettyPrint())
    if err != nil {
        return nil, err
    }
    
    // 创建指标提供者
    provider := metric.NewMeterProvider(
        metric.WithResource(resource.NewWithAttributes(
            attribute.String("service.name", serviceName),
        )),
        metric.WithReader(metric.NewPeriodicReader(exporter, metric.WithInterval(1*time.Second))),
        metric.WithView(view.New(
            view.MatchInstrumentName("*"),
            view.WithAggregation(aggregation.ExplicitBucketHistogram{
                Boundaries: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
            }),
        )),
    )
    
    otel.SetMeterProvider(provider)
    
    meter := otel.Meter(serviceName)
    
    counter, err := meter.Int64Counter("http_requests_total", metric.WithDescription("Total number of HTTP requests"))
    if err != nil {
        return nil, err
    }
    
    histogram, err := meter.Float64Histogram("http_request_duration_seconds", metric.WithDescription("HTTP request duration in seconds"))
    if err != nil {
        return nil, err
    }
    
    gauge, err := meter.Float64ObservableGauge("service_memory_usage_bytes", metric.WithDescription("Current memory usage in bytes"))
    if err != nil {
        return nil, err
    }
    
    return &MetricsCollector{
        meter:     meter,
        counter:   counter,
        histogram: histogram,
        gauge:     gauge,
    }, nil
}

// 记录请求计数
func (mc *MetricsCollector) RecordRequest(method, path string, statusCode int) {
    attrs := []attribute.KeyValue{
        attribute.String("http.method", method),
        attribute.String("http.path", path),
        attribute.Int("http.status_code", statusCode),
    }
    
    mc.counter.Add(context.Background(), 1, metric.WithAttributes(attrs...))
}

// 记录请求耗时
func (mc *MetricsCollector) RecordDuration(method, path string, duration time.Duration) {
    attrs := []attribute.KeyValue{
        attribute.String("http.method", method),
        attribute.String("http.path", path),
    }
    
    mc.histogram.Record(context.Background(), duration.Seconds(), metric.WithAttributes(attrs...))
}

// 记录内存使用
func (mc *MetricsCollector) RecordMemoryUsage(memoryUsage float64) {
    mc.gauge.Set(context.Background(), memoryUsage)
}

// HTTP中间件实现指标收集
type MetricsMiddleware struct {
    collector *MetricsCollector
}

func NewMetricsMiddleware(collector *MetricsCollector) *MetricsMiddleware {
    return &MetricsMiddleware{collector: collector}
}

func (mm *MetricsMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // 创建响应包装器以捕获状态码
        wrapped := &responseWriterWrapper{
            ResponseWriter: w,
            statusCode:     http.StatusOK,
        }
        
        next.ServeHTTP(wrapped, r)
        
        duration := time.Since(start)
        
        // 记录指标
        mm.collector.RecordRequest(r.Method, r.URL.Path, wrapped.statusCode)
        mm.collector.RecordDuration(r.Method, r.URL.Path, duration)
    })
}

// 响应包装器
type responseWriterWrapper struct {
    http.ResponseWriter
    statusCode int
}

func (rww *responseWriterWrapper) WriteHeader(code int) {
    rww.statusCode = code
    rww.ResponseWriter.WriteHeader(code)
}

完整的微服务示例

服务架构设计

// 完整的服务实现示例
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "sync"
    "time"

    "github.com/gin-gonic/gin"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/trace"
)

// 服务配置
type ServiceConfig struct {
    Port          string
    ServiceName   string
    ConsulAddress string
    MetricsPort   string
}

// 主服务结构
type MicroService struct {
    config     *ServiceConfig
    router     *gin.Engine
    tracer
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000