本文是个人在学习书籍《Go 语言高并发与微服务实战》第9章内容过程中,动手做的小实验,涉及 Consul、go-kit、Kong、JWT、Zipkin 相关知识。
微服务网关概述
┌─────────────────────────────────────────────────────────────┐
│ 微服务网关架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ API Gateway │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ 路由转发 │ 负载均衡 │ 限流熔断 │ 认证鉴权 │ 监控日志 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────┬───────────┼───────────┬──────────┐ │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌─────────┐┌─────────┐┌─────────┐┌─────────┐┌─────────┐ │
│ │User Svc ││Order Svc││Product ││Payment ││Search │ │
│ │ ││ ││Service ││Service ││Service │ │
│ └─────────┘└─────────┘└─────────┘└─────────┘└─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
网关核心功能
| 功能 | 说明 |
|---|---|
| 路由转发 | 根据请求路径转发到后端服务 |
| 负载均衡 | 多实例流量分发 |
| 限流熔断 | 保护后端服务 |
| 认证鉴权 | JWT/Session 验证 |
| 监控日志 | 请求追踪和日志记录 |
简易网关 Go 实现
项目结构
gateway/
├── main.go # 主程序
├── proxy.go # 反向代理
├── discovery.go # 服务发现
├── limiter.go # 限流器
├── circuitbreaker.go # 熔断器
└── auth.go # 认证中间件
主程序
// gateway/main.go
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"net/http/httputil"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/go-kit/kit/log"
"github.com/hashicorp/consul/api"
)
func main() {
// 解析命令行参数
var (
consulHost = flag.String("consul.host", "127.0.0.1", "consul server host")
consulPort = flag.String("consul.port", "8500", "consul server port")
gatewayPort = flag.String("gateway.port", "9090", "gateway port")
)
flag.Parse()
// 创建日志组件
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
// 创建 Consul 客户端
consulConfig := api.DefaultConfig()
consulConfig.Address = fmt.Sprintf("http://%s:%s", *consulHost, *consulPort)
consulClient, err := api.NewClient(consulConfig)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
// 创建处理器
discovery := NewDiscovery(consulClient, logger)
handler := NewProxyHandler(discovery, logger)
// 创建 HTTP 服务器
server := &http.Server{
Addr: fmt.Sprintf(":%s", *gatewayPort),
Handler: handler,
}
// 启动服务器
go func() {
logger.Log("gateway", "starting", "port", *gatewayPort)
if err := server.ListenAndServe(); err != nil {
logger.Log("gateway", "exit", "err", err)
}
}()
// 优雅退出
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
logger.Log("signal", <-ch)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Log("shutdown", err)
}
}
服务发现
// gateway/discovery.go
package main
import (
"context"
"github.com/go-kit/kit/log"
"github.com/hashicorp/consul/api"
"math/rand"
)
type Discovery struct {
client consul.Client
logger log.Logger
}
func NewDiscovery(client consul.Client, logger log.Logger) *Discovery {
return &Discovery{
client: client,
logger: logger,
}
}
// GetService 获取服务实例
func (d *Discovery) GetService(ctx context.Context, serviceName string) (*api.ServiceEntry, error) {
entries, _, err := d.client.Health().Service(serviceName, "", true, nil)
if err != nil {
return nil, err
}
if len(entries) == 0 {
return nil, fmt.Errorf("no available instances for %s", serviceName)
}
// 负载均衡:随机选择
index := rand.Intn(len(entries))
return &entries[index], nil
}
// GetServices 获取所有服务实例
func (d *Discovery) GetServices(ctx context.Context, serviceName string) ([]*api.ServiceEntry, error) {
entries, _, err := d.client.Health().Service(serviceName, "", true, nil)
if err != nil {
return nil, err
}
return entries, nil
}
反向代理
// gateway/proxy.go
package main
import (
"context"
"fmt"
"github.com/go-kit/kit/log"
"net/http"
"net/http/httputil"
"strings"
)
type ProxyHandler struct {
discovery *Discovery
logger log.Logger
}
func NewProxyHandler(discovery *Discovery, logger log.Logger) *ProxyHandler {
return &ProxyHandler{
discovery: discovery,
logger: logger,
}
}
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 解析请求路径:/service-name/path
path := r.URL.Path
parts := strings.SplitN(path, "/", 3)
if len(parts) < 3 {
http.Error(w, "invalid path", http.StatusBadRequest)
return
}
serviceName := parts[1]
targetPath := "/" + parts[2]
// 获取服务实例
instance, err := h.discovery.GetService(r.Context(), serviceName)
if err != nil {
h.logger.Log("service", serviceName, "err", err)
http.Error(w, "service unavailable", http.StatusServiceUnavailable)
return
}
// 构建代理请求
targetURL := fmt.Sprintf("http://%s:%d%s",
instance.Service.Address,
instance.Service.Port,
targetPath)
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
if err != nil {
http.Error(w, "proxy error", http.StatusInternalServerError)
return
}
// 复制请求头
for key, values := range r.Header {
for _, value := range values {
proxyReq.Header.Add(key, value)
}
}
// 执行代理
client := &http.Client{}
resp, err := client.Do(proxyReq)
if err != nil {
h.logger.Log("proxy", err)
http.Error(w, "proxy error", http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 复制响应
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
w.WriteHeader(resp.StatusCode)
w.Write([]byte(fmt.Sprintf("Proxy to: %s", targetURL)))
}
// 反向代理实现
func newSingleHostReverseProxy(target string) *httputil.ReverseProxy {
return httputil.NewSingleHostReverseProxy(target)
}
Kong 网关
Kong 简介
Kong 是一个云原生、可扩展、统一 API 层(API Gateway)。
Docker 部署
下面给出「单机演示」的常见步骤:创建网络 → 启动数据库 → (按所用 Kong 镜像文档执行)数据库迁移 → 启动 Kong。具体镜像名与启动参数请以你选用的 Kong 发行版 README 为准,不同主版本 CLI 与环境变量略有差异。
docker network create kong-net
docker run -d --name kong-db \
--network kong-net \
-p 5432:5432 \
-e POSTGRES_USER=kong \
-e POSTGRES_PASSWORD=kong \
-e POSTGRES_DB=kong \
postgres:13
# 数据库就绪后,在同一网络内执行 kong migrations bootstrap(命令随镜像而异),再启动 kong 网关与管理端口。
# 典型端口:8000(代理)、8443(HTTPS)、8001(Admin API)。正式环境请改用编排模板并开启 TLS / RBAC。
本地体验也可使用 Kong Ingress Controller(Kubernetes)或云端托管网关服务;本节重点在于:网关前面负责 TLS、路由、插件(鉴权、限流、熔断),后面的微服务仍可沿用前文自定义代理或 Istio Linkerd 等服务网格。
小结
网关层把「路由、观测、防护」收口在一处:开发阶段可用 Nginx / 手写反向代理入门,业务量上升后再引入 Kong / APISIX / Envoy 等平台化能力。