golang http服务实现多ip监听,及优雅重启

这篇具有很好参考价值的文章主要介绍了golang http服务实现多ip监听,及优雅重启。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在工作中,有时需要对http服务实现多监听,http服务重启等需求。大多数web框架只实现的是单ip监听,要实现多ip监听就需要循环监听ip;

而重启http服务,首先想到的是用endless来优雅的实现服务的重启,但是当多ip监听时,一个项目不能用一个endLess,多了会报错,且windows环境也无法实现重启;

所以,我在工作中最终使用了gracehttp(grace)来实现多ip监听及优雅的重启,但是grace也是只能做到linux的重启和启动,应为window没有定义signal,这里需要吐槽一下window;最借鉴了一下另外一个开源包window gracehttp,是多grace的扩展,实现了http服务在window的多ip监听,但是重启一样报错(window 不支持signal), 只能再次修改开源包.

话不多说上代码:

linux版:

//go:build !windows
// +build !windows

package utils

import (
	"crypto/tls"
	"github.com/facebookgo/grace/gracehttp"
	"github.com/labstack/echo/v4"
	"net/http"
	"sync"
	"time"
)

/**
http server 优雅地重启
*/

var grace *GraceHttp
var gOnce sync.Once
var SysRestart = make(chan int)

type GraceHttp struct {
	SrvList sync.Map
}

func NewGrace() *GraceHttp {
	gOnce.Do(func() {
		grace = &GraceHttp{}
	})
	return grace
}

func (g *GraceHttp) AddService(name string, srv *http.Server) {
	g.SrvList.Store(name, srv)
}

func (g *GraceHttp) Run() {
	srvs := make([]*http.Server, 0)
	g.SrvList.Range(func(key, value any) bool {
		srvs = append(srvs, value.(*http.Server))
		g.SrvList.Delete(key)
		return true
	})

    // 调用grace启动
	if err := gracehttp.ServeWithOptions(srvs); err != nil {
		panic(err)
	}
}

func InitServer(address string, router *echo.Echo, cfg *tls.Config) *http.Server {
	s := &http.Server{
		Addr:           address,
		Handler:        router,
		ReadTimeout:    20 * time.Second,
		WriteTimeout:   20 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	if cfg != nil {
		s.TLSConfig = cfg
	}
	return s
}

windows版:

//go:build windows
// +build windows

package utils

import (
	"crypto/tls"
	"github.com/labstack/echo/v4"
	"net/http"
	"sync"
	"time"
)

var grace *GraceHttp
var gOnce sync.Once
var SysRestart = make(chan int)

type GraceHttp struct {
	SrvList sync.Map
}

func NewGrace() *GraceHttp {
	gOnce.Do(func() {
		grace = &GraceHttp{}
	})
	return grace
}

func (g *GraceHttp) AddService(name string, srv *http.Server) {
	g.SrvList.Store(name, srv)
}

func (g *GraceHttp) Run() {
	srvs := make([]*http.Server, 0)
	g.SrvList.Range(func(key, value any) bool {
		srvs = append(srvs, value.(*http.Server))
		g.SrvList.Delete(key)
		return true
	})
	// g.SrvList = sync.Map{}
    // 使用改版的grace
	if err := Serve(srvs...); err != nil {
		panic(err)
	}
}

func InitServer(address string, router *echo.Echo, cfg *tls.Config) *http.Server {
	s := &http.Server{
		Addr:           address,
		Handler:        router,
		ReadTimeout:    20 * time.Second,
		WriteTimeout:   20 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	if cfg != nil {
		s.TLSConfig = cfg
	}
	return s
}

改版的windows grace,注这是对开源代码的改版。

package utils

import (
	"bytes"
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/facebookgo/grace/gracenet"
	"github.com/facebookgo/httpdown"
	"log"
	"net"
	"net/http"
	"os"
	"sync"
	"syscall"
)

var (
	verbose    = flag.Bool("gracehttp.log", true, "Enable logging.")
	didInherit = os.Getenv("LISTEN_FDS") != ""
	ppid       = os.Getppid()
)

const SIGUSR2 = syscall.Signal(0x1f)

// An app contains one or more servers and associated configuration.
type app struct {
	servers   []*http.Server
	http      *httpdown.HTTP
	net       *gracenet.Net
	listeners []net.Listener
	sds       []httpdown.Server
	errors    chan error
}

func newApp(servers []*http.Server) *app {
	return &app{
		servers:   servers,
		http:      &httpdown.HTTP{},
		net:       &gracenet.Net{},
		listeners: make([]net.Listener, 0, len(servers)),
		sds:       make([]httpdown.Server, 0, len(servers)),

		// 2x num servers for possible Close or Stop errors + 1 for possible
		// StartProcess error.
		errors: make(chan error, 1+(len(servers)*2)),
	}
}

func (a *app) listen() error {
	for _, s := range a.servers {
		// TODO: default addresses
		l, err := a.net.Listen("tcp", s.Addr)
		if err != nil {
			return err
		}
		if s.TLSConfig != nil {
			l = tls.NewListener(l, s.TLSConfig)
		}
		a.listeners = append(a.listeners, l)
	}
	return nil
}

func (a *app) serve() {
	for i, s := range a.servers {
		a.sds = append(a.sds, a.http.Serve(s, a.listeners[i]))
	}
}

func (a *app) wait() {
	var wg sync.WaitGroup
	fmt.Println("wg lent:", len(a.sds))
	wg.Add(len(a.sds) * 2) // Wait & Stop
	fmt.Printf("wg:%+v", wg)
	go a.signalHandler(&wg)
	for _, s := range a.sds {
		go func(s httpdown.Server) {
			fmt.Println("等待启动.........")
			defer wg.Done()
			if err := s.Wait(); err != nil {
				a.errors <- err
			}
		}(s)
	}
	wg.Wait()
}

var WinRestart = make(chan int)

//func (a *app) term(wg *sync.WaitGroup) {
//	for _, s := range a.sds {
//		go func(s httpdown.Server) {
//			defer wg.Done()
//			if err := s.Stop(); err != nil {
//				a.errors <- err
//			}
//		}(s)
//	}
//}

func (a *app) signalHandler(wg *sync.WaitGroup) {
	select {
	case <-WinRestart:
		fmt.Println("收到重启请求....")
		for _, s := range a.sds {
			fmt.Println("关闭http。。。。。。。。")
			if err := s.Stop(); err != nil {
				a.errors <- err
			}
			wg.Done()
		}
	}
	SysRestart <- 1
}

// Serve will serve the given http.Servers and will monitor for signals
// allowing for graceful termination (SIGTERM) or restart (SIGUSR2).
func Serve(servers ...*http.Server) error {
	a := newApp(servers)

	// Acquire Listeners
	if err := a.listen(); err != nil {
		return err
	}

	// Some useful logging.
	if *verbose {
		if didInherit {
			if ppid == 1 {
				log.Printf("Listening on init activated %s", pprintAddr(a.listeners))
			} else {
				const msg = "Graceful handoff of %s with new pid %d and old pid %d"
				log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid)
			}
		} else {
			const msg = "Serving %s with pid %d"
			log.Printf(msg, pprintAddr(a.listeners), os.Getpid())
		}
	}

	// Start serving.
	a.serve()

	// Close the parent if we inherited and it wasn't init that started us.
	if didInherit && ppid != 1 {
		if err := terminateProcess(ppid); err != nil {
			return fmt.Errorf("failed to close parent: %s", err)
		}
	}

	waitdone := make(chan struct{})
	go func() {
		defer close(waitdone)
		a.wait()
	}()

	select {
	case err := <-a.errors:
		if err == nil {
			panic("unexpected nil error")
		}
		return err
	case <-waitdone:
		if *verbose {
			log.Printf("Exiting pid %d.", os.Getpid())
		}
		return nil
	}
}

// Used for pretty printing addresses.
func pprintAddr(listeners []net.Listener) []byte {
	var out bytes.Buffer
	for i, l := range listeners {
		if i != 0 {
			fmt.Fprint(&out, ", ")
		}
		fmt.Fprint(&out, l.Addr())
	}
	return out.Bytes()
}

func terminateProcess(pid int) error {
	process, err := os.FindProcess(pid)
	if err != nil {
		return err
	}

	return process.Signal(syscall.SIGTERM)
}

在需要重启的地方调用重启方法:

func Reload() error {
	linuxVersion, err := GetLinuxPlatformFamily()
	if err != nil {
		return err
	}
	var cmd *exec.Cmd
	pid := os.Getpid()
	switch linuxVersion {
	case "windows":
		go func() {
			fmt.Println("发送重启命令")
			WinRestart <- 1
		}()
		return nil
	case LINUXRHEL:
		cmd = exec.Command("bash", "-c", "rpm -qa | grep -i supervisor")
	case LINUXDEBIAN:
		cmd = exec.Command("bash", "-c", "service --status-all |grep supervisor")
	}
	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr
	cmd.Run()
	// 安装了supervisor,就让supervisor拉起,否则重启
	if out.String() != "" && strings.Contains(out.String(), "[ + ]  supervisor") {
		os.Exit(0)
		return nil
	}

	cmd = exec.Command("kill", "-USR2", strconv.Itoa(pid))
	return cmd.Run()
}

最后在main里面实现一个守护进程,进行windows的重启:

// 守护进程用于windows重启
func (e *Engine) windowsDeamon() {
	if runtime.GOOS == "windows" {
		for {
			select {
			case <-utils.SysRestart:
				fmt.Println("windows deamon program server restart................")
				x.SafeGo(func() {
					e.runBackend()
					e.api.Start()
					utils.NewGrace().Run()
				})
			}
		}
	}
}

最后就实现了,http 服务在windows和linux的多ip监听及优雅重启。 

注意事项:

在wg.done后面执行channel之类的阻塞,可能会出现wg.done成负值错误,需要控制在毫米级以下。 文章来源地址https://www.toymoban.com/news/detail-411092.html

到了这里,关于golang http服务实现多ip监听,及优雅重启的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • springboot服务端接口外网远程调试,并实现HTTP服务监听

    前后端分离项目中,在调用接口调试时候,我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试,本次教程我们以Java服务端接口为例。 JDK1.8 IDEA SpringBoot Maven Tomcat9.0 Postman 搭建一个springboot服务的项目,编写一个接口,为了更好直观看到,这里创建一个p

    2024年02月11日
    浏览(39)
  • springboot服务端接口公网远程调试 - 实现HTTP服务监听【端口映射】

    转载自cpolar内网穿透的文章:Springboot服务端接口公网远程调试,并实现HTTP服务监听 前后端分离项目中,在调用接口调试时候,我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试,本次教程我们以Java服务端接口为例。 JDK1.8 IDEA SpringBoot Maven Tomcat9.

    2024年02月06日
    浏览(65)
  • 互联网大厂技术-HTTP请求-Springboot整合Feign更优雅地实现Http服务调用

    目录 一、SpringBoot快速整合Feign 1.添加Pom依赖 2.启动类添加注解 3.引用Feign服务 二、为请求添加Header的3种方式 1.添加固定header 2.通过接口签名添加header 3.动态添加header 三、为请求添加超时配置 1.默认超时时间 3.超时异常 4.全局超时配置 5.为单个服务设置超时配置 四、为请求配

    2024年02月04日
    浏览(58)
  • springboot服务端接口外网远程调试,并实现HTTP服务监听 - 内网穿透

    转载自远程内网穿透的文章:springboot服务端接口公网远程调试,并实现HTTP服务监听 前后端分离项目中,在调用接口调试时候,我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试,本次教程我们以Java服务端接口为例。 JDK1.8 IDEA SpringBoot Maven Tomcat9.

    2023年04月22日
    浏览(46)
  • 如何在Spring Boot服务端实现公网远程调试并进行HTTP服务监听

    转载自cpolar内网穿透的文章:Springboot服务端接口公网远程调试,并实现HTTP服务监听 前后端分离项目中,在调用接口调试时候,我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试,本次教程我们以Java服务端接口为例。 JDK1.8 IDEA SpringBoot Maven Tomcat9.

    2024年02月06日
    浏览(115)
  • 01、uwsgi、gunicorn如何实现优雅重启

    在实际开发过程中,我们会不断迭代升级产品,每次迭代后,都需要在线上服务器更新代码。一般小公司的迭代升级,是没有做到像金丝雀发布或者使用到kubernetes这些东西的。那如何保证更新的时候,之前接收到的请求能够正常处理完成呢,这个时候就需要实现优雅重启了。

    2023年04月10日
    浏览(35)
  • 如何通过内网穿透实现外部网络对Spring Boot服务端接口的HTTP监听和调试?

    前后端分离项目中,在调用接口调试时候,我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试,本次教程我们以Java服务端接口为例。 JDK1.8 IDEA SpringBoot Maven Tomcat9.0 Postman 搭建一个springboot服务的项目,编写一个接口,为了更好直观看到,这里创建一个p

    2024年02月10日
    浏览(52)
  • 互联网大厂技术-HTTP请求-Springboot整合Feign更优雅地实现Http服务调用 no suitable HttpMessageConverter found for response type

    目录 一、SpringBoot快速整合Feign 1.添加Pom依赖 2.启动类添加注解 3.引用Feign服务 二、为请求添加Header的3种方式 1.添加固定header 2.通过接口签名添加header 3.动态添加header 三、为请求添加超时配置 1.默认超时时间 3.超时异常 4.全局超时配置 5.为单个服务设置超时配置 四、为请求配

    2024年02月11日
    浏览(53)
  • Golang优雅实现按比例切分流量

    我们在进行灰度发布时,往往需要转发一部分流量到新上线的服务上,进行小规模的验证,随着功能的不断完善,我们也会逐渐增加转发的流量,这就需要按比例去切分流量,那么如何实现流量切分呢? 我们很容易想到通过生成随机数方式进行实现,通过判断生成随机数是否

    2024年02月04日
    浏览(39)
  • golang平滑重启库overseer实现原理

    overseer主要完成了三部分功能: 1、连接的无损关闭,2、连接的平滑重启,3、文件变更的自动重启。 下面依次讲一下: golang官方的net包是不支持连接的无损关闭的,当主监听协程退出时,并不会等待各个实际work协程的处理完成。 以下是golang官方代码: Go/src/net/http/server.go

    2024年02月07日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包