vr模式的网站建设公司/友链交换平台
在服务器开发领域,有很多未知的原因可能导致用户的请求失败。比如:你的服务器并发请求了多个下游服务,并在等待它们返回所有结果。但是很不幸,其中一个服务返回了错误,此时其它所有其它请求我们也就没有必要再等待下去了,而是直接返回并告诉用户本次请求失败。
上面的场景在服务器开发领域非常常见,处理起来也比较困难。无论你是写 C++ 还是 Java,或是 Nodejs,都不那么容易处理。
多说不益,我们使用一个简单的服务器模拟请求下游,并人为取消请求。
1. 请求模拟
目标:假设我们的服务同时发起三次 HTTP GET 请求下游,得到结果,并打印到屏幕。
1.1 模拟的 Get 请求函数
下面是我们自己编写的一个模拟发送 HTTP GET 请求的函数。当然它并不会真的请求某个真实的下游。这个函数可以模拟网络拥塞的场景,比如长时间不返回结果。为了能让你看到效果,我特意将请求耗时随机设置在 [2,7] 秒之间。
关于 time.After 函数就不用我多说了,前面已经讲过了,当然你也可以自行查阅文档。
func Get() string {duration := rand.Intn(5) + 2tick := time.After(time.Duration(duration) * time.Second)select {case <-tick:return fmt.Sprintf("get page %d", duration)}
}
1.2 模拟请求下游服务
package mainimport ("fmt""math/rand""sync""time"
)func Get() string {duration := rand.Intn(5) + 2tick := time.After(time.Duration(duration) * time.Second)select {case <-tick:return fmt.Sprintf("get page %d", duration)}
}func main() {rand.Seed(time.Now().Unix())var wg sync.WaitGroupwg.Add(3)go func() {fmt.Println(Get())wg.Done()}()go func() {fmt.Println(Get())wg.Done()}()go func() {fmt.Println(Get())wg.Done()}()wg.Wait()
}
下面是运行效果。
图1 并发 Get 请求
2. cancel 请求
在上一篇《火箭发射游戏》里,你已经学会了使用 channel 来取消火箭发射。其实在这里,你仍然可以借鉴这个思路。
具体的,我们可以通过创建一个 channel 来控制。修改的代码如下:
package mainimport ("fmt""math/rand""os""sync""time"
)func Get(done <-chan struct{}) string {duration := rand.Intn(5) + 2tick := time.After(time.Duration(duration) * time.Second)// 使用 select 来监听 channelselect {case <-tick:return fmt.Sprintf("get page %d", duration)case <-done:return fmt.Sprintf("cancel %d", duration)}
}func main() {rand.Seed(time.Now().Unix())// 创建一个 channeldone := make(chan struct{})var wg sync.WaitGroupgo func() {os.Stdin.Read(make([]byte, 1))// 因为我们发起了 3 次 Get 请求,所以要发送 3 次数据到 done channelfor n := 0; n < 3; n++ {done <- struct{}{}}}()wg.Add(3)go func() {fmt.Println(Get(done))wg.Done()}()go func() {fmt.Println(Get(done))wg.Done()}()go func() {fmt.Println(Get(done))wg.Done()}()wg.Wait()
}
这一次,我们再次运行,并在请求结束前,按下你的 ENTER 键来结束请求。
图2 cancel 请求
在图 2 中,第一次我没有打断请求,程序自然结束。第二次,程序一启动我就按下 ENTER 打断了请求。第三次,程序第一个请求结束后我按下 ENTER 键,打断了后面的两个 GET 请求。
那么,我们的任务就到此为止了吗?显然不会。上面的实现太不优雅了,假设我们有 100 个并发请求出去了?难道就得把循环向 channel 发送 100 次数据吗?
有没有一种方向,可以广播的通知所有的 goroutine? 答案是有的。我们利用 channel 的另一个特性:如果从一个已经关闭的 channel 读取数据,程序会立即返回而不阻塞。
这样的就简单了,我们只要稍微修改一下那个看起来不太舒服的 for 循环就行:
// 不要使用这种方式了
for n := 0; n < 3; n++ {done <- struct{}{}
}// 改成下面这样
close(done)
稍作修改,你的程序就能达到和图 2 一样的效果。
3. 总结
- 掌握 channel 的关闭后的特性
- 掌握取消并发 goroutine 的方法
在 Golang 里,取消请求太常见了,以致于 Golang 为其实现了一些标准库来完成这些事情。下一讲,我们来看看它到底是什么。