.NET SAAS 架构与设计 -SqlSugar O
294 2023-04-03 03:41:50
本文将会介绍数据系统底层的基础概念,⽆论是在单台机器上运⾏的单点数据系统,还是分布在多台机器上的分布式数据系统都适⽤。
目标与意义
现今很多应⽤程序都是 数据密集型(data-intensive) 的,⽽⾮ 计算密集型(compute-intensive)
的。因此CPU很少成为这类应⽤的瓶颈,更⼤的问题通常来⾃数据量、数据复杂性、以及数据的变更速
度。
数据密集型应⽤通常由标准组件构建⽽成,标准组件提供了很多通⽤的功能;例如,许多应⽤程序都需
要:
如果这些功能听上去平淡⽆奇,那是因为这些 数据系统(data system) 是⾮常成功的抽象:我们⼀直不假思索地使⽤它们并习以为常。绝⼤多数⼯程师不会幻想从零开始编写存储引擎,因为在开发应⽤时,数据库已经是⾜够完美的⼯具了。
但现实没有这么简单。不同的应⽤有着不同的需求,因⽽数据库系统也是百花⻬放,有着各式各样的特性。实现缓存有很多种⼿段,创建搜索索引也有好⼏种⽅法,诸如此类。因此在开发应⽤前,我们依然有必要先弄清楚最适合⼿头⼯作的⼯具和⽅法。⽽且当单个⼯具解决不了你的问题时,组合使⽤这些⼯具可能还是有些难度的。
本部分将从我们所要实现的基础⽬标开始:可靠、可扩展、可维护的数据系统,以及探讨考量这些⽬标的⽅法。
可靠性(Reliability)
可扩展性(Scalability)
可扩展性意味着即使在负载增加(数据量、流量、复杂性)的情况下也有保持性能的策略。为了讨论可扩展性,我们⾸先需要定量描述负载和性能的⽅法。
描述负载
描述性能
⼀旦系统的负载被描述好,就可以研究当负载增加会发⽣什么。我们可以从两种⻆度来看:
a。增加负载参数并保持系统资源(CPU、内存、⽹络带宽等)不变时,系统性能将受到什么影响?
b。增加负载参数并希望保持性能不变时,需要增加多少系统资源?
这两个问题都需要性能数据,所以让我们简单地看⼀下如何描述系统性能。
吞吐量(throughput):即每秒可以处理的记录数量,或者在特定规模数据集上运⾏作业的总时间。
响应时间(response time):即客户端发送请求到接收响应之间的时间。
测量的数值分布(distribution)指标
算术平均值(arithmetic mean):平均值并不是⼀个⾮常好的指标,
百分位点(percentiles)法:如果你想知道“典
型(typical)”响应时间,通常使⽤百分位点会更好。如果将响应时间列表按最快到最慢排序,那么中位数(median)就在正中间。中位数也被称为第50百分位点,有时缩写为p50。
响应时间的⾼百分位点(也称为尾部延迟(tail latencies))⾮常重要,因为它们直接影响⽤户的服务体验。
应对负载的⽅法
可维护性(Maintainability)
可维护性有许多⽅⾯,但实质上是关于⼯程师和运维团队的⽣活质量的。良好的抽象可以帮助降低复杂度,并使系统易于修改和适应新的应⽤场景。良好的可操作性意味着对系统的健康状态具有良好的可⻅性,并拥有有效的管理⼿段。
我们应该以这样⼀种⽅式来设计软件:在设计之初就尽量考虑尽可能减少维护期间的痛苦,从⽽避免⾃⼰的软件系统变成遗留系统。为此,我们将特别关注软件系统的三个设计原则:
目标与意义:数据模型可能是软件开发中最重要的部分了,因为它们的影响如此深远:不仅仅影响着软件的编写⽅式,⽽且影响着我们的解题思路。
多数应⽤使⽤层层叠加的数据模型构建。每个层都通过提供⼀个明确的数据模型来隐藏更低层次中的复杂性。这些抽象允许不同的⼈群有效地协作,例如数据库⼚商的⼯程师和使⽤数据库的应⽤程序开发⼈员。
因为数据模型对上层软件的功能(能做什
么,不能做什么)有着⾄深的影响,所以选择⼀个适合的数据模型是⾮常重要的。
常见数据模型
关系模型
文档模型
Nosql
数据通常是⾃我包含的,⽽且⽂档之间的关系⾮常稀少。
在表示多对⼀和多对多的关系时,关系数据库和⽂档数据库并没有根本的不同:在这两种情况
下,相关项⽬都被⼀个唯⼀的标识符引⽤,这个标识符在关系模型中被称为外键,在⽂档模型中称为⽂档引⽤【9】。该标识符在读取时通过连接或后续查询来解析。
访问记录的唯⼀⽅法是跟随从根记录起沿这些链路所形成的路径。这被称为访问路径(access path)。
对比关系模型和文档模型
使应用程序代码更简单方面
灵活性方面
查询数据的局部性方面
图数据模型
如果你的应⽤程序⼤多数的关系是⼀对多关系(树状结构化数据),或者⼤多数记录之间不存在关系,那么使⽤⽂档模型是合适的。
但是,要是多对多关系在你的数据中很常⻅,随着数据之间的连接变得更加复杂,使用图数据模型更加⾃然。
⼀个图由两种对象组成:顶点(vertices)(也称为节点(nodes) 或实体(entities)),和边 (edges)( 也称为关系(relationships)或弧 (arcs) )。
有⼏种不同但相关的⽅法⽤来构建和查询图中的数据。如属性图模型和三元组存储模型(triple-store)。
属性图模型
⼀组出边(outgoing edges)
⼀组⼊边(ingoing edges)
⼀组属性(键值对)
每条边(edge)包括:
唯⼀标识符
边的起点/尾部顶点(tail vertex)
边的终点/头部顶点(head vertex)
描述两个顶点之间关系类型的标签
⼀组属性(键值对)
可以将图存储看作由两个关系表组成:⼀个存储顶点,另⼀个存储边。可以用头部和尾部顶点⽤来存储每条边;顶点的输⼊或输出边也同理。
- 关于这个模型的⼀些重要特性,这些特性为数据建模提供了很⼤的灵活性:
任何顶点都可以有⼀条边连接到任何其他顶点。没有模式限制哪种事物可不可以关联。
给定任何顶点,可以⾼效地找到它的⼊边和出边,从⽽遍历图,即沿着⼀系列顶点的路径前后移动。
通过对不同类型的关系使⽤不同的标签,可以在⼀个图中存储⼏种不同的信息,同时仍然保持⼀个清晰的数据模型。
4.在可演化性方面富有优势:当向应⽤程序添加功能时,可以轻松扩展图以适应应⽤程序数据结构的变化。
- Cypher查询语⾔
- Cypher是属性图的声明式查询语⾔,为Neo4j图形数据库⽽发明。(它是以电影“⿊客帝国”中的⼀个⻆⾊来命名的,⽽与密码术中的密码⽆关。)
通常对于声明式查询语⾔来说,在编写查询语句时,不需要指定执⾏细节:查询优化程序会⾃动选择预测效率最⾼的策略,因此你可以继续编写应⽤程序的其他部分。
- 三元组存储模型- 三元组存储模式⼤体上与属性图模型相同,⽤不同的词来描述相同的想法。在三元组存储中,所有信息都以⾮常简单的三部分表示形式存储(主语,谓语,宾语)。例如,三元组(吉姆, 喜欢 ,⾹蕉)中,吉姆是主语,喜欢是谓语(动词),⾹蕉是宾语。- 三元组的主语相当于图中的⼀个顶点。⽽宾语是下⾯两者之⼀:
原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓语和宾语相当于主语顶点上的属性的键和值。例如, (lucy, age, 33) 就像属性 {“age”:33} 的顶点lucy。
图中的另⼀个顶点。在这种情况下,谓语是图中的⼀条边,主语是其尾部顶点,⽽宾语是其头部顶点。例如,在 (lucy, marriedTo, alain) 中主语和宾语 lucy 和 alain 都是顶点,并且谓语
marriedTo 是连接他们的边的标签。
- SPARQL查询语⾔
- SPARQL是⼀种⽤于三元组存储的⾯向RDF数据模型的查询语⾔。(它是SPARQL协议和RDF查询语⾔的缩写,发⾳为“sparkle”。)SPARQL早于Cypher,并且由于Cypher的模式匹配借鉴于
SPARQL,这使得它们看起来⾮常相似【37】。
- 特点- 多对多的关系:任意事物都可能与任何事物相关联。- 查询和更新数据库的代码变得复杂不灵活。- 更改应⽤程序的数据模型很难。
查询语言
当引⼊关系模型时,关系模型包含了⼀种查询数据的新⽅法:SQL是⼀种【声明式】查询语⾔,⽽IMS和CODASYL使⽤【命令式】代码来查询数据库。
声明式查询语言
声明式查询语言紧密地遵循关系代数的结构。
关注结果不关注过程:在声明式查询语⾔(如SQL或关系代数)中,你只需指定所需数据的模式 - 结果必须符合哪些条件,以及如何将数据转换(例如,排序,分组和集合) - 但不是如何实现这⼀⽬标。数据库系统的查询优化器决定使⽤哪些索引和哪些连接⽅法,以及以何种顺序执⾏查询的各个部分。
简洁易懂:声明式查询语⾔是迷⼈的,因为它通常⽐命令式API更加简洁和容易。但更重要的是,它还隐藏了数据库引擎的实现细节,这使得数据库系统可以在⽆需对查询做任何更改的情况下进⾏性能提升。
适合并⾏执⾏:声明式语⾔往往适合并⾏执⾏。现在,CPU的速度通过内核的增加变得更快,⽽不是以⽐以前更⾼的时钟速度运⾏。命令代码很难在多个内核和多个机器之间并⾏化,因为它指定了指令必须以特定顺序执⾏。
图的声明式查询语⾔
命令式查询语言
其他(专业)数据模型
目标&意义
驱动数据库的数据结构
哈希索引
索引(index)为了⾼效查找数据库中特定键的值的一种数据结构。添加与删除索引,不会影响数据的内容,只影响查询的性能。维护额外的结构会产⽣开销,特别是在写⼊时。
存储系统中⼀个重要的权衡:精⼼选择的索引加快了读查询的速度,但是每个索引都会拖慢写⼊速度。
内存中的哈希映射,其中每个键都映射到⼀个数据⽂件中的字节偏移量,指明可以找到对应值的位置。当你想查找⼀个值时,使⽤哈希映射来查找数据⽂件中的偏移量,寻找(seek)该位置并读取该值。
为什么不更新⽂件,只能追加设计的原因
有⼏个:
哈希表索引的局限性:
SSTable和LSM树
SSTable:在普通⽇志结构存储段都是⼀系列键值对,键值对的顺序为写⼊的顺序出现,⽇志中稍后的值优先于⽇志中较早的相同键的值。此时,如果我们要求键值对的序列按键排序。则这种格式就是【排序字符串表(Sorted String Table)】,简称SSTable。
SSTable的优势:
构建和维护SSTables
SSTables的应用
LSM存储引擎
Lucene索引引擎
性能优化:
LSM树算法在大多数情况向性能表现良好,但当查找数据库中不存在的键时,LSM树算法可能会很慢:您必须检查内存表,然后将这些段⼀直回到最⽼的(可能必须从磁盘读取每⼀个),然后才能确定键不存在。为了优化这种访问,存储引擎通常使⽤额外的Bloom过滤器。
不同的策略来也会影响SSTables被压缩和合并顺序和时机。最常⻅的选择是⼤⼩分级压缩和分层压缩。
大小分级压缩
分层压缩
LSM树的基本思想简单⽽有效:保存⼀系列在后台合并的SSTables。
即使数据集⽐可⽤内存⼤得多,它仍能继续正常⼯作。由于数据按排序顺序存储,因此可以⾼效地执⾏范围查询(扫描所有⾼于某些最⼩值和最⾼值的所有键),并且因为磁盘写⼊是连续的,所以LSM树可以⽀持⾮常⾼的写⼊吞吐量。
B树
以上讨论的⽇志结构索引正处在逐渐被接受,而应用最广泛的索引结构是B树。
像SSTables⼀样,B树保持按键排序的键值对,这允许⾼效的键值查找和范围查询。
⽇志结构索引将数据库分解为可变⼤⼩的段,通常是⼏兆字节或更⼤的⼤⼩,并且总是按顺序编写段。而B树将数据库分解成固定⼤⼩的块或⻚⾯,传统上⼤⼩为4KB(有时会更⼤),并且⼀次只能读取或写⼊⼀个⻚⾯。这种设计更接近于底层硬件,因为磁盘也被安排在固定⼤⼩的块中。
每个⻚⾯都可以使⽤地址或位置来标识,这允许⼀个⻚⾯引⽤另⼀个⻚⾯,类似于指针。我们可以在磁盘中使⽤这些⻚⾯引⽤来构建⼀个⻚⾯树,⽽不是在内存中。
让B树更可靠
B树优化
比较B树与LSM树
我们将简要讨论⼀些在衡量存储引擎性能时值得考虑的事情
LSM树
优点
缺点
B树
优点
缺点
其他结构
主键索引primary index:
主键唯⼀标识关系表中的⼀⾏,或⽂档数据库中的⼀个⽂档或图形数据库中的⼀个顶点。数据库中的其他记录可以通过其主键(或ID)引⽤该⾏/⽂档/顶点,并且索引⽤于解析这样的引⽤。
二级索引:
⼀个⼆级索引可以很容易地从⼀个键值索引构建。⼆级索引的键不是唯⼀的,可能有许多⾏(⽂档,顶点)具有相同的键。
聚簇索引clustered index:
在索引中存储所有⾏数据。在某些情况下,从索引到堆⽂件的额外跳跃对读取来说性能损失太⼤,因此可能希望将索引⾏直接存储在索引中,这被称为聚集索引。例如,在MySQL的InnoDB存储引擎中,表的主键总是⼀个聚簇索引,⼆级索引⽤主键⽽不是堆⽂件中的位置。
非聚簇索引nonclustered index:
仅在索引中存储对数据的引⽤。
覆盖索引covering index:
在聚集索引和⾮聚集索引之间的折衷被称为包含列的索引(index with included columns) 或覆盖索引,其存储表的⼀部分在索引内。这允许通过单独使⽤索引来回答⼀些查询,这种情况叫做:索引覆盖(cover)了查询。
内存数据库
事务处理还是分析处理?
存储引擎分类
a.在线事务处理(OLTP, OnLine
Transaction Processing)
b.在线分析处理(OLAP, OnLine Analytice
Processing)
c.OLTP对比OLAP
OLTP两大主流存储引擎
a。日志结构学派
b。就地更新学派
列式存储
可演化性:应⽤程序不可避免地随时间⽽变化。新产品的推出,对需求的深⼊理解,或者商业环境的变化,总会伴随着功能(feature)的增增改改。第⼀章介绍了可演化性(evolvability)的概念:应该尽⼒构建能灵活适应变化的系统(参阅“可演化性:拥抱变化”)。
编码影响点
编码格式及其兼容性
概念
在内存中,数据保存在对象,结构体,列表,数组,哈希表,树等中。 这些数据结构针对CPU的⾼效访问和操作进⾏了优化(通常使⽤指针)。
如果要将数据写⼊⽂件,或通过⽹络发送,则必须将其编码(encode)为某种⾃包含的字节序列(例如,JSON⽂档)。 由于每个进程都有⾃⼰独⽴的地址空间,⼀个进程中的指针对任何其他进程都没有意义,所以这个字节序列表示会与通常在内存中使⽤的数据结构完全不同 1 。
- 所以,需要在两种表示之间进⾏某种类型的翻译。 从内存中表示到字节序列的转换称为编码
(Encoding)也称为序列化(serialization)或编组(marshalling),反过来称为解码
(Decoding) 解析(Parsing),反序列化(deserialization),反编组(unmarshalling) 。
1.编程语⾔特定的编码
2.JSON、XML和CSV等文本结构
3.Thrift、Protocol Buffers和Avro等二进制模式驱动的格式