接入层设计(二):安全防护、HTTPS卸载与全链路追踪
上一讲我们把接入层的负载均衡和限流聊透了。今天咱们继续深入,聊聊接入层剩下的三个关键能力:安全防护、HTTPS卸载和请求日志采集。
这三个东西,说白了就是接入层的「守门员」、「翻译官」和「记录员」。缺一个,你的高并发人脸识别系统都可能出大问题。
安全防护:WAF与IP黑白名单
人脸识别API暴露在公网上,什么妖魔鬼怪都会来敲门。我见过最夸张的一次,某个客户的API刚上线两小时,就被爬虫扫了十几万次。嗯,那时候WAF还没配好。
WAF(Web应用防火墙)
WAF不是传统防火墙,它工作在应用层(L7)。它能识别并拦截SQL注入、XSS、CSRF、恶意爬虫这些攻击。在人脸识别场景下,最常见的攻击其实是恶意图片上传和参数篡改。
我的做法是:
- 在WAF层面只做协议合规检查(请求头、请求方法、Content-Type)
- 对图片内容的检测下沉到业务层去做
- 开启WAF的限速模块,对同一IP的请求频率做二次限制
IP黑白名单
IP黑白名单是成本最低、效果最直接的安全手段。但要注意,它不能单独作为安全防线。
| 类型 | 适用场景 | 注意事项 |
|---|---|---|
| 白名单 | 内部API、管理后台、专线接入 | 维护成本高,IP变动频繁 |
| 黑名单 | 恶意IP、爬虫IP、攻击源 | 需要动态更新,配合威胁情报 |
| 动态限速 | 高频访问IP自动加入临时黑名单 | 设置合理的阈值和过期时间 |
我个人习惯把IP黑白名单放在Nginx层用lua脚本实现,而不是交给业务代码。为什么?因为业务代码处理请求时,攻击已经打到应用服务器了,资源已经被消耗了。在接入层就拦住,才是最省资源的做法。
HTTPS卸载:让后端专心算人脸
HTTPS卸载,也叫SSL终结。就是把TLS/SSL的加解密工作从后端服务器剥离,交给接入层处理。
你想想看,人脸识别本身已经是计算密集型任务了。如果每张图片的传输还要做一次加解密,CPU开销至少增加30%。我见过一个团队,把SSL卸载放在应用服务器上,结果人脸识别的QPS直接腰斩。
为什么要在接入层做?
- 性能提升:Nginx/OpenResty处理SSL的效率远高于Java/Python应用服务器
- 证书统一管理:只需要在接入层配置证书,后端服务不用关心HTTPS
- 减少后端复杂度:后端服务之间走HTTP通信,省去证书验证的麻烦
实践要点
我建议的架构是这样的:
客户端 → HTTPS(443) → 接入层(Nginx) → HTTP(80) → 应用服务
↓
SSL卸载点
证书管理、OCSP Stapling
TLS 1.3、HSTS、HTTP/2
这里有几个坑,我踩过:
- Session复用:开启SSL Session缓存,减少重复握手。我一般设置10分钟的超时时间
- OCSP Stapling:必须开启。否则浏览器每次都要去CA查证书状态,延迟增加50-100ms
- HSTS:强制客户端使用HTTPS,防止降级攻击
- HTTP/2:人脸识别API通常需要传输大图片,HTTP/2的多路复用能显著提升并发性能
请求日志采集与全链路追踪
日志这东西,平时没人关心。一旦出问题,它就是救命稻草。
在高并发场景下,日志采集不能「什么都记」。我曾经接手过一个项目,每请求打200行日志,结果日志系统比业务系统还吃资源。嗯,后来我们砍掉了80%的日志,反而更容易定位问题了。
TraceId:全链路追踪的基石
TraceId是一个全局唯一的请求标识。它在接入层生成,贯穿整个请求链路。从Nginx到应用服务,再到缓存、数据库、消息队列,所有环节都带着这个ID。
为什么需要TraceId?
- 一个用户上传照片,经过接入层、人脸检测、特征提取、比对服务,可能跨5-6个微服务
- 没有TraceId,你根本不知道这个请求经历了什么
- 有了TraceId,你可以把一次请求的所有日志串起来看
TraceId的生成与传递
我习惯在Nginx层用lua生成TraceId,然后通过HTTP头传递给后端:
-- Nginx lua 生成TraceId
local uuid = require "resty.jit-uuid"
local trace_id = uuid.generate_v4()
-- 设置请求头
ngx.req.set_header("X-Trace-Id", trace_id)
-- 同时写入access_log
ngx.log(ngx.INFO, "trace_id=", trace_id)
后端服务收到请求后,从请求头中提取TraceId,然后传递给所有下游调用。这样,一次请求的所有日志都有同一个TraceId。
日志分级采集
不是所有日志都值得存。我一般分三级:
| 级别 | 内容 | 存储周期 | 用途 |
|---|---|---|---|
| 全量日志 | 请求方法、URL、状态码、响应时间、TraceId | 7天 | 监控、排障 |
| 采样日志 | 请求体(图片大小)、响应体(人脸数、置信度) | 30天 | 业务分析、模型优化 |
| 错误日志 | 4xx/5xx请求的完整上下文 | 90天 | 问题排查 |
全量日志用Nginx的access_log直接写,采样日志和错误日志通过异步消息队列发送到日志中心。这样既保证了核心日志不丢失,又不会因为日志写入阻塞业务请求。
本章知识体系
下面这张图,把接入层的安全防护、HTTPS卸载和日志追踪串起来了。你可以看到,一个请求从客户端到后端,经历了哪些环节:
避坑指南
最后,分享几个我踩过的坑:
- WAF规则太严:曾经因为WAF拦截了包含「select」关键词的请求,导致用户上传的身份证照片(上面有「请选择」字样)全部被拦截。后来我们改成了只拦截SQL注入特征明显的请求
- HTTPS证书过期:这个坑最蠢。有一次凌晨3点证书过期,整个API不可用。后来我写了自动化脚本,提前30天发告警
- 日志采集阻塞业务:同步写日志,在高并发下会导致请求排队。一定要用异步方式,或者用本地缓冲区批量写入
- TraceId传递丢失:异步消息队列和RPC调用时,TraceId容易丢失。需要在所有中间件层面都做透传