关于数据库的整体设计,很少有书籍专门试讲,因为这需要博大精深的理论水平以及阅历丰富的工作经验,但往往这两者不可兼得,在设计数据库时需要考虑多方面的因素,如需求,数据体量,数据库架构,硬件环境,成本。这些因素综合起来才可以决定最终的体系架构。数据库以及方案的优化需要进行整体的设计,本文是以[数.据.仓.库]IO读写量大并发量小为例进行设计。
1.从用户需求出发要考虑数据库该采用哪种“模式”
总体而言,采用分布式数据库大体会分为三种情形
A类似于OLAP,数据容量大,倾向于统计分析,但短时间内并发事务少
B类似于交易场景,数据容量不一定很大,但并发事务较多
C介于A与B之间
这三种模式一般很少单一出现,往往伴随着复杂的需求,比如OLAP尽管并发事务少之又少,但海量数据入仓同样会给分布式数据库带来较大的入仓压力,体现在IO瓶颈以及分布式锁表等问题上,这些因素不容忽视,必须要综合考虑。
2.根据用户需求选择“最适合”的体系架构
“最适合”放在这里表述最恰当不过了,因为没有最好的,只有最合适的,合适一词我认为应该包括几层意思。
A能够尽可能满足用户的需求,不一定是最优最大化,这一点后面会提及
B容易向其他层级扩展,集群机器之间尽量减少高度耦合,包括水平扩展以及高可用主备切换
C没有复杂的架构设计,易于维护且成本相对低廉
D对每台机器选择最合适的数据库设计具体包括表模式、结构、规则、触发事件等等(关于这个后后续会深入讲解)
E受制于硬件以及成本原因,一般利用2/3硬件资源,保持一定的冗余度
3.软件环境及版本的“选择”
因PG数据库更新快,版本之间差异大,因此在选择版本时要慎重,同时包括数据库安装环境以及ETL处理工具。
4.几个“关键词”介绍
A.高可用性
H.A.(High Availability)指的是通过尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间,以提高系统和应用的可用性。它与被认为是不间断操作的容错技术有所不同。HA系统是企业防止核心计算机系统因故障停机的最有效手段。
一般情况下,数据库高可用采用的是主备模式,即通过设定主数据库+备用数据库的模式进行高可用设置,当主节点遇到故障无法启用时,会通过脚本或程序机制启用备用节点,将数据库无缝切换。因分布式数据库的特性,一般会很少提及这个问题 ,但为主节点提供一个备用主节点依然相当重要,这个对高并发事务的实时交易系统显得尤为重要。
B.读写分离
为了确保数据库产品的稳定性,很多数据库拥有双机热备功能。也就是,第一台数据库服务器,是对外提供增删改业务的生产服务器;第二台数据库服务器,主要进行读的操作。·
这个主要是针对一些数据库读需求高、写需求低的数据库采用的模式,同时考虑分布式数据库设计的问题,读模式的数据库不论从数量功能以及架构设计上都要匹配自身特性及用户需求。因只读数据库传输数据的模式不同:如流处理(FLINK)、分布式机制处理(CITUS等)、以及传统的SQL语句处理(SPARK)等等,会直接影响数据库的整体IO性能以及用户需求性能。
C.分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
以往单体数据库的事务问题会交给数据库进行处理,到分布式数据库下,这一点同样可以交给数据库进行处理,但不同的是,分布式数据库由于分布式数据节点多、读写匹配的特性,会很容易造成分布式锁,这一点必须要求设计数据库时尽可能的减少表占用时间,同时业务端也要减少表事务量。
5提升性能的几个“策略”【下面是重点】
A.索引设计
既不可没有,也不能够太多。每种数据库对建立索引都有明确的规定,如建议多少个以内。索引一般选择散列度较高的列进行设置,在分布式数据库如CITUS中,索引需要遵守同样的规定。
要充分利用索引的特性,根据字段类型及内容选择最贴切的,如BREE等不同索引类型采用的寻址排序模式都不同。
1、B-tree
PostgreSQL中,B-tree索引是最常用的一种索引类型。
用索引扫描比顺序扫描速度快,因为它可能只需要读取少部分页面,而顺序扫描可能读取几千个页面。
默认情况下,使用CREATE INDEX语句,会创建一个B-tree索引,这对于大多数常用数据类型比如文本、数字等的适用性很强。
2、GIN
当数据类型在一列中包含多个值时适用。
这种情况下最常见的数据类型是hstore、range、jsonb等,并不是所有的数据类型都支持这种索引类型。
3、GiST
GiST索引适用的情况是:
有一些数据,它们和其他行的同一列中的值在某种程度上相互覆盖,此时适用。
最合适的数据类型是:几何类型、全文检索时的文本类型。
4、SP-GiST
空间分区GiST索引,适用于较大的数据,当数据有自然聚类元素时最适用,典型的例子是电话号码。
5、BRIN
适用于较大的数据,和SP-GiST类似。
当有非常大的数据表,而且按时间、邮政编码排好顺序时,BRIN索引允许快速的跳过或排除很多不需要的数据。
6、哈希索引
可以提供比B-tree索引更快的查询。但最大的问题是被限制在等值上所以需要寻找准确的匹配。这使得哈希索引不那么灵活。
B-tree 适用于大多数数据类型和查询。
GIN 适用于json/hstore数据类型。
GiST 适用于全文检索和地理空间数据类型。
SP-GiST 适用于带有自然但不均匀聚类的较大的数据库。
BRIN 适用于顺序排列的特别大的表。
Hash 适用于等值查询操作。
同时在使用SQL语句上避免索引失效的问题
检验索引的一条有用语句是EXPLAIN SQL QUERY。POSTGRESQL会帮助你分析是否用到索引以及相关SQL语句所付出的查询代价。
B.数据库分区设计
数据库分区是一种物理数据库设计技术,DBA和数据库建模人员对其相当熟悉。虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操作中减少数据读写的总量以缩减响应时间。
这是替代以往数据库水平分库或者垂直分库的一种较为优雅的模式, PG分区有以下一些模式。CITUS支持范围和哈希两种分区,在选择分布式表和分区函数可以进行优先考虑
CREATE TABLE pkslow_person_r (
age int not null,
city varchar not null
) PARTITION BY RANGE (age);
这是一段对表分区的SQL语句。对于分布式数据库CITUS同样可以在分区之后将本地表变为分布表,利用表分区+数据库分布式的双重有点实现快速精准的数据查询。
C.协调节点以及分布式表的结构选择
充分利用分布式表、本地表、参考表的特性。不当的分区表设计会造成IO读写压力过大,大部分资源都耗在了网络带宽,间接也导致了整个数据库因为事务等待导致性能下降。
分布式表是每个WORK节点上都存在一张分量表
本地表是只存在于CN节点
参考表是每个节点复制一张相同的全量表
对于有读写关联度大的表要使用协同定位功能
D.数据库参数优化
包括端口并发数量、内存设置、缓存、并发CPU数量、减少日志写入数量、减少VACUUM优化频次等等,这些参数可以去PG官网阅读相关手册进行优化设计。
这里不再赘述。
E.分布式参数优化
同样官网手册讲的也很详细,这里只是提及下,节点数量、分片大小以及分布式列的选择都会影响数据库性能,在设计数据库时务必要认真考虑。以分布式列为例,尽量不选择表分区所采用的列,否则会造成性能下降。
对于OLAP类数据库,完全可以采用部门/.用户UUID主键作分布式列,日期时间作为表分区列的模式进行构建。
F.充分利用各种插件
PG自带丰富插件,如PG_CRON自动化脚本,可以为自动创建索引、分区表进行设置,简化操作。同时PG丰富的插件可以支持远程读写PG、MYSQL、ORACLE等数据库的特点,这个用于解决不同数据模式整合的问题。
几个比较好用的插件:
1.PG_CRON:定时器脚本,能够自动调用SQL
2.PG_PATHMAN:比较实用的分区脚本,支持CITUS等分布式架构
3.ORACLE_FDW/MYSQL_FDW:用于PG数据库连接ORACLE等数据库,简易的大数据处理解决方案
4.FILE_FDW:直接读写数据文件
5.DB_LINK:连接PG数据库,简化数据同步操作
6.POSTGRES_FDW,这个是新标准的外连数据库的解决插件,对于只同步部分数据可以结合pg_cron实现定期取数更新功能,相对便捷
G.启用负载均衡
如使用pgpool-II 同时连接只读节点BACKUP_A和读写CN节点,提高系统负载均衡能力,缓解压力到一个点
H.大数据处理框架探索
如FLINK,SPARK,这些框架现在处理实时数据成为首选,也为OLAP处理数据提供了良好的借鉴。美中不足的是,因为需要编写业务端逻辑,导致分布式数据库门槛提高。可以利用大数据处理平台降低分布式处理数据的运算量,因为这些框架依赖于内存和CPU。
I.使用必要的系统优化及软件优化策略
一是使用时间同步软件,ntpdate等软件,确保时钟频率保持相同频道。
二是数据库的文件系统尽量选择高性能的文件系统,如ext4等等。
三是系统安装软件应尽量轻量化,减少重度IO和CPU占用的软件。
J.在数据入仓之前就完成“该做的工作”
如针对数据行比较不同,最有利的方式是在数据入仓之前对行内容进行MD5加密,以MD5加密内容作为主键进行比较下面附上python 处理代码
###########修改字段类型
import hashlib
import time
def call_newfile_insert_md5(filename,encoding,sep,startdt,enddt):
file_list = open(filename,encoding=encoding)
fw = open(filename+".new","w",encoding=encoding)
start =time.time()
for get_str in file_list:
if get_str[-1] == '\n':
get_str = get_str[:-1]
get_crc = hashlib.md5(get_str.encode(encoding)).hexdigest()
get_str = startdt + sep + enddt + sep + get_crc + sep + get_str + '\n'
fw.write(get_str)
end = time.time()
#print(end-start)
return
###########修改字段类型
def call_insert_md5(filename,encoding,sep,startdt,enddt):
call_newfile_insert_md5(filename,encoding,sep,startdt,enddt)
call_removefile(filename)
call_rename(filename+".new",filename)
return
#对原数据文本进行快速处理
call_insert_md5("/home/data/xxx.txt","UTF-8",",","2022-12-31","9999-12-31")
数据库性能问题及优化可私信资讯。
点击链接加入群聊【CITUS POSTGRESQL MPP数据库】
【发文不易,路过高抬贵手点个赞^.^】