golang双端口服务让我脑袋疼,你们呢?

简介: golang双端口服务让我脑袋疼,你们呢?

一生经过彷徨的挣扎,自信可改变未来。  《中国合伙人》


1. 前言



很多人在启动双端口服务的时候很搞笑,经常会直接go出去就不管了或者go出去之后select一下就以为万事大吉了,我现在告诉大家启动双端口服务真的没你想的那么简单,那么这篇文章我们来聊聊双端口到底如何实现的。


2. 需求描述



研发童鞋们为了排查问题会在生产环境部署一个debug服务,专门采集线上性能数据,比如golang的pprof工具采集cpu,内存以及磁盘等相关指标。但是又不能为了这个需求单独部署一个服务,所以在APP项目中又额外加入一个debug端口服务,供研发来调用。


3. 实现双端口



package main
import (
 "context"
 "fmt"
 "net/http"
)
func Server(addr string, handler http.Handler, stop <- chan struct{}) error {
 s := http.Server{
  Addr: addr,
  Handler: handler,
 }
 //接受信号 关闭服务 
 go func() {
  <-stop
  s.Shutdown(context.Background())
 }()
 return s.ListenAndServe()
}
//线上debug服务
func ServerDebug(stop <- chan struct{}) error {
 return Server("127.0.0.1:8001", nil, stop)
}
//app服务
func ServerApp(stop <- chan struct{}) error {
 return Server("127.0.0.1:8002", nil, stop)
}
func main() {
 done := make(chan error, 2)
 stop := make(chan struct{})
 //启动debug服务
 go func() {
  done <- ServerDebug(stop)
 }()
 //启动app服务
 go func() {
  done <- ServerApp(stop)
 }()
 var stopped bool
 for i:=0; i<cap(done); i++ {
  if err := <- done; err != nil {
   fmt.Println("error is ", err)
  }
  //只要有一个错误,就发stop信号 把两个服务端口都停掉
  if !stopped {
   stopped = true
   close(stop)
  }
 }
}


实现方式包括:

  1. 创建done接受服务异常的信号
  2. 创建stop发送服务关闭的信号
  3. 把goroutine的调度交给caller


第一步就是接受这两个端口服务任意一个因为错误返回的err,收集到错误之后启动第二步就是发送关闭信号,这样两个端口服务都会收到这个信号而shutdown退出,最后一步就是把创建goroutine的权利交给调度者。


这样的写法可以很好的控制goroutine退出和服务关闭。


4. 不好的实现方式1



package main
import (
 "net/http"
)
func Server(addr string, handler http.Handler) error {
 s := http.Server{
  Addr: addr,
  Handler: handler,
 }
 return s.ListenAndServe()
}
//线上debug服务
func ServerDebug() error {
 return Server("127.0.0.1:8001", nil)
}
//app服务
func ServerApp() error {
 return Server("127.0.0.1:8002", nil)
}
func main() {
 //启动debug服务
 go func() {
  ServerDebug()
 }()
 //启动app服务
 go func() {
  ServerApp()
 }()
 select {}
}


直接用select{}来阻塞处理,这样的缺点就是当某个端口服务挂掉的时候你根本不知道什么原因挂掉,而且理论上某个端口挂了,应该整个程序都关掉才对,不然这样的写法一点意义都没有。


那你可能说了加一个log.Fatal,比如:


//启动debug服务
 go func() {
  err := ServerDebug()
  if err != nil {
   log.Fatal(err)
  }
 }()


这样可以退出程序,但是万一我们main中还需要处理defer呢,log.fatal是调用底层exit的,不走defer的,所以这种建议不可取。


5. 不好的实现方式2



package main
import (
 "net/http"
)
func Server(addr string, handler http.Handler) error {
 s := http.Server{
  Addr: addr,
  Handler: handler,
 }
 return s.ListenAndServe()
}
//线上debug服务
func ServerDebug() {
 go Server("127.0.0.1:8001", nil)
}
//app服务
func ServerApp() {
 go Server("127.0.0.1:8002", nil)
}
func main() {
 //启动debug服务
 ServerDebug()
 //启动app服务
 ServerApp()
 select {}
}


这种写法也不推荐,第一个原因就是对于调用者来说,ServerDebugServerApp是同步调用的,一眼看不到有goroutine,所以对调度者不太友好。第二个原因就是它俩不再受main goroutine的控制了,大家注意这种情况是最可怕的,一定要避免。第三个原因就是底层Server函数的err没办法在main中收集,导致调度者没办法做最后的收尾工作。


6. 小结



双端口服务比较常见,尤其线上排查问题的时候比较常用,所以建议大家亲自试试,以备不时之需。今天就先到这里了,下期我们再见。


7. 关注公众号



 微信公众号:堆栈future

相关文章
|
5月前
|
缓存 NoSQL 网络安全
【Azure Redis 缓存】Azure Redis服务开启了SSL(6380端口), PHP如何访问缓存呢?
【Azure Redis 缓存】Azure Redis服务开启了SSL(6380端口), PHP如何访问缓存呢?
|
8月前
|
监控 算法 Go
Golang深入浅出之-Go语言中的服务熔断、降级与限流策略
【5月更文挑战第4天】本文探讨了分布式系统中保障稳定性的重要策略:服务熔断、降级和限流。服务熔断通过快速失败和暂停故障服务调用来保护系统;服务降级在压力大时提供有限功能以保持整体可用性;限流控制访问频率,防止过载。文中列举了常见问题、解决方案,并提供了Go语言实现示例。合理应用这些策略能增强系统韧性和可用性。
488 0
|
8月前
|
传感器 监控 物联网
golang开源的可嵌入应用程序高性能的MQTT服务
golang开源的可嵌入应用程序高性能的MQTT服务
359 3
|
8月前
|
前端开发 Java 数据库连接
Springboot-MyBatis配置-配置端口号与服务路径(idea社区版2023.1.4+apache-maven-3.9.3-bin)
Springboot-MyBatis配置-配置端口号与服务路径(idea社区版2023.1.4+apache-maven-3.9.3-bin)
92 0
|
2月前
|
安全 Linux 网络安全
nmap 是一款强大的开源网络扫描工具,能检测目标的开放端口、服务类型和操作系统等信息
nmap 是一款强大的开源网络扫描工具,能检测目标的开放端口、服务类型和操作系统等信息。本文分三部分介绍 nmap:基本原理、使用方法及技巧、实际应用及案例分析。通过学习 nmap,您可以更好地了解网络拓扑和安全状况,提升网络安全管理和渗透测试能力。
185 5
|
8月前
|
网络协议 Go 数据安全/隐私保护
golang开源的可嵌入应用程序高性能的MQTT服务
golang开源的可嵌入应用程序高性能的MQTT服务
451 2
|
3月前
|
缓存 负载均衡 应用服务中间件
Nginx 实现一个端口代理多个前后端服务
【10月更文挑战第19天】Nginx 的强大功能不仅限于此,它还可以与其他技术和工具相结合,为我们的应用提供更强大的支持和保障。在不断发展的互联网时代,掌握 Nginx 的使用技巧将为我们的工作和生活带来更多的便利和效益。
|
5月前
|
开发框架 .NET Linux
【Azure 应用服务】 部署到App Service for Linux 服务的Docker 镜像,如何配置监听端口呢?
【Azure 应用服务】 部署到App Service for Linux 服务的Docker 镜像,如何配置监听端口呢?
|
5月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】Azure Cache for Redis服务中,除开放端口6379,6380外,对13000,13001,15000,15001 为什么也是开放的呢?
【Azure Redis 缓存】Azure Cache for Redis服务中,除开放端口6379,6380外,对13000,13001,15000,15001 为什么也是开放的呢?
|
5月前
|
Prometheus 监控 Kubernetes
将service类型由"ClusterIP"改为"NodePort"无法使用nodeip+端口访问服务解决方法.
将service类型由"ClusterIP"改为"NodePort"无法使用nodeip+端口访问服务解决方法.

热门文章

最新文章