1)概述:
http服务是一个独立协程,也有一个独立协程在监听操作系统信号,通过github.com/oklog/run包组合起来协同,当监听操作系统系统信号的协程接收到杀死信号则会结束本协程,此时http服务协程也会自动结束。使用github.com/oklog/run包来组合多个相对独立又相互关联的协程,是非常方便的。
代码:https://github.com/gzlj/http-demo.git
2)代码结构
3)主要代码
http-demo/cmd/gracehttp-demo/main.go文件
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/gzlj/http-demo/pkg/prober"
"github.com/oklog/run"
"github.com/pkg/errors"
httpserver "github.com/gzlj/http-demo/pkg/server/http"
)
const (
logFormatLogfmt = "logfmt"
logFormatJson = "json"
)
func main() {
var logFormat string = "logfmt"
logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
if logFormat == logFormatJson {
logger = log.NewJSONLogger(log.NewSyncWriter(os.Stdout))
}
logger = log.With(logger, "ts", log.DefaultTimestamp)
logger = log.With(logger, "caller", log.DefaultCaller)
httpProbe := prober.NewHTTP()
probers := prober.Combine(
httpProbe,
)
//返回http服务结构体实例,它封装了原生http服务
srv := httpserver.New(
logger,
"test-http",
httpProbe,
httpserver.WithListen(":80"),
httpserver.WithGracePeriod(time.Duration(5)),
)
var g run.Group
//启动http服务
g.Add(
//此方法是actor的execute()方法
func() error {
probers.Healthy()
return srv.ListenAndServe()
},
//此方法是actor的interrupt()方法
func(err error) {
probers.NotReady(err)
defer probers.NotHealthy(err)
//平滑关闭http服务
srv.Shutdown(err)
})
//监听来自操作系统的杀死信号
{
cancel := make(chan struct{})
g.Add(func() error {
return interrupt(logger, cancel)
}, func(error) {
close(cancel)
})
}
//Run方法中,先遍历所有actor执行actor的execute()方法
//一旦有一个actor返回error接口(值可能是nil),则遍历actor调用其interrupt()方法(入参都是这个error接口 )
//interrupt()中往往记录日志以及平滑关闭actor
//Run方法最后部分是等待所有剩余actor执行退出
//Run方法返回的是第一个actor返回的error接口
if err := g.Run(); err != nil {
level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "command run failed")))
os.Exit(1)
}
level.Info(logger).Log("msg", "exiting")
}
func interrupt(logger log.Logger, cancel <-chan struct{}) error {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
//接收到杀死信号或结束信号时,进行方法返回
select {
case s := <-c:
level.Info(logger).Log("msg", "caught signal. Beginng to exit.", "signal", s)
return nil
case <-cancel:
return errors.New("canceled")
}
}
http-demo/pkg/server/http/http.go
package http
import (
"context"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/gzlj/http-demo/pkg/prober"
"github.com/pkg/errors"
"net/http"
)
// A Server defines parameters for serve HTTP requests, a wrapper around http.Server.
type Server struct {
logger log.Logger
prober *prober.HTTPProbe
mux *http.ServeMux
srv *http.Server
opts options
}
// constructor
func New(logger log.Logger, name string, prober *prober.HTTPProbe, opts ...Option) *Server {
options := options{}
for _, o := range opts {
o.apply(&options)
}
mux := http.NewServeMux()
registerProbes(mux, prober, logger)
return &Server{
logger: log.With(logger, "service", "http/server", "component", name),
prober: prober,
mux: mux,
srv: &http.Server{Addr: options.listen, Handler: mux},
opts: options,
}
}
func registerProbes(mux *http.ServeMux, p *prober.HTTPProbe, logger log.Logger) {
if p != nil {
mux.Handle("/-/healthy", p.HealthyHandler(logger))
mux.Handle("/-/ready", p.ReadyHandler(logger))
}
}
func (s *Server) ListenAndServe() error {
level.Info(s.logger).Log("msg", "listening for requests and metrics", "address", s.opts.listen)
return errors.Wrap(s.srv.ListenAndServe(), "serve HTTP and metrics")
}
func (s *Server) Shutdown(err error) {
level.Info(s.logger).Log("msg", "internal server is shutting down", "err", err)
if err == http.ErrServerClosed {
level.Warn(s.logger).Log("msg", "internal server closed unexpectedly")
return
}
if s.opts.gracePeriod == 0 {
s.srv.Close()
level.Info(s.logger).Log("msg", "internal server is shutdown", "err", err)
return
}
ctx, cancel := context.WithTimeout(context.Background(), s.opts.gracePeriod)
defer cancel()
if err := s.srv.Shutdown(ctx); err != nil {
level.Error(s.logger).Log("msg", "internal server shut down failed", "err", err)
return
}
level.Info(s.logger).Log("msg", "internal server is shutdown gracefully", "err", err)
}
func (s *Server) Handle(pattern string, handler http.Handler) {
s.mux.Handle(pattern, handler)
}
4)效果
访问http服务的健康接口:
往http服务发送杀死信号: