关键业务遭受攻击时的只读降级模式设计,重点不是把整个系统切成“只能看”,而是识别哪些写入必须暂停、哪些查询仍能安全提供。订单查询、创建和取消可能共用接口入口,却依赖不同的数据和服务;按依赖逐项降级,才能避免攻击扩大时误停全部业务,或让不可靠的写操作继续发生。
先区分读写路径,不要只按页面开关
一项业务功能可能同时调用身份校验、订单数据库、库存服务和消息队列。页面显示为只读,并不代表后端没有写入:访问记录、查询计数、会话刷新等操作也可能改变状态。设计时应以服务端接口和依赖为准,列出每个操作会访问哪些系统、产生哪些副作用。
例如,订单详情查询可以保留身份校验与授权,再读取订单数据;创建订单通常还会涉及库存预留、支付状态或消息投递,不应因为入口仍可访问就继续执行。这里的只读事务是数据库层面的保护手段之一,例如 PostgreSQL 支持将事务设为只读;但它不能替代应用层对接口副作用的检查。
按依赖划分可用能力
给查询设定安全边界
把功能分成“可继续”“受限提供”和“必须关闭”三类。可继续的查询应保留身份认证、对象级授权和必要的审计;受限查询可以缩短时间范围、减少返回字段或降低频率;涉及状态变更的操作则关闭或转入明确的人工处理流程。只读不等于匿名访问,也不等于降低数据保密要求。
若查询依赖 PostgreSQL 只读副本,应检查复制延迟:副本数据可能落后主库,页面需标明数据时效,涉及刚提交的订单时可提示稍后刷新,不能把旧状态误称为最新结果。Redis 缓存能减轻数据库压力,但缓存过期时间和失效策略应按数据敏感度设置;账户权限、支付结果等关键状态不宜仅凭可能陈旧的缓存作最终判断。
明确必须停写的依赖
如果写入依赖库存服务、支付接口或消息队列,就要分别定义这些依赖不可用时的行为。熔断器可以在依赖持续失败时阻止请求继续堆积,但熔断后不能把失败的下单伪装成成功。使用 Kafka 等消息系统时,也要区分“已接收待处理”和“业务已完成”;只有可靠记录了请求及其处理状态,才适合承诺稍后补处理。
按步骤落地与验证
- 画出依赖图:从查询、创建、取消等接口开始,标出数据库、缓存、外部服务和消息队列,并注明每条调用是否会改变业务状态。
- 定义降级清单:写明哪些接口保持可用、哪些缩减功能、哪些返回明确的暂不可用信息,同时规定权限校验和审计不得跳过。
- 确定触发信号:结合错误率、延迟、队列积压和异常流量等指标设置告警。阈值应参考本系统正常基线与容量测试结果,不宜照搬通用固定数字。
- 设置写入保护:在服务端拦截写请求,并在数据库、消息消费或任务执行层增加相应限制;对已受理但尚未完成的操作,说明其状态,避免客户端重复提交造成重复处理。幂等键可以帮助识别重复请求,但不能替代业务状态核对。
- 演练恢复:先确认攻击或故障信号解除,再检查积压任务、数据一致性与依赖健康状况,逐步恢复写入;观察指标正常后再扩大流量,并准备回退到只读状态的条件。
模式比较与常见误区
全站只读开关实现简单,适合依赖故障范围不清、需要迅速止损的场景,缺点是会连正常查询和无关功能一起关闭。按服务或接口降级更精细,适合依赖关系清楚、具备独立监控的系统,但配置和演练成本更高。关键业务遭受攻击时的只读降级模式设计,应优先选择可解释、可验证的边界;若系统暂时无法可靠区分读写依赖,宁可先关闭高风险写入,也不要保留未经验证的“安全写入”。
还要注意,只读模式不能修复已经发生的数据泄露,也无法阻止所有查询型攻击。仍需保留访问控制、请求限速、异常监控和审计能力,并限制批量导出、深分页等容易放大资源消耗的查询。
常见问题
只读降级会不会丢掉用户提交的订单?
可能出现请求未受理、已受理待处理或状态未知等不同情况。返回信息应准确对应后端实际状态;不要在写入失败后显示“订单成功”,也不要无记录地延后执行。
缓存能否作为只读期间的主要数据来源?
可以用于对陈旧数据容忍度较高的查询,但应明确时效和失效规则。权限、支付及关键订单状态要结合权威数据校验,不能默认缓存始终正确。
什么时候恢复写入?
关键依赖恢复、错误与积压回到系统设定范围,并完成必要的一致性检查后,再分批开放。关键业务遭受攻击时的只读降级模式设计也应预先写明这些恢复条件,而不是只定义如何关闭写入。