从数据库发展历程到数据结构设计探析
Tech导读
本文针对数据存储相关名词概念进行了解释,重点介绍了数据库技术的发展史,并又从数据结构设计层面进行了部分技术实战能力的外延扩展,阐述了拉链表、位运算、环形队列等相关数据结构在软件开发领域的应用,希望本文给你带来收获。
(me)>
导读
本文针对数据存储相关名词概念进行了解释,重点介绍了数据库技术的发展史,并又从数据结构设计层面进行了部分技术实战能力的外延扩展,阐述了拉链表、位运算、环形队列等相关数据结构在软件开发领域的应用,希望本文给你带来收获。
01 数据库发展史
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
起初,数据的管理方式是文件系统,数据存储在文件中,数据管理和维护都由程序员完成。后来发展出树形结构和网状结构的数据库,但都存在着难以扩展和维护的问题。直到七十年代,关系数据库理论的提出,以表格形式组织数据,数据之间存在关联关系,具有了良好的结构化和规范化特性,成为主流数据库类型。
先来看一张数据库发展史图鉴:02
数据库名词概念
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
2.1 RDBS
2.2 NoSQL
NoSQL(Not Only SQL) 数据库也即非关系型数据库,它是在大数据的时代背景下产生的,它可以处理分布式、规模庞大、类型不确定、完整性没有保证的“杂乱”数据,这是传统的关系型数据库远远不能胜任的。NoSQL数据库并没有一个统一的模型,是以牺牲事务机制和强一致性机制,来获取更好的分布式部署和横向扩展能力,使其在不同的应用场景下,对特定业务数据具有更强的处理性能。常用数据模型示例如下:
产品代表 | 应用场景 | 数据模型 | 优缺点 | |
---|---|---|---|---|
键值数据库 | Redis/Memcached | 内容缓存,如会话,配置文件等;频繁读写,拥有简单数据模型的应用; | 键值对,通过散列表来实现 | 优点:扩展性和灵活性好,性能高;缺点:数据无结构化,只能通过键来查询 |
列簇数据库 | HBase/ClickHouse | 分布式数据存储管理 | 以列簇存储,将同一列存在一起 | 优点:简单,扩展性强,查询速度快 缺点:功能局限,不支持事务的强一致性 |
文档数据库 | MongoDB/CouchDB | Web应用,存储面向文档或半结构化数据 | 键值对,value是JSON结构文档 | 优点:数据结构灵活 缺点:缺乏统一查询语法 |
图形数据库 | Neo4j/InfoGrid | 社交网络,应用监控,推荐系统等专注构建关系图谱 | 图结构 | 优点:支持复杂的图形算法 缺点:复杂性高,支持数据规模有限 |
2.3 NewSQL
NewSQL 是一类新的关系型数据库, 是各种新的可扩展和高性能的数据库的简称。它不仅具有 NoSQL 数据库对海量数据的存储管理能力,同时还保留了传统数据库支持的 ACID 和 SQL 特性,典型代表有TiDB和OceanBase。
2.4 OLTP
联机事务处理过程(On-Line Transaction Processing):也称为面向交易的处理过程,其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理,并在很短的时间内给出处理结果,是对用户操作快速响应的方式之一。
2.5 OLAP
关于OLTP和OLAP的区别,借用一张表格对比如下:
2.6 HTAP
03
领域数据库
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
3.1 列式数据库
两种存储结构的区别如下图:
更高的压缩比率:由于每个列中的数据类型相同,列式数据库可以使用更高效的压缩算法来压缩数据(压缩比可达到5~20倍),从而减少存储空间的使用。
更快的查询速度:列式数据库可以只读取需要的列,而不需要读取整行数据,从而加快查询速度。
更好的扩展性:列式数据库可以更容易地进行水平扩展,即增加更多的节点和服务器来处理更大规模的数据。
更好的数据分析支持:由于列式数据库可以处理大规模的数据,它可以支持更复杂的数据分析和处理操作,例如数据挖掘、机器学习等。
更慢的写入速度:由于数据是按列存储,每次写入都需要写入整个列,而不是单个行,因此写入速度可能较慢。
更复杂的数据模型:由于数据是按列存储,数据模型可能比行式数据库更复杂,需要更多的设计和开发工作。
金融:金融行业的交易数据和市场数据,例如股票价格、外汇汇率、利率等。列式数据库可以更快速地处理这些数据,并且支持更复杂的数据分析和处理操作,例如风险管理、投资分析等。
医疗:医疗行业的病历数据、医疗图像和实验数据等。列式数据库可以更高效地存储和处理这些数据,并且支持更复杂的医学研究和分析操作。
电信:电信行业的用户数据和通信数据,例如电话记录、短信记录、网络流量等。列式数据库可以更快速地处理这些数据,并且支持更复杂的用户行为分析和网络优化操作。
能源:能源行业的传感器数据、监测数据和生产数据等。列式数据库可以更高效地存储和处理这些数据,并且支持更复杂的能源管理和控制操作。
物流:物流行业的运输数据、库存数据和订单数据等。列式数据库可以更快速地处理这些数据,并且支持更复杂的物流管理和优化操作。
3.2 时序数据库
时序数据库发展史:
当下最常见的Kubernetes容器管理系统中,通常会搭配普罗米修斯(Prometheus)进行监控,Prometheus就是一套开源的监控&报警&时间序列数据库的组合。
3.3 图数据库
目前市面上较为流行的图数据库产品有以下几种:
1.更快的查询速度:图数据库可以快速遍历图数据,找到节点之间的关联和路径,因此查询速度更快。
2.更好的扩展性:图数据库可以轻松地扩展到大规模的数据集,因为它们可以分布式存储和处理数据。
3.更好的数据可视化:图数据库可以将数据可视化为图形,使用户更容易理解和分析数据。
4.更好的数据一致性:图数据库可以确保数据的一致性,因为它们可以在节点和边之间建立强制性的关系。
04 数据结构设计
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
4.1 拉链表
工号 | 职位 | 部门 | 入职时间 | 生效时间 | 失效时间 | |
---|---|---|---|---|---|---|
张三 | 001 | 经理 | 技术 | 2010-01-01 | 2010-01-01 | 2012-12-31 |
张三 | 001 | 总监 | 技术 | 2013-01-01 | 2013-01-01 | 2015-12-31 |
张三 | 001 | 总经理 | 技术 | 2016-01-01 | 2016-01-01 | 9999-12-31 |
这里的生效时间指的是该记录生效的时间,失效时间指的是该记录失效的时间。例如,张三最初是技术部经理,生效时间为入职时间,失效时间为2012年底,之后晋升为技术部总监,生效时间为2013年初,失效时间为2015年底,最后又晋升为技术部总经理,生效时间为2016年初,失效时间为9999年底。
拉链表通常包括以下几个字段:
主键:唯一标识每个记录的字段,通常是一个或多个列的组合。
生效时间:记录的生效时间,即该记录开始生效的时间。
失效时间:记录的失效时间,即该记录失效的时间。
版本号:记录的版本号,用于标识该记录的版本。
其他维度属性:记录的其他维度属性,如客户名、产品名、员工名等。
当一个记录的维度属性发生变化时,不再新增一条记录,而是修改原有记录的失效时间,同时新增一条新的记录。新记录的生效时间为变化的时间,失效时间为9999年底。这样就能够记录每个维度属性的历史变化信息,同时保证查询时能够正确获取某个时间点的维度属性信息。
4.2 巧用位运算
4.2.1 位运算
使用位运算实现开关和多选项叠加(资源权限)等应用场景。一个int类型有32个位,理论上可以表示32个开关状态或业务选项;以用户每个月的签到场景举例:用一个int字段来表示用户一个月的签到情况,0表示未签到,1表示签到。想知道某一天是否签到,则只需要判断对应的比特位上是否为1。计算一个月累计签到了多少次,只需要统计有多少个比特位为1就可以了。这种设计巧妙的数据存储结构在后面的位图(BitMap)中,还会进行更为详细的介绍。
使用位运算实现业务优先级计算:
public abstract class PriorityManager {
// 定义业务优先级常量
public static final int PRIORITY_LOW = 1; // 二进制:001
public static final int PRIORITY_NORMAL = 2; // 二进制:010
public static final int PRIORITY_HIGH = 4; // 二进制:100
// 定义用户权限常量
public static final int PERMISSION_READ = 1; // 二进制:001
public static final int PERMISSION_WRITE = 2; // 二进制:010
public static final int PERMISSION_DELETE = 4;// 二进制:100
// 定义用户权限和业务优先级的组合值
public static final int PERMISSION_LOW_PRIORITY = PRIORITY_LOW | PERMISSION_READ; // 二进制:001 | 001 = 001
public static final int PERMISSION_NORMAL_PRIORITY = PRIORITY_NORMAL | PERMISSION_READ | PERMISSION_WRITE; // 二进制:010 | 001 | 010 = 011
public static final int PERMISSION_HIGH_PRIORITY = PRIORITY_HIGH | PERMISSION_READ | PERMISSION_WRITE | PERMISSION_DELETE; // 二进制:100 | 001 | 010 | 100 = 111
// 判断用户权限是否满足业务优先级要求
public static boolean checkPermission(int permission, int priority) {
return (permission & priority) == priority;
}
}
其它使用位运算的典型场景:HashMap中的队列长度的设计和线程池ThreadPoolExcutor中使用AtomicInteger字段ctl,存储当前线程池状态和线程数量(高3位表示当前线程的状态,低29位表示线程的数量)。
4.2.2 BitMap
如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)≈7.45G
如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为 (2000000000/8/1024/1024/1024)≈0.233G
存储空间可以压缩节省31倍!那么它是如何通过二进制位实现数字标记的呢?其原理是用每个二进制位(下标)表示一个真实数字,0表示不存在,1表示存在,这样我们可以很容易表示{1,2,4,6}这几个数:
4.2.3 布隆过滤器
位图(Bitmap)这种数据存储结构,如果数据量大到一定程度,比如64bit类型的数据,简单算一下存储空间就知道,海量硬件资源要求,已经不太现实了:
图9. 64bit类型的数计算一下存储空间
Java中Guava工具包中实现;
Redis 4.0开始以插件形式提供布隆过滤器功能。
网页爬虫对URL的去重,避免爬去相同的URL地址,比如Chrome浏览器就是使用了一个布隆过滤器识别恶意链接;
垃圾邮件过滤,从数十亿个垃圾邮件列表中判断某邮箱是否是垃圾邮箱;
解决数据库缓存击穿,黑客攻击服务器时,会构建大量不存在于缓存中的key向服务器发起请求,在数据量足够大的时候,频繁的数据库查询会导致挂机;
谷歌Bigtable、Apache HBase、Apache Cassandra和PostgreSQL使用布隆过滤器来减少对不存在的行或列的磁盘查找;
秒杀系统,查看用户是否重复购买。
4.2.4 小结
位运算有着执行速度快,占用空间小,代码实现简洁等优点,往往能起到四两拨千斤的效果。同样也有着代码可读性差,使用范围和可维护性受限等不足;
在BitMap中,占用空间大小还与实际应用场景有关,这种结构无法容忍误判,只能判断一个元素是否存在,如果数据离散度过高,空间利用率反而更低;
布隆过滤器则有着空间利用率高,可以容忍一定的误判率的优点。与BitMap相比,也存在着无法删除元素,误判率无法达到0等不足。
4.3 环形队列
环形队列是一种用于表示一个固定尺寸、头尾相连的数据结构,很适合缓存数据流。在通信开发(Socket,TCP/IP,RPC开发),在内核的进程间通信(IPC),视频音频播放等各种场景中,都有其身影。日常开发过程中使用的Dubbo、Netty、Akka、Quartz、ZooKeeper 、Kafka等各种中间件,也都有环形队列的思想。下面介绍两种常用的环形数据结构:Hash环和时间轮。
4.3.1 一致性Hash环
一致性哈希将整个哈希值空间组织成一个虚拟的圆环,假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整型),所有的输入值都被映射到 0-2^32-1 之间,组成一个圆环。整个哈希空间环如下:
带有虚拟节点的Hash环:
4.3.2 时间轮分片
时间轮(TimeWheel)是一种实现延迟功能(定时器)的精妙的算法,可以实现高效的延时队列。以Kafka中的时间轮实现方案为例,它是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(TimerTaskList)。TimerTaskList是一个环形的双向链表,链表中的每一项表示的都是定时任务项(TimerTaskEntry),其中封装了真正的定时任务TimerTask。
1.任务的添加与移除,都是O(1)级的复杂度;
2.只需要有一个线程去推进时间轮,不会占用大量的资源;
时间轮是为解决高效调度任务而产生的调度模型。在周期性定时任务,延时任务,通知任务等场景都可以发挥效用。
05 总结
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
注:本文个别图片来自互联网
sharding-jdbc分库连接数优化
软件架构可视化及C4模型,架构设计不仅仅是UML
三十分钟入门基础Go
求分享
求点赞
求在看