GateKeeper一个不依赖分布式数据库的 API 网关

e421083458 · 2019-10-19 21:31:23 · 1941 次点击 · 预计阅读时间 11 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2019-10-19 21:31:23 的文章,其中的信息可能已经有所发展或是发生改变。

GateKeeper

GateKeeper 是一个使用 Go (golang) 编写的不依赖分布式数据库的 API 网关, 使用它可以高效进行服务代理 以及 在线化服务配置并且你无需重启服务器。 项目地址:https://github.com/didi/gatekeeper

特性

  • httptcp服务代理
  • 服务发现
  • 加权负载轮询
  • URL地址重写
  • 服务限流:支持独立IP限流
  • 高拓展性:支持自定义 请求前验证request方法请求后更改response方法tcp中间件http中间件 等。
  • 高可用性:单个服务异常不影响其他服务。
  • 最少依赖:无需任何额外组件即可运行,mysqlredis 只做管理和统计使用可随时关闭。

内容

快速开始


安装GateKeeper之前,需要安装Go环境 (golang版本>=1.11), 如果需要界面管理则需要 mysqlredis支持。

  1. clone 代码到本地
git clone git@github.com:didichuxing/gatekeeper.git
  1. 开启 go mod 支持及代理支持
export GO111MODULE=on
export GOPROXY=https://goproxy.cn
  1. 创建 db 并导入数据

如果不使用在线服务接入以及统计功能,可以跳过本步。 默认使用:gatekeeper 作为数据库名

mysql -h localhost -u root -p -e "CREATE DATABASE gatekeeper DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;"
mysql -h localhost -u root -p gatekeeper < install/db.sql --default-character-set=utf8
  1. 调整 mysqlredis 配置文件

修改 ./conf/dev/mysql.toml./conf/dev/redis.toml 为自己的环境配置。

如果不使用在线服务接入,删除 ./conf/dev/mysql.toml./conf/dev/redis.toml 即可。

  1. 运行代码
go run main.go
  1. 登陆管理后台

http://127.0.0.1:8081/admin/login

默认账号密码: admin / 123456

并发压测

压测条件:

  • cpu: Xeon(R) CPU E5-2670 48核心
  • 内存:128G
  • 软件:centos 6.5nginx1.9.9ab压力测试工具
  • 请求地址:单独 golang 服务器,无任何逻辑

压测结果:

image

image

使用手册

  • 集群配置及部署

    集群架构如图 image

    • 结合架构图对每个步骤说明如下:

      1. 用户通过接入层连接到 GateKeeper 实例中。
      2. 每个 GateKeeper 实例,针对每个服务模块,单独进行服务探测。
      3. 在线服务管理时,配置数据先保存到 GateKeeper 配置 DB 中,然后再通过调用配置更新接口( /reload ),更新所有实例机器配置。
    • 接入层一般选用 nginxHaproxyLVS

      nginx作为接入层为例,可根据网络需求确认是否暴露管理地址(/admin)。

        upstream gatekeeper { 
              server 10.90.80.16:8081; 
              server 10.90.80.17:8081; 
        }
        server {
            listen       8007;
            root         /home/webroot/official-website-api/;
            location ^~ /gatekeeper{
                    proxy_pass http://gatekeeper;
                    proxy_set_header Host $host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
        }
      
    • 配置集群

      通过修改 ./conf/dev/base.toml 中的 cluster 节点完成集群配置。

      • cluster_ip

        表示实际对外的访问地址,如果是域名填写域名。

      • cluster_addr

        http 服务需要监听的端口

      • cluster_list

        集群子机器 ip ,多 ip 以逗号分隔

    • 集群配置同步

      通过访问集群子机器的 /reload 接口确保所有代理机器配置统一。

在线管理接入

  • 管理登陆

    登陆账号密码在 ./conf/dev/admin.toml 中配置,使用以下地址登陆。

    http://127.0.0.1:8081/admin/login

  • 服务管理

    • 服务列表 image

      • 服务地址,如:http://10.90.164.31:8081/gatekeeper/test_http

        这里的 10.90.164.31:8081 表示集群地址,可以在 ./conf/dev/base.toml 中设置

      • QPS 集群当前QPS

      • QPD 集群当天总流量
      • NODE 当前可用节点数/总节点数
    • 新增、修改 http服务

      • 访问前缀设置,如:/gatekeeper/test_http

        这里的 /gatekeeper 是整个http对外路由的公共前缀。你可以在 ./conf/dev/base.toml中更改。

      • 探活地址,如:/pingurl重写对该地址无影响

        表示需要探测的目标服务器除去主机信息后的地址。若目标主机为: 10.90.164.31:8072,则真实地址为:http://10.90.164.31:8072/ping ,需要保证该地址访问可以正常返回200状态。

      • 重写规则:如:^/gatekeeper/test_http(.*) $1

        如果访问网关的地址是:http://127.0.0.1:8081/gatekeeper/test_http/ping, 则访目标的地址是: http://10.90.164.31:8072/ping, 如果不重写则访问目标的地址是: http://10.90.164.31:8072/gatekeeper/test_http/ping, 创建时如未填写则自动填写 ^访问前缀(.*) $1

      • 客户端IP限流

        表示单台网关实例当前服务最大允许QPS,0表示不限流。

    • 添加tcp服务:功能设置同http

    • 流量控制:可在针对某台目标机器进行流量关闭

  • 租户管理

    • 租户列表 image

      • 使用租户信息访问下游服务

        基于 app_idsecret 可以计算出签名 sign (为简化操作,我们这里直接使用 secret 作为了签名),然后直接使用get参数传入就可以访问下游服务了。 如: http://127.0.0.1:8081/gatekeeper/test_http/ping?app_id=test_app&sign=62fda0f2212eaffd90dbf04136768c5f

      • 租户鉴权

        参考下文中的 定义请求前验证 request 方法 service.AuthAppToken

      • 新增、修改租户

        接口列表,如:/gatekeeper/test_http。表示通过对应租户id,所有能请求到的接口的前缀。这样就可以起到限制某租户访问对应服务的功能了。目前租户只对http接口起作用。

      • 日请求总量

        表示目前租户,最多允许请求的总量

      • Qps限流

        表示当前租户最大允许QPS,0表示无限制

配置文件接入

  • 纯配置文件接入

    首先确保配置文件中不存在 mysql_map.tomlredis_map.toml, 否则配置会被重启覆盖。

    • 服务配置文件

      参照以下demo,编辑 ./conf/dev/module.toml

      # http服务示例,时间单位ms
      [[module]]
      [module.base]
        load_type = "http" #服务类型
        name = "test_http" #服务标识
        service_name = "test_http" #服务名称
      
      [[module.match_rule]]
        type = "url_prefix" #匹配类型
        rule = "/gatekeeper/test_http" #访问前缀
        url_rewrite = "^/gatekeeper/test_http(.*) $1" #重写规则
      
      [module.load_balance]
        check_method = "httpchk" #探测类型
        check_url = "/ping" #探测地址
        check_timeout = 2000 #探测超时时间
        check_interval = 5000 #探测频率
        type = "round-robin" #轮询类型
        ip_list = "10.90.164.31:8072,10.90.163.51:8072,10.90.163.52:8072,10.90.165.32:8072" #目标服务ip
        weight_list = "50,50,50,80" #目标权重
        forbid_list = "10.90.165.32:8072" #禁用目标ip
        proxy_connect_timeout = 10001 #连接目标服务器超时时间
        proxy_header_timeout = 10002 #获取header头超时
        proxy_body_timeout = 10003 #获取body内容超时
        max_idle_conn = 200 #连接最大空闲时间
        idle_conn_timeout = 10004 #最大空闲连接数
      
      [module.access_control]
        black_list = "" #ip黑名单
        white_list = "" #ip白名单
        white_host_name = "" #host白名单
        client_flow_limit = 0 #客户端IP限流
        open = 1 #访问权限(黑名单与白名单)控制是否打开 1为打开 0为关闭
      
      #tcp配置示例,时间单位ms
      [[module]]
      [module.base]
        load_type = "tcp" #服务类型
        name = "test_tcp" #服务标识
        service_name = "test_tcp" #服务名称
        frontend_addr = ":8900" #监听端口
      
      [module.load_balance]
        check_method = "tcpchk" #探测类型
        check_timeout = 2000 #探测超时时间
        check_interval = 5000 #探测频率
        type = "round-robin" #负载类型
        ip_list = "127.0.0.1:8018" #目标ip列表
        weight_list = "50" #目标权重列表
        forbid_list = "" #禁用ip列表
        proxy_connect_timeout = 10001 #连接超时时间
      
      [module.access_control]
        black_list = "" #黑名单
        white_list = "" #白名单
        white_host_name = "" #host白名单
        client_flow_limit = 0 #客户端ip限流
        open = 1 #访问权限(黑名单与白名单)控制是否打开 1为打开 0为关闭
      
    • 热加载服务配置

      sh ./reload.sh 8081
      

功能拓展

  • 定义请求前验证 request 方法 比如:租户权限验证方法
    //AuthAppToken app的签名校验
    func AuthAppToken(m *dao.GatewayModule, req *http.Request, res http.ResponseWriter) (bool,error) {
      ctx:=public.NewContext(res,req)
      //验证签名
      if err:=AuthAppSign(ctx);err!=nil {
          return false,err
      }
      //限速等操作
      if err := AfterAuthLimit(ctx); err != nil {
          return false,err
      }
      //todo 可以在这里加入sso跳转逻辑
      //ctx.Redirect("/sso/login",301)
      return true,nil
    }
    
    router.HttpServerRun() 运行之前调用注册函数
service.RegisterBeforeRequestAuthFunc(service.AuthAppToken)
  • 定义请求后修改 response 方法 比如:过滤返回中的城市数据函数
//定义数据修改方法
func FilterCityData(filterURLs []string) func(m *dao.GatewayModule, req *http.Request, res *http.Response) error{
    return func(m *dao.GatewayModule, req *http.Request, res *http.Response) error {
        //获取原始请求地址
        v:=req.Context().Value("request_url")
        requestURL,ok := v.(string)
        if !ok{
            requestURL = req.URL.Path
        }

        //获取请求内容
        payload, err := ioutil.ReadAll(res.Body)
        if err!=nil{
            return err
        }

        //验证是否匹配
        for _,matchURL:=range filterURLs{
            if matchURL==requestURL {
                //过滤规则
                filterData, err := filterJsonTreeByKey(string(payload),"data.list", "city_id", []string{"12"},)
                if err!=nil{
                    return err
                }
                payload = []byte(filterData)
                break
            }
        }

        //重写请求内容
        res.Body = ioutil.NopCloser(bytes.NewBuffer(payload))
        res.ContentLength = int64(len(payload))
        res.Header.Set("Content-Length", strconv.FormatInt(int64(len(payload)), 10))
        return nil
    }
}

router.HttpServerRun() 运行之前调用注册函数

//注册内容更改函数
service.RegisterModifyResponseFunc(service.FilterCityData([]string{"/gatekeeper/tester_filter/goods_list"}))
  • 注册http中间件

    参照: ./middleware/http_limit.go

  • 注册tcp中间件

    参照: ./middleware/tcp_limit.go

测试

测试套件基于goconvey,所以需要确认安装了goconvey

  • 安装 goconvey
go get github.com/smartystreets/goconvey
  • 运行测试用例
sh bootstrap.sh

License

gatekeeper is licensed under Apache License.

非常感谢以下项目对开源做出的贡献:


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1941 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传