DDIA

DDIA 二、数据模型与查询语言 关系模型 优点 关系模型为连接、多对一和多对多的关系提供更好的支持 缺点 如果数据存储在关系表中,在应用程序代码中的对象和数据库中的表、行和列模型之间需要一个尴尬的转换层。两种模型之间的脱节有时被称为阻抗不匹配。 文档模型 优点 JSON表示法比多表模式有更好的定位性。如果你想在关系型例子中获取个人资料,你需要进行多次查询(通过user_id查询每个表)或者在user表和它的下级表之间进行混乱的多路连接。在JSON表示中,所有的相关信息都在一个地方,一个查询就足够了。 模式的灵活性,由于局部性而有更好的性能,而且对于某些应用来说,它更接近于应用所使用的数据结构。 局部性带来的问题 地方性的优势只适用于你同时需要文档的大部分内容的情况。数据库通常需要加载整个文档,即使你只访问其中的一小部分,这对大型文档来说可能是一种浪费。 在更新文档时,通常需要重写整个文档–只有那些不改变文档编码大小的修改可以很容易地在原地进行。由于这些原因,一般建议你保持文档相当小,并避免增加文档大小的写入。 这些性能限制大大减少了文档数据库有用的情况。 缺点 多对一、多对多都不适合文档数据库模型,因为需要join操作(类似关系数据库模型),但文档模型往往对join的支持很弱 文档模型也可以通过文档ID来支持多对多 如何选择合适的数据模型? 取决于应用 在文档数据库中对连接的支持很差,这可能是一个问题,也可能不是一个问题,这取决于应用。例如,在一个使用文档数据库来记录哪个时间段发生的事件的分析应用程序中,可能永远不需要多对多的关系。然而,如果你的应用程序确实使用了多对多的关系,那么文档模型就不那么吸引人了。可以通过去规范化来减少对连接的需求,但随后应用程序代码需要做额外的工作来保持去规范化的数据的一致性。 三、存储与检索 LSM-tree Log-structured merge-tree 合并和压缩排序文件 LSM树被用于数据存储,如Apache AsterixDB、Bigtable、HBase、LevelDB、SQLite4、Tarantool、RocksDB、WiredTiger、Apache Cassandra、InfluxDB和ScyllaDB B-tree B-tree无法支持多维索引 SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079 AND longitude > -0.1162 AND longitude < -0.1004; 内存数据库为什么性能更好? 反直觉的是,内存数据库的性能优势并不是因为它们不需要从磁盘读取的事实。即使是基于磁盘的存储引擎也可能永远不需要从磁盘读取,因为操作系统在内存中缓存了最近使用的磁盘块。相反,它们更快的原因在于省去了将内存数据结构编码为磁盘数据结构的开销。 OLTP or OLAP 四、编码和进化 向后兼容与向前兼容 代码兼容数据 向后兼容:较新的代码可以读取由较旧的代码编写的数据。新代码旧数据 向前兼容:较旧的代码可以读取由较新的代码编写的数据。旧代码新数据 Schemas Protocol Buffers, Thrift, and Avro‘s schemas 比文本编码(json、xml、csv等)的数据压缩性更好,比如忽略字段名; 对于静态类型的编程语言的用户来说,从模式中生成代码的能力是很有用的,因为它可以在编译时进行类型检查; 保存一个模式数据库可以让你在部署任何东西之前检查模式变化的前向和后向兼容性; 模式是一种有价值的文档形式,由于模式是解码所必需的,你可以确保它是最新的(而人工维护的文档可能很容易与现实相背离) 分布式 为什么需要分布式? 1、可扩展性 如果你的数据量、读取负荷或写入负荷的增长超过了单台机器的处理能力,你就有可能将负荷分散到多台机器上。
▶️ Read more gq

microservices-patterns

microservices-patterns 第一章 单体架构服务 单体架构服务 弊端 开发缓慢。编辑–建立–运行–测试的循环需要很长的时间,这严重影响了生产力。 从提交到部署是很漫长的过程。无法做到持续交付。 应用扩展困难。这是因为不同的应用模块有相互冲突的资源需求。例如,餐厅的数据被存储在一个大型的内存数据库中,最好是部署在有大量内存的服务器上。相反,图像处理模块是CPU密集型的,最好部署在有大量CPU的服务器上。由于这些模块是同一个应用程序的一部分,FTGO必须在服务器配置上做出妥协。 可靠性不能保证。一个模块的问题可能会导致应用程序的所有实例崩溃,最终导致停产。 单一的架构使其难以采用新的框架和语言。 微服务 好处 它使大型复杂应用的持续交付和部署成为可能。 服务规模小,易于维护。 服务可独立部署。 服务可独立扩展。 每个服务可以部署在最适合其资源要求的硬件上。这与使用单体架构的情况完全不同,在单体架构中,资源要求完全不同的组件–例如,CPU密集型与内存密集型–必须部署在一起。 微服务架构使团队能够自治。 它允许轻松实验和采用新技术。 原则上,在开发一个新的服务时,开发者可以自由选择最适合该服务的语言和框架。 在单体架构中,你最初的技术选择严重限制了你在未来使用不同语言和框架的能力。 它具有更好的故障隔离。 缺点 寻找正确的服务组合是具有挑战性的。 分布式系统很复杂,这使得开发、测试和部署都很困难。 部署跨越多个服务的功能需要仔细协调。 决定何时采用微服务架构是困难的 微服务需要解决的问题 服务拆分 服务间通信 使用微服务架构构建的应用程序是一个分布式系统 数据一致性 使用分布式事务的传统(2PC)方法对于现代应用程序来说并不是一个可行的选择。相反,一个应用程序需要通过使用Saga模式来保持数据的一致性。 数据的查询 服务部署 服务监控 健康检查API-暴露一个端点,返回服务的健康状况。 日志汇总-记录服务活动,并将日志写入一个集中的日志服务器,提供搜索和警报功能。log 分布式跟踪–给每个外部请求分配一个唯一的ID,请求在服务之间流动时跟踪它们。trace 异常跟踪–将异常报告给异常跟踪服务,该服务对异常进行细分,提醒开发者,并跟踪每个异常的解决情况。 应用指标-维护指标,如计数器和仪表,并将其暴露给指标服务器。metrics 审计日志-记录用户的行动。 服务的自动化测试 消费者驱动的合同测试-验证一项服务是否符合其客户的期望。 消费者端合同测试-验证服务的客户端可以与服务进行通信 服务组件测试–孤立地测试一个服务 处理跨领域问题 在微服务架构中,有许多关注点是每个服务必须实现的,包括可观察性模式和发现模式。它还必须实现外部化配置模式,该模式在运行时向服务提供配置参数,如数据库凭证。当开发一个新的服务时,从头开始重新实现这些关注点是非常耗时的。一个更好的方法是应用微服务底盘模式,在处理这些问题的框架之上构建服务 安全 在微服务架构中,用户通常由API网关进行认证。然后,它必须将用户的信息,如身份和角色,传递给它所调用的服务。一个常见的解决方案是应用访问令牌模式。API网关将访问令牌,如JWT(JSON Web令牌),传递给服务,服务可以验证令牌并获得关于用户的信息。 第二章 服务拆分 两种拆分策略 》Pattern: Decompose by business capability 》Pattern: Decompose by subdomain 在应用微服务架构时,DDD有两个概念非常有用:subdomains+bounded contexts(子域和有界的上下文) 一些拆分准则 1、单一责任原则(Single Responsibility Principle,SRP) 一个class 应该只有一个改变的理由。
▶️ Read more gq

go之内存分配器

go之内存分配器 内存管理组件 内存布局 const numSpanClasses = 68x2 //线程缓存 type mcache struct { ... alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass ... } //中心缓存 //每个中心缓存都会管理某个跨度类的内存管理单元,它会同时持有两个 runtime.spanSet,分别存储包含空闲对象和不包含空闲对象的内存管理单元。 type mcentral struct { spanclass spanClass //跨度类 partial [2]spanSet // list of spans with a free object full [2]spanSet // list of spans with no free objects } //页堆 type mheap struct { ... central [numSpanClasses]struct { mcentral mcentral pad [cpu.CacheLinePadSize - unsafe.
▶️ Read more gq

栈 Linux 进程在内存布局 多任务操作系统中的每个进程都在自己的内存沙盒中运行。在 32 位模式下,它总是 4GB 内存地址空间,内存分配是分配虚拟内存给进程,当进程真正访问某一虚拟内存地址时,操作系统通过触发缺页中断,在物理内存上分配一段相应的空间再与之建立映射关系,这样进程访问的虚拟内存地址,会被自动转换变成有效物理内存地址,便可以进行数据的存储与访问了。 Kernel space:操作系统内核地址空间; Stack:栈空间,是用户存放程序临时创建的局部变量,栈的增长方向是从高位地址到地位地址向下进行增长。在现代主流机器架构上(例如x86)中,栈都是向下生长的。然而,也有一些处理器(例如B5000)栈是向上生长的,还有一些架构(例如System Z)允许自定义栈的生长方向,甚至还有一些处理器(例如SPARC)是循环栈的处理方式; Heap:堆空间,堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减; BBS segment:BSS 段,存放的是全局或者静态数据,但是存放的是全局/静态未初始化数据; Data segment:数据段,通常是指用来存放程序中已初始化的全局变量的一块内存区域; Text segment:代码段,指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。 栈相关概念 栈: 调用栈call stack,简称栈,是一种栈数据结构,用于存储有关计算机程序的活动 subroutines 信息。在计算机编程中,subroutines 是执行特定任务的一系列程序指令,打包为一个单元。 栈帧: 栈帧stack frame又常被称为帧frame是在调用栈中储存的函数之间的调用关系,每一帧对应了函数调用以及它的参数数据。 有了函数调用自然就要有调用者 caller 和被调用者 callee ,如在 函数 A 里 调用 函数 B,A 是 caller,B 是 callee。 调用者与被调用者的栈帧结构如下图所示: BP:基准指针寄存器,维护当前栈帧的基准地址,以便用来索引变量和参数,就像一个锚点一样,在其它架构中它等价于帧指针FP,只是在 x86 架构下,变量和参数都可以通过 SP 来索引; SP:栈指针寄存器,总是指向栈顶; 栈区的内存一般由编译器自动分配和释放,其中存储着函数的入参以及局部变量,这些参数会随着函数的创建而创建,函数的返回而消亡,一般不会在程序中长期存在,这种线性的内存分配策略有着极高地效率,但是工程师也往往不能控制栈内存的分配,这部分工作基本都是由编译器完成的。 Goroutine 栈 在 Goroutine 中有一个 stack 数据结构,里面有两个属性 lo 与 hi,描述了实际的栈内存地址: stack.lo:栈空间的低地址; stack.hi:栈空间的高地址; 在 Goroutine 中会通过 stackguard0 来判断是否要进行栈增长:
▶️ Read more gq

go之GPM

[TOC] 免费的性能大餐 CPU设计者主要从三个方面提升CPU性能: 时钟速度 提升时钟速度将增大单位时间的时钟周期数。让CPU跑得更快,就意味着能让同样工作或多或少更快完成。 执行优化 可以在每个时钟周期内完成更多工作。 目前的CPU中,一些指令被不同程度地做了优化,如管线、分支预测、同一时钟周期内执行更多指令,甚至指令流再排序支持乱序执行等。引入这些技术的目的是让指令流更好、更快执行,降低延迟时间,挖掘每一时钟周期内芯片的工作潜能。 指令再排序可能改变程序原意,造成程序不响应程序员的正常要求,但是性能优化>让程序员吃苦头 缓存 增大与RAM分离的片内高速缓存。 RAM一直比CPU慢很多,因此让数据近可能靠近处理器就很重要——当然那就是片内了 过去任何方法带来的速度提升,无论是顺序(非并行的单线程或单进程)、还是并发执行的程序,都能直接受益。当然,适当时候,我们重新编译程序,可以利用CPU的新指令(如MMX、SSE[译注9])和新特性提升系统性能。但总的来说,即使放弃使用新指令和新特性,不做任何更改,老程序在新CPU也会跑得更快,让人心花怒放。 曾经的世界是这般美好,可如今,她就要变了颜色。 受制于一些物理学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题等,时钟速度的提升已经越来越难。 大多数现在的应用软件将不再可能不作大规模重构,就能像过去那样从处理器免费获益。 接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的: 超线程 是指在单个CPU内,并行两个或多个线程。 多核 主要是指在一块芯片上运行两个或多个处理器。 片内缓存 CPU和主存交互的代价是巨大的,如果能避免,那就尽量不要和它打交道。在目前的系统里,从主存获取数据所花时间,通常是从缓存获得数据的10到50倍。 空间就是速度 Go语言的GPM调度器是什么? 导读 相信很多人都听说过Go语言天然支持高并发,原因是内部有协程(goroutine)加持,可以在一个进程中启动成千上万个协程。那么,它凭什么做到如此高的并发呢?那就需要先了解什么是并发模型。 并发模型 七种并发模型 线程与锁 函数式编程 Clojure之道 actor 通讯顺序进程(CSP) 数据级并行 Lambda架构 CSP CSP,全称Communicating Sequential Processes,意为通讯顺序进程,它是七大并发模型中的一种,它的核心观念是将两个并发执行的实体通过通道channel连接起来,所有的消息都通过channel传输。其实CSP概念早在1978年就被东尼·霍尔提出,由于近来Go语言的兴起,CSP又火了起来。 GPM调度模型 流程图 G 会在 M 上得到执行,内核线程是在 CPU 核心上调度,而 G 则是在 M 上进行调度。 GPM代表了三个角色,分别是Goroutine、Processor、Machine。 Goroutine:就是咱们常用的用go关键字创建的执行体,它对应一个结构体g,主要存储执行栈、goroutine的状态信息、CPU 的一些寄存器的值(SP、BP、PC) Machine:它代表一个工作线程,或者说系统线程。G 需要调度到 M 上才能运行,M 是真正工作的人。结构体 m 就是我们常说的 M,它保存了 M 自身使用的栈信息、当前正在 M 上执行的 G 信息、与之绑定的 P 信息……
▶️ Read more gq