3.接口超时控制

文章探讨了在微服务架构中如何处理外部接口调用的超时问题,通过Golang的Chan、Select和Time.After来实现基础的超时控制。进一步地,引入了Context进行更优雅的管理,特别是在处理复杂的协程树和多级超时场景时,Context能提供更好的解决方案。
摘要由CSDN通过智能技术生成

在微服务的架构体系中,经常需要调用外部的接口,而外部接口一般是不可控制的,它随时可能会down掉,也可能会由于网络问题或者外部接口内部问题导致超时,我们作为调用方,不可能永久的等待下去,所以调用方一般会设置一个超时时间。

golang中一般会使用chanselect以及time.After控制超时,属于一种惯用写法了,如下

package main

import (
	"net/http"
	"time"
)

func readDB() string {
	time.Sleep(200 * time.Millisecond)
	return "ok"
}

func home(w http.ResponseWriter, req *http.Request) {
	var resp string
	done := make(chan struct{}, 1) // 使用chan,完成协调通信
	go func() {                    // 开启协程去完成业务逻辑
		resp = readDB()
		done <- struct{}{} // 业务逻辑完成后,往通道中写入消息
	}()

	select { // 监听相关chan,如果300ms后,业务逻辑都没有完成,则会进入超时的case
	case <-done:
	case <-time.After(300 * time.Millisecond):
		resp = "timeout"
	}
	w.Write([]byte(resp))
}

func main() {
	http.HandleFunc("/", home)
	http.ListenAndServe("127.0.0.1:8080", nil)
}

启动后,访问结果如下,因为读取DB耗时200ms,没有超过300ms,所以没有超时,返回了ok
在这里插入图片描述

当把DB中的耗时改为400ms后,再次访问,则返回的是超时了
在这里插入图片描述

上面的实现实际还不够优雅,因为是手动写的chan去控制,工作中更常用的是context,可以应对子协程非常多,且树形结构复杂的协程树退出。

package main

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


// 场景:设定一个超时时间,若是在指定超时时间后没有返回结果,则重试
func main() {
	// 经过context的WithTimeout设置一个有效时间为800毫秒的context
	// 该context会在耗尽800毫秒后或者方法执行完成后结束,结束的时候会向通道ctx.Done发送信号
	start := time.Now()
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
	defer func() {
		// 注意,这里要记得调用cancel(),否则即便提早执行完了,还要傻傻等到WithTimeout设置的时间(800毫秒)后context才会被释放
		cancel()
		fmt.Println(time.Since(start))
	}()

	// 注意ctx应该作为子协程的第一个参数
	go func(ctx context.Context) {
		// 发送HTTP请求
		fmt.Println("处理请求!")
		time.Sleep(300 * time.Millisecond) // 假设业务逻辑耗时300ms
		fmt.Println("请求处理完毕!")
	}(ctx)

	// 主要看WithTimeout设置的时间和下面time.After设置的时间谁先到就走谁,或者在time.After设置的时间到达前主动调用cancel,则ctx.Done产生的chan会关闭
	// 关闭的chan是默认可读的,因此会走ctx.Done的case
	select { 
	case <-ctx.Done(): // 800ms后,通道关闭,ctx.Done()通道可读
		fmt.Println("call successfully!!!")
		return
	// 这里已经设置了context的有效时间,为何还要加上这个time.After呢
	// 这是由于该方法内的context是本身声明的,能够手动设置对应的超时时间,可是在大多数场景,这里的ctx是从上游一直传递过来的,
	// 对于上游传递过来的context还剩多少时间,咱们是不知道的,因此这时候经过time.After设置一个本身预期的超时时间就颇有必要了
	case <-time.After(time.Duration(time.Millisecond * 700)):
		fmt.Println("timeout!!!")
		return
	}
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值