Golang健康检查接口的实现

 更新时间:2026年04月21日 09:36:58   作者:穿越時空  
本文主要介绍了Golang健康检查接口的实现,包括存活探针和就绪探针的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Go 语言健康检查接口的设计需要区分 存活探针(Liveness)就绪探针(Readiness),两者在 Kubernetes 中的行为截然不同。以下是完整的设计指南和代码实现。

1. 三种探针的区别

探针类型职责失败后果适用场景
Liveness(存活)进程是否活着重启容器检测死锁、内存泄漏导致的僵死
Readiness(就绪)是否准备好接收流量从负载均衡移除初始化未完成、依赖服务不可用
Startup(启动)慢启动应用是否已启动禁用其他探针启动耗时长的应用(JVM/加载大量数据)

2. Go 健康检查接口实现

基础版本(最小可用)

package main
import (
	"encoding/json"
	"log"
	"net/http"
	"sync/atomic"
	"time"
)
var ready atomic.Bool
func main() {
	go initialize()
	http.HandleFunc("/healthz", livenessHandler)   // 存活探针
	http.HandleFunc("/readyz", readinessHandler)   // 就绪探针
	http.HandleFunc("/startupz", startupHandler)   // 启动探针(可选)
	log.Println("Server starting on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
func initialize() {
	log.Println("Initializing...")
	time.Sleep(5 * time.Second) // 模拟启动耗时
	ready.Store(true)
	log.Println("Ready")
}
// 存活探针:只检查进程是否活着,不要检查外部依赖
func livenessHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "alive"})
}
// 就绪探针:检查应用是否准备好接收流量
func readinessHandler(w http.ResponseWriter, r *http.Request) {
	if !ready.Load() {
		w.WriteHeader(http.StatusServiceUnavailable)
		json.NewEncoder(w).Encode(map[string]string{"status": "not ready"})
		return
	}
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
}
// 启动探针:用于慢启动应用
func startupHandler(w http.ResponseWriter, r *http.Request) {
	if !ready.Load() {
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}
	w.WriteHeader(http.StatusOK)
}

生产级版本(带依赖检查)

package health
import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"net/http"
	"sync"
	"time"
)
// Status 健康状态
type Status string
const (
	StatusHealthy   Status = "healthy"
	StatusUnhealthy Status = "unhealthy"
	StatusDegraded  Status = "degraded"
)
// CheckResult 单个检查项结果
type CheckResult struct {
	Name     string        `json:"name"`
	Status   Status        `json:"status"`
	Message  string        `json:"message,omitempty"`
	Duration time.Duration `json:"duration_ms,omitempty"`
}
// HealthResponse 整体健康响应
type HealthResponse struct {
	Status    Status        `json:"status"`
	Timestamp time.Time     `json:"timestamp"`
	Checks    []CheckResult `json:"checks,omitempty"`
}
// Checker 依赖检查器接口
type Checker interface {
	Name() string
	Check(ctx context.Context) CheckResult
}
// Service 健康检查服务
type Service struct {
	checkers []Checker
	timeout  time.Duration
	mu       sync.RWMutex
	ready    bool
}
func NewService(timeout time.Duration) *Service {
	return &Service{timeout: timeout}
}
func (s *Service) Register(checkers ...Checker) {
	s.checkers = append(s.checkers, checkers...)
}
func (s *Service) SetReady(ready bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.ready = ready
}
func (s *Service) IsReady() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.ready
}
// LivenessHandler 存活探针 - 只检查进程是否活着
func (s *Service) LivenessHandler(w http.ResponseWriter, r *http.Request) {
	// 存活探针必须保持简单,不要检查外部依赖
	// 否则数据库故障会导致所有 Pod 被重启,引发级联故障 [^15^]
	response := HealthResponse{
		Status:    StatusHealthy,
		Timestamp: time.Now(),
	}
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(response)
}
// ReadinessHandler 就绪探针 - 检查依赖是否就绪
func (s *Service) ReadinessHandler(w http.ResponseWriter, r *http.Request) {
	if !s.IsReady() {
		w.WriteHeader(http.StatusServiceUnavailable)
		json.NewEncoder(w).Encode(HealthResponse{
			Status:    StatusUnhealthy,
			Timestamp: time.Now(),
			Message:   "service not initialized",
		})
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), s.timeout)
	defer cancel()
	results := make([]CheckResult, 0, len(s.checkers))
	overallStatus := StatusHealthy
	for _, checker := range s.checkers {
		start := time.Now()
		result := checker.Check(ctx)
		result.Duration = time.Since(start)
		results = append(results, result)
		if result.Status == StatusUnhealthy {
			overallStatus = StatusUnhealthy
		} else if result.Status == StatusDegraded && overallStatus == StatusHealthy {
			overallStatus = StatusDegraded
		}
	}
	response := HealthResponse{
		Status:    overallStatus,
		Timestamp: time.Now(),
		Checks:    results,
	}
	if overallStatus == StatusUnhealthy {
		w.WriteHeader(http.StatusServiceUnavailable)
	} else {
		w.WriteHeader(http.StatusOK)
	}
	json.NewEncoder(w).Encode(response)
}
// ========== 具体检查器实现 ==========
// DatabaseChecker 数据库连接检查
type DatabaseChecker struct {
	DB *sql.DB
}
func (d *DatabaseChecker) Name() string { return "database" }
func (d *DatabaseChecker) Check(ctx context.Context) CheckResult {
	// 使用 PingContext 而非复杂查询,避免影响性能 [^15^]
	err := d.DB.PingContext(ctx)
	if err != nil {
		return CheckResult{
			Name:    d.Name(),
			Status:  StatusUnhealthy,
			Message: err.Error(),
		}
	}
	return CheckResult{
		Name:   d.Name(),
		Status: StatusHealthy,
	}
}
// RedisChecker Redis 连接检查
type RedisChecker struct {
	// redis.Client
}
func (r *RedisChecker) Name() string { return "redis" }
func (r *RedisChecker) Check(ctx context.Context) CheckResult {
	// 实现类似 DatabaseChecker
	return CheckResult{Name: r.Name(), Status: StatusHealthy}
}
// ExternalAPIChecker 外部 API 检查
type ExternalAPIChecker struct {
	URL    string
	Client *http.Client
}
func (e *ExternalAPIChecker) Name() string { return "external-api" }
func (e *ExternalAPIChecker) Check(ctx context.Context) CheckResult {
	req, _ := http.NewRequestWithContext(ctx, "GET", e.URL, nil)
	resp, err := e.Client.Do(req)
	if err != nil {
		return CheckResult{
			Name:    e.Name(),
			Status:  StatusDegraded, // 外部服务不可用,标记为降级而非不可用
			Message: err.Error(),
		}
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return CheckResult{Name: e.Name(), Status: StatusHealthy}
	}
	return CheckResult{
		Name:    e.Name(),
		Status:  StatusDegraded,
		Message: "status: " + resp.Status,
	}
}

3. 优雅关闭集成

package main
import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)
func main() {
	healthSvc := health.NewService(3 * time.Second)
	healthSvc.Register(
		&health.DatabaseChecker{DB: db},
		&health.RedisChecker{},
	)
	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", healthSvc.LivenessHandler)
	mux.HandleFunc("/readyz", healthSvc.ReadinessHandler)
	server := &http.Server{Addr: ":8080", Handler: mux}
	// 启动服务
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatal(err)
		}
	}()
	// 等待信号
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	<-sigChan
	// 优雅关闭:先标记为未就绪,停止接收新流量
	log.Println("Shutting down gracefully...")
	healthSvc.SetReady(false)
	// 等待负载均衡器更新(匹配 K8s readiness probe 周期)
	time.Sleep(10 * time.Second)
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	if err := server.Shutdown(ctx); err != nil {
		log.Printf("Server forced to shutdown: %v", err)
	}
	log.Println("Server exited")
}

4. Kubernetes 配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-go-app
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: app
          image: my-go-app:latest
          ports:
            - containerPort: 8080
          # 存活探针:只检查进程是否活着
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 10   # 启动后等待时间
            periodSeconds: 10          # 检查频率
            timeoutSeconds: 3          # 超时时间(必须 < 代码中的 timeout)
            failureThreshold: 3        # 连续失败次数才重启 [^12^][^15^]
          # 就绪探针:检查依赖是否就绪
          readinessProbe:
            httpGet:
              path: /readyz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5           # 检查频率更高,快速切换流量
            timeoutSeconds: 3
            failureThreshold: 3
          # 启动探针:保护慢启动应用(可选)
          startupProbe:
            httpGet:
              path: /readyz
              port: 8080
            failureThreshold: 30       # 最多允许 30 次失败
            periodSeconds: 10          # 总启动时间 = 30 * 10 = 300s
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]  # 给优雅关闭留时间

5. 关键最佳实践 

✅ 应该做的

  • 存活探针保持简单:只检查进程是否响应,不要检查数据库/Redis,避免级联重启
  • 就绪探针检查依赖:数据库、缓存、配置是否加载完成
  • 超时设置合理:代码中的 timeout < K8s timeoutSeconds,通常 2-3 秒
  • 使用原子操作atomic.Boolsync.RWMutex 管理就绪状态
  • 记录状态变更:健康状态变化时打印日志,便于排查

❌ 不应该做的

  • 不要在存活探针里做 DB Ping:数据库故障会导致所有 Pod 无限重启
  • 不要做全表扫描或复杂查询:健康检查频率高,避免影响性能
  • 不要返回过多信息:探针失败时 K8s 只关心状态码,详细日志用于调试即可
  • 不要把外部服务失败标记为 Unhealthy:建议用 Degraded,避免流量全部切走

6. gRPC 健康检查

如果服务使用 gRPC,需要实现标准健康检查协议:

import (
	"google.golang.org/grpc/health/grpc_health_v1"
)
type HealthService struct{}
func (h *HealthService) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
	return &grpc_health_v1.HealthCheckResponse{
		Status: grpc_health_v1.HealthCheckResponse_SERVING,
	}, nil
}
func (h *HealthService) Watch(req *grpc_health_v1.HealthCheckRequest, stream grpc_health_v1.Health_WatchServer) error {
	return nil
}
// 注册
grpc_health_v1.RegisterHealthServer(grpcServer, &HealthService{})

Kubernetes v1.25+ 原生支持 gRPC 探针:

livenessProbe:
  grpc:
    port: 50051
    service: "my-service"

到此这篇关于Golang健康检查接口的实现的文章就介绍到这了,更多相关Golang健康检查接口内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文带你掌握GoLang中的指针

    一文带你掌握GoLang中的指针

    针是 Go 编程语言的重要组成部分,它们提供了一种直接引用和操作内存中数据的方法,在这篇文章中,我们将探讨一些技巧和技巧,以掌握 GoLang 中的指针并编写高效的代码,需要的可以参考一下
    2023-05-05
  • Golang中Gin框架的使用入门教程

    Golang中Gin框架的使用入门教程

    这篇文章主要为大家详细介绍了Golang中Gin框架的使用教程,文中通过简单的示例为大家讲解了Gin框架的安装与使用,感兴趣的小伙伴开业跟随小编一起学习一下
    2022-10-10
  • golang http请求未释放造成的错误问题

    golang http请求未释放造成的错误问题

    这篇文章主要介绍了golang http请求未释放造成的错误问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • go gin中间件关于 c.next()、c.abort()和return的使用小结

    go gin中间件关于 c.next()、c.abort()和return的使用小结

    中间件的执行顺序是按照注册顺序执行的,中间件可以通过 c.abort() + retrurn 来中止当前中间件,后续中间件和处理器的处理流程, 这篇文章给大家介绍go gin中间件关于 c.next()、c.abort()和return的使用小结,感兴趣的朋友跟随小编一起看看吧
    2024-03-03
  • go语言K8S 的 informer机制浅析

    go语言K8S 的 informer机制浅析

    这篇文章为大家主要介绍了go语言K8S 的 informer机制浅析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Go中的格式化字符串fmt.Sprintf()和fmt.Printf()使用示例

    Go中的格式化字符串fmt.Sprintf()和fmt.Printf()使用示例

    这篇文章主要为大家介绍了Go中的格式化字符串fmt.Sprintf()和fmt.Printf()使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • golang踩坑实战之channel的正确使用方式

    golang踩坑实战之channel的正确使用方式

    Golang channel是Go语言中一个非常重要的特性,除了用来处理并发编程的任务中,它还可以用来进行消息传递和事件通知,这篇文章主要给大家介绍了关于golang踩坑实战之channel的正确使用方式,需要的朋友可以参考下
    2023-06-06
  • Go Redis客户端使用的两种对比

    Go Redis客户端使用的两种对比

    这篇文章主要为大家介绍了Go Redis客户端使用对比详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Golang连接Redis数据库的方法

    Golang连接Redis数据库的方法

    这篇文章主要介绍了Golang连接Redis数据库的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • 使用Go实现一个百行聊天服务器的示例代码

    使用Go实现一个百行聊天服务器的示例代码

    前段时间, redis作者整了个c语言版本的聊天服务器,代码量拢共不过百行,于是, 心血来潮下, 我也整了个Go语言版本, 简单来说就是实现了一个聊天室的功能,文中通过代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2023-12-12

最新评论