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

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 服务器,无任何逻辑

压测结果:

使用手册

  • 集群配置及部署

    集群架构如图

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

      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
    
  • 服务管理

    • 服务列表

      • 服务地址,如: 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

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

  • 租户管理

    • 租户列表

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

      基于 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 underApache License.

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

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章