💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## 微服务数库设计,把复杂的关系简单化 |方法/工具 | 优点 | 缺点 | 技术边界|流程与例子|  |:--|:--|:--|:--|:--| |域驱动设计|聚焦业务概念,简洁模型|初期投入较大,学习曲线陡|需要投入较长周期进行域研究|实施DDD,进行域分析与建模,设计领域模型|  |Event Storming|发现业务隐藏元素,简化流程|事件抽取难度大,成果依人|难以实现完全自动化|通过工作坊演练域事件,发现实体及数据结构| |数据库范式理论|消除数据冗余,简化依赖|过度归约影响查询性能|需要在理论与实践之间权衡|按照1NF到3NF,规范化数据模型|  |物理模型转换|选择主键和拆分表简化关系|容易过度优化,造成查询难度|需要在性能与理论之间平衡|在逻辑模型的基础上进行物理优化,如添加索引、拆分表等| |观测与迭代|根据实际场景不断优化|初期投入难以全面考虑各种复杂场景|需要长期稳定的产品架构进行演进|观察业务场景与访问模式,对模型设计进行持续优化| |ER图工具|直观展示实体关系,方便优化|手工建模难以大规模解析|可以结合其他方法部分自动生成|使用工具绘制ER图,优化实体与关系| |数据库迁移工具 |简化物理模型变更的回归|迁移过程中的数据一致性难保证|需要清晰的物理模型映射关系|在逻辑模型变更后使用工具更新数据库| |代码生成工具|简化物理模型的设计过程|生成的代码质量依赖工具|需要对应支持的编程语言与框架|通过建模工具生成代码,手工补充业务逻辑| |ORM框架|简化SQL语句编写,屏蔽数据库差异|性能开销较大,Debug难度较大|需要对ORM框架有深入理解,选择合适的 level|在代码中使用ORM API操作数据库   | |分库分表工具|简化单库表关系,应对高并发|分库分表策略难以设计与调整|需要在扩展性与关系完整性之间权衡|根据业务规则使用工具切分数据到不同数据库|  |缓存中间件|简化数据库查询,部分逻辑在缓存中处理|数据一致性较难保证,缓存雪崩风险|需要设计好缓存过期和更新策略|业务查询先查缓存,缓存未命中再查询DB| |搜索引擎|简化应用直接访问数据库|数据导入过程较为复杂,同步延迟难以避免|需要对不同搜索引擎有深入理解,选择最适合的数据建模方式|应用通过搜索引擎API查询数据,搜索引擎对应维护数据库| |消息队列|简化不同系统模块的数据库关系|增加系统复杂性,消息丢失或重复的风险|需要保证MQ的高可用性并严格保证消息的幂等性|使用MQ使模块异步通讯,不直接操作数据库| |文档数据库|简化关系模型的表连接概念|查询性能较差,事务支持较弱|需要在关系完整性与扩展性之间权衡|直接使用文档进行数据存储| ## 微服务架构的关系数据库优化 ### 1)分库分表: 根据业务功能对数据库进行纵向拆分,形成相对独立的数据库。再根据业务量对单表进行横向拆分,实现分表。这有利于降低单表访问压力,提高数据库扩展性. ***** 1. 分库:根据业务功能将数据库进行纵向拆分,形成相对独立的数据库。这可以降低单个数据库的访问压力,也方便后续的水平扩展。 2. 分表:单个数据库的表数据量过大时,可以将表进行横向拆分,实现表的扩展。一般按照主键范围或时间范围进行拆分。 3. sharding key选择:选择合适的分片键,可以使数据分布较均衡。如果数据分布不均,会导致数据倾斜,影响扩展性。 4. 扩展策略:预先制定表的扩展策略,如每多少数据进行拆分一个表等。这可以指导业务量增长时的数据库扩展规划。 5. ID生成策略:使用分布式ID生成方案,为新 insert 的数据选择 correct 的表。这确保了数据路由的准确性。 6. 路由机制:将 SQL 路由至正确的数据库或表进行查询和更新。常用的路由方式有中间件路由、应用程序路由等。 7. 分片的数据同步:对于需要备份或主从的数据库,要实现分片数据的同步复制。这确保了分片后的高可用性。 8. Query 重写:对查询进行解析和重写,严格按照分片策略路由查询至相关的数据库或表。避免出现跨分片的查询。 9. 监控报警:监控各数据库和表的负载情况,当某个分片的数据或访问量过大时,及时进行告警,并扩展对其进行扩容。 - 代码示例: ``` python # 应用层路由 - 选择数据库 db = db_map[table_prefix] # 应用层路由 - 选择表 table = '%s_%s' % (table_prefix, sharding_key % MAX_TABLE_NUM) # 分布式ID - 取模 uid = user_counter.get_next() % MAX_TABLE_NUM # Query 重写 table_name = 'user_%s' % uid sql = 'SELECT * FROM %s WHERE ...' % table_name ``` > 数据分片可以有效提高数据库的扩展性,但也带来了额外的复杂性。这需要系统架构师在设计阶段就考虑到表的扩展与分片,并且选择合理的方案与切分策略。 要确保分片后的高可用与事务支持,并实现查询的重写与路由。这需要架构师对各种分片数据库方案与技术都很熟悉,可以设计出合理可行的系统架构。数据库的拆分与扩展直接影响着系统的性能与扩展能力。这需要架构师具有很强的未来预见能力,提前规划数据库的扩展路线图,按需进行系统升级,满足业务量的增长需求。要在系统性能、扩展性和实现难度之间取得平衡,选择最适合业务发展的技术方案。 ***** ***** ***** ### 2)读写分离: 将数据库分为主数据库(写)和从数据库(读),读请求指向从数据库,写请求指向主数据库。后续将主数据库的数据同步到从数据库。这减轻了主数据库的负载,提高了读查询性能。 ***** 1\. 部署主数据库(写)和从数据库(读),主数据库用于写入数据,从数据库用于读取数据。 2\. 应用层使用数据库连接池,定义读数据库连接和写数据库连接。读请求使用读数据库连接,写请求使用写数据库连接。 3\. 写入数据时,将数据写入主数据库。并同步将数据写入从数据库,保证主从数据一致。 4\. 读取数据时,首先从从数据库读取,若读取不到(如刚插入数据还未同步),再从主数据库读取。后续的读取请求直接从从数据库获取。 5\. 增加从数据库的数量,来扩展读服务能力。多个从数据库的数据通过主数据库同步。 6\. 使用代理方式,将读请求分发到不同从数据库。并使用负载均衡算法选择从数据库。这实现了读请求的分流,进一步扩展了系统的读处理能力。 7\. 监控主从数据库的数据同步状态和延迟。如果发现主从数据不同步或同步滞后,需要及时修复,保证最终一致性。 - 具体的实现代码示例: ``` python # 数据库连接池 db_pool = PooledDB(creator=pymysql, ...) # 获取读数据库连接 rd_conn = db_pool.connection() # 获取写数据库连接 wt_conn = db_pool.connection() # 写入数据 with wt_conn.cursor() as cursor: cursor.execute(sql, args) # 同步写入从数据库 # 读取数据 with rd_conn.cursor() as cursor: cursor.execute(sql, args) result = cursor.fetchall() # 如果从数据库没有读取到 if not result: with wt_conn.cursor() as cursor: cursor.execute(sql, args) result = cursor.fetchall() ``` > 该实现使用数据库连接池获取不同的数据库连接,并在业务逻辑中正确使用。同时通过同步主从数据来确保最终一致性,并在从数据库读取不到数据的情况下,及时补充从主数据库读取,保证业务logic的准确性。 在高并发场景下,要特别关注最小空闲连接数的设置,保证有足够的连接随时可用。同时要监控连接池的状态,在连接数达到上限时及时作出响应,避免影响业务的正常运行。 - 连接池实现完整过程: ``` python # 初始化连接池 pool = PooledDB(creator=pymysql, # 使用pymysql数据库 maxconnections=10, # 最大连接数 mincached=5, # 最少空闲数 maxcached=7, # 最大空闲数 blocking=True, # 达到最大数时是否阻塞等待 maxusage=None, # 单个连接最大复用次数 setsession=[], # 开始会话前执行的命令 ping=0, ) # 获取连接 conn = pool.connection() # 使用连接 cur = conn.cursor() cur.execute(sql) # 释放连接 conn.close() ``` ***** ***** ***** ### 3)缓存数据库查询结果: 使用缓存(如Redis)记录数据库查询结果,后续查询先访问缓存,若存在则直接返回,否则再访问数据库。这大幅减少了数据库交互次数,提高性能。 ***** 1\. 分析业务场景,找到数据库查询结果中存在高频且稳定的部分。这些查询结果适合写入缓存。 2\. 选择合适的缓存工具,如Redis。考虑数据持久化与容错机制,确保缓存数据的安全性。 3\. 在应用层代码中,优先从缓存获取数据。只有当缓存不存在对应数据时,才访问数据库查询。 4\. 将数据库查询结果在返回应用层前写入缓存。并设置合理的缓存时间,避免数据失效。 5\. 对数据更新操作,需要同步更新缓存中的数据。确保缓存与数据库保持一致。 6\. 监控缓存命中率和数据库访问量,若缓存命中率过低,需要调整缓存策略。 7\. 根据业务特点选择不同的缓存更新策略: \- 直接删除:对于独立数据,直接删除缓存。 \- 先删除后更新:先删除缓存,然后异步更新数据库,最后更新缓存。 \- 队列更新:将更新操作入队,由后台任务异步执行数据库更新和缓存更新。 代码实现示例: ``` python redis_cli = Redis(host='localhost', port=6379) # 读取数据,优先从缓存获取 result = redis_cli.get(key) if not result: # 缓存不存在,从数据库查询 result = db.query(sql) # 将结果写入缓存,设置缓存时间为1小时 redis_cli.set(key, result, ex=3600) # 数据更新操作 def update(sql): db.execute(sql) # 同步更新缓存 redis_cli.delete(key) # 直接删除缓存 new_result = db.query(sql) # 获取最新结果 redis_cli.set(key, new_result, ex=3600) # 更新缓存 # 使用队列异步执行更新 q.put(update) # 将更新任务入队 ``` > 使用缓存可以有效减少数据库访问,提高系统性能。但也增加了系统的复杂性,需要开发人员在设计时理解不同的缓存策略,并根据业务场景选择最优方案。同时要确保缓存与数据库保持一致,避免由于数据不一致产生的逻辑错误。这需要对系统进行全面测试,在开发阶段就考虑到高并发下的缓存更新问题,选择合理可行的方案。总的来说,要在性能、一致性和复杂性之间取得平衡。 ***** ***** ***** ### 4)使用索引: 在频繁作为查询条件和排序依据的字段上创建索引,这加速了数据库数据的查找速度,减少查询时间。但索引也会消耗一定存储空间和处理能力,需要权衡使用。 ***** 1. 确定索引字段:需要确定作为查询条件和排序依据的字段,这些字段的数据频繁变化和参与查询,并且对查询性能有较大影响。 2. 选择索引类型:常见的有B树索引和哈希索引,这里选择B树索引作为示例。 3. 创建索引:在数据库中针对选择的字段创建B树索引,例如: ``` CREATE INDEX idx_user_name ON user (name) ``` 4. 验证索引:创建索引后,对包含该字段的查询语句进行验证,确认索引生效并且提高了查询性能。 ``` SELECT * FROM user WHERE name = 'Tom' ``` 5. 选择索引策略:对不同的字段可以采用不同的索引策略: - 单字段索引:如果仅有一个字段用于查询过滤或排序,可以在该字段上单独创建索引。 - 复合索引:如果多个字段组合用于查询过滤或排序,可以在这些字段上创建复合索引,来避免建多份单字段索引。 - 唯一索引:如果字段值需要唯一且频繁作为查询条件,可以将索引设置为唯一索引。这既满足唯一性要求,也可以加速查询。 - 覆盖索引:如果查询返回的字段都被索引覆盖,数据库可以直接从索引中获取数据,加速查询速度。 - 索引下推:在查询过滤条件的字段上创建索引,用于加速WHERE子句的过滤操作。 - 索引上推:在查询排序的字段上创建索引,用于加速ORDER BY子句的排序操作。 6. 监控索引:创建索引后,需要对各索引的使用情况进行监控,主要监控: - 索引命中率:索引被查询使用的次数。如果太低,说明索引没有发挥作用,可以考虑删除。 - 索引大小:索引文件本身占用的空间。如果过大,会消耗较多存储资源和处理能力,需要权衡索引带来的好处。 - 表大小:索引会增加插入和更新语句的消耗,监控表大小异常增长需要检查索引使用策略。 ***** ***** ***** ### 5)连接池技术: 使用数据库连接池在应用层管理数据库连接,避免频繁创建和释放连接导致性能损失。这有效地重复使用了数据库连接,减少系统消耗。 ***** ***** ***** ### 6)SQL 编写优化: 合理利用数据库的并行执行能力,编写高效的 SQL 语句。如使用批处理方式 INSERT/UPDATE 多条数据,使用 EXISTS 替代 IN 子查询等。这可以大幅提升数据库处理性能。 ***** 1. 选择合适的字段查询方式: - 使用指定字段名查询,避免 SELECT *。 - 用字段别名简化查询结果。 - chose 合适的字段类型,避免转换开销。 2. WHERE 条件过滤: - 使用索引字段进行过滤查询。 - IN 替换 OR 条件。 - IN 列表长度适当,避免过长。 - EXISTS 替代 IN 子查询。 3. 合理使用索引: - 单列索引或组合索引。 - 索引前缀最左前缀匹配原则。 - 避免在索引列上进行运算或函数运算。 - 索引列选择具有区分度的字段。 - 考虑索引的复合性与查询性能之间的平衡。 4. 其他优化技巧: - 使用连接(JOIN)替代子查询。 - 适当增加查询条件来过滤结果。 - 程序中拼接SQL时使用参数而非直接拼接。 - 利用数据库内置函数进行查询。 - 考虑使用统计信息进行查询优化。 - 尽量减少在数据库中进行的计算或数据转换。 5. 存储过程和视图: - 复杂而频繁的查询适合创建存储过程。 - 要考虑存储过程的可维护性。 - 视图可以简化查询语句,屏蔽表结构变更。 代码示例: ``` sql # 选择指定字段 SELECT id, name FROM users; # 别名简化 SELECT id AS uid, name AS uname FROM users; # IN 替代 OR SELECT * FROM users WHERE id IN (1, 3, 5); # EXISTS 替代 IN 子查询 SELECT * FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE user_id=users.id); # 单列索引 CREATE INDEX idx_user_name ON users(name); # 联合索引 CREATE INDEX idx_user_order ON users(name, order_date); # 存储过程 DELIMITER $$ CREATE PROCEDURE get_user_orders(IN uid INT) BEGIN SELECT * FROM orders WHERE user_id=uid; END$$ DELIMITER ; ``` ***** ***** ***** ### 7)分散压力的中间件: 使用消息队列等中间件在 web 服务器和数据库之间进行解耦。这避免直接将大量请求发送到数据库,实现流量削峰,分散数据库压力。 ***** 1\. 选择合适的消息队列,如Kafka、RabbitMQ等。考虑队列的耐久性、可扩展性等因素,确保其能够满足系统需求。 2\. 区分同步操作和异步操作。同步操作需要直接访问数据库,异步操作可以入队列,由消费者异步处理。 3\. 消费者从消息队列中取出任务,进行真正的数据库操作和数据处理。 4\. 监控消息队列的积压数量和消费速度。如果消费出现滞后,需要及时增加消费者数量。 5\. 消息队列与数据库操作的结果,需要返回给用户。可以通过以下方式实现: \- 消息队列返回消息ID,用于查询操作结果。 \- 触发器机制,消息入队后立即返回成功,触发器异步执行数据库操作。 \- 轮询机制,用户轮询查询操作结果。 代码实现示例: ``` python # 消费者 def consume(): while True: # 消费者从消息队列取出任务 msg = queue.get() # 执行真正的数据库操作 db.execute(msg['sql'], msg['args']) # 用户请求 # 同步操作直接访问数据库 result = db.query(sql, args) # 异步操作入消息队列 queue.put({'sql': sql, 'args': args}) # 返回消息ID用于结果查询 msg_id = queue.put_id # 用户查询操作结果 result = db.get_result(msg_id) # 使用消息ID查询 # 或使用轮询查询 while True: result = db.get_result(msg_id) if result: break ``` > 使用消息队列可以有效分散数据库压力,避免直接把大量请求打到数据库上。同时也增加了系统的复杂性,需要开发人员理解消息队列与数据库的交互机制。 要确保消息没有遗漏或重复消费,并且结果能正确返回给用户。这需要对系统的异步链路进行全面测试,确保业务的准确性。 开发人员在设计系统架构时,要根据业务场景选择恰当的消息队列方式与数据库交互方案。要在性能、数据准确性与系统复杂度间取得平衡,构建高效稳定的系统。 ***** ***** ***** ### 8)服务器升级: 必要时可以选择升级数据库服务器配置,如增加 CPU、内存、存储以增强处理能力。但这也增加了运维成本,需要综合判断。 ***** 1. CPU:增加CPU核数可以提高数据库的执行能力,特别是在计算与排序等方面。但也增加了并发控制的难度,需要结合业务场景选择合适的CPU数量。 2. 内存:增加内存可以减少磁盘IO,特别是在数据缓存、索引、临时表等方面。但也增加了数据库系统的成本,需要根据工作负载选择恰当的内存容量。 3. 存储:选择高速固态硬盘(SSD)可以大幅提高数据库的IO吞吐量与访问速度。对于日志、临时表和索引等经常读写的数据,使用SSD存储可以产生更高的效果。 4. RAID配置:使用RAID5或RAID10等方式对多个磁盘进行行列式组合,既可以提高读写性能,也具有一定容错能力。这能够提高数据库的整体IO能力与数据安全性。 5. 网络:采用高速网卡与交换机可以加速数据库服务器间的数据交互速度。特别是主备切换或读写分离等架构下,网络性能起着关键的作用。需要选择与业务量相匹配的网络配置。 6. 操作系统:选择专业的数据库操作系统,如RedHat Enterprise Linux等。这类系统默认具有更好的数据库运行优化配置,可以充分发挥硬件性能,保证数据库的稳定运行。 7. 其他:使用SSD缓存、增加交换空间、文件系统选择等方式可以进一步提高系统性能。但也增加了系统的复杂性,需要结合产品特性与业务需要进行评估。