一个靠谱的C端网关服务应该包含什么内容

发布时间:2026/8/2 11:07:53
一个靠谱的C端网关服务应该包含什么内容 C端网关一般都会有路由转发、鉴权、限流等这些功能但是还得考虑一个事情就是稳定性的比如Redis挂了请求是放行还是拒绝用户退出登录了之前签发的token还能不能用下游服务抛了个500用户看到的是什么系统要停机升级半小时几百万打开小程序的用户看到的是什么。。。。各种异常场景。。一个C端网关靠不靠谱看的就是这些异常场景怎么处理。下面是我真实打造过的一个生产环境级别的网关实战内容应付的量级用户是上亿的。技术栈是Spring Cloud Gateway。这次分享出来希望你读完后对如何做一个靠谱的C端网关心里有个底。先给一个全局视角一个请求从进到出要过的关卡大致是这些后面按这个顺序逐个展开另外顺序不是随便排的要遵守一个规则的越省资源的检查越靠前。验签和身份认证只读请求头代价最小限流要查一次Redis权限校验可能一查就是好几次。请求如果需要要被拦下的那在靠前的关卡里拦住浪费的资源就少。身份认证不只是验tokenC端的身份认证一般用JWT客户端登录后拿到token之后每个请求带上网关验签通过后放行。这个大家应该都很熟悉了网络有大把的介绍但是正如我前面说的要处理异常场景的。第一个问题用户退出登录了token还没过期怎么办?JWT的特性是签发之后无法收回到期才失效。用户点了退出登录或者账号在另一台设备登录被挤掉旧token从密码学上讲仍然有效。解决方式是黑名单登出时把这个token写进Redis网关验签之前先查一次黑名单命中就直接拒绝。这里还有一个要注意的点查黑名单依赖RedisRedis万一抖动了一下是不是所有请求都过不了我们当时的选择是查黑名单失败时放行// 查黑名单出错时当成未登出不影响主链路.onErrorResume(throwable-{log.warn(load logout token from redis fail,throwable);returnMono.just(false);});这个取舍的依据是概率登出黑名单本身拦截的是小概率场景绝大多数请求既没有登出也不会伪造token。为一个小概率防护牺牲全站可用性不划算。当然前提是你的业务能接受Redis故障的那几十秒里已登出的token还能访问如果是金融级场景这个取舍就要反过来做。第二个问题验完身份怎么把用户身份传给下游?常见做法是把解析出的用户id、渠道这些信息塞进请求头下游服务从请求头拿。这里有一个我认为最值得讲的一个细节写入之前必须先把客户端传上来的同名头删掉。ServerHttpRequest.BuilderbuilderoriginRequest.mutate().headers(headers-{headers.remove(HEADER_USER_ID);headers.remove(HEADER_CHANNEL);headers.remove(HEADER_SOURCE);});builder.header(HEADER_USER_ID,token.getUserId());不删会发生什么客户端可以自己构造一个请求在请求头里直接写上用户id。如果网关只往里塞不先清一个请求里就有两个同名字段下游用getFirst拿到的可能是客户端伪造的那个。等于任何人在请求头里写个管理员id就能以管理员身份调接口。这是当年找人帮我们做渗透性安全测试时发现的。因此先删后写就几行代码把伪造身份这条路彻底给堵死。第三个问题token不合法返回什么?是返回401吗? 我们这边返回的是HTTP 200加业务错误码只有对非客户端来源才返回401。原因在于客户端的处理方式小程序端有一个统一的响应拦截器识别到「未登录」业务码就清空本地登录态、跳登录页用户无感知。如果走401这个逻辑就要分散到每个请求的异常分支里处理而且C端链路上401还可能被某些中间代理改写成自己的错误页行为不可控。这个做法确实有违HTTP语义但网关的下游是自己的客户端团队约定大于规范两边约定好用业务码驱动登录跳转就可以了。对第三方开放平台那种调用方不受控的场景才轮到401出场。开放接口验签没有token的如何处理?不是所有请求都带用户token。有一类接口是开放给合作方或者内部App的调用方没有用户身份只有应用身份。这类接口的应付方式是签名。具体机制调用方有一个client-id和一个只有双方知道的secret请求参数里带上client-id、当前时间戳timestamp和签名sign。网关收到请求后做三件事查client-id找到对应secret检查时间戳和当前时间的差值超过TTL就拒绝用同样的算法重算签名做比对。// 参与签名的参数按key升序排序拼接value后追加secret做SHA256paramList.sort(Comparator.comparing(Entry::getKey));paramList.forEach(e-sb.append(e.getValue()));sb.append(secret);returnDigestUtils.sha256Hex(sb.toString().getBytes(StandardCharsets.UTF_8));签名和时间戳解决的是两个不同的问题少一个都不行。签名防篡改参数被中间人改一个字节重算的签名就对不上。时间戳防重放有人把合法请求原样截获下来过一小时再发一遍签名依然合法但时间戳已经过期。一定要做到防得住篡改同时也防得住重放。接口级别限流限流这块要注意三件事按什么维度限、配置怎么改、出错了怎么办。按什么维度限全局限一个总QPS意义不大因为接口和接口的抗压能力完全不同。下单接口要落库、调库存、调支付50QPS可能就顶不住查商品列表走了缓存500QPS也没事。一刀切的阈值对重的接口太松对轻的接口太紧。所以可以按「HTTP方法路径」的维度配置阈值路径支持pattern匹配先查精确匹配查不到再走/orders/{id}这种模式// 先按完整路径精确匹配匹配不到再逐个尝试路径模式ConfigconfigrateLimitTable.get(method,path);if(confignull){configmatchByPathPattern(rateLimitTable.row(method),path);}配置怎么改限流阈值是典型的运行期配置大促前要把核心接口限流参数设置的小一些发现误伤要马上放宽不需要发版重启。具体的办法是配置存Redis的hash里运营在后台改完立即生效到Redis网关侧用一个本地缓存后台线程每5秒刷新一次。每个请求读的是本地缓存不走Redis。按照「改配置只写Redis读配置只走本地缓存后台线程定时同步」这个结构在后面的维护模式、权限数据里反复出现是这个网关配置体系的通用做法。出错了怎么办两个方法。第一个是全局限流开关一个Redis key控制限流功能整体启停发现限流规则配置失误大面积误伤时先把开关关掉止血再慢慢改规则不用回滚代码第二个是限流器本身故障时的策略Redis执行Lua脚本出错直接放行// 限流计算出错时放行不让Redis成为流量的硬依赖.onErrorResume(throwable-{log.error(rate limiter redis lua exception,throwable);returnFlux.just(Arrays.asList(1L,-1L));});这个fail-open的做法是有必要的因为限流代码的bug或者Redis宕机都不应该影响正常请求要在所有层级兜住异常让限流器失效时开放而不是关闭并且一定要留一个能人工关停限流的开关。限流是保护措施保护措施本身不能成为故障源。权限校验可以用插件的形式身份认证解决「你是谁」权限校验解决「你能干什么」。C端网关的权限校验需求杂而且会变管理端接口要按角色鉴权AB测试中的接口要对灰度外的用户隐藏某些接口要限制请求方法。如果把这些揉在一个大过滤器里加一种校验就改主干代码会很乱的。把每种校验做成一个独立的AccessValidator实现过滤器里注入整个列表逐个执行// 所有校验器组成列表任一校验器拦下请求就终止returnFlux.fromIterable(accessValidators).flatMap(validator-validator.validate(context)).next().switchIfEmpty(chain.filter(exchange));新增一种访问控制只需要新写一个实现类注册成Bean主干代码一行不动。这是这个网关几年里不断有新校验需求而代码确不腐烂的关键。维护模式停机升级时用户看到什么像我们之前遇到了一个超级大故障数据库压力太大了但是用户请求会不断进入这个时候要做的事情是不是立马去调整限流参数来不及了。而是应该立刻丢掉流量。你可以这么干马上切到维护模式让所有C端接口返回一个约定的业务码和维护信息客户端识别后渲染成一张友好的维护页告诉用户「系统升级中预计xxxx后恢复」。// 命中维护范围且维护开关打开直接返回维护信息不再往后转发MaintenanceInfomaintenancemaintenanceRepository.getMaintenance();if(maintenance!null){setResponseStatus(exchange,HttpStatus.OK);setAlreadyRouted(exchange);returnwriteJsonResponse(exchange,BaseResult.error(MAINTENANCE_CODE,maintenance));}实现就是百来行代码一个过滤器加一个配置项。用户看到后无法再进行任何操作请求到网关这一层就直接返回了不会再打到后面的业务服务和数据库后端压力瞬间就能降下来。开发和运维这个时候就可以加紧处理故障了。熔断降级处理下游服务超时或错误率飙升熔断器需要打开请求会被短路到fallback上。Spring Cloud Gateway集成了熔断配一个fallback地址就行这部分框架已经包办了。你要注意的是fallback返回什么。统一的提示「系统繁忙」够吗不够的。C端接口有核心和非核心之分下单、支付是核心而像猜你喜欢、积分查询是非核心出问题就出问题。熔断时如果所有接口返回同一个错误码客户端只能给同一个提示那就会导致很差的用户体验。正确的做法是fallback接口里拿到原始请求路径按配置的正则区分命中核心接口清单的返回一个专门的业务码客户端对应展示「活动太火爆了稍后再试」并保留重试入口没命中的返回通用错误。熔断降级不是让所有请求都失败而是资源紧张的时候优先保住核心业务。值得收藏的清单能力解决什么问题实现要点配置怎么热更身份认证确认用户是谁处理登出失效JWT验签token登出黑名单黑名单查询故障时放行登出状态实时写Redis身份透传把身份安全地交给下游请求头先删后写防客户端伪造身份头无配置代码保证开放接口验签无用户token的应用级准入client-id配secret参数签名时间戳TTL防重放密钥走配置中心接口级限流突发流量不打垮重接口RedisLua令牌桶按方法路径配阈值出错放行Redis存配置本地缓存5秒刷新总开关权限校验管理端鉴权、AB灰度保护校验器插件链RBAC用户-角色-接口三层MQ广播失效本地缓存5分钟维护模式停服升级时的用户体验返回约定业务码维护信息客户端渲染维护页支持按路由圈定范围Redis配置本地缓存15秒熔断降级下游故障时保住核心按路径正则区分核心与非核心返回不同业务码正则清单走配置中心小结这套网关的技术栈是好几年前的了Hystrix、Eureka、Sleuth放到今天都已经是上一代组件现在如果重写一遍肯定是用新技术了。但不管用什么技术要解决的问题不会变。如果你的团队正在搭或者重构网关可以根据实际情况参考我这篇文章。