初始化文档
This commit is contained in:
78
02 - Design Standard/2.1 系统架构设计原则.md
Normal file
78
02 - Design Standard/2.1 系统架构设计原则.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# **2.1 系统架构设计原则**
|
||||
|
||||
本章节规定了 CPS 研发团队在进行系统设计和架构演进时应遵循的核心指导原则。我们的目标是构建**高内聚、低耦合、易于演进且对开发者友好**的软件系统。
|
||||
|
||||
### **2.1.1 架构与框架边界原则**
|
||||
|
||||
**核心理念:对外统一规范,对内顺应框架(求同存异)。**
|
||||
|
||||
很多团队在追求“跨语言/跨框架统一”时,往往会造出过度抽象的中间层,导致“对抗框架”的现象。我们主张明确界定“宏观系统架构”与“微观框架实现”的边界:
|
||||
|
||||
* **系统间(宏观边界):遵循绝对统一的标准。**
|
||||
* 微服务之间、前后端之间的通信,必须遵循团队统一的 API 规范(如 RESTful / gRPC)、统一的鉴权机制、统一的链路追踪(TraceId)和日志埋点标准。
|
||||
* 系统间的交互应当是语言无关、框架无关的。
|
||||
* **系统内(微观落地):优先顺应所选框架的最佳实践。**
|
||||
* **避免过度抽象:** 不要为了所谓的“框架无关性”去生搬硬套。例如,在 Spring Boot 中应当充分利用其依赖注入(DI)和注解生态(如 @Transactional);在 Django 中应当拥抱其强大的 ORM 和信号机制。
|
||||
* **不强求内部绝对一致:** 如果团队同时存在 Java 和 Python 栈,不要强求 Python 按照 Java 的厚重分层来写,也不要强求 Java 采用 Python 的极简脚本流。尊重并利用每种语言/框架的原生优势。
|
||||
|
||||
### **2.1.2 微服务与单体划分原则与边界**
|
||||
|
||||
**核心理念:单体优先,演进式拆分,警惕“分布式单体”。**
|
||||
|
||||
* **默认选择:模块化单体 (Modular Monolith)**
|
||||
* 对于新业务或探索型项目,默认采用**模块化单体**架构。通过良好的包划分(Package/Module)和代码审查来保持内部逻辑的隔离,而不是一上来就拆分物理微服务。
|
||||
* **微服务拆分的触发条件(何时拆分?):**
|
||||
* **隔离性要求:** 核心高可用业务(如交易链路)与边缘易错业务(如报表导出)需要物理隔离,防止边缘业务 OOM 拖垮核心逻辑。
|
||||
* **扩展性要求(Scale):** 计算密集型模块(需要大量 CPU)与 IO 密集型模块(需要大内存/高并发连接)需要采用不同的扩容策略。
|
||||
* **独立部署与演进:** 某些模块变更极其频繁,而核心模块要求极度稳定,通过拆分实现独立发版。
|
||||
* **康威定律(团队规模):** 单一代码库的协同开发人数过多,导致大量的代码冲突和发布阻塞。
|
||||
* **拆分边界与底线:**
|
||||
* **禁止跨库 JOIN:** 微服务必须拥有独立的数据库(或独立的 Schema)。如果两个服务频繁需要跨库 JOIN,说明它们本该是一个服务。
|
||||
* **避免网状依赖:** 服务间的调用链路应尽量保持单向或树状,严禁出现 A 调用 B,B 调用 C,C 又调用 A 的循环依赖。
|
||||
|
||||
### **2.1.3 分层架构与灵活决策原则**
|
||||
|
||||
**核心理念:没有银弹。架构必须匹配业务阶段与框架特性,绝不为了分层而分层。**
|
||||
|
||||
软件分层的唯一目的是**隔离变化,控制复杂度**(将业务复杂度与技术实现复杂度剥离开来)。在实际落地时,团队必须基于以下基本原则,**结合项目实际情况进行灵活决策,并在架构决策记录(ADR)中予以说明**。
|
||||
|
||||
#### **1\. 分层设计的通用底线原则(必须遵守)**
|
||||
|
||||
* **关注点分离 (Separation of Concerns):** 无论采用何种架构,必须严格区分“协议处理(如 HTTP/RPC)”、“业务逻辑计算”和“数据持久化”。**严禁在 Controller/View 层直接拼接 SQL,也严禁在数据访问层包含核心业务判断。**
|
||||
* **单向依赖流 (Unidirectional Dependency):** 上层可以依赖下层(或外层依赖内层),**绝对禁止**下层反向依赖上层(例如:数据库持久化类绝不能引入 API 层的 request 对象或状态码)。
|
||||
* **演进式设计 (Evolutionary Design):** 架构是长出来的,不是一开始就设计完美的。允许系统从简单的 CRUD 逐渐重构为复杂的领域驱动模型,前提是模块间的边界清晰。
|
||||
|
||||
#### **2\. 因地制宜的模式选择建议(灵活应用)**
|
||||
|
||||
以下模式是我们的“架构工具箱”,严禁教条主义,应根据业务场景的实际痛点组合或选择使用:
|
||||
|
||||
* **场景 A:敏捷交付与数据驱动型业务(如:后台管理、报表、内容发布)**
|
||||
* **决策建议:拥抱框架原生的 MVC / MTV 模式,追求效率。**
|
||||
* 如果使用 Spring Boot,可以采用经典的 Controller \-\> Service \-\> Mapper。Service 作为事务边界,处理所有逻辑。
|
||||
* 如果使用 Django / Rails,应当顺应框架特色,采用\*\*“胖模型、瘦视图 (Fat Model, Skinny View)”\*\*。利用框架内置的 ORM、信号量和 Serializer 快速打通 CRUD,不要强行造一个虚伪的 Service 层。
|
||||
* **场景 B:核心交易与复杂规则型业务(如:订单流转、促销计价、资产核心)**
|
||||
* **决策建议:采用 领域驱动设计 (DDD) 或 干净架构/六边形架构,保护业务资产。**
|
||||
* 将复杂的业务规则收拢到独立于任何框架的**领域层 (Domain Layer)** 中(纯粹的 POJO/POCO)。
|
||||
* 基础设施(如数据库、Redis、MQ)通过**依赖倒置 (DIP)** 接入。即使未来从 MySQL 换到 MongoDB,或者从 Spring 换到其他框架,核心业务规则代码一行都不需要改。
|
||||
* **场景 C:多端展现或前端聚合诉求强烈的业务(如:同时支持 App、小程序、复杂 Web 端)**
|
||||
* **决策建议:引入 BFF (Backend for Frontend) 层。**
|
||||
* 不要让底层的核心微服务为了适配某个特定的 UI 而妥协返回臃肿的数据。
|
||||
* 在前端与核心服务之间建立 BFF 层,专门负责接口聚合、字段裁剪、格式化。BFF 归属前端/客户端团队主导,底层服务归属后端团队主导。
|
||||
* **场景 D:读写模型严重不平衡,存在性能瓶颈(如:秒杀扣减与复杂商品详情查询)**
|
||||
* **决策建议:局部或全局引入 CQRS(命令与查询职责分离)。**
|
||||
* 写操作走复杂的领域模型,保证数据一致性(写入主库)。
|
||||
* 读操作绕过所有复杂的业务对象,直接通过原生 SQL 或缓存(Redis/ES)构建扁平的 DTO 输出给前端,实现极致的性能。
|
||||
|
||||
### **2.1.4 依赖与解耦原则**
|
||||
|
||||
**核心理念:降低变更成本,控制爆炸半径。**
|
||||
|
||||
* **依赖倒置原则 (DIP \- Dependency Inversion Principle):**
|
||||
* 高层模块不应该依赖低层模块,两者都应该依赖其抽象(接口)。
|
||||
* *实践:* 业务逻辑代码不应直接实例化具体的数据库仓储类或第三方 API 客户端,而是通过构造器注入(Constructor Injection)接口,方便后续的替换和单元测试(Mock)。
|
||||
* **事件驱动与异步解耦:**
|
||||
* **区分核心流与旁路流:** 主业务流程(如“用户下单”)应当是同步的;而非核心流程(如“下单后发送短信通知”、“增加积分”)应当通过**事件总线 (Event Bus)** 或 **消息队列 (MQ)** 异步解耦。
|
||||
* *底线:* 旁路流的失败(如短信网关宕机)绝对不能导致主业务流程的回滚或失败。
|
||||
* **防腐层设计 (ACL \- Anti-Corruption Layer):**
|
||||
* 在对接老旧的遗留系统或不可控的第三方外部 API(如支付网关、SaaS 接口)时,必须在系统边界建立防腐层(Adapter/Facade)。
|
||||
* *实践:* 将外部混乱的数据模型在防腐层转换为系统内部干净的标准模型,防止外部概念“污染”我们的核心业务代码。外部接口升级时,只需修改防腐层即可。
|
||||
155
02 - Design Standard/2.2 API 接口设计规范.md
Normal file
155
02 - Design Standard/2.2 API 接口设计规范.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# **2.2 API 接口设计规范**
|
||||
|
||||
API 是前后端、微服务之间沟通的**核心契约**。优秀的 API 设计不仅能大幅降低沟通成本,还能提升系统的稳定性和可维护性。一旦 API 契约对外发布,就必须保持向前兼容。
|
||||
|
||||
### **2.2.1 协议选型与设计原则**
|
||||
|
||||
根据不同的业务场景,我们支持以下三种主要的接口协议,严禁在不恰当的场景滥用协议:
|
||||
|
||||
#### **1\. RESTful API (默认首选)**
|
||||
|
||||
适用于绝大多数 Web 端、App 端的外部接口以及常规业务交互。
|
||||
|
||||
* **面向资源:** URL 必须全部使用**名词**的复数形式,严禁在 URL 中包含动词(如 /get-users 是错误示范,应为 GET /users)。名词推荐使用短横线分隔(Kebab-case,如 /user-profiles)。
|
||||
* **HTTP 动词语义化:**
|
||||
* GET:查询资源,幂等,绝对不能产生副作用(如修改数据)。
|
||||
* POST:创建新资源,非幂等。
|
||||
* PUT:全量更新资源,幂等。
|
||||
* PATCH:局部更新资源。
|
||||
* DELETE:删除资源。
|
||||
* **版本控制:** API 必须具备版本号,推荐放在 URL 路径中(如 /api/v1/users),以便于后续重大重构时的平滑迁移。
|
||||
* **务实原则:** 追求“实用级” REST(Level 2),不强求满足极致的 HATEOAS(Level 3,返回一堆超链接),避免过度设计增加前端解析负担。
|
||||
|
||||
#### **2\. gRPC (内部微服务首选)**
|
||||
|
||||
适用于**后端微服务之间**的高频、内部通信。
|
||||
|
||||
* **优势:** 基于 HTTP/2 和 Protobuf,强类型约束,序列化体积小,性能远超 HTTP+JSON。
|
||||
* **规约:** 严禁将 gRPC 服务直接暴露给公网 Web/App 端(除非有特定的 API Gateway 或 gRPC-Web 转换层)。对外一律暴露 RESTful 或 GraphQL。
|
||||
|
||||
#### **3\. GraphQL (复杂聚合场景可选)**
|
||||
|
||||
适用于 BFF(聚合层)或前端对数据灵活性要求极高、且存在严重“过度获取(Over-fetching)”或“获取不足(Under-fetching)”的复杂场景。
|
||||
|
||||
* **规约:** 不要在简单的 CRUD 业务中引入 GraphQL,它会极大地增加后端的 N+1 查询优化难度和权限控制复杂度。必须在架构决策(ADR)中经过严格评审后方可使用。
|
||||
|
||||
### **2.2.2 统一的请求头 (Headers) 与鉴权机制**
|
||||
|
||||
为了实现全链路追踪、多语言支持和统一的安全管控,所有 HTTP 请求必须遵循以下 Headers 规范:
|
||||
|
||||
#### **1\. 标准请求头列表**
|
||||
|
||||
* Authorization: 鉴权 Token(必须)。格式为 Bearer \<Token\>。
|
||||
* X-Request-Id (或 Trace-Id): 全链路追踪 ID。由网关或客户端生成,后端原样透传并在日志中打印,这是排查线上客诉的唯一生命线。
|
||||
* X-Client-Type: 客户端类型(如 iOS, Android, Web, MiniProgram),用于后端进行渠道统计或特定逻辑下发。
|
||||
* X-Client-Version: 客户端版本号(如 1.0.5),用于后端做兼容性控制。
|
||||
* Accept-Language: 多语言标识(如 zh-CN, en-US),用于国际化错误提示。
|
||||
|
||||
#### **2\. 鉴权机制 (JWT)**
|
||||
|
||||
* 系统默认采用 **JWT (JSON Web Token)** 进行无状态鉴权。
|
||||
* **安全性底线:**
|
||||
* 严禁在 JWT 的 payload 中携带用户密码、敏感业务数据等隐私信息(JWT 仅防篡改,不防窃听,前端可直接 Decode)。
|
||||
* Token 必须设置合理的过期时间(Access Token 建议 2 小时内,Refresh Token 建议 7 天)。
|
||||
* 登出(Logout)或踢人操作,必须依赖 Redis 黑名单机制(Token 屏蔽),不能仅靠客户端删除 Token。
|
||||
|
||||
### **2.2.3 标准响应数据格式 (Response 包装类)**
|
||||
|
||||
*注:考虑到不同技术栈和框架的差异(如 Django DRF 原生倾向于直接返回资源 JSON 而不包外层,或者 GraphQL 有其自有的结构),本章节定义的 Response 包装格式为**推荐规范**。建议团队根据实际业务需求与框架特性灵活决策。一旦选定,同一项目/子系统内必须保持格式一致,避免前端解析困难。*
|
||||
|
||||
#### **1\. 推荐标准外层结构(如需包装)**
|
||||
|
||||
如果业务决定采用统一包装格式,建议结构如下:
|
||||
|
||||
{
|
||||
"code": 20000, // 业务状态码(非 HTTP 状态码)
|
||||
"message": "success", // 面向开发者的提示信息 / 面向用户的国际化错误提示
|
||||
"data": { ... }, // 实际的业务数据(对象或数组)
|
||||
"traceId": "9b1deb4d..." // 必返:当前请求的追踪ID,方便前端直接带此ID报障
|
||||
}
|
||||
|
||||
#### **2\. 分页数据标准结构与实现方案**
|
||||
|
||||
分页的实现方式多种多样,不同的框架往往有其自带的最佳实践(如 Spring Data 的 Page 接口,Django DRF 的 PageNumberPagination 或 CursorPagination)。在此仅作**推荐与指导性建议**。
|
||||
|
||||
团队在设计分页接口时,应根据具体的业务端(Admin Web 或 App 瀑布流)选择合适的方法:
|
||||
|
||||
**方案 A:传统页码分页 (Offset/Limit)**
|
||||
|
||||
* **适用场景:** 后台管理系统 (Admin)、PC 端数据表格(允许用户指定跳到第 N 页)。
|
||||
* **推荐返回结构示例:**
|
||||
|
||||
{
|
||||
"data": {
|
||||
"list": \[ {...}, {...} \], // 当前页的数据列表
|
||||
"total": 150, // 总条数 (注意:数据量极大时 count() 会有性能瓶颈)
|
||||
"page": 1, // 当前页码
|
||||
"pageSize": 20, // 每页大小
|
||||
"hasNext": true // 是否还有下一页
|
||||
}
|
||||
}
|
||||
|
||||
**方案 B:游标分页 (Cursor-based / Keyset Pagination)**
|
||||
|
||||
* **适用场景:** 移动端 App 的“无限滚动”瀑布流、社交动态流。能有效解决数据实时增删导致的“数据重复出现”或“数据漏滑”问题,且性能极佳。
|
||||
* **推荐返回结构示例:**
|
||||
|
||||
{
|
||||
"data": {
|
||||
"list": \[ {...}, {...} \], // 当前页的数据列表
|
||||
"next\_cursor": "eyJpZCI6IDE1MiwgImNyZWF0ZV90aW1lIjogIjIwMjM..." // 用于请求下一页的游标 (通常是 Base64 编码的最后一条数据标识)
|
||||
}
|
||||
}
|
||||
|
||||
*规约提醒:不强制所有分页结构完全统一,但后端在输出分页数据时,应顺应其所用框架的原生分页器能力,并在 Swagger/API 文档中清晰标明采用的分页模式。*
|
||||
|
||||
#### **3\. Null 值与空值规约 (极度容易引发 NPE 和前端白屏)**
|
||||
|
||||
不论外层是否包装,实际数据体中的空值处理应遵循:
|
||||
|
||||
* **集合/数组:** 如果列表为空,必须返回空数组 \[\],**严禁返回 null**。
|
||||
* **对象:** 如果对象为空,可以返回 {} 或 null(需与前端约定好)。
|
||||
* **字符串:** 如果字符串为空,必须返回 "",**严禁返回字符串 "null"**。
|
||||
* **布尔值:** 严禁使用 0/1 替代 true/false,JSON 必须保持类型严谨。
|
||||
|
||||
### **2.2.4 全局错误码 (Error Codes) 分配与定义字典**
|
||||
|
||||
*注:对于敏捷开发或复杂度一般的单体项目,维护庞大的五位数业务错误码字典往往会带来过高的管理成本。因此,本节为**推荐规范**。团队应根据项目规模灵活决策:轻量级项目完全可以仅依靠“标准 HTTP 状态码 \+ 清晰明确的 message”来处理异常;如果业务链路较长,需要精细化区分错误类型,则推荐引入以下错误码规范。*
|
||||
|
||||
我们需要明确区分 **HTTP 协议状态码** 与 **业务错误码**。
|
||||
|
||||
#### **1\. HTTP 状态码规约(代表网络与网关层面的状态)**
|
||||
|
||||
后端必须正确使用 HTTP 状态码,严禁将所有请求都返回 200 OK,即使系统内部发生了致命异常。
|
||||
|
||||
* 200 OK: 请求成功,业务逻辑被执行。
|
||||
* 400 Bad Request: 参数校验失败(如缺少必填字段,格式错误)。
|
||||
* 401 Unauthorized: 鉴权失败(Token 缺失、无效或过期)。前端收到后应统一跳转登录页。
|
||||
* 403 Forbidden: 权限不足(Token 有效,但当前角色无权操作)。
|
||||
* 404 Not Found: 路由不存在(资源未找到)。
|
||||
* 429 Too Many Requests: 触发限流,请稍后重试。
|
||||
* 500 Internal Server Error: 后端发生了未捕获的严重异常(代码 Bug 或数据库宕机)。
|
||||
|
||||
#### **2\. 业务错误码规约 (Response Body 中的 code 字段,若启用包装类)**
|
||||
|
||||
若团队决定启用全局错误码,推荐采用 **5 位数字** 或 **纯字符串枚举**(如 USER\_NOT\_FOUND)。以 5 位数字为例:A-BB-CC。
|
||||
|
||||
* **A (1位):系统级分类**
|
||||
* 2:成功。
|
||||
* 4:客户端调用错误(业务约束拦截)。
|
||||
* 5:服务端内部错误(业务预期内的异常)。
|
||||
* **BB (2位):模块或子系统编号**
|
||||
* 00:全局公共错误。
|
||||
* 01:用户中心。
|
||||
* 02:订单中心。
|
||||
* **CC (2位):具体错误序号**
|
||||
|
||||
**全局核心字典示例:**
|
||||
|
||||
* 20000: 业务执行成功。
|
||||
* 40000: 客户端错误通配。
|
||||
* 40102: 账户已被冻结。
|
||||
* 40202: 账户余额不足。
|
||||
* 50000: 服务器内部错误通配。
|
||||
|
||||
*规约:如果决定使用错误码,则禁止在代码中硬编码(如 return new Response(40102, "被冻结")),必须统一定义在 ErrorCodeEnum 枚举类中集中管理。*
|
||||
88
02 - Design Standard/2.3 数据库与存储设计规范.md
Normal file
88
02 - Design Standard/2.3 数据库与存储设计规范.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# **2.3 数据库与存储设计规范**
|
||||
|
||||
数据库与缓存是系统最核心的底座。代码可以随时重构,但糟糕的数据结构一旦上线并积累了大量数据,重构成本将是毁灭性的。本规范旨在确保数据结构的**可读性、扩展性及高并发下的性能稳定**。
|
||||
|
||||
*注:本章节主要以关系型数据库(如 MySQL/PostgreSQL)及 Redis 为基准,其他 NoSQL 存储可参照核心思想灵活应用。*
|
||||
|
||||
### **2.3.1 表结构与字段设计规范**
|
||||
|
||||
#### **0\. 框架原生范式顺应原则 (极其重要)**
|
||||
|
||||
许多现代 Web 框架及其绑定的 ORM(如 Django ORM, Ruby on Rails ActiveRecord, Spring Data JPA 等)拥有自己强烈推崇的数据库设计范式(例如:特定的表名生成规则、默认的审计字段命名 created\_at、隐含的关联表主外键逻辑)。
|
||||
|
||||
* **规约:** 在此重申“避免对抗框架”的原则。如果项目高度依赖此类重型全栈框架,**请优先遵守框架的原生数据库约定**,以最大化利用框架的内置生态和开发效率;但对于使用 MyBatis, GORM, SQLAlchemy 等轻量级或自定义程度较高的 ORM 项目,则**必须严格遵守**下述通用的结构与命名规范。
|
||||
|
||||
#### **1\. 命名规范(通用红线)**
|
||||
|
||||
* **表名与字段名:** 必须全部使用**小写字母**,单词之间使用下划线 \_ 分隔(Snake Case)。严禁使用驼峰命名或拼音。
|
||||
* **表名要求:** 必须是名词,建议增加业务模块前缀。例如:sys\_user(系统模块-用户)、ord\_order(订单模块-订单)。
|
||||
* **禁用保留字:** 严禁使用数据库保留字(如 desc, order, match, key 等)作为表名或字段名。
|
||||
|
||||
#### **2\. 字段设计与类型选择**
|
||||
|
||||
* **主键设计:** \* 默认推荐使用 BIGINT 配合**雪花算法 (Snowflake ID)** 或单调递增的分布式 ID 作为主键,有利于分库分表。
|
||||
* 若为中小型单体项目,允许使用数据库自增 AUTO\_INCREMENT。
|
||||
* **强烈不建议**使用无序的 UUID 字符串作为主键(会导致 B+ 树频繁页分裂,严重影响写入性能)。
|
||||
* **金额类型:** 涉及财务、金额的字段,**必须**使用 DECIMAL(如 DECIMAL(10,2))或将元转为分存储为 BIGINT。**绝对禁止**使用 FLOAT 或 DOUBLE,会产生精度丢失!
|
||||
* **布尔与枚举值:** 建议使用 TINYINT(1) 或 TINYINT(4)。字段命名建议以 is\_ 或 has\_ 开头(如 is\_deleted,has\_attachments),并在注释中明确写清枚举含义(如 0:否, 1:是)。
|
||||
* **字符串类型:** 定长使用 CHAR,变长使用 VARCHAR。尽量避免使用 TEXT 或 BLOB;如必须使用,建议将此类大字段**垂直拆分**到附属表中,以免拖慢主表的查询效率。
|
||||
* **字符集:** 默认强制使用 utf8mb4,以完美支持 Emoji 表情和生僻字;排序规则推荐使用 utf8mb4\_general\_ci。
|
||||
|
||||
#### **3\. 必备的审计字段**
|
||||
|
||||
所有核心业务表,强烈建议包含以下四个基础审计字段(*注:若所用框架有内置约定的字段名如 created\_at / updated\_at,则以框架约定为准*):
|
||||
|
||||
* id: 主键。
|
||||
* create\_time: 创建时间(推荐 DATETIME 或 TIMESTAMP,默认值 CURRENT\_TIMESTAMP)。
|
||||
* update\_time: 更新时间(默认值 CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP)。
|
||||
* is\_deleted: 逻辑删除标记(0正常,1删除)。*注意:使用逻辑删除时,需仔细评估与唯一索引(Unique Key)的冲突问题。*
|
||||
|
||||
### **2.3.2 索引建立原则与慢查询规避**
|
||||
|
||||
慢查询是导致数据库 CPU 飙升、系统雪崩的头号元凶。开发人员在编写 SQL 时必须脑中有 B+ 树的结构。
|
||||
|
||||
#### **1\. 索引建立原则 \[推荐规范\]**
|
||||
|
||||
* **控制索引数量:** 单表索引数量建议不超过 5 个。索引不是越多越好,过多的索引会严重拖慢 INSERT/UPDATE 的性能。
|
||||
* **高区分度优先:** 尽量在区分度高的列上建立索引(如 user\_id,order\_no)。**不要**在性别、状态这类只有少数几个值的列上单独建立索引。
|
||||
* **拥抱联合索引(最左前缀法则):** \* 频繁一起查询的多个字段,应建立联合索引,而非多个单列索引。
|
||||
* 联合索引的设计必须遵循**最左前缀法则**。例如索引为 (a, b, c),那么 WHERE a=?、WHERE a=? AND b=? 会走索引,但 WHERE b=? AND c=? 绝对不会走该联合索引。
|
||||
* **覆盖索引优先:** 尽量让查询的列直接包含在联合索引中,避免“回表”操作,极大提升查询效率。
|
||||
|
||||
#### **2\. 慢查询规避红线(必须遵守)**
|
||||
|
||||
* **禁止 SELECT \*:** 只查询业务需要的字段。SELECT \* 会导致无法使用覆盖索引,并增加网络传输负担。
|
||||
* **禁止左模糊查询:** LIKE '%keyword' 或 LIKE '%keyword%' 会导致索引全表扫描。如需全文检索,请引入 ElasticSearch。右模糊 LIKE 'keyword%' 是允许的(可走索引范围查询)。
|
||||
* **禁止在索引列上做计算或函数操作:** 例如 WHERE YEAR(create\_time) \= 2023 会导致索引失效。应改写为 WHERE create\_time \>= '2023-01-01' AND create\_time \< '2024-01-01'。
|
||||
* **隐式类型转换会导致索引失效:** 如果字段类型是 VARCHAR,但查询时写成了 WHERE phone \= 13800138000(数字类型),MySQL 会隐式转换导致全表扫描。必须加引号:WHERE phone \= '13800138000'。
|
||||
* **警惕深分页问题:** LIMIT 100000, 20 会让数据库扫描 100020 行数据然后丢弃前十万行。
|
||||
* *解决建议:* 限制最大允许跳页数,或者结合 2.2 章节推荐的“游标分页(Cursor)”机制(如 WHERE id \> last\_max\_id LIMIT 20)。
|
||||
|
||||
### **2.3.3 Redis 缓存设计规范**
|
||||
|
||||
Redis 虽然快,但不当的使用会导致内存泄漏和严重的并发故障。
|
||||
|
||||
#### **1\. 键值 (Key) 命名规范**
|
||||
|
||||
为了防止不同业务线的 Key 产生冲突,且便于在可视化工具(如 RDM)中分类查看,Key 必须遵循**层级命名法则**:
|
||||
|
||||
* **格式:** 项目名:模块名:实体名:唯一标识
|
||||
* **示例:** \* 用户信息缓存:cps:user:info:10086
|
||||
* 订单防重放锁:cps:order:lock:REQ\_99823
|
||||
* **规范:** 全部小写或大写(团队统一即可,推荐小写),严禁包含空格、换行或特殊字符,长度尽量控制在 64 字节以内以节省内存。
|
||||
|
||||
#### **2\. Value 设计规范**
|
||||
|
||||
* **控制大 Key(BigKey):** 单个 String 类型的 Value 不应超过 10KB;Hash、List、Set 元素个数不应超过 5000 个。大 Key 在删除或传输时会阻塞 Redis 单线程模型。
|
||||
* **选择合适的数据结构:** \* 简单的全量缓存用 String (配合 JSON 序列化)。
|
||||
* 如果只需要频繁更新或读取对象的某几个字段,应使用 Hash,避免频繁的完整 JSON 序列化和反序列化开销。
|
||||
|
||||
#### **3\. 缓存生命周期与高可用防范(极其重要)**
|
||||
|
||||
* **强制设置过期时间 (TTL):** **绝不允许**向 Redis 写入永久不过期的业务缓存(配置类或基准字典除外)。否则 Redis 内存迟早被撑爆。
|
||||
* **防范缓存雪崩 (Cache Avalanche):** \* **问题:** 大量缓存在同一时刻集体过期,导致巨量请求瞬间全部打向数据库,击穿 DB。
|
||||
* **对策:** 在设置 TTL 时,必须**加上一个随机的抖动时间**(例如,基础过期时间 2 小时 \+ 随机 0\~300 秒)。
|
||||
* **防范缓存穿透 (Cache Penetration):** \* **问题:** 黑客恶意查询一个数据库中绝对不存在的值(如 id=-1),导致缓存永远不命中,所有请求全打到 DB。
|
||||
* **对策:** 即使数据库返回 Null,也要把这个 Null 值缓存进 Redis(设置一个较短的 TTL,如 5 分钟);或者引入布隆过滤器 (Bloom Filter) 提前拦截。
|
||||
* **防范缓存击穿 (Cache Breakdown):** \* **问题:** 某一个“极其热点”的 Key 突然过期的一瞬间,上万个并发请求发现缓存未命中,同时去查询 DB 并尝试重写缓存,导致 DB 崩溃。
|
||||
* **对策:** 使用分布式锁(如 Redisson)控制,只让一个线程去查询 DB 并重建缓存,其他线程等待;或针对极度热点的数据设置“逻辑过期”而非物理过期。
|
||||
Reference in New Issue
Block a user