云数据库 RDS
目录
二、Redis + RDS 缓存最小示例(Cache-Aside)
一、云数据库 RDS
目标:会按企业规范选库、连库、备份、做高可用,而不是只会“买一个 MySQL”。
1、RDS 是什么
RDS(Relational Database Service) = 阿里云托管的关系型数据库。
常见引擎:MySQL、PostgreSQL、SQL Server、MariaDB 等。
和“自己在 ECS 上装 MySQL”比:
| ECS 自建库 | RDS | |
|---|---|---|
| 要管什么 | 安装、备份、主备切换、补丁、监控… | 主要管业务库表与账号 |
| 高可用 | 自己搭主从 | 产品级主备/多可用区 |
| 备份恢复 | 自己脚本 | 自动备份 + 控制台恢复 |
| 适合 | 极特殊定制 | 绝大多数企业业务库 |
总结:
ECS 负责算,OSS 负责存文件,RDS 负责存结构化业务数据(订单、用户、库存…)。
2、核心概念
| 概念 | 描述 |
|---|---|
| 实例 | 一套数据库服务(有规格、存储、网络) |
| 主节点 | 负责写(也可读) |
| 备节点 | 热备,故障时可切换;高可用系列备库一般不给业务直连读 |
| 只读实例 | 专供读请求扩展的副本 |
| 数据库代理 | 前置代理,可做读写分离等 |
| 白名单 | 哪些 IP/网段能连这个库 |
| 连接地址 | 内网/外网域名+端口;生产优先内网 |
3、企业级铁律
- 生产不用基础版(单节点),用 高可用 / 集群 + 多可用区
- 禁止白名单
0.0.0.0/0;生产优先 VPC 内网访问 - 应用走内网地址;外网地址仅临时运维,且收紧白名单
- 开启自动备份 + 日志备份;定期做恢复演练
- 账号最小权限:应用账号别用高权限账号;禁共用 root
- 可维护时间窗口避开业务高峰
- 读多写少 上只读实例 + 数据库代理,别死扛主库
- 敏感数据 考虑 SSL、TDE(透明加密)、SQL 审计
4、系列怎么选(先定架构)
以 RDS MySQL 为例(其他引擎类似思路):
| 系列 | 特点 | 企业用法 |
|---|---|---|
| 基础版 | 单节点 | 仅开发/测试 |
| 高可用版 | 一主一备,自动切换 | 大多数生产默认 |
| 集群版 | 一主多备、备可读、更灵活扩展 | 更高可用/读扩展诉求 |
部署建议:
- 多可用区:主备跨 AZ,同城容灾,通常不额外加“容灾费”
- 单可用区:机器级冗余有,但扛不住整个可用区故障
官方建议:生产选多可用区高可用/集群。见 搭建高可用架构。
5、内容了解
(1)创建一台“企业规范”的 RDS(示例)
承接之前的 VPC:
| 项 | 建议 |
|---|---|
| 引擎 | MySQL 8.0(或公司标准版本) |
| 系列 | 高可用 |
| 部署 | 多可用区 |
| 网络 | 与 ECS 同一 VPC |
| 交换机 | 放在数据库交换机(如 |
| 规格 | 按业务起步,可监控后再升配 |
| 存储 | SSD/ESSD,预留增长空间 |
| 白名单 | 只加应用交换机网段或 ECS 私网 IP |
| 账号 | 业务库专用账号,最小权限 |
| 备份 | 自动备份开启,保留按合规(如 7~30 天+) |
| 维护窗口 | 业务低峰(如凌晨) |
创建后立刻检查:
- 是否只有内网地址在用
- 白名单有没有
0.0.0.0/0 - 备份策略是否生效
- ECS 能否内网连通,公网是否应关闭
(2)应用怎么连库(正确姿势)
ALB → ECS/ACK(应用)──内网──► RDS
↑
白名单放行应用网段
连接串注意:
- 用 RDS 内网地址
- 连接池合理(别每个请求新建连接)
- 密码放配置中心/KMS,别写进镜像
- 生产库 不要 给开发笔记本长期开公网
ECS 安全组与 RDS 白名单要同时满足:只放行业务端口(如 3306),来源是应用网段。
(3)高可用的理解
高可用系列大致是:
主节点(可读写) ←实时同步→ 备节点(热备)
│
主挂了 → 自动切换到备(秒级,对应用尽量透明)
要知道:
- 切换时可能有短暂闪断,应用应有重连
- 多可用区比单可用区更能抗机房级故障
- 切换后连接地址通常不变(以产品说明为准),但要在应用层做好重试
(4)读写分离(读扛不住时)
场景:查询很多、写入相对少,主库 CPU/连接被 SELECT 打满。
做法:
- 创建 只读实例(可多个,建议跨 AZ)
- 开通 数据库代理
- 应用连接 代理地址(不是直连主实例地址)
- 代理:写 → 主;读 → 只读(按权重)
应用 ──► 数据库代理
├─ INSERT/UPDATE → 主实例
└─ SELECT → 只读1 / 只读2
注意:
- 直连主地址 = 不会自动读写分离
- 只读有同步延迟,强一致读要走主库或强制主库 hint
- 建议至少 2 个只读,避免单只读故障影响面过大
官方:读写分离实践
(5)备份与恢复
| 能力 | 作用 |
|---|---|
| 数据备份 | 库的定期全量/增量备份 |
| 日志备份 | binlog 等,支撑更细时间点恢复 |
| 恢复到新实例 / 时间点恢复 | 误删、误更新后的救命手段 |
| 跨地域备份 | 地域级灾难备份(更高要求) |
企业要求:
- 自动备份必须开
- 保留周期满足合规
- 每季度至少做一次恢复演练(备份不能“假备份”)
- 大变更前可手动备份
(6)安全与审计
| 项 | 建议 |
|---|---|
| 白名单 | 最小网段;禁用 |
| SSL | 外网或高安全场景开启 |
| TDE | 静态数据加密(敏感行业) |
| SQL 审计 / SQL 洞察 | 追溯谁执行了什么 SQL |
| 账号治理 | 一人一账号;应用账号分离;定期审权限 |
| RAM | 谁能在控制台删库、改白名单,要严控 |
和堡垒机/运维:人连库尽量经可控通道,避免人人直连生产。
(7)监控与容量
至少盯这些:
- CPU、内存、磁盘、IOPS
- 连接数、慢 SQL
- 主从延迟(有只读时)
- 空间水位(提前扩容)
空间到 80%~85% 就要告警;别等写满业务停摆。
慢 SQL 要用索引/改写治理,而不是只靠无限升配。
(8)和前后知识串起来
- RAM:谁能创建/删除 RDS、改白名单
- VPC:RDS 放数据库交换机,与应用分网段
- 安全组/白名单:只让应用访问 3306
- ECS/ACK:应用内网连 RDS
- ALB/ESS:无状态扩缩;会话/数据在 RDS/Redis/OSS
- OSS:大文件、备份包;不要把大文件塞数据库 BLOB 滥用
- 堡垒机:人访问跳板,不是把 RDS 暴露公网
典型企业链路:
用户 → ALB → ECS(可 ESS 伸缩)
├─ RDS(交易数据)
├─ Redis(缓存/会话,后续可学)
└─ OSS(文件)
6、生产 vs 测试对照
| 项 | 测试 | 生产 |
|---|---|---|
| 系列 | 基础版可 | 高可用/集群 + 多 AZ |
| 白名单 | 可临时放宽,用完收回 | 严格网段,禁全开放 |
| 备份 | 可短保留 | 按合规保留 + 演练 |
| 规格 | 小 | 按压测/监控定,留余量 |
| 公网 | 可临时开 | 默认关 |
| 审计 | 可选 | 建议开 |
7、常见坑
| 坑 | 后果 |
|---|---|
| 生产用基础版 | 单点故障直接停业 |
| 白名单 | 暴露暴力破解与拖库风险 |
| ECS 与 RDS 不同 VPC 却指望直连 | 不通或要额外打通 |
| 连主地址却以为开了读写分离 | 读仍打主库 |
| 忽略只读延迟 | 刚写入立刻读只读,读不到 |
| 从不做恢复演练 | 真出事才发现备份不能用 |
| 把 RDS 当文件存储 | 膨胀、备份变慢、性能差 |
小结
RDS 是托管关系数据库:生产选高可用多 AZ,内网访问、白名单收紧,备份可恢复,读多了上只读+代理,安全靠账号/SSL/审计,和应用放同一 VPC、分交换机部署。
二、Redis + RDS 缓存最小示例(Cache-Aside)
模式:先查 Redis → 没有再查 RDS → 回写 Redis(带过期);更新时 先改 RDS,再删缓存。
1、流程
读:GET key
├─ Redis 命中 → 直接返回
└─ 未命中 → 查 RDS → SETEX 回写 → 返回写:更新业务
└─ UPDATE RDS → DEL key(下次读再重建缓存)
2、Python 最小示例
依赖:
pip install redis pymysql
import json
import redis
import pymysql
from pymysql.cursors import DictCursor
# ---------- 连接(改成你的内网地址) ----------
r = redis.Redis(
host="r-xxxxxxxx.redis.rds.aliyuncs.com",
port=6379,
password="your_redis_password",
decode_responses=True,
)
def get_db():
return pymysql.connect(
host="rm-xxxxxxxx.mysql.rds.aliyuncs.com",
user="app_user",
password="your_db_password",
database="shop",
charset="utf8mb4",
cursorclass=DictCursor,
)
TTL = 600 # 正常缓存 10 分钟
NULL_TTL = 60 # 空值缓存 1 分钟(防穿透)
NULL_FLAG = "__NULL__"
def cache_key(product_id: int) -> str:
return f"product:prod:detail:{product_id}"
# ---------- 读:Redis → RDS ----------
def get_product(product_id: int) -> dict | None:
key = cache_key(product_id)
# 1) 先查缓存
cached = r.get(key)
if cached is not None:
if cached == NULL_FLAG:
return None
return json.loads(cached)
# 2) 未命中,查数据库
conn = get_db()
try:
with conn.cursor() as cur:
cur.execute(
"SELECT id, name, price FROM product WHERE id=%s",
(product_id,),
)
row = cur.fetchone()
finally:
conn.close()
# 3) 回写缓存
if row is None:
r.setex(key, NULL_TTL, NULL_FLAG) # 防穿透
return None
r.setex(key, TTL, json.dumps(row, ensure_ascii=False))
return row
# ---------- 写:先 DB,再删缓存 ----------
def update_product_price(product_id: int, price: float) -> None:
conn = get_db()
try:
with conn.cursor() as cur:
cur.execute(
"UPDATE product SET price=%s WHERE id=%s",
(price, product_id),
)
conn.commit()
finally:
conn.close()
# 删缓存,避免读到旧数据
r.delete(cache_key(product_id))
# ---------- 试用 ----------
if __name__ == "__main__":
print(get_product(10086)) # 第一次查 RDS,第二次走 Redis
update_product_price(10086, 99.9) # 改价并删缓存
print(get_product(10086)) # 重新从 RDS 加载
3、Node.js 最小示例
npm i ioredis mysql2
const Redis = require("ioredis");
const mysql = require("mysql2/promise");
const redis = new Redis({
host: "r-xxxxxxxx.redis.rds.aliyuncs.com",
port: 6379,
password: "your_redis_password",
});
const pool = mysql.createPool({
host: "rm-xxxxxxxx.mysql.rds.aliyuncs.com",
user: "app_user",
password: "your_db_password",
database: "shop",
});
const TTL = 600;
const NULL_TTL = 60;
const NULL_FLAG = "__NULL__";
const cacheKey = (id) => `product:prod:detail:${id}`;
async function getProduct(productId) {
const key = cacheKey(productId);
// 1) Redis
const cached = await redis.get(key);
if (cached !== null) {
if (cached === NULL_FLAG) return null;
return JSON.parse(cached);
}
// 2) RDS
const [rows] = await pool.query(
"SELECT id, name, price FROM product WHERE id = ?",
[productId]
);
const row = rows[0] || null;
// 3) 回写
if (!row) {
await redis.set(key, NULL_FLAG, "EX", NULL_TTL);
return null;
}
await redis.set(key, JSON.stringify(row), "EX", TTL);
return row;
}
async function updateProductPrice(productId, price) {
await pool.query("UPDATE product SET price = ? WHERE id = ?", [
price,
productId,
]);
await redis.del(cacheKey(productId));
}
(async () => {
console.log(await getProduct(10086));
await updateProductPrice(10086, 99.9);
console.log(await getProduct(10086));
process.exit(0);
})();
4、配套表结构(测试用)
CREATE TABLE product (
id BIGINT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
price DECIMAL(10,2) NOT NULL
);
INSERT INTO product (id, name, price) VALUES (10086, '示例商品', 88.00);
5、要点
| 点 | 说明 |
|---|---|
| Key 带业务前缀 | 如 |
| 必须设 TTL | 避免缓存永不过期占满内存 |
| 空值短 TTL | 防缓存穿透 |
| 更新先 DB 再删缓存 | 简单稳妥;别只改 Redis |
| 用内网地址 | ECS 与 Redis/RDS 同 VPC |
| 连接池 | 生产用池,别每次新建裸连 |
总结
读:Redis 优先,未命中查 RDS 再回写;写:改 RDS 后删除对应 Key。
更多推荐
所有评论(0)