返回

系统可靠性提升利器 - 无侵入式自动重试

后端

引言

重试是提高系统可用性的重要手段,我们在业务代码中经常可以看到大量的重试逻辑。然而,这些重试逻辑往往都是手动实现的,不仅繁琐而且容易出错。有没有一种办法可以无侵入地实现重试,让业务代码完全无感知呢?

业务重试

常见的业务代码如下:

func ExampleRPCSend(ctx context.Context, request *request.SendRequest, opts ...gax.CallOption) (*request.SendResponse, error) {
	//ctx := context.Background()
	c, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()
	resp, err := c.Send(request, opts...)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

对于上面这段代码,我们希望在遇到网络错误或超时错误时进行重试。我们可以手动添加重试逻辑,如下所示:

func ExampleRPCSendWithRetry(ctx context.Context, request *request.SendRequest, opts ...gax.CallOption) (*request.SendResponse, error) {
	//ctx := context.Background()
	c, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()
	var err error
	var resp *request.SendResponse
	for i := 0; i < maxRetries; i++ {
		resp, err = c.Send(request, opts...)
		if err == nil || !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, errors.Unavailable) {
			break
		}
		time.Sleep(time.Second)
	}
	return resp, err
}

这种方式虽然可以实现重试,但非常繁琐,而且容易出错。有没有一种更简单的方法呢?

无侵入式自动重试

现在,让我们介绍一种无侵入式的自动重试机制。这种机制可以自动检测到网络错误或超时错误,并自动进行重试。业务代码完全无感知,不需要任何修改。

这种机制的原理是使用代理模式。我们创建一个代理对象,并把这个代理对象作为业务对象。当业务对象调用某个方法时,代理对象会拦截这个调用,并在调用失败时自动进行重试。

这种机制的优点是:

  • 无需修改业务代码,开箱即用
  • 非常简单易用,不需要任何配置
  • 可以自动检测到网络错误或超时错误
  • 可以自动进行重试,直到成功或达到最大重试次数

使用方式

使用这种机制非常简单,只需要在业务对象前加上一层代理对象即可。例如:

type SendClient struct {
	// target is the target address of the service.
	target string
	// opts is the optional gRPC call options.
	opts []gax.CallOption
	// retryer is the retryer used by the client.
	retryer gax.Retryer
}

func NewSendClient(target string, opts ...gax.CallOption) (*SendClient, error) {
	return &SendClient{
		target: target,
		opts:   opts,
		// Use default retryer.
		retryer: DefaultRetryer,
	}
}

func (c *SendClient) Send(ctx context.Context, req *request.SendRequest, opts ...gax.CallOption) (*request.SendResponse, error) {
	// Add retryer to the call options.
	opts = append(opts, c.retryer)

	// Delegate the call to the real client.
	return c.c.Send(ctx, req, opts...)
}

在上面的代码中,我们创建了一个代理对象SendClient。这个代理对象包含了一个真正的业务对象c。当SendClient调用Send方法时,它会把retryer添加到调用选项中,然后把调用委托给真正的业务对象。这样,业务对象就可以自动进行重试了。

结语

无侵入式的自动重试机制可以帮助您轻松提升系统可靠性。无需修改业务代码,即可实现故障重试,让您的系统更稳定、更可靠。