关于go:上游服务不可用了下游服务如何应对

60次阅读

共计 5664 个字符,预计需要花费 15 分钟才能阅读完成。

1. 引言

在零碎中,上游服务和上游服务是两个要害概念。上游服务通常指的是提供某种性能或数据的服务端,它接管来自上游服务的申请,并依据申请进行解决和响应。上游服务通常指的是发动申请并依赖上游服务的客户端,它们通过发送申请向上游服务申请数据或执行某些操作。

上游服务和上游服务之间的合作是零碎中实现整体性能的要害。上游服务提供了外围的业务逻辑和数据,上游服务则依赖于上游服务来实现其特定工作。

上游服务的稳定性和可用性间接依赖于上游服务的可靠性和性能。如果上游服务不可用或呈现故障,同时上游服务没有采取任何应答措施,此时将可能呈现以下问题:

  1. 无奈失常执行工作:上游服务可能无奈执行其性能,因为它依赖于上游服务的数据或资源。这将导致上游服务无奈失常工作。
  2. 提早和性能降落:如果上游服务不对上游服务的不可用进行适当解决,而是无限期期待或一直尝试申请,零碎的响应工夫会减少,导致性能降落。
  3. 级联故障:如果上游服务持续发动大量的申请到不可用的上游服务,它可能会导致上游服务资源被全副占用,无奈为其余申请提供服务。这可能导致级联故障,使整个零碎不可用。

因而,上游服务应该采取适当的应答措施来解决上游服务不可用的状况,以确保零碎的稳定性和可用性。

2. 状况分类

对于上游服务的不可用,此时能够辨别为短暂不可用和长时间不可用两种状况,从而采纳不同的形式来进行解决。

短暂不可用,是指上游服务在一段时间内临时无奈提供失常的服务,通常是因为网络稳定或负载过低等起因导致的。这种状况下,上游服务可能会在很短的工夫内自行复原,并从新可用。短暂不可用通常持续时间较短,能够通过重试机制来解决。上游服务能够在申请失败后进行重试,直到上游服务恢复正常。

长时间不可用,是指上游服务在较长的工夫内无奈提供失常的服务,无奈自行复原或复原工夫较长。这种状况可能是因为重大的故障、零碎解体,或其余长期性问题导致的。在这种状况下,简略地进行无限度的重试可能会导致系统被该申请全副占用,甚至引发级联故障。

短暂不可用和长时间不可用是上游服务不可用的两种常见状况,它们须要不同的应答策略。理解并辨别这两种状况对于确保零碎的稳定性和可用性至关重要。

3. 短暂不可用

3.1 解决形式

短暂的不可用可能只是临时性的,可能是网络拥塞或其余暂时性问题导致的。此时能够通过重试机制,上游服务能够尝试从新发送申请,减少申请胜利的机会,防止因为零碎的短暂不可用导致申请的失败。

3.2 代码示例

重试机制在许多客户端库中曾经成为规范性能。这些客户端库提供了一些配置选项,以管制重试行为。上面是一个应用 gRPC 的示例代码,展现了如何配置和应用重试机制,而后基于此防止因为零碎的短暂不可用导致申请的失败。

首先展现服务端代码,用于向外提供服务:

package main

import (
        "context"
        "log"
        "net"

        pb "path/to/your/protobuf"
        "google.golang.org/grpc"
)

type server struct {pb.UnimplementedYourServiceServer}

func (s *server) YourRPCMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
        // 解决申请逻辑
        return &pb.Response{Message: "Hello, world!"}, nil
}

func main() {listener, err := net.Listen("tcp", ":50051")
        if err != nil {log.Fatalf("Failed to listen: %v", err)
        }

        srv := grpc.NewServer()
        pb.RegisterYourServiceServer(srv, &server{})

        log.Println("Server started")
        if err := srv.Serve(listener); err != nil {log.Fatalf("Failed to serve: %v", err)
        }
}

接下来展现客户端代码,其在服务端呈现谬误时,会进行重试,防止因为服务的短暂不可用,导致申请的失败,保证系统的可用性:

package main

import (
        "context"
        "log"
        "time"
        pb "path/to/your/protobuf" 
        "google.golang.org/grpc"
)

func main() {conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
        if err != nil {log.Fatalf("Failed to connect: %v", err)
        }
        defer conn.Close()

        client := pb.NewYourServiceClient(conn)

        // 设置重试次数和超时工夫
        retryOptions := []grpc.CallOption{grpc.WithRetryMax(3),           // 最大重试次数
                grpc.WithTimeout(5 * time.Second), // 超时工夫
        }
        // 发动 gRPC 申请(带重试和超时工夫)ctx := context.Background()
        response, err := client.YourRPCMethod(ctx, &pb.Request{}, retryOptions...)
        if err != nil {log.Fatalf("Failed to make request: %v", err)
        }

        log.Println("Response:", response.Message)
}

在客户端代码中,咱们通过 grpc.WithRetryMax 设置最大重试次数,通过 grpc.WithTimeout 设置超时工夫。

这样,当遇到服务端短暂不可用时,客户端将进行最多 3 次的重试,并在每次重试时设置 5 秒的超时工夫。可能让上游服务在短暂不可用状况下,依然可能最大限度得保障上游服务申请的胜利。

3.3 最佳实际

然而并非当申请失败时,便一直进行重试,直到申请胜利,须要设置适当的重试策略和超时机制。防止因为大量的重试导致上游服务压力变大,从而导致服务从短暂不可用转变成了长时间不可用。

首先 确定最大重试次数,依据零碎需要和上游服务的个性,确定最大重试次数。防止无限度地进行重试,因为过多的重试可能会给上游服务造成过大的累赘。通常,3- 5 次重试是一个常见的值,但具体数字应依据理论状况进行调整。

其次 设置正当的超时工夫,为了防止上游服务长时间期待不可用的上游服务,设置正当的申请超时工夫是很重要的。超时工夫应依据上游服务的预期响应工夫和网络提早进行调整。一般来说,超时工夫应该在几秒钟的范畴内,以确保及时地放弃不可用的申请并进行后续解决。

4. 长时间不可用

4.1 会导致的问题

长时间不可用的状况可能导致重大的问题,其中包含系统资源被全副占用和级联故障的危险。

首先是上游服务可能间接解体。当上游服务面临大量申请失败时,如果上游服务依然继续地发动申请并无脑重试,此时随着申请数的减少,零碎的资源(例如线程、内存、连贯等)将被耗尽,导致系统无奈解决其余的申请。后果是整个零碎变得不可响应,甚至解体。

其次是级联故障的危险。长时间不可用的上游服务可能引发级联故障。在分布式系统中,不同的服务通常相互依赖和交互,上游服务的不可用性可能会影响到上游服务。如果上游服务在遇到上游服务不可用时不具备适合的应答机制,它们可能会持续尝试发送申请,造成资源节约和沉积。这样的状况可能导致上游服务也不可用,甚至影响到更多的依赖零碎,最终导致整个零碎的级联故障。

所以,咱们在设计零碎时,上游服务在一些要害节点应该采取适当的应答措施来解决上游服务不可用的状况,以确保零碎的稳定性和可用性。

4.2 解决形式

当上游服务长时间不可用时,上游服务须要采取一些措施来解决这个问题,以防止系统资源被全副占用和避免级联故障的产生。有两种常见的解决形式:引入熔断器和数据降级。

首先能够引入熔断器:熔断器是一种用于监控上游服务可用性并在须要时进行熔断的机制。当上游服务长时间不可用时,熔断器会间接熔断上游服务对上游服务的申请,而不是无脑地持续发送申请。这样能够防止上游服务继续占用系统资源,并且避免级联故障的产生。

熔断器通常有三个状态:敞开状态、关上状态和半开状态。在敞开状态下,申请会失常通过;当错误率达到肯定阈值时,熔断器会关上,所有申请都会被熔断;在一段时间后,熔断器会进入半开状态,容许局部申请通过,以查看上游服务是否曾经恢复正常。通过熔断器的状态转换,上游服务能够更好地适应上游服务的不可用状况。

其次是进行数据缓存与降级,如果上游服务提供的数据不是实时性要求很高,上游服务能够思考在上游服务可用时缓存一部分数据。当上游服务不可用时,上游服务能够应用曾经缓存的数据作为降级策略,确保零碎的失常运行。这样能够缩小对上游服务的依赖,防止系统资源被全副占用。

两种常见的形式,能够帮忙上游服务在上游服务长时间不可用的状况下放弃稳定性,并防止系统资源被全副占用和级联故障的产生。

4.3 代码示例

上面展现一个示例代码,展现上游服务向上游服务发送申请,并实现熔断机制和缓存降级操作,从而在上游服务不可用时,保障上游服务的稳定性:

package main

import (
        "fmt"
        "sync"
        "time"

        "github.com/sony/gobreaker"
)

var (
        circuitBreaker *gobreaker.CircuitBreaker
        cache          map[string]string
        cacheMutex     sync.RWMutex
)

func main() {
        // 初始化熔断器
        circuitBreaker = gobreaker.NewCircuitBreaker(gobreaker.Settings{
                Name:        "MyCircuitBreaker",
                MaxRequests: 3,                  // 最大申请数
                Interval:    5 * time.Second,    // 统计工夫窗口
                Timeout:     1 * time.Second,    // 超时工夫
                ReadyToTrip: customTripFunc,     // 自定义熔断判断函数
        })

        // 初始化缓存
        cache = make(map[string]string)

        // 模仿上游服务发送申请
        for i := 0; i < 10; i++ {response, err := makeRequest("https://baidu.com")
                if err != nil {fmt.Println("Request failed:", err)
                } else {fmt.Println("Response:", response)
                }
                time.Sleep(1 * time.Second) // 距离一段时间再次发送申请
        }
}

// 发送申请的函数
func makeRequest(url string) (string, error) {
        // 应用熔断器包装申请函数
        response, err := circuitBreaker.Execute(func() (interface{}, error) {
                // 假如向上游服务发送 HTTP 申请
                // ...
                // 返回响应数据或谬误
                return "Response from  service", nil
        })

        if err != nil {
                // 申请失败,应用缓存降级
                cacheMutex.RLock()
                cachedResponse, ok := cache[url]
                cacheMutex.RUnlock()

                if ok {return cachedResponse, nil}

                return "", err
        }
        // 申请胜利,更新缓存
        cacheMutex.Lock()
        cache[url] = response.(string)
        cacheMutex.Unlock()

        return response.(string), nil
}

// 自定义熔断判断函数,依据理论状况进行判断
func customTripFunc(counts gobreaker.Counts) bool {
        // 依据失败次数或其余指标来判断是否触发熔断
        return counts.ConsecutiveFailures > 3
}

上述代码示例中,应用了 gobreaker 开源我的项目实现了熔断机制,并应用本地缓存实现了缓存降级操作。

在发送申请的函数 makeRequest 中,应用熔断器包装申请函数,在申请函数外部理论发送申请给上游服务。如果申请胜利,则更新缓存,并返回响应。

如果申请失败,则再次尝试从缓存中获取数据,如果缓存中存在,则返回缓存的响应。如果缓存中也不存在数据,则返回谬误。

这样,上游服务能够在上游服务不可用的状况下,通过缓存提供肯定的降级性能,防止对上游服务进行频繁有效的申请。而熔断机制则能够在上游服务可用性不高时,间接熔断申请,缩小对上游服务的负载,进步整体零碎的稳定性。

5. 总结

本文次要介绍了上游服务不可用时,上游服务的应答措施。次要分为短暂不可用和长时间不可用两种状况。

短暂不可用通常由网络稳定或其余暂时性问题导致。在这种状况下,能够采纳重试机制来胜利申请上游服务,确保资源的可用性。重试机制是一种简略而无效的办法,通过多次重试申请,以应答短暂的不可用状况,防止上游服务受到影响。

长时间不可用可能导致重大的问题,如上游服务解体或级联故障。为了应答这种状况,能够引入熔断器爱护机制来确保上游服务的稳定性。熔断器可能疾速切断对不可用的上游服务的申请,防止系统资源被全副占用。此外,依据具体服务的个性,能够思考应用缓存降级来解决长时间不可用的状况。通过缓存上游服务的响应数据,即便上游服务不可用,上游服务仍能够从缓存中获取数据,放弃服务的可用性。

综上所述,重试机制和熔断器是应答上游服务不可用的要害措施。重试机制实用于短暂不可用状况,而熔断器则用于长时间不可用的状况下爱护上游服务。此外,依据具体情况,采纳缓存降级等策略能够进一步晋升服务的稳定性和可用性。

正文完
 0