高性能&高存储量数据库解决方案
背景
在StepN
项目的推动下,我们在不断同步链上数据,其中被同步的内容有:
- 交易条目
Token
额度变化SOL
额度变化鞋、盲盒
及其相关数据
在同步完历史数据之后,数据量在20M
左右,在数据更新方案制定中,每三个小时去对链上数据做一次同步(不会同步到已同步的数据),这个过程大概会持续1H+
,因为需要在三小时内同步完几十上百万数据,需要使用到 Go 中的多协程方案,这样也导致执行更新的前 10~20 分钟,数据库每秒插入数据量在 0.5~1.2K。
磁盘 IO、每秒读写相关数据:
在 API 端,避免了对数据库的直接查询,而改用定时任务去访问数据库计算得到数据,将数据以Json
格式存储在Redis
中,API Controller 中只读Redis
而不读MySQL
,从而避免因客户端调用而产生的数据库查询压力或其他压力异常的情况。
链上数据分析业务痛点
-
数据插入量大,但读取次数并不多,传统业务中,可能读取次数是远远大于写入次数的,差不 9:1,但这里是不同,是 1:9。
-
查询次数虽然少,但几乎每次都是全表聚合查询,数据量大且执行时间高,目前我随意 count 系统主表任意一张,时间在 10 ~ 30S。
随着时间和业务的继续增长,只使用单机版MySQL
有些难以满足业务需求。
在清楚“痛点”之后,我们需要将侧重解决的地方放在数据插入效率和全表查询效率上的优化。
通过配置参数提升 MySQL 的数据插入效率
innodb_flush_log_at_trx_commit
默认为 1,这也是MySQL
官方出厂设置好的参数,在 MySQL
的手册中,为了确保事务的持久性和一致性,都是建议将这个参数设置为1。
,在这样的情况
参数值 | 作用 | 实际表现数值 |
---|---|---|
0 | 日志缓冲按秒的写入到日志文件,并且对日志文件做到磁盘操作的刷新。 | 插入 50W 行数据所花时间 25.08 秒 |
1 | 在事务提交时,日志缓冲被写到日志文件,对日志做到磁盘操作的刷新。 | 插入 50W 行数据所花时间 17 分 21.91 秒 |
2 | 在事务提交时,日志缓冲被写到文件,但不对日志文件做到磁盘操作的刷新,对日志文件每秒刷新一次。 | 插入 50W 行数据所花时间 1 分 0.35 秒 |
当innodb_flush_log_at_trx_commit
和sync_binlog
都为 1
时是最安全的,在 mysqld 服务崩溃或者服务器主机 crash 的情况下,binary log 只有可能丢失最多一个语句 或者一个事务。
在默认情况,当海量数据被插入的情况下,会导致频繁的 IO 操作,因此该模式也是最慢的一种方式。
- 当 innodb_flush_log_at_trx_commit 设置为 0,mysqld 进程的崩溃会导致上一秒钟所有事务数据的丢失。
- 当 innodb_flush_log_at_trx_commit 设置为 2,只有在操作系统崩溃或者系统掉电的情况下,上一秒钟所有事务数据才可能丢失。
temp_table_size、heap_table_size
这两个参数主要影响临时表 temporary table 的写入,设置太小,会出现table is full
的报错信息.
要根据实际业务情况设置大于需要写入的数据量占用空间大小才行。
max_allowed_packet、net_buffer_length、autocommit
这三个参数是在数据库需要备份或者恢复备份的时候,需要配置的。
max_allowed_packet=256M,net_buffer_length=16M,set autocommit=0
这样配置,备份的速度会很快。
innodb_data_file_path
autoextend
就是让表空间自动扩展,不够时默认情况下只会扩展10M
,在大数据量写入时,可以将这个参数调大。
innodb_log_file_size、innodb_log_files_in_group、innodb_log_buffer_size
设置事务缓存的大小、日志组数量、日志文件大小,以下是默认参数:
在innodb
中,数据通常是先写缓存,再写入事务日志,再写入数据文件,如果值设置的太小,那么在大批量数据写入的场景下,必然会导致频繁的触发数据库的检查点,去把日志中的数据写入到磁盘数据文件。
频繁的刷新 buff 去写入到磁盘文件中、切换日志文件,会导致大批量写入数据的性能降低。
将innodb_log_files_in_group
设置到 4 组,innodb_log_file_size
设置到官方推荐的机器内存的 25%,可提升数据插入性能。
innodb_buffer_pool_size
它可以设置InnoDB
可用缓存的大小,可以将值设置更大,参考innodb_log_file_size
的值来设置较好。
innodb_thread_concurrency
控制并发线程数,理论上是设置的越多越快,但最好不要超过官方推荐的 CPU*2 的数量。
write_buffer_size
控制单个会话写入缓存的大小,默认值是 4K,在频繁写入的场景,可以将值设置成 2M,写入速度会有一定提升。
innodb_buffer_pool_instance
默认为 1,设置内存缓冲池的个数,控制并发读写 innodb_buffer_pool 的个数,调大该参数,也会带来写入速度的提升,
数据库架构调整
主从复制
一主 N 从,,相互备份保障数据安全,不同业务连接不同的数据库服务器,通过这种架构,分散业务系统的并发写入 IO 从一台服务器到多台服务器,同样能够提升数据库的写入速度。
读写分离
同理,一样的为了分散 IO 压力的操作,业务上将不同的数据库操作链接到不同的数据库即可。
硬件优化
用物理机,固态硬盘,使用更高效的 CPU,也可以显著的提升写入、查询的速度。
穷则战术穿插,富则火力覆盖,烧钱优化硬件当然可以显著的提升速度,而且是最快的方式。
Clickhouse
Clickhouse
是一个知名的列式数据,它的设计非常适合海量数据且需要对个别字段全表查询,在数据分析业务角度上看,它是一个非常值得考虑的方案。
目前来说,网上有很多资料证明Clickhouse
的查询性能比MySQL
快上一百到几十倍,实际上,两者各有不同的应用场景和局限性,下面说明一下两者最大的差距——存储结构。
传统的行式数据库的存储结构:
Clickhouse 列式数据库的存储结构:
另外,Mysql
是按表来生成数据存储文件的,但Clickhouse
是按列来生成存储文件的,这样的存储结构,一定程度上就定义了Clickhouse
非常适合少列、单表查询,也正是因为这样的存储结构,使它的列查询速度极快,而我们数据分析的需求,正是每次在全表中查询少量的列用于计算。
快的原因很大一部分来自于:只需要读取需要计算的列的数据,而非整行式的读取数据,降低 IO 消耗。
用动画来展示查询原理,这是行式数据库的查询逻辑,需要一行一行的获取数据,最终计算出来。
而列式数据库的查询过程:
说明:
- 针对分析类的查询,通常只需要读取表中的一小段列列,在列式数据库中读取你需要的列数据,例如:一个表有 100 列,只需要里面的 5 个列,这可以帮助你减少 20 倍的 IO 消耗。
- 由于列式数据库的查询,总是被批量读取的,所以这样压缩是相对比较简单的,同时,数据按列存储,这也会更加容易压缩,降低了 IO 体积。
- 由于 I/O 的降低,这将帮助更多的数据被系统缓存
劣势
- 多列查询慢,多表关联查询更慢
- 不适合关系型查询,比如:传统业务中的查询,例如:用户的多个钱包,用户的多个皮肤
- Clickhouse 会为了查询性能尽可能的调度机器资源,吃机器配置
- 社区、资料、支持度不够高,因为也是近两年火起来的项目
用一下社区内其他用户放出来的测试结果:
MongoDB
MongoDB
是一个基于分布式文件存储的数据库,由 C++编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB
是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,也被称为越来越像MySQL
的NoSQL
产品。
它支持的数据结构非常松散,是类似于Json
格式的Bson
,因此可以存储比较复杂的数据结构,特点是它的查询功能比较强大,其语法有点类似于面向对象的查询语言,支持对数据建立索引。
MongoDB
支持丰富的数据结构,它的Value
可以是整形、字符串、数组、嵌套的子文档、这里强调一下嵌套子文档
,它的好处在于一次即可查询出来所有你需要的内容。
MongoDB
在大数据场景下的解决方案
在分布式场景下,MongoDB
提供了数据自动异步或同步的复制能力,一个新的节点加入到复制集中会自动进行数据初始同步,随后使用oplog
进行行增量复制,无需人工干预。
另外,MongoDB
的选举模式,能够在主节点宕机时,自动选举出一个新主,进行从主切换。
MongoDB
提供了sharding
机制用于实现业务的水平扩展,每个shard
都会保存业务的一部分数据,shard
可以配置为复制集。
shard
内部是由一系列连续的chunk
组成的,chunk
是某一篇区域内的数据记录的集合,mongo
用于业务请求的路由,将业务负载分摊在不同的shard
上,此外 mongos 还会对 shard 上超过一定大小的 chunk 进行分裂(split);根据不同 shard 中数据量的大小,在 shard 将进行 chunk 迁移(migrate),应该说 sharding 提供了完善的业务数据和负载水平扩展的机制。
sharding 并不是必须的,并不是新业务起来的时候就马上部署 sharding 集群,只有当业务的数据量达到单个复制集无法支撑、或者业务的负载超过了复制集的服务能力的时候,才考虑部署 sharding,毕竟相比复制集,sharding 在部署和管理上都复杂很多。MongoDB 复制集可以平滑升级到 shard,所以当你真正需要 sharding 时,再去考虑参考官方文档去升级方案。
什么是 MongoDB 复制集
它是Mongodb
将数据同步在多个服务器的一种解决方案。
复制集提供了数据的冗余备份,并在多个服务器上存储数据备份,提高了数据的可用性,并保障了数据的安全性。
复制还允许从硬件故障中断中恢复数据。
数据实验结论
- 数据量:28272614,两千两百万+
- 表名:token_balances
SQL 内容:
SELECT
FROM_UNIXTIME(timestamp, '%Y-%m-%d') AS date,
SUM(`change`) / power(10, 9) AS value
FROM
token_balances
WHERE
`change` < 0
AND owner_address = 'STEPNq2UGeGSzCyGVr2nMQAzf8xuejwqebd84wcksCK'
AND token = 'AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB'
GROUP BY
date
ORDER BY
date DESC
数据库 | 执行耗时 |
---|---|
MySQL(列做好索引) | 10.28 s |
Clickhouse(无索引) | 1.3 ~ 3S |
MongoDB 的数据,没有参考意义,它的优势不在于查询速度有多快,在于它是轻量级的分布式存储方案。