每日一Go-51、Go微服务--API网关-Kong

一、什么是微服务的API网关?

微服务的 API 网关是系统对外的****唯一入口 ,负责统一接收请求,并完成鉴权、路由、限流、协议转换 等通用能力,再将请求转发给内部微服务。它的作用是屏蔽内部复杂性,保护系统稳定,让业务服务专注业务本身

二、API网关能解决哪些核心问题?

  1. 统一入口

客户端只需要访问统一地址:

js 复制代码
api.your-domain.com

内部发生什么变化,客户端完全不感知。这是微服务"可演进"的前提。

  1. 统一鉴权
  • API网关统一完成:

  • JWT/OAuth2校验

  • 用户身份解析

  • 权限控制

Go服务只关心:

css 复制代码
uid := c.Value("uid")

鉴权是入口问题,不是业务问题

  1. 统一限流与防护

API网关可以统一做:

  • 全局限流

  • 单用户限流

  • IP限流

  • 防刷防爬

把流量风险挡在系统外。

  1. 统一协议与路由

API网关可以:

  • HTTP -> gRPC

  • REST -> RPC

  • 路径重写

  • 版本控制

这让Go微服务可以:

  • 内部专心用gRPC

  • 外部保持HTTP友好

  1. 服务聚合(BFF场景)

一个接口需要:

  • 用户信息

  • 订单信息

  • 推荐数据

API网关/BFF可以:

  • 并发调用多个服务

  • 聚合后返回

客户端请求次数大幅减少。

  1. 流量治理与发布能力

API网关可以:

  • 灰度发布

  • 金丝雀发布

  • A/B测试

  • 熔断、降级

这是成熟系统必备能力

三、API网关Kong实例(文末有源码地址)

  1. 编写docker-compose.yml(./docker-compose.yml)
bash 复制代码
networks:
    codee_jun:
        driver: bridge
      # external: true
services:
    consul:
        container_name: consul
        image: consul:1.15.4
        ports:
            - 8500:8500
        restart: always
        networks:
            - codee_jun
    kong:
        container_name: kong
        image: kong:ubuntu
        ports:
            - 8000:8000
            - 8001:8001
        environment:
          KONG_DATABASE: "off"
          KONG_DECLARATIVE_CONFIG: /kong/kong.yml
          KONG_PROXY_ACCESS_LOG: /dev/stdout
          KONG_ADMIN_ACCESS_LOG: /dev/stdout
          KONG_PROXY_ERROR_LOG: /dev/stderr
          KONG_ADMIN_ERROR_LOG: /dev/stderr
          KONG_ADMIN_LISTEN: 0.0.0.0:8001
        depends_on:
            - consul
        volumes:
            - ./kong/kong.yml:/kong/kong.yml
        restart: always
        networks:
            - codee_jun
    user-server:
        container_name: user-server
        build:
            context: ./user-server
            dockerfile: Dockerfile
        ports:
            - 8080:8080
        depends_on:
            - consul
        restart: always
        networks:
            - codee_jun
    order-server:
        container_name: order-server
        build:
            context: ./order-server
            dockerfile: Dockerfile
        ports:
            - 8081:8081
        depends_on:
            - consul
        restart: always
        networks:
            - codee_jun
  1. Kong配置文件(./kong/kong.yml)
perl 复制代码
# Kong 声明式配置文件版本
# 表示该配置文件遵循 Kong 3.x 的格式规范
_format_version: "3.0"
# services 定义的是「Kong 要代理的后端服务」
# 每一个 service,通常对应一个真实的微服务
services:
    # service 的唯一名称,用于:
    # - 管理
    # - 绑定路由
    # - 挂载插件(限流、鉴权等)
  - name: user-service
    # 后端真实服务地址
    # Kong 会把匹配到的请求转发到这个地址
    # 这里是一个 Go 用户服务,监听在 8080 端口
    url: http://192.168.1.31:8080
    # routes 定义「什么样的请求会被转发到这个 service」
    routes:
      - name: user-route
        # route 名称,仅用于管理和排查问题
        # paths 表示路径匹配规则
        # 当请求路径以 /api/user 开头时
        # Kong 会将请求转发给 user-service
        paths:
          - /api/user
          
  - name: order-service
    url: http://192.168.1.31:8081
    routes:
      - name: order-route
        paths:
          - /api/order
# methods 表示 HTTP 方法匹配规则
  1. 启动Kong
js 复制代码
docker-compose up -d 
  1. 编写一个user-server服务(./user-server/main.go)
go 复制代码
package main
import (
    ...
)
const (
    srvName = "golang_per_day_51:user-server"
    srvHost = "user-server" //这里写user-server,方便在docker-compose中使用
    srvPort = 8080
    srvID   = "golang_per_day_51-user-server:8080"
)
var (
    appConfig *Config
    lock      sync.RWMutex
)
type Config struct {
    Consul ConsulConfig `mapstructure:"consul" json:"consul"`
    Redis  RedisConfig  `mapstructure:"redis" json:"redis"`
}
type RedisConfig struct {
    Host     string `mapstructure:"host" json:"host"`
    Port     int    `mapstructure:"port" json:"port"`
    Db       int    `mapstructure:"db" json:"db"`
    Password string `mapstructure:"password" json:"password"`
}
type ConsulConfig struct {
    Host string `mapstructure:"host" json:"host"`
    Port int    `mapstructure:"port" json:"port"`
}
func main() {
    r := gin.Default()
    // 定义一个 ping 接口
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong from user-server",
        })
    })
    // 1. 定义健康检查接口
    r.GET("/health", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "status": "ok",
        })
    })
    // 2. 读取配置中心的配置,这里用Consul
    config := api.DefaultConfig()
    config.Address = "192.168.1.31:8500" // Consul 的地址
    client, _ := api.NewClient(config)
    kvPath := "config/golang_per_day/51/user"
    loadConfig(client, kvPath)
    go watchConfig(client, kvPath)
    // 4. 启动 Gin 服务
    go func() {
        if err := r.Run(fmt.Sprintf(":%d", srvPort)); err != nil {
            log.Fatal("服务启动失败: ", err)
        }
    }()
    // 5. 优雅退出:监听信号注销服务
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    // 退出前注销服务
    deregisterFromConsul()
    log.Println("服务已退出并从 Consul 注销")
}
// 注册逻辑
func registerToConsul() {
    config := api.DefaultConfig()
    config.Address = fmt.Sprintf("%s:%d", appConfig.Consul.Host, appConfig.Consul.Port) // Consul 的地址
    client, _ := api.NewClient(config)
    registration := &api.AgentServiceRegistration{
        ID:      srvID,
        Name:    srvName,
        Address: srvHost,
        Port:    srvPort,
        Check: &api.AgentServiceCheck{
            HTTP:     fmt.Sprintf("http://%s:%d/health", srvHost, srvPort),
            Interval: "5s", // 每 5 秒检查一次
            Timeout:  "3s", // 超时时间
        },
    }
    err := client.Agent().ServiceRegister(registration)
    if err != nil {
        log.Fatal("注册失败: ", err)
    }
    log.Println("服务注册成功!")
}
// 注销逻辑
func deregisterFromConsul() {
    config := api.DefaultConfig()
    client, _ := api.NewClient(config)
    _ = client.Agent().ServiceDeregister(srvID)
}
// 读取配置中心的 Redis 配置
func loadConfig(client *api.Client, path string) {
    pair, _, err := client.KV().Get(path, nil)
    if err != nil {
        log.Fatal("读取配置失败: ", err)
    }
    v := viper.New()
    v.SetConfigType("yaml")
    err = v.ReadConfig(bytes.NewBuffer(pair.Value))
    if err != nil {
        log.Fatal("读取配置失败: ", err)
    }
    newConfig := Config{}
    err = v.Unmarshal(&newConfig)
    if err != nil {
        log.Fatal("解析配置失败: ", err)
    }
    lock.Lock()
    defer lock.Unlock()
    appConfig = &newConfig
    log.Println("Redis 配置更新: ", appConfig)
    // 3. 注册服务到 Consul
    registerToConsul()
}
// 监听 Consul K/V 变化
func watchConfig(client *api.Client, path string) {
    var lastIndex uint64
    for {
        // Consul 的 WaitIndex 机制:如果有变化会立即返回,否则阻塞直到超时
        pair, meta, err := client.KV().Get(path, &api.QueryOptions{
            WaitIndex: lastIndex,
        })
        if err != nil {
            log.Printf("监听出错: %v", err)
            continue
        }
        if pair != nil && meta.LastIndex > lastIndex {
            lastIndex = meta.LastIndex
            loadConfig(client, path)
        }
    }
}
  1. 再加一个order-server(./order-server/main.go)
go 复制代码
package main
import (
    ...
)
const (
    srvName = "golang_per_day_51:order-server"
    srvHost = "order-server" //这里写order-server,方便在docker-compose中使用
    srvPort = 8081
    srvID   = "golang_per_day_51-order-server:8081"
)
...
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong from order-server",
        })
    })
...
  1. 启动整个项目服务
css 复制代码
docker-compose up -d --build

可以看见consul和kong都已经启动成功,user-server和order-server都还在不断重启,那是因为没有读到consul的kv配置。

  1. 在consul中配置kv
javascript 复制代码
- config/golang_per_day/51/user
```yaml
consul: 
 host: consul
 port: 8500
redis:
 addr: 192.168.1.31
 port: 6379
 password: "123456"
 db: 1
```
- config/golang_per_day/51/order 
```yaml
consul: 
 host: consul
 port: 8500
redis:
 addr: 192.168.1.31
 port: 6379
 password: "123456"
 db: 2
```

配置完成后,等待微服务重启几次,就可以看见服务好了

  1. 在浏览器分别访问http://localhost:8000/api/user/ping和http://localhost:8000/api/order/ping,可以看见网关访问了不同的服务

四、API网关和Go微服务的边界

API网关应该做以下事情:路由、鉴权、限流、熔断、协议转换;

不应该做:写业务逻辑、访问数据库、领域计算

API网关只做"控制流",不做"业务流"。

五、什么时候需要上API网关?

当你的Go微服务数量≥3的时候,你就应该上API网关了。

六、一句话总结

  • API 网关,是微服务系统的门

  • 复杂性应该止步于入口

  • 没有 API 网关的微服务,是伪微服务

*源码地址* 私给


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!

相关推荐
梦想与想象-广州大智汇2 小时前
告别“内存刺客”!sync-canal-go:轻量mysql实时同步数据到Elasticsearch‌,clickhouse,redis
mysql·elasticsearch·golang·同步数据
波波0072 小时前
每日一题:什么是CQRS,在微服务中如何应用
微服务·架构
大罗LuoSir2 小时前
分布式微服务全貌了解-整体架构、特征和需关注解决的问题
java·缓存·微服务·zookeeper·容器·服务发现·负载均衡
lolo大魔王2 小时前
Go语言的defer语句和Test功能测试函数
开发语言·后端·golang
aXin_ya2 小时前
微服务 第一天
java·运维·微服务
lolo大魔王3 小时前
Go语言的结构体
开发语言·后端·golang
aXin_ya3 小时前
微服务 第二天
java·数据库·微服务
码界奇点4 小时前
基于Spring Boot的插件化微服务热更新系统设计与实现
spring boot·后端·微服务·架构·毕业设计·源代码管理
XMYX-04 小时前
14 - Go 结构体(struct):从基础到高级实战
开发语言·golang