一、微服务架构里流量管控的核心作用

很多人做微服务的时候,都会遇到一个头疼的问题:业务拆成十几个甚至几十个小服务后,流量像没头的苍蝇一样乱撞,一会儿某个服务被瞬间冲垮,一会儿用户访问慢得离谱,还有的时候不该暴露的接口被随便调用。其实这些问题的核心,就是流量管控没做好。

流量管控说白了,就是给微服务的访问流量定规矩:哪些流量能进、哪些不能进,进来的流量该往哪个服务走,走的时候要留多少带宽,要是服务出问题了流量该怎么绕开。以前很多团队会自己写一套流量管控的代码,比如在每个服务里加限流、加鉴权,但这样不仅每个服务都要重复开发,改规则的时候还要改每个服务的代码,特别麻烦。后来大家发现,不如把流量管控的功能单独抽出来,做一个专门的“流量管理员”,所有服务的流量都先经过这个管理员,再由管理员分配,这样就不用每个服务都自己管流量了。

而APISIX就是现在用得最多的“流量管理员”之一,它是专门做API网关的,所有的流量管控功能都能通过配置来实现,不用改业务代码,也不用重新部署服务,特别适合微服务架构。

二、APISIX在微服务流量管控的核心功能及实践

APISIX的核心功能都是围绕流量管控设计的,接下来我们结合具体的场景和示例,一步步说怎么用这些功能。

2.1 基础路由:给流量找对路

基础路由是流量管控的第一步,说白了就是告诉APISIX:“用户访问A地址的时候,把流量发给服务X;访问B地址的时候,发给服务Y”。

比如我们现在有两个微服务:一个是用户服务(负责处理用户注册、登录),一个是订单服务(负责处理订单查询、创建)。这两个服务都跑在本地,用户服务的地址是http://localhost:8081,订单服务的地址是http://localhost:8082。我们要让用户访问/user/*的时候,流量走用户服务;访问/order/*的时候,流量走订单服务。

这里我们用APISIX的配置来实现,APISIX的配置是JSON格式的,所有配置都可以通过它的Admin API来提交,不用改代码。首先我们先明确技术栈:本次示例统一使用APISIX 3.9.0版本,所有配置通过APISIX Admin API提交,不涉及其他框架。

首先我们先给用户服务加路由:

{
  "uri": "/user/*", // 匹配所有以/user开头的请求
  "upstream": {
    "type": "roundrobin", // 负载均衡方式:轮询,一个一个轮流转发
    "nodes": {
      "localhost:8081": 1 // 转发到用户服务,权重1(这里只有一个服务,权重不影响)
    }
  }
}

提交这个配置到APISIX的Admin API,命令如下:

# 提交路由配置,Admin API默认地址是http://localhost:9180/apisix/admin/routes
curl -X PUT http://localhost:9180/apisix/admin/routes/user-service \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "/user/*",
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "localhost:8081": 1
      }
    }
  }'

然后再给订单服务加路由:

curl -X PUT http://localhost:9180/apisix/admin/routes/order-service \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "/order/*",
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "localhost:8082": 1
      }
    }
  }'

加完之后,我们测试一下:访问http://localhost:9080/user/login,这个请求会先到APISIX,APISIX发现是/user/*开头,就会把流量转发到用户服务的/user/login接口;访问http://localhost:9080/order/query,就会转发到订单服务的/order/query接口。

2.2 限流管控:防止流量冲垮服务

微服务里经常会遇到突发流量,比如大促的时候,某个接口的访问量突然翻几十倍,如果不加限制,很容易把服务冲垮。限流就是给每个接口设置一个最大访问次数,超过这个次数就拒绝访问,保证服务的稳定。

APISIX的限流功能是通过插件实现的,插件就是APISIX的“功能模块”,每个插件负责一个特定的功能,比如限流插件、鉴权插件、日志插件等等。

比如我们现在给订单查询接口加限流,要求每个用户每分钟最多访问10次,超过10次就返回“访问太频繁”的提示。这里我们用APISIX的limit-count插件来实现,这个插件的原理是:记录每个用户的访问次数,超过阈值就拦截。

我们先修改订单服务的路由配置,加上限流插件:

curl -X PUT http://localhost:9180/apisix/admin/routes/order-service \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "/order/*",
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "localhost:8082": 1
      }
    },
    "plugins": {
      "limit-count": {
        "count": 10, // 允许的最大访问次数
        "time_window": 60, // 时间窗口,单位是秒,这里是1分钟
        "key": "remote_addr", // 限流的依据:用户的IP地址,同一个IP算一个用户
        "rejected_code": 429, // 超过限制后返回的状态码,429是标准的“访问太频繁”状态码
        "rejected_msg": "您的访问太频繁,请稍后再试" // 超过限制后返回的提示信息
      }
    }
  }'

加完之后,我们测试一下:连续访问http://localhost:9080/order/query11次,前10次会正常返回订单服务的结果,第11次就会返回429状态码和“您的访问太频繁,请稍后再试”的提示。

这里要注意限流的依据(key),除了用IP,还可以用用户的Token、请求头的某个参数,比如如果是登录后的用户,我们可以用用户的ID来限流,这样同一个用户不管换什么IP,都能被限制住。

2.3 鉴权管控:只让合法流量进入

很多接口是需要登录后才能访问的,比如用户的个人信息、订单详情,如果没登录就能访问,就会有安全风险。鉴权就是判断访问的用户是不是合法的,合法的才让流量进去,不合法的就拒绝。

APISIX的鉴权功能也有专门的插件,比如jwt-auth插件,用来验证JWT Token的合法性。JWT是现在最常用的登录验证方式,用户登录成功后,服务会返回一个Token,用户后续访问需要登录的接口时,要把这个Token放在请求头里,服务验证Token合法后才允许访问。

比如我们现在给用户的个人信息接口加鉴权,要求访问/user/info的时候,必须带合法的JWT Token,否则拒绝访问。

首先我们要给APISIX配置JWT的公钥,因为JWT的验证是用公钥来解密的,公钥是和服务端的私钥对应的,服务端用私钥生成Token,APISIX用公钥验证Token。

假设我们的服务端生成JWT Token用的公钥是-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx...-----END PUBLIC KEY-----(这里是简化的公钥,实际用的时候要换成自己的公钥),我们先把这个公钥配置到APISIX里:

curl -X PUT http://localhost:9180/apisix/admin/ssls/jwt-public-key \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
  -H "Content-Type: application/json" \
  -d '{
    "cert": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx...-----END PUBLIC KEY-----",
    "key": "", // 公钥不需要私钥,所以这里为空
    "sni": "*" // 所有域名都可以用这个公钥
  }'

然后我们修改用户服务的路由配置,给/user/info接口加JWT鉴权插件:

curl -X PUT http://localhost:9180/apisix/admin/routes/user-service \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "/user/*",
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "localhost:8081": 1
      }
    },
    "plugins": {
      "jwt-auth": {
        "key": "Authorization", // Token放在请求头的Authorization字段里
        "alg": "RS256", // JWT的加密算法,和服务端用的算法一致
        "public_key": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx...-----END PUBLIC KEY-----",
        "exp": 3600, // Token的过期时间,单位是秒,这里是1小时
        "rejected_code": 401, // 验证失败返回的状态码,401是未授权
        "rejected_msg": "请先登录"
      }
    }
  }'

加完之后测试:不带Token访问http://localhost:9080/user/info,会返回401状态码和“请先登录”的提示;带合法的Token访问,就能正常拿到用户信息。

2.4 熔断降级:防止故障扩散

微服务架构里,一个服务出问题很容易影响到其他服务。比如订单服务依赖用户服务,如果用户服务出问题了,订单服务调用用户服务的时候就会一直超时,导致订单服务的线程被占满,最后订单服务也出问题,这就是故障扩散。

熔断降级就是解决这个问题的:当某个服务的故障次数达到一定阈值时,就暂时切断对这个服务的访问,等服务恢复后再重新开放,防止故障扩散到其他服务。

APISIX的熔断功能是通过circuit-breaker插件实现的,我们给用户服务加熔断规则:如果1分钟内,对用户服务的请求有50%失败,就触发熔断,熔断时间是30秒,30秒后再尝试恢复访问。

修改用户服务的路由配置,加上熔断插件:

curl -X PUT http://localhost:9180/apisix/admin/routes/user-service \
  -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "/user/*",
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "localhost:8081": 1
      },
      "circuit_breaker": {
        "max_connections": 10, // 最大并发连接数,超过就限制
        "max_requests": 100, // 最大请求数
        "max_errors": 5, // 最大错误次数
        "error_timeout": 60, // 错误统计的时间窗口,1分钟
        "timeout": 30, // 熔断时间,30秒
        "rejected_code": 503, // 熔断时返回的状态码,503是服务不可用
        "rejected_msg": "服务暂时不可用,请稍后再试"
      }
    }
  }'

测试一下:把用户服务停掉,连续访问/user/login,前几次会返回服务不可用,等30秒后,APISIX会尝试发送一个测试请求,如果用户服务还没恢复,就继续熔断;如果恢复了,就重新开放访问。

三、APISIX流量管控的应用场景

APISIX的流量管控功能适合很多微服务的场景,最常见的有这几个: 第一个是大促活动的流量管控,大促的时候某个接口的访问量会暴增,用APISIX的限流可以防止服务被冲垮,用熔断可以防止某个服务出问题影响整个活动。 第二个是接口的权限控制,比如内部接口只能内部员工访问,外部接口只能合法用户访问,用APISIX的鉴权可以统一管理所有接口的权限,不用每个服务都自己写鉴权代码。 第三个是服务的灰度发布,灰度发布就是先把新版本的服务给一部分用户访问,没问题再全量上线,用APISIX的路由功能可以把一部分流量转发到新版本的服务,一部分转发到旧版本的服务,实现灰度发布。 第四个是跨服务的流量监控,APISIX可以记录所有流量的访问情况,比如每个接口的访问次数、错误率、响应时间,通过这些数据可以发现哪个服务有问题,哪个接口需要优化。

四、APISIX流量管控的优缺点

4.1 优点

第一个是配置灵活,所有功能都是通过插件实现的,要加什么功能就加什么插件,不用改业务代码,也不用重新部署服务,改配置就能生效,特别适合微服务的快速迭代。 第二个是性能高,APISIX是用Go语言写的,性能比很多其他的网关高很多,每秒能处理几十万次请求,完全能满足大流量的场景。 第三个是功能全,几乎覆盖了所有的流量管控需求,从基础的路由、负载均衡,到高级的限流、鉴权、熔断、灰度发布,都有对应的插件,不用再找其他的工具。 第四个是社区活跃,APISIX是开源的,社区更新很快,经常会加新的功能,遇到问题也能在社区找到解决方案。

4.2 缺点

第一个是学习成本,APISIX的功能很多,插件也很多,要完全掌握需要花一定的时间,尤其是一些高级功能,比如自定义插件、复杂的路由规则,需要对APISIX的架构有一定的了解。 第二个是配置复杂,对于复杂的场景,比如多层的限流规则、复杂的鉴权逻辑,配置会比较复杂,容易出错,需要仔细测试。 第三个是监控的问题,虽然APISIX能记录流量数据,但要把这些数据可视化,比如做成监控面板,需要自己对接Prometheus、Grafana等工具,不是开箱即用的。

五、使用APISIX流量管控的注意事项

第一个是配置的测试,改完配置一定要测试,尤其是限流、鉴权、熔断这些功能,配置错了可能会导致正常的流量被拦截,影响业务。比如限流的时间窗口设置错了,可能会导致用户正常访问被限制。 第二个是安全的配置,APISIX的Admin API是用来管理配置的,一定要设置密码,不能公开访问,否则别人可以随便改你的配置,甚至把流量转发到恶意的地址。另外,鉴权的公钥一定要保管好,不能泄露。 第三个是性能的考虑,虽然APISIX的性能很高,但如果加了太多的插件,也会影响性能,所以不要加没用的插件,比如不需要日志的接口就不要加日志插件。 第四个是版本的兼容性,APISIX的不同版本之间,插件的配置可能会有变化,升级版本的时候一定要注意,避免升级后配置不兼容,导致功能失效。

六、文章总结

APISIX是微服务架构里流量管控的一个很好的选择,它把所有的流量管控功能都集中到一个网关里,不用每个服务都自己开发相关的功能,大大降低了开发和维护的成本。通过基础路由、限流、鉴权、熔断这些核心功能的配合,能保证微服务架构的稳定、安全和高效。

在使用APISIX的时候,要根据自己的业务场景选择合适的功能,配置的时候要注意测试和安全,遇到问题可以参考官方文档或者社区的解决方案。总的来说,APISIX的流量管控最佳实践就是:用最少的配置,实现最适合自己业务的流量规则,保证业务的正常运行。