PostgreSQL教程FG297-PG多租户实战:Schema/数据库级隔离方案
本文档风哥主要介绍PostgreSQL的多租户实施方案,包括Schema级、数据库级和行级隔离方案。风哥教程参考PostgreSQL官方文档和多租户最佳实践,适合SaaS应用和企业内部多租户场景。更多视频教程www.fgedu.net.cn
Part01-基础概念与理论知识
1.1 多租户概述
多租户是指在一个软件系统中,多个租户(客户)共享同一套系统资源,但彼此的数据和配置相互隔离。多租户的核心目标:
- 资源共享:多个租户共享系统资源,提高资源利用率
- 数据隔离:确保租户之间的数据相互隔离,保护数据安全
- 成本降低:通过资源共享,降低系统部署和维护成本
- 管理简化:集中管理多个租户,简化系统维护
多租户架构可以显著降低系统部署和维护成本,提高资源利用率,同时为租户提供定制化的服务。对于SaaS应用来说,多租户是一种常见的架构选择。
1.2 隔离级别与方案
PostgreSQL的多租户隔离方案主要包括:
## 1. Schema级隔离
– **定义:** 每个租户使用独立的Schema,共享同一个数据库
– **实现方式:** 为每个租户创建独立的Schema,通过Schema切换实现隔离
– **特点:** 资源共享程度高,隔离级别中等
## 2. 数据库级隔离
– **定义:** 每个租户使用独立的数据库,共享同一个PostgreSQL实例
– **实现方式:** 为每个租户创建独立的数据库,通过数据库连接切换实现隔离
– **特点:** 隔离级别高,资源共享程度中等
## 3. 行级隔离
– **定义:** 所有租户共享同一个数据库和Schema,通过行级标记实现隔离
– **实现方式:** 在表中添加租户ID字段,通过查询条件过滤实现隔离
– **特点:** 资源共享程度最高,隔离级别最低
## 4. 实例级隔离
– **定义:** 每个租户使用独立的PostgreSQL实例
– **实现方式:** 为每个租户部署独立的PostgreSQL实例
– **特点:** 隔离级别最高,资源共享程度最低
1.3 各方案优缺点
不同多租户隔离方案的优缺点:
## 1. Schema级隔离
### 优点
– 资源利用率高,多个租户共享数据库资源
– 管理简单,所有租户在同一个数据库中
– 跨租户查询方便
– 备份和恢复简单
### 缺点
– 隔离级别中等,可能存在Schema名称冲突
– 单个租户的问题可能影响其他租户
– 扩展性有限,受限于单个数据库的性能
## 2. 数据库级隔离
### 优点
– 隔离级别高,租户之间完全隔离
– 单个租户的问题不会影响其他租户
– 可以为不同租户配置不同的数据库参数
– 扩展性较好,可根据租户需求独立扩展
### 缺点
– 资源利用率中等,每个数据库有一定的资源开销
– 管理复杂度增加,需要管理多个数据库
– 跨租户查询复杂
– 备份和恢复复杂度增加
## 3. 行级隔离
### 优点
– 资源利用率最高,所有租户共享同一个数据库和Schema
– 管理最简单,只有一个数据库和Schema
– 扩展性最好,理论上可以支持无限多租户
– 备份和恢复最简单
### 缺点
– 隔离级别最低,依赖应用层实现隔离
– 查询性能可能受到影响,需要额外的过滤条件
– 安全风险较高,可能存在数据泄露风险
– 难以实现租户级别的资源限制
## 4. 实例级隔离
### 优点
– 隔离级别最高,租户之间完全独立
– 可以为不同租户配置不同的硬件和软件环境
– 单个租户的问题不会影响其他租户
– 最灵活,可以根据租户需求定制环境
### 缺点
– 资源利用率最低,每个实例有较大的资源开销
– 管理复杂度最高,需要管理多个PostgreSQL实例
– 成本最高,每个实例需要独立的硬件和软件资源
– 跨租户查询几乎不可能
Part02-生产环境规划与建议
2.1 规划考虑因素
PostgreSQL多租户方案的规划考虑因素:
## 1. 业务需求
– **租户数量:** 预计支持的租户数量
– **数据量:** 每个租户的数据量和总数据量
– **性能要求:** 每个租户的性能要求和峰值负载
– **数据敏感程度:** 数据的敏感程度和隔离要求
## 2. 技术因素
– **隔离级别:** 选择合适的隔离级别
– **扩展性:** 系统的可扩展性要求
– **备份和恢复:** 备份和恢复策略
– **监控和管理:** 监控和管理策略
## 3. 成本因素
– **硬件成本:** 服务器和存储成本
– **软件成本:** 数据库和相关软件成本
– **运维成本:** 系统维护和管理成本
– **扩展成本:** 系统扩展的成本
## 4. 合规因素
– **数据隐私:** 数据隐私保护要求
– **法规合规:** 行业法规和合规要求
– **审计需求:** 审计和监控要求
## 5. 风险因素
– **单点故障:** 系统的单点故障风险
– **性能瓶颈:** 系统的性能瓶颈风险
– **安全风险:** 系统的安全风险
– **可维护性:** 系统的可维护性风险
2.2 资源估算
PostgreSQL多租户方案的资源估算:
## 1. 存储估算
– **数据存储:** 每个租户的数据量 × 租户数量
– **索引存储:** 约为数据存储的20-30%
– **WAL日志:** 约为数据存储的10-20%
– **备份存储:** 至少为数据存储的1-2倍
## 2. 内存估算
– **共享缓冲区:** 总内存的25%
– **工作内存:** 根据查询复杂度和并发度
– **维护工作内存:** 根据维护操作的需求
– **操作系统内存:** 约为总内存的25%
## 3. CPU估算
– **核心数:** 根据并发查询数和查询复杂度
– **性能:** 根据查询响应时间要求
– **扩展性:** 考虑未来业务增长
## 4. 网络估算
– **带宽:** 根据数据传输量和并发用户数
– **延迟:** 根据应用响应时间要求
– **可靠性:** 考虑网络故障的影响
## 5. 资源计算公式
– **存储:** 总存储 = 租户数 × 每个租户数据量 × (1 + 索引比例 + WAL比例 + 备份比例)
– **内存:** 总内存 = 共享缓冲区 + 工作内存 × 并发数 + 维护工作内存 + 操作系统内存
– **CPU:** 核心数 = 并发查询数 × 每个查询的CPU需求
2.3 可扩展性规划
PostgreSQL多租户方案的可扩展性规划:
- 垂直扩展:增加单个PostgreSQL实例的资源(CPU、内存、存储)
- 水平扩展:增加PostgreSQL实例的数量,将租户分布到多个实例
- 分片:将数据分片到多个PostgreSQL实例
- 读写分离:主库处理写操作,从库处理读操作
Part03-生产环境项目实施方案
3.1 Schema级隔离
3.1.1 实现方案
## 1. 创建租户Schema
“`sql
— 创建租户Schema
CREATE SCHEMA tenant1;
CREATE SCHEMA tenant2;
— 为租户创建表
CREATE TABLE fgedu_tenant1.fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE TABLE fgedu_tenant2.fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
“`
## 2. 配置租户角色
“`sql
— 创建租户角色
CREATE ROLE tenant1_fgedu WITH LOGIN PASSWORD ‘tenant1_password’;
CREATE ROLE tenant2_fgedu WITH LOGIN PASSWORD ‘tenant2_password’;
— 授予Schema权限
GRANT USAGE ON SCHEMA tenant1 TO tenant1_fgedu;
GRANT USAGE ON SCHEMA tenant2 TO tenant2_fgedu;
— 授予表权限
GRANT SELECT, INSERT, UPDATE, DELETE ON tenant1.fgedus TO tenant1_fgedu;
GRANT SELECT, INSERT, UPDATE, DELETE ON tenant2.fgedus TO tenant2_fgedu;
— 授予序列权限
GRANT USAGE, SELECT ON SEQUENCE tenant1.fgedus_id_seq TO tenant1_fgedu;
GRANT USAGE, SELECT ON SEQUENCE tenant2.fgedus_id_seq TO tenant2_fgedu;
— 设置默认Schema
ALTER ROLE tenant1_fgedu SET search_path TO tenant1, public;
ALTER ROLE tenant2_fgedu SET search_path TO tenant2, public;
“`
## 3. 管理租户Schema
“`sql
— 列出所有Schema
SELECT schema_name FROM information_schema.schemata;
— 切换Schema
SET search_path TO tenant1;
— 查看当前Schema
SHOW search_path;
— 删除租户Schema
DROP SCHEMA tenant1 CASCADE;
“`
## 4. 跨租户查询
“`sql
— 跨租户查询
SELECT * FROM tenant1.fgedus;
SELECT * FROM tenant2.fgedus;
“`
## 5. 自动化管理脚本
“`sql
— 创建租户函数
CREATE OR REPLACE FUNCTION create_tenant(tenant_name TEXT, tenant_password TEXT)
RETURNS void AS $$
DECLARE
schema_name TEXT := tenant_name;
role_name TEXT := tenant_name || ‘_fgedu’;
BEGIN
— 创建Schema
EXECUTE format(‘CREATE SCHEMA %I’, schema_name);
— 创建角色
EXECUTE format(‘CREATE ROLE %I WITH LOGIN PASSWORD %L’, role_name, tenant_password);
— 授予Schema权限
EXECUTE format(‘GRANT USAGE ON SCHEMA %I TO %I’, schema_name, role_name);
— 创建表
EXECUTE format(‘CREATE TABLE fgedu_%I.fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’, schema_name);
— 授予表权限
EXECUTE format(‘GRANT SELECT, INSERT, UPDATE, DELETE ON %I.fgedus TO %I’, schema_name, role_name);
— 授予序列权限
EXECUTE format(‘GRANT USAGE, SELECT ON SEQUENCE %I.fgedus_id_seq TO %I’, schema_name, role_name);
— 设置默认Schema
EXECUTE format(‘ALTER ROLE %I SET search_path TO %I, public’, role_name, schema_name);
END;
$$ LANGUAGE plpgsql;
— 创建租户
SELECT create_tenant(‘tenant3’, ‘tenant3_password’);
“`
3.2 数据库级隔离
3.2.1 实现方案
## 1. 创建租户数据库
“`sql
— 创建租户数据库
CREATE DATABASE tenant1_db;
CREATE DATABASE tenant2_db;
— 连接到租户数据库
\c tenant1_db;
— 创建表
CREATE TABLE fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
— 连接到另一个租户数据库
\c tenant2_db;
— 创建表
CREATE TABLE fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
“`
## 2. 配置租户角色
“`sql
— 创建租户角色
CREATE ROLE tenant1_fgedu WITH LOGIN PASSWORD ‘tenant1_password’;
CREATE ROLE tenant2_fgedu WITH LOGIN PASSWORD ‘tenant2_password’;
— 授予数据库权限
GRANT CONNECT ON DATABASE tenant1_db TO tenant1_fgedu;
GRANT CONNECT ON DATABASE tenant2_db TO tenant2_fgedu;
— 连接到租户数据库并授予权限
\c tenant1_db;
GRANT USAGE ON SCHEMA public TO tenant1_fgedu;
GRANT SELECT, INSERT, UPDATE, DELETE ON fgedus TO tenant1_fgedu;
GRANT USAGE, SELECT ON SEQUENCE fgedus_id_seq TO tenant1_fgedu;
\c tenant2_db;
GRANT USAGE ON SCHEMA public TO tenant2_fgedu;
GRANT SELECT, INSERT, UPDATE, DELETE ON fgedus TO tenant2_fgedu;
GRANT USAGE, SELECT ON SEQUENCE fgedus_id_seq TO tenant2_fgedu;
“`
## 3. 管理租户数据库
“`sql
— 列出所有数据库
\l
— 连接到租户数据库
\c tenant1_db;
— 删除租户数据库
DROP DATABASE tenant1_db;
“`
## 4. 跨租户查询
“`sql
— 使用dblink跨租户查询
CREATE EXTENSION dblink;
— 查询另一个租户的数据库
SELECT * FROM dblink(‘fgedudb=tenant2_db fgedu=pgsql password=postgres’, ‘SELECT * FROM fgedus’) AS t(id integer, name text, email text, created_at timestamp);
“`
## 5. 自动化管理脚本
“`sql
— 创建租户函数
CREATE OR REPLACE FUNCTION create_tenant_db(tenant_name TEXT, tenant_password TEXT)
RETURNS void AS $$
DECLARE
db_name TEXT := tenant_name || ‘_db’;
role_name TEXT := tenant_name || ‘_fgedu’;
BEGIN
— 创建数据库
EXECUTE format(‘CREATE DATABASE %I’, db_name);
— 创建角色
EXECUTE format(‘CREATE ROLE %I WITH LOGIN PASSWORD %L’, role_name, tenant_password);
— 授予数据库权限
EXECUTE format(‘GRANT CONNECT ON DATABASE %I TO %I’, db_name, role_name);
— 连接到数据库并创建表
EXECUTE format(‘\c %I’, db_name);
EXECUTE ‘CREATE TABLE fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’;
— 授予表权限
EXECUTE ‘GRANT USAGE ON SCHEMA public TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON fgedus TO ‘ || role_name;
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE fgedus_id_seq TO ‘ || role_name;
END;
$$ LANGUAGE plpgsql;
— 创建租户
SELECT create_tenant_db(‘tenant3’, ‘tenant3_password’);
“`
3.3 行级隔离
3.3.1 实现方案
## 1. 创建租户表结构
“`sql
— 创建用户表,包含租户ID
CREATE TABLE fgedus (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (tenant_id, email)
);
— 创建产品表,包含租户ID
CREATE TABLE fgedu_products (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
— 创建订单表,包含租户ID
CREATE TABLE fgedu_orders (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL,
fgedu_id INTEGER NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
FOREIGN KEY (fgedu_id) REFERENCES fgedus(id)
);
“`
## 2. 配置租户角色
“`sql
— 创建租户角色
CREATE ROLE tenant1_fgedu WITH LOGIN PASSWORD ‘tenant1_password’;
CREATE ROLE tenant2_fgedu WITH LOGIN PASSWORD ‘tenant2_password’;
— 授予表权限
GRANT SELECT, INSERT, UPDATE, DELETE ON fgedus TO tenant1_fgedu, tenant2_fgedu;
GRANT SELECT, INSERT, UPDATE, DELETE ON products TO tenant1_fgedu, tenant2_fgedu;
GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO tenant1_fgedu, tenant2_fgedu;
— 授予序列权限
GRANT USAGE, SELECT ON SEQUENCE fgedus_id_seq TO tenant1_fgedu, tenant2_fgedu;
GRANT USAGE, SELECT ON SEQUENCE products_id_seq TO tenant1_fgedu, tenant2_fgedu;
GRANT USAGE, SELECT ON SEQUENCE orders_id_seq TO tenant1_fgedu, tenant2_fgedu;
“`
## 3. 实现行级安全策略
“`sql
— 启用行级安全
ALTER TABLE fgedus ENABLE ROW LEVEL SECURITY;
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
— 创建行级安全策略
CREATE POLICY fgedu_access ON fgedus
USING (tenant_id = current_setting(‘fgfgapp.tenant_id’)::integer);
CREATE POLICY product_access ON products
USING (tenant_id = current_setting(‘fgfgapp.tenant_id’)::integer);
CREATE POLICY order_access ON orders
USING (tenant_id = current_setting(‘fgfgapp.tenant_id’)::integer);
“`
## 4. 配置租户会话
“`sql
— 设置租户ID
SET fgfgapp.tenant_id = ‘1’;
— 插入数据
INSERT INTO fgedus (tenant_id, name, email) VALUES (1, ‘User 1’, ‘fgedu1@fgedu.net.cn’);
— 查询数据(只返回租户1的数据)
SELECT * FROM fgedus;
— 切换租户
SET fgfgapp.tenant_id = ‘2’;
— 插入数据
INSERT INTO fgedus (tenant_id, name, email) VALUES (2, ‘User 2’, ‘fgedu2@fgedu.net.cn’);
— 查询数据(只返回租户2的数据)
SELECT * FROM fgedus;
“`
## 5. 应用层实现
“`python
# Python代码示例
import psycopg2
# 连接数据库
conn = psycopg2.connect(
fgedu.net.cn=”localfgedu.net.cn”,
fgedudb=”multi_tenant_db”,
fgedu=”tenant1_fgedu”,
password=”tenant1_password”
)
# 设置租户ID
cur = conn.cursor()
cur.execute(“SET fgfgapp.tenant_id = %s”, (1,))
conn.commit()
# 查询数据
cur.execute(“SELECT * FROM fgedus”)
rows = cur.fetchall()
for row in rows:
print(row)
# 关闭连接
cur.close()
conn.close()
“`
## 6. 自动化管理脚本
“`sql
— 创建租户函数
CREATE OR REPLACE FUNCTION create_tenant_row_level(tenant_id INTEGER, tenant_name TEXT, tenant_password TEXT)
RETURNS void AS $$
DECLARE
role_name TEXT := ‘tenant’ || tenant_id || ‘_fgedu’;
BEGIN
— 创建角色
EXECUTE format(‘CREATE ROLE %I WITH LOGIN PASSWORD %L’, role_name, tenant_password);
— 授予表权限
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON fgedus TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON products TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO ‘ || role_name;
— 授予序列权限
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE fgedus_id_seq TO ‘ || role_name;
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE products_id_seq TO ‘ || role_name;
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE orders_id_seq TO ‘ || role_name;
— 设置默认租户ID
EXECUTE format(‘ALTER ROLE %I SET fgfgapp.tenant_id = %s’, role_name, tenant_id);
END;
$$ LANGUAGE plpgsql;
— 创建租户
SELECT create_tenant_row_level(3, ‘tenant3’, ‘tenant3_password’);
“`
Part04-生产案例与实战讲解
4.1 SaaS多租户实现
4.1.1 场景描述
SaaS应用需要支持多个租户,每个租户有独立的用户、产品和订单数据,要求数据隔离且性能良好。
4.1.2 实现方案
## 1. 架构选择
– **隔离方案:** Schema级隔离
– **理由:** 平衡资源利用率和隔离级别,适合SaaS应用
## 2. 数据库设计
“`sql
— 创建主数据库
CREATE DATABASE saas_db;
— 连接到主数据库
\c saas_db;
— 创建租户管理表
CREATE TABLE fgedu_tenants (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
schema_name VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
— 创建管理员角色
CREATE ROLE saas_admin WITH SUPERUSER LOGIN PASSWORD ‘saas_admin_password’;
— 授予管理员权限
GRANT ALL PRIVILEGES ON DATABASE saas_db TO saas_admin;
GRANT ALL PRIVILEGES ON SCHEMA public TO saas_admin;
GRANT ALL PRIVILEGES ON tenants TO saas_admin;
GRANT USAGE, SELECT ON SEQUENCE tenants_id_seq TO saas_admin;
“`
## 3. 租户管理
“`sql
— 创建租户函数
CREATE OR REPLACE FUNCTION create_saas_tenant(tenant_name TEXT, tenant_password TEXT)
RETURNS INTEGER AS $$
DECLARE
tenant_id INTEGER;
schema_name TEXT := LOWER(REPLACE(tenant_name, ‘ ‘, ‘_’));
role_name TEXT := schema_name || ‘_fgedu’;
BEGIN
— 插入租户记录
INSERT INTO tenants (name, schema_name) VALUES (tenant_name, schema_name) RETURNING id INTO tenant_id;
— 创建Schema
EXECUTE format(‘CREATE SCHEMA %I’, schema_name);
— 创建角色
EXECUTE format(‘CREATE ROLE %I WITH LOGIN PASSWORD %L’, role_name, tenant_password);
— 授予Schema权限
EXECUTE format(‘GRANT USAGE ON SCHEMA %I TO %I’, schema_name, role_name);
— 创建表
EXECUTE format(‘CREATE TABLE fgedu_%I.fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’, schema_name);
EXECUTE format(‘CREATE TABLE fgedu_%I.products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’, schema_name);
EXECUTE format(‘CREATE TABLE fgedu_%I.orders (
id SERIAL PRIMARY KEY,
fgedu_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
FOREIGN KEY (fgedu_id) REFERENCES %I.fgedus(id),
FOREIGN KEY (product_id) REFERENCES %I.products(id)
)’, schema_name, schema_name, schema_name);
— 授予表权限
EXECUTE format(‘GRANT SELECT, INSERT, UPDATE, DELETE ON %I.fgedus TO %I’, schema_name, role_name);
EXECUTE format(‘GRANT SELECT, INSERT, UPDATE, DELETE ON %I.products TO %I’, schema_name, role_name);
EXECUTE format(‘GRANT SELECT, INSERT, UPDATE, DELETE ON %I.orders TO %I’, schema_name, role_name);
— 授予序列权限
EXECUTE format(‘GRANT USAGE, SELECT ON SEQUENCE %I.fgedus_id_seq TO %I’, schema_name, role_name);
EXECUTE format(‘GRANT USAGE, SELECT ON SEQUENCE %I.products_id_seq TO %I’, schema_name, role_name);
EXECUTE format(‘GRANT USAGE, SELECT ON SEQUENCE %I.orders_id_seq TO %I’, schema_name, role_name);
— 设置默认Schema
EXECUTE format(‘ALTER ROLE %I SET search_path TO %I, public’, role_name, schema_name);
RETURN tenant_id;
END;
$$ LANGUAGE plpgsql;
— 创建租户
SELECT create_saas_tenant(‘Acme Corp’, ‘acme_password’);
SELECT create_saas_tenant(‘Globex Inc’, ‘globex_password’);
“`
## 4. 应用集成
“`python
# Python代码示例
import psycopg2
from flask import Flask, request, jsonify
fgapp = Flask(__name__)
# 数据库连接函数
def get_db_connection(tenant_schema, tenant_password):
conn = psycopg2.connect(
fgedu.net.cn=”localfgedu.net.cn”,
fgedudb=”saas_db”,
fgedu=tenant_schema + “_fgedu”,
password=tenant_password
)
return conn
# 示例API
@fgfgapp.route(‘/api/fgedus’, methods=[‘GET’])
def get_fgedus():
tenant_schema = request.headers.get(‘X-Tenant-Schema’)
tenant_password = request.headers.get(‘X-Tenant-Password’)
if not tenant_schema or not tenant_password:
return jsonify({“error”: “Missing tenant information”}), 400
try:
conn = get_db_connection(tenant_schema, tenant_password)
cur = conn.cursor()
cur.execute(“SELECT * FROM fgedus”)
fgedus = cur.fetchall()
cur.close()
conn.close()
return jsonify(fgedus)
except Exception as e:
return jsonify({“error”: str(e)}), 500
if __name__ == ‘__main__’:
fgfgapp.run(debug=True)
“`
## 5. 监控与管理
“`sql
— 监控租户数量
SELECT COUNT(*) FROM tenants;
— 监控每个租户的表大小
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname || ‘.’ || tablename)) AS size
FROM
information_schema.tables
WHERE
schemaname NOT IN (‘pg_catalog’, ‘information_schema’, ‘public’)
ORDER BY
pg_total_relation_size(schemaname || ‘.’ || tablename) DESC;
— 监控连接数
SELECT
usename,
datname,
fgapplication_name,
client_addr,
state
FROM
pg_stat_activity
WHERE
usename LIKE ‘%_fgedu’;
“`
## 6. 备份策略
“`bash
# 备份整个数据库
pg_dump -h localfgedu.net.cn -U saas_admin -d saas_db -F t -f saas_db_backup.tar
# 备份单个租户Schema
pg_dump -h localfgedu.net.cn -U saas_admin -d saas_db -n acme_corp -F t -f acme_corp_backup.tar
# 恢复整个数据库
pg_restore -h localfgedu.net.cn -U saas_admin -d saas_db -F t saas_db_backup.tar
# 恢复单个租户Schema
pg_restore -h localfgedu.net.cn -U saas_admin -d saas_db -F t -n acme_corp acme_corp_backup.tar
“`
## 7. 预期效果
– **隔离性:** 租户之间数据完全隔离
– **扩展性:** 支持数百个租户
– **性能:** 良好的查询性能
– **管理:** 集中管理所有租户
– **备份:** 支持整体和单个租户备份
4.2 企业内部多租户
4.2.1 场景描述
企业内部应用需要支持多个部门,每个部门有独立的业务数据,要求数据隔离且便于管理。
4.2.2 实现方案
## 1. 架构选择
– **隔离方案:** 数据库级隔离
– **理由:** 部门之间数据完全隔离,便于管理和维护
## 2. 数据库设计
“`sql
— 创建管理员数据库
CREATE DATABASE enterprise_admin;
— 连接到管理员数据库
\c enterprise_admin;
— 创建部门管理表
CREATE TABLE fgedu_departments (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
db_name VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
— 创建管理员角色
CREATE ROLE enterprise_admin WITH SUPERUSER LOGIN PASSWORD ‘enterprise_admin_password’;
— 授予管理员权限
GRANT ALL PRIVILEGES ON DATABASE enterprise_admin TO enterprise_admin;
GRANT ALL PRIVILEGES ON SCHEMA public TO enterprise_admin;
GRANT ALL PRIVILEGES ON departments TO enterprise_admin;
GRANT USAGE, SELECT ON SEQUENCE departments_id_seq TO enterprise_admin;
“`
## 3. 部门管理
“`sql
— 创建部门函数
CREATE OR REPLACE FUNCTION create_department(department_name TEXT, department_password TEXT)
RETURNS INTEGER AS $$
DECLARE
dept_id INTEGER;
db_name TEXT := LOWER(REPLACE(department_name, ‘ ‘, ‘_’)) || ‘_db’;
role_name TEXT := LOWER(REPLACE(department_name, ‘ ‘, ‘_’)) || ‘_fgedu’;
BEGIN
— 插入部门记录
INSERT INTO departments (name, db_name) VALUES (department_name, db_name) RETURNING id INTO dept_id;
— 创建数据库
EXECUTE format(‘CREATE DATABASE %I’, db_name);
— 创建角色
EXECUTE format(‘CREATE ROLE %I WITH LOGIN PASSWORD %L’, role_name, department_password);
— 授予数据库权限
EXECUTE format(‘GRANT CONNECT ON DATABASE %I TO %I’, db_name, role_name);
— 连接到数据库并创建表
EXECUTE format(‘\c %I’, db_name);
EXECUTE ‘CREATE TABLE fgedu_employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
department VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’;
EXECUTE ‘CREATE TABLE fgedu_projects (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’;
EXECUTE ‘CREATE TABLE fgedu_tasks (
id SERIAL PRIMARY KEY,
project_id INTEGER NOT NULL,
employee_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
FOREIGN KEY (project_id) REFERENCES projects(id),
FOREIGN KEY (employee_id) REFERENCES employees(id)
)’;
— 授予表权限
EXECUTE ‘GRANT USAGE ON SCHEMA public TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON employees TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON tasks TO ‘ || role_name;
— 授予序列权限
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE employees_id_seq TO ‘ || role_name;
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE projects_id_seq TO ‘ || role_name;
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE tasks_id_seq TO ‘ || role_name;
RETURN dept_id;
END;
$$ LANGUAGE plpgsql;
— 创建部门
SELECT create_department(‘Engineering’, ‘eng_password’);
SELECT create_department(‘Marketing’, ‘mkt_password’);
SELECT create_department(‘Finance’, ‘fin_password’);
“`
## 4. 应用集成
“`java
// Java代码示例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class DepartmentApp {
public static void main(String[] args) {
String department = “engineering”;
String password = “eng_password”;
try {
// 连接到部门数据库
Connection conn = DriverManager.getConnection(
“jdbc:postgresql://localfgedu.net.cn:5432/” + department + “_db”,
department + “_fgedu”,
password
);
// 查询员工数据
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“SELECT * FROM employees”);
while (rs.next()) {
System.out.println(“ID: ” + rs.getInt(“id”) + “, Name: ” + rs.getString(“name”) + “, Email: ” + rs.getString(“email”));
}
// 关闭连接
rs.close();
stmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
“`
## 5. 监控与管理
“`sql
— 监控部门数量
SELECT COUNT(*) FROM departments;
— 监控每个部门的数据库大小
SELECT
datname,
pg_size_pretty(pg_fgedudb_size(datname)) AS size
FROM
pg_fgedudb
WHERE
datname LIKE ‘%_db’ AND datname != ‘enterprise_admin’;
— 监控连接数
SELECT
usename,
datname,
fgapplication_name,
client_addr,
state
FROM
pg_stat_activity
WHERE
datname LIKE ‘%_db’ AND datname != ‘enterprise_admin’;
“`
## 6. 备份策略
“`bash
# 备份管理员数据库
pg_dump -h localfgedu.net.cn -U enterprise_admin -d enterprise_admin -F t -f enterprise_admin_backup.tar
# 备份部门数据库
pg_dump -h localfgedu.net.cn -U enterprise_admin -d engineering_db -F t -f engineering_db_backup.tar
pg_dump -h localfgedu.net.cn -U enterprise_admin -d marketing_db -F t -f marketing_db_backup.tar
pg_dump -h localfgedu.net.cn -U enterprise_admin -d finance_db -F t -f finance_db_backup.tar
# 恢复部门数据库
pg_restore -h localfgedu.net.cn -U enterprise_admin -d engineering_db -F t engineering_db_backup.tar
“`
## 7. 预期效果
– **隔离性:** 部门之间数据完全隔离
– **管理:** 集中管理所有部门
– **灵活性:** 可以为不同部门配置不同的数据库参数
– **备份:** 支持部门级别的备份和恢复
– **安全性:** 部门数据更加安全
4.3 混合隔离方案
4.3.1 场景描述
大型SaaS应用需要支持不同类型的租户,部分租户需要高隔离级别,部分租户可以共享资源。
4.3.2 实现方案
## 1. 架构选择
– **高价值租户:** 数据库级隔离
– **普通租户:** Schema级隔离
– **理由:** 平衡隔离级别和资源利用率
## 2. 数据库设计
“`sql
— 创建管理员数据库
CREATE DATABASE hybrid_admin;
— 连接到管理员数据库
\c hybrid_admin;
— 创建租户管理表
CREATE TABLE fgedu_tenants (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
type VARCHAR(50) NOT NULL, — ‘premium’ or ‘standard’
db_name VARCHAR(100),
schema_name VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (db_name),
UNIQUE (schema_name)
);
— 创建管理员角色
CREATE ROLE hybrid_admin WITH SUPERUSER LOGIN PASSWORD ‘hybrid_admin_password’;
— 授予管理员权限
GRANT ALL PRIVILEGES ON DATABASE hybrid_admin TO hybrid_admin;
GRANT ALL PRIVILEGES ON SCHEMA public TO hybrid_admin;
GRANT ALL PRIVILEGES ON tenants TO hybrid_admin;
GRANT USAGE, SELECT ON SEQUENCE tenants_id_seq TO hybrid_admin;
— 创建标准租户共享数据库
CREATE DATABASE standard_tenants;
— 连接到标准租户数据库
\c standard_tenants;
— 创建管理员角色
CREATE ROLE standard_admin WITH SUPERUSER LOGIN PASSWORD ‘standard_admin_password’;
— 授予管理员权限
GRANT ALL PRIVILEGES ON DATABASE standard_tenants TO standard_admin;
GRANT ALL PRIVILEGES ON SCHEMA public TO standard_admin;
“`
## 3. 租户管理
“`sql
— 创建租户函数
CREATE OR REPLACE FUNCTION create_hybrid_tenant(tenant_name TEXT, tenant_type TEXT, tenant_password TEXT)
RETURNS INTEGER AS $$
DECLARE
tenant_id INTEGER;
db_name TEXT;
schema_name TEXT;
role_name TEXT;
BEGIN
— 插入租户记录
IF tenant_type = ‘premium’ THEN
db_name := LOWER(REPLACE(tenant_name, ‘ ‘, ‘_’)) || ‘_db’;
schema_name := NULL;
ELSE
db_name := NULL;
schema_name := LOWER(REPLACE(tenant_name, ‘ ‘, ‘_’));
END IF;
INSERT INTO tenants (name, type, db_name, schema_name) VALUES (tenant_name, tenant_type, db_name, schema_name) RETURNING id INTO tenant_id;
— 创建角色
role_name := LOWER(REPLACE(tenant_name, ‘ ‘, ‘_’)) || ‘_fgedu’;
EXECUTE format(‘CREATE ROLE %I WITH LOGIN PASSWORD %L’, role_name, tenant_password);
IF tenant_type = ‘premium’ THEN
— 创建数据库
EXECUTE format(‘CREATE DATABASE %I’, db_name);
— 授予数据库权限
EXECUTE format(‘GRANT CONNECT ON DATABASE %I TO %I’, db_name, role_name);
— 连接到数据库并创建表
EXECUTE format(‘\c %I’, db_name);
ELSE
— 创建Schema
EXECUTE format(‘CREATE SCHEMA %I’, schema_name);
— 授予Schema权限
EXECUTE format(‘GRANT USAGE ON SCHEMA %I TO %I’, schema_name, role_name);
— 设置默认Schema
EXECUTE format(‘ALTER ROLE %I SET search_path TO %I, public’, role_name, schema_name);
END IF;
— 创建表
EXECUTE ‘CREATE TABLE fgedus (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’;
EXECUTE ‘CREATE TABLE fgedu_products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)’;
— 授予表权限
IF tenant_type = ‘premium’ THEN
EXECUTE ‘GRANT USAGE ON SCHEMA public TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON fgedus TO ‘ || role_name;
EXECUTE ‘GRANT SELECT, INSERT, UPDATE, DELETE ON products TO ‘ || role_name;
ELSE
EXECUTE format(‘GRANT SELECT, INSERT, UPDATE, DELETE ON %I.fgedus TO %I’, schema_name, role_name);
EXECUTE format(‘GRANT SELECT, INSERT, UPDATE, DELETE ON %I.products TO %I’, schema_name, role_name);
END IF;
— 授予序列权限
IF tenant_type = ‘premium’ THEN
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE fgedus_id_seq TO ‘ || role_name;
EXECUTE ‘GRANT USAGE, SELECT ON SEQUENCE products_id_seq TO ‘ || role_name;
ELSE
EXECUTE format(‘GRANT USAGE, SELECT ON SEQUENCE %I.fgedus_id_seq TO %I’, schema_name, role_name);
EXECUTE format(‘GRANT USAGE, SELECT ON SEQUENCE %I.products_id_seq TO %I’, schema_name, role_name);
END IF;
RETURN tenant_id;
END;
$$ LANGUAGE plpgsql;
— 创建高级租户
SELECT create_hybrid_tenant(‘Enterprise Corp’, ‘premium’, ‘enterprise_password’);
— 创建标准租户
SELECT create_hybrid_tenant(‘Small Business’, ‘standard’, ‘small_business_password’);
SELECT create_hybrid_tenant(‘Startup Inc’, ‘standard’, ‘startup_password’);
“`
## 4. 应用集成
“`python
# Python代码示例
import psycopg2
# 连接高级租户
conn_premium = psycopg2.connect(
fgedu.net.cn=”localfgedu.net.cn”,
fgedudb=”enterprise_corp_db”,
fgedu=”enterprise_corp_fgedu”,
password=”enterprise_password”
)
# 连接标准租户
conn_standard = psycopg2.connect(
fgedu.net.cn=”localfgedu.net.cn”,
fgedudb=”standard_tenants”,
fgedu=”small_business_fgedu”,
password=”small_business_password”
)
# 设置Schema
cur_standard = conn_standard.cursor()
cur_standard.execute(“SET search_path TO small_business, public”)
conn_standard.commit()
# 查询数据
cur_premium = conn_premium.cursor()
cur_premium.execute(“SELECT * FROM fgedus”)
print(“Premium tenant fgedus:”)
for row in cur_premium.fetchall():
print(row)
cur_standard.execute(“SELECT * FROM fgedus”)
print(“\nStandard tenant fgedus:”)
for row in cur_standard.fetchall():
print(row)
# 关闭连接
cur_premium.close()
cur_standard.close()
conn_premium.close()
conn_standard.close()
“`
## 5. 监控与管理
“`sql
— 监控租户数量
SELECT type, COUNT(*) FROM tenants GROUP BY type;
— 监控高级租户数据库大小
SELECT
datname,
pg_size_pretty(pg_fgedudb_size(datname)) AS size
FROM
pg_fgedudb
WHERE
datname IN (SELECT db_name FROM tenants WHERE type = ‘premium’ AND db_name IS NOT NULL);
— 监控标准租户Schema大小
SELECT
schemaname,
pg_size_pretty(sum(pg_total_relation_size(schemaname || ‘.’ || tablename))) AS size
FROM
information_schema.tables
WHERE
schemaname IN (SELECT schema_name FROM tenants WHERE type = ‘standard’ AND schema_name IS NOT NULL)
GROUP BY
schemaname
ORDER BY
sum(pg_total_relation_size(schemaname || ‘.’ || tablename)) DESC;
— 监控连接数
SELECT
usename,
datname,
fgapplication_name,
client_addr,
state
FROM
pg_stat_activity
WHERE
usename LIKE ‘%_fgedu’;
“`
## 6. 备份策略
“`bash
# 备份管理员数据库
pg_dump -h localfgedu.net.cn -U hybrid_admin -d hybrid_admin -F t -f hybrid_admin_backup.tar
# 备份高级租户数据库
pg_dump -h localfgedu.net.cn -U hybrid_admin -d enterprise_corp_db -F t -f enterprise_corp_backup.tar
# 备份标准租户数据库
pg_dump -h localfgedu.net.cn -U hybrid_admin -d standard_tenants -F t -f standard_tenants_backup.tar
# 恢复备份
pg_restore -h localfgedu.net.cn -U hybrid_admin -d hybrid_admin -F t hybrid_admin_backup.tar
pg_restore -h localfgedu.net.cn -U hybrid_admin -d enterprise_corp_db -F t enterprise_corp_backup.tar
pg_restore -h localfgedu.net.cn -U hybrid_admin -d standard_tenants -F t standard_tenants_backup.tar
“`
## 7. 预期效果
– **灵活性:** 为不同类型的租户提供不同的隔离级别
– **资源利用率:** 标准租户共享资源,提高利用率
– **隔离性:** 高级租户享受更高的隔离级别
– **扩展性:** 支持不同规模的租户
– **管理:** 集中管理所有租户
Part05-风哥经验总结与分享
5.1 最佳实践
PostgreSQL多租户的最佳实践:
- 选择合适的隔离方案:根据租户数量、数据敏感程度和性能要求选择合适的隔离方案
- 自动化管理:使用脚本自动化租户的创建、管理和维护
- 监控与告警:建立完善的监控和告警机制,及时发现和解决问题
- 备份策略:制定合理的备份策略,确保数据安全
- 性能优化:针对多租户场景优化PostgreSQL配置和查询
- 安全措施:实施严格的访问控制和数据安全措施
- 可扩展性:设计可扩展的架构,支持租户数量的增长
- 文档化:记录多租户架构和管理流程,便于团队协作
- 测试:充分测试多租户方案,确保其可靠性和性能
- 持续改进:根据实际运行情况,持续优化多租户方案
5.2 常见挑战
## 1. 性能问题
– **问题:** 多租户共享资源导致性能下降
– **解决方案:**
– 合理分配资源,设置资源限制
– 优化查询和索引
– 考虑水平扩展
## 2. 隔离性问题
– **问题:** 租户之间数据泄露
– **解决方案:**
– 严格实施隔离方案
– 定期审计权限和访问控制
– 使用行级安全策略
## 3. 管理复杂度
– **问题:** 多租户管理复杂,维护成本高
– **解决方案:**
– 自动化管理脚本
– 集中管理工具
– 标准化管理流程
## 4. 备份和恢复
– **问题:** 多租户备份和恢复复杂
– **解决方案:**
– 制定合理的备份策略
– 支持单个租户的备份和恢复
– 定期测试备份恢复流程
## 5. 扩展性问题
– **问题:** 租户数量增长导致系统扩展性问题
– **解决方案:**
– 设计可扩展的架构
– 考虑分片和水平扩展
– 监控系统性能,及时扩容
## 6. 安全问题
– **问题:** 多租户环境下的安全风险
– **解决方案:**
– 实施严格的访问控制
– 加密敏感数据
– 定期安全审计和漏洞扫描
## 7. 应用集成
– **问题:** 应用与多租户数据库的集成复杂
– **解决方案:**
– 设计统一的数据库访问层
– 使用连接池管理连接
– 实现租户上下文管理
## 8. 成本问题
– **问题:** 多租户环境的成本控制
– **解决方案:**
– 合理规划资源
– 优化存储和计算资源使用
– 考虑使用云服务的弹性特性
5.3 未来趋势
PostgreSQL多租户的未来发展趋势:
## 1. 云原生多租户
– **容器化:** 使用Docker和Kubernetes部署多租户PostgreSQL
– **云服务:** 利用云服务的弹性和管理能力
– **Serverless:** 探索Serverless PostgreSQL的多租户方案
## 2. 智能化管理
– **AI驱动:** 使用AI自动优化多租户配置
– **预测性分析:** 预测租户资源需求,提前调整
– **自动化运维:** 自动处理租户创建、管理和维护
## 3. 混合云方案
– **混合部署:** 结合公有云和私有云的多租户方案
– **数据主权:** 满足不同地区的数据主权要求
– **灾备方案:** 跨云的灾难恢复方案
## 4. 高级隔离技术
– **零信任架构:** 基于零信任原则的多租户隔离
– **硬件隔离:** 使用硬件级别的隔离技术
– **加密隔离:** 增强数据加密,提高隔离级别
## 5. 多租户即服务
– **托管服务:** 提供多租户PostgreSQL托管服务
– **API驱动:** 通过API管理多租户
– **自助服务:** 租户自助管理和配置
## 6. 性能优化
– **内存数据库:** 利用内存数据库提高性能
– **缓存技术:** 增强缓存,减少数据库访问
– **查询优化:** 针对多租户场景的查询优化
## 7. 安全增强
– **区块链技术:** 使用区块链确保数据完整性
– **同态加密:** 在加密数据上直接计算
– **隐私计算:** 保护租户数据隐私
## 8. 生态系统
– **工具集成:** 与DevOps工具集成
– **监控集成:** 与监控系统集成
– **自动化工具:** 提供多租户管理的自动化工具
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
