网关编程:如何通过用户网关和缓存降低研发成本?
如果说用户的流量就像波涛汹涌的海浪,那网关就是防御冲击的堤坝。在大型的互联网项目里,网关必不可少,是我们目前最好用的防御手段。通过网关,我们能把大量的流量分流到各个服务上,如果配合使用 Lua 脚本引擎提供的一些能力,还能大大降低系统的耦合度和性能损耗,节约我们的成本。一般来说,网关分为外网网关和内网网关。外网网关主要负责做限流、入侵预防、请求转发等工作,常见方式是使用 Nginx + Lua 做类似的工作;而最近几年,内网网关发展出现了各种定制功能的网关,比如 ServiceMesh、SideCar 等方式,以及类似 Kong、Nginx Unit 等,它们的用途虽然有差异,但是主要功能还是做负载均衡、流量管理调度和入侵预防这些工作。
外网网关功能
我们先从外网网关的用法说起,我会给你分享两类外网网关的实用设计,两个设计可以帮助我们预防入侵和接触业务的依赖。
蜘蛛嗅探识别
在处理大流量网站时,常见的安全问题包括非法引用和机器人抓取。为了防止这些问题,我们可以采取一些有效的策略,例如通过网关实现限速和入侵检测功能。
非法引用防范:
非法引用通常会导致我们网站的资源被滥用。一种常见的防范方法是检查请求中的 referer
字段。如果请求的 referer
不是本站域名,就拒绝该请求。这能有效降低资源被未经授权访问的风险。
机器人抓取防范:
机器人抓取是另一个常见的问题。为了识别和防止机器人的抓取,我们可以采取以下几种方法:
- 分析匿名用户的请求:对于匿名用户,我们可以通过统计其 IP 地址和请求的时间段,识别出请求频率异常的 IP 地址。高频次的请求 IP 应该被标记为重点关注对象。
- 分析登录用户的行为:对于已登录用户,我们可以通过时间块统计用户的请求次数。如果某个用户的请求频率超过正常范围,我们可以拒绝其请求,并将其列入“可疑名单”进行后续调查。
- 动态注入 JS 嗅探代码:为了更准确地识别机器人,我们可以在网关层针对可疑的用户或 IP 动态注入 JS 嗅探代码。该代码会在用户的
Cookie
或LocalStorage
中写入特定的密文,并要求前端 JS 代码检测该密文。如果密文存在且符合特定模式,前端会进入“反机器人模式”,检查客户端是否有鼠标移动或点击行为。这一检测能够帮助我们确认该用户是人工操作还是机器人行为。如果用户长时间没有相应的动作,系统会将其列为待封禁对象,并且封禁该请求。 - 白名单策略:由于这种反机器人设计可能会影响搜索引擎的抓取,特别是对于 SEO(搜索引擎优化)不友好,因此我们可以采取白名单策略,允许一些常见的搜索引擎机器人访问网站。我们可以根据 UserAgent 来放行主流搜索引擎的爬虫,并定期审核其 IP 地址。
- 接口加签策略:对于一些核心的接口,可以引入“必须附带时间戳签名”的规则,只有正确签名的请求才能访问。这一做法可以有效防止一些简单的爬虫通过直接访问接口来抓取数据。
通过这些措施,我们可以有效应对非法引用和机器人抓取问题,保护网站的资源和数据安全。同时也能保持对搜索引擎的友好性,保证 SEO 的正常运作。
网关鉴权与用户中心解耦
之前我们讨论了如何利用网关来阻挡非法用户的骚扰。实际上,网关除了防御攻击、防止资源被恶意消耗外,还能帮助我们解除一些业务依赖。比如在用户登录的设计上,每个业务不需要直接依赖用户中心来验证用户的合法性。
用户鉴权通常会通过在各个子业务中集成用户中心的 SDK 来实现一致的校验逻辑。这种方式带来便利的同时,也产生了新的问题:SDK 的同步依赖和升级维护。基础公共组件一般会通过 SDK 为业务开发提供便捷,而如果仅通过 API 提供服务,一些特殊的操作可能会需要重复实现。然而,一旦 SDK 发布,我们就需要做好同时维护多个版本 SDK 在线工作的准备,以确保兼容性和功能稳定。
以下图展示了使用 SDK 鉴权 token 方式和直接通过用户中心接口进行鉴权的效果对比:
在集成 SDK 的情况下,各业务可以自行校验用户身份,无需频繁请求用户中心。然而,这种方案也带来了一些挑战。由于 SDK 是嵌入在各个项目中的组件,项目通常不会频繁升级其版本来保持稳定性。这使得用户中心的后续升级面临阻力,因为升级时必须考虑到所有依赖的业务。每次基础服务的大规模升级都需要大量人力来同步更新 SDK,从而增加了维护的复杂性。
为了解决这种耦合问题,我们可以考虑另一种设计思路,即将用户登录鉴权功能放到网关层。这样一来,业务系统不再需要直接依赖用户中心的 SDK,而是通过网关完成身份验证和权限校验。通过这种方式,网关可以在接收到请求时直接进行用户鉴权,只有通过验证的请求才会被转发至具体的业务服务,从而解耦了用户中心与各业务系统的直接依赖关系。
以下图所示为这一设计下的请求流程,请结合示意图参考,我将进一步分析其工作机制和优点。
结合上图,我们来看这种设计的实现流程。当用户请求业务接口时,网关首先会对请求用户的身份进行鉴定。如果通过验证,用户信息会被放入 header
并传递给后端服务,而业务 API 无需关注用户中心的实现细节,直接从 header
中获取用户信息即可继续工作。
如果业务要求用户必须登录才能使用,可以在业务逻辑中增加一个判断,检查请求 header
是否包含 uid
。若缺少 uid
,则返回统一的错误码给前端,提示用户需要先登录。这种鉴权服务设计有效解耦了业务模块和用户中心。即使用户中心逻辑变更,也无需业务模块同步升级。
此外,除了基本的登录鉴权,这种设计还可以在网关层实现更加灵活的权限管理。例如,可以为某些域名开启基于角色的访问控制(RBAC)或基于属性的访问控制(ABAC),为不同的业务场景量身定制权限控制策略。通过网关,我们还可以为不同用户提供不同权限的功能,支持灰度测试等高级功能,从而提高系统的灵活性和安全性。
内网网关服务
了解了外网的两种妙用,我们再看看内网的功能。它可以提供失败重试服务和平滑重启机制,我们分别来看看。
失败重试
在项目发布升级或服务发生故障重启时,系统可能会短暂不可用。这段期间如果有用户请求,可能会因为后端未响应而返回 504 错误,影响用户体验。为提升用户体验,可以利用内网网关的自动重试功能。
当请求到达后端,但因服务返回 500、403 或 504 等错误时,网关可以避免立即返回错误。相反,网关可以让请求稍作等待,之后再重试;或者直接返回之前缓存的内容。通过这种方式,业务能够实现平滑的热更新,让服务看起来更为稳定,使用户不会明显感知到线上升级过程中的波动。
平滑重启
在服务升级过程中,平滑重启机制能够避免服务进程在接收到 kill
信号后立即退出。具体做法是先让服务停止接收新的请求,同时等待当前正在处理的请求完成。如果请求处理超时(比如超过 10 秒),则强制退出服务。这种机制有助于保证正在进行的请求得到妥善处理,减少服务中断对用户的影响。
通过这个机制,用户请求处理就不会被中断,这样就能保证正在处理中的业务事务是完整的,否则很有可能会导致业务事务不一致,或只做了一半的情况。有了这个重试和平滑重启的机制后,我们可以随时在线升级发布我们的代码,发布新的功能。不过开启这个功能后,可能会屏蔽一些线上的故障,这时候可以配合网关服务的监控,来帮我们检测系统的状态。
内外网关综合应用
首先来看网关接口缓存功能,也就是利用网关实现一些接口返回内容的缓存,适合用在服务降级场景,用它短暂地缓解用户流量的冲击,或者用于降低内网流量的冲击。具体实现如下图所示:
通过上图,我们可以看到,网关的缓存机制通常是通过临时缓存和 TTL(生存时间)方式来实现的。当用户请求某个服务接口时,如果该接口的响应已被缓存,并且缓存没有过期,网关就会直接返回缓存的数据给客户端。这种方法能够显著减轻后端数据服务的负担。
然而,这种方式的选择是经过权衡的,它牺牲了数据的强一致性以换取性能上的提升。同时,缓存机制对性能要求较高,必须确保网关缓存能够处理外部流量的高 QPS(每秒查询数)。为了避免过多的穿透流量,可以定期通过脚本刷新缓存数据。这样,当网关发现有有效的缓存时,就直接返回;如果缓存没有命中,才会请求后端服务,并缓存结果。
这种实现方式比单纯依赖缓存更为灵活,能够提高数据一致性,但同时也增加了开发和维护的复杂度,需要额外的代码和运维来确保系统的稳定性和数据的一致性。
当然这种缓存的数据长度建议不超过 5KB(10w QPS X 5KB = 488MB/s),因为数据太长,会拖慢我们的缓存服务响应速度。
服务监控
最后,我们来讨论利用网关进行服务监控的问题。在没有链路跟踪的情况下,大多数系统的监控通常依赖于网关的日志。通过分析网关的访问日志中的 HTTP 状态码,我们可以判断服务是否正常运行。同时,结合请求的响应时间信息,我们能够实现基本的系统监控功能。
具体来说,网关的访问日志会记录每个请求的 HTTP 状态码(如 200、500、404 等)和响应时间。这些信息可以帮助我们监控服务的健康状况,例如判断是否有异常的错误码(如 500 错误)或请求超时,进而及时发现潜在问题。
下面的图示展示了如何通过网关来监控服务的运行状态,请结合图示进行进一步理解,我会继续分析这个过程的细节。
为了更方便地判断线上服务状况,可以先对信息进行统计。具体方法是定期聚合访问日志中的错误,并汇总不同接口的请求错误数量。例如,通过聚合后我们可以得到类似的数据:“30秒内发生 500 错误 20 次,504 错误 15 次,某域名接口响应时间超过 1 秒的情况 40 次”。这些统计数据帮助我们快速分析服务的健康状态。
与其他监控方式不同,网关监控能够覆盖所有业务,虽然监控的粒度较粗,但仍然是一个有效的方案。如果结合 Trace,我们可以在访问日志中记录 Trace ID,通过这些 ID 进一步排查问题的具体原因。这种实现方式在一些公司(如好未来和极客时间)已经得到了应用,提高了故障排查的便利性。