架构设计系列4:如何设计高性能架构
在架构设计系列1:什么是架构设计中,我们讲了架构设计的主要目的,是为了解决软件系统复杂度带来的问题,今天我们来聊聊软件系统复杂度的来源之一高性能。
一、什么是高性能架构?
要搞清楚什么是高性能架构,我们需先弄明白高性能是什么。
高性能定义
首先,性能是什么,怎么理解它?
简单来说,性能,指的是一件事务的处理能力。
那么,什么是高性能呢?
高性能,指的是处理一件事务的速度更快,所消耗的资源更少。
高性能架构
那么,什么是高性能架构呢?
高性能架构,是指在有限的资源投入下,通过采用适当的技术和策略,让系统具备了优秀的性能。
对于技术人员来说,在有限的资源投入下,如何提高系统性能,这是一个挑战,更是一个机遇。
可以想象一下,同为架构师,你做的架构,在性能相同的情况下,成本更低,那是不是你的优势呢。
二、为什么高性能架构重要
在进入互联网时代后,业务的发展速度是远远超乎你的想象的。例如:
- 2016 年,双11支付宝每秒峰值达 12 万笔支付。
- 2017 年,春节微信红包收发红包每秒达到 76 万个。
我们提炼下关键信息,每秒12万笔支付,每秒收发76万个红包。这两个数字意味着,有成千上万,甚至上亿的用户,在同时使用系统。
在用户体量这么庞大的情况下,可以想象系统的压力有多大,尤其还是支付和红包这种复杂的业务,这要求整个链路都保证高性能。而一个复杂系统的链路往往是很长的,要保证链路上的每个环节配合起来达到高性能的目的,是一件个复杂且具有挑战性的任务。
如此,高性能架构就派上了用场。
我们可以通过高性能架构设计,最大限度地提高系统的处理速度、吞吐量和效率,从而提供稳定和可靠的系统服务,以满足大规模、高并发和复杂的业务需求。
高性能系统,一般具备如下特点:
- 快速响应
- 高吞吐量
- 低延迟
- 高并发性
- 可扩展性
三、如何设计高性能架构
软件系统中高性能带来的复杂度主要体现在两个方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
因此,我们可以从单机高性能和集群高性能两个方面来理解软件系统的高性能。
接下来,我们一起来探讨一下有哪些常用技术可以提升单机性能和集群性能。
1、提升性能的发力方向
1.1 单机高性能
单机高性能最关键的地方就是操作系统。
计算机性能的发展本质上是硬件发展驱动的,尤其是CPU的性能发展。著名的“摩尔定律”表明了 CPU 的处理能力每隔 18 个月就翻一番;而将硬件性能充分发挥出来的关键就是操作系统。
所以操作系统本身其实也是跟随硬件的发展而发展的,操作系统是软件系统的运行环境,操作系统的复杂度直接决定了软件系统的复杂度。
操作系统和性能最相关的就是进程和线程。
1. 多进程
计算机如何并行运行任务?
在早期计算机一次只能执行一个任务,如果某个任务需要从 I/O 设备(例如磁带)读取大量的数据,在 I/O 操作的过程中,CPU 其实是空闲的,而这个空闲时间本来是可以进行其他计算的。
为了提升性能,用进程来对应一个任务,每个任务都有自己独立的内存空间,进程间互不相关,由操作系统来进行调度。为了达到多进程并行运行的目的,采取了分时的方式,即把 CPU 的时间分成很多片段,每个片段只能执行某个进程中的指令。
虽然从操作系统和 CPU 的角度来说还是串行处理的,但是由于 CPU 的处理速度很快,从用户的角度来看,感觉是多进程在并行处理。
进程间如何通信?
多进程虽然要求每个任务都有独立的内存空间,进程间互不相关,但从用户的角度来看,如果两个任务之间能够在运行过程中就进行通信,会让任务设计变得更加灵活高效。
否则如果两个任务运行过程中不能通信,只能是 A 任务将结果写到存储,B 任务再从存储读取进行处理,不仅效率低,而且任务设计更加复杂。
为了解决这个问题,进程间通信的各种方式被设计出来了,包括管道、消息队列、信号量、共享存储等。
2. 多线程
进程内如何并行运行任务?
多进程让多任务能够并行处理,但本身还有缺点,单个进程内部只能串行处理,而实际上很多进程内部的子任务并不要求是严格按照时间顺序来执行的,也需要并行处理。
为了解决这个问题,人们又发明了线程,线程是进程内部的子任务,但这些子任务都共享同一份进程数据。为了保证数据的正确性,又发明了互斥锁机制。
有了多线程后,操作系统调度的最小单位就变成了线程,而进程变成了操作系统分配资源的最小单位。
3. 编码过程中如何优化单机性能?
要提升单机的性能,其中一个关键点就是服务器采取的并发模型,而这就涉及到多进程、多线程和异步非阻塞、同步非阻塞的IO模型。
IO 多路复用
IO 多路复用技术的两个关键点:
- 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epoll、kqueue 等。
- 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。
Reactor 和 Proactor 架构模式
在后端系统设计里面,要想实现单机的高性能,那在 IO 多路复用基础之上,我们的整个网络框架,还需要配合池化技术来提高我们的性能。
因此,业界一般都是采用 I/O 多路复用 + 线程池 的方式来提高性能。与之对应的,在业界常用的两个单机高性能的架构模式就是Reactor 和 Proactor 模式。Reactor 模式属于同步非阻塞网络模型,Proactor 模式属于异步非阻塞网络模型。
在业内开源软件里面,Redis 采用的是 单 Reactor 单进程的方式,Memcache 采用的是 多 Reactor 多线程的方式,Nginx 采用的是多 Reactor 多进程的方式。
4. 小结
如果我们要完成一个高性能的软件系统,需要考虑如多进程、多线程、进程间通信、多线程并发等技术点。
多进程和多线程虽然让多任务并行处理的性能大大提升,但本质上还是分时系统,并不能做到同一时刻真正的并行。解决这个问题的方式,就是让多个 CPU 能够同时执行计算任务,从而实现真正意义上的多任务并行。
目前最常见的多核处理器就是SMP方案。SMP,全称 Symmetric Multi-Processor,对称多处理器结构。
1.2 集群高性能
虽然计算机硬件的性能快速发展,但和业务的发展速度相比,还是小巫见大巫了,尤其是进入互联网时代后,业务的发展速度远远超过了硬件的发展速度。
就像前面提到的,支付和红包这两种复杂的业务,单机的性能无论如何是无法支撑的,必须采用集群的方式来达到高性能。例如,支付宝和微信这种规模的业务系统,后台系统的机器数量都是万台级别的。
通过大量机器来提升性能,并不仅仅是增加机器这么简单,让多台机器配合起来达到高性能的目的,是一个复杂的任务。常见的方式有:
1. 任务分配
任务分配是指每台机器都可以处理完整的业务任务,将不同的任务分配到不同的机器上执行。
高性能集群设计的复杂性主要体现在需要增加一个任务分配器,以及选择一个合适的任务分配算法。
对于任务分配器,更流行的叫法是负载均衡器。但这个名称会让人潜意识里觉得,任务分配的目的是要保持各个计算单元的负载达到均衡状态。而实际上任务分配并不仅限于考虑计算单元的负载均衡,不同的任务分配算法有不一样的目标,有的基于负载考虑,有的基于性能(吞吐量、响应时间)考虑,有的基于业务考虑。
选择合适的任务分配器也是一件复杂的事情,需要综合考虑性能、成本、可维护性、可用性等各方面的因素。
常见的任务分配器的分类有:
DNS负载均衡
这是最简单最常见的负载均衡方式,经常用来实现地理级别的均衡。
DNS负载均衡的本质是不同地理位置的用户访问时,DNS解析同一个域名可以返回不同的IP地址。比如,北方用户访问位于北京的机房,南方的用户则访问深圳机房。以www.baidu.com来说,北方用户获取的地址是61.135.165.224,南方用户获取的是14.215.177.38。
- 优点:实现简单、成本低,无须自己开发或者维护、就近访问,访问速度快
- 缺点:更新不及时、扩展性差、分配策略简单
硬件负载均衡
通过单独的硬件设备实现负载均衡的功能,此类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。当前典型的主要有两款:F5和A10。
- 优点:性能强劲(支持100万以上的并发)、功能强大(支持所有层级的负载均衡,支持全面的均衡算法)
- 缺点:价格昂贵、扩展能力差
软件负载均衡
通过提供负载均衡软件来实现负载均衡功能,比较常见的有LVS和Nginx,其中LVS是Linux内核的4层负载均衡,Nginx是软件的7层负载均衡。
除了使用开源的系统进行负载均衡,如果业务比较特殊,也有可能基于开源系统进行定制(例如,Nginx插件),甚至进行自研。
- 优点:简单、便宜、灵活,4层和7层负载均衡都可以根据业务需求进行选择和扩展
- 缺点:相对硬件负载均衡来说性能一般,功能没有那么强大
软件负载均衡和硬件负载均衡的最主要区别就在于性能上,硬件负载均衡性能远远高于软件。比如Nginx的性能是万级,一般的Linux服务器上安装Nginx后大概能达到5万/秒;LVS的性能是十万级,据说可达到80万/秒;而F5性能是百万级,从 200万/秒到800万/秒都有。
负载均衡典型架构
一般情况下,我们会对着三种负载均衡方式基于各自的优缺点进行组合使用。组合的基本原则为:DNS负载均衡实现地理级别的负载均衡;硬件负载均衡实现集群级别的负载均衡;软件负载均衡实现机器级别的负载均衡。
2. 任务分解
通过任务分配的方式,我们能够突破单台机器处理性能的瓶颈,通过增加更多的机器来满足业务的性能需求,但如果业务本身也越来越复杂,单纯只通过任务分配的方式来扩展性能,收益会越来越低。
例如,业务简单的时候 1 台机器扩展到 10 台机器,性能能够提升 8 倍(需要扣除机器群带来的部分性能损耗,因此无法达到理论上的 10 倍那么高),但如果业务越来越复杂,1 台机器扩展到 10 台,性能可能只能提升 5 倍。
造成这种现象的主要原因是业务越来越复杂,单台机器处理的性能会越来越低。为了能够继续提升性能,我们需要采用新的方式:任务分解。
微服务架构就采用了这种思路,通过这种任务分解的方式,能够把原来大一统但复杂的业务系统,拆分成小而简单但需要多个系统配合的业务系统。
从业务的角度来看,任务分解既不会减少功能,也不会减少代码量(事实上代码量可能还会增加,因为从代码内部调用改为通过服务器之间的接口调用),那为何通过任务分解就能够提升性能呢?主要有如下几方面的因素:
-
简单的系统更容易做到高性能
系统的功能越简单,影响性能的点就越少,就更加容易进行有针对性的优化。 -
可以针对单个任务进行扩展
当各个逻辑任务分解到独立的子系统后,整个系统的性能瓶颈更加容易发现,而且发现后只需要针对有瓶颈的子系统进行性能优化或者提升,不需要改动整个系统,风险会小很多。
既然将一个大一统的系统分解为多个子系统能够提升性能,那是不是划分得越细越好呢?
其实不然,这样做性能不仅不会提升,反而还会下降,最主要的原因是如果系统拆分得太细,为了完成某个业务,系统间的调用次数会呈指数级别上升,而系统间的调用通道目前都是通过网络传输的方式,性能远比系统内的函数调用要低得多。
因此,任务分解带来的性能收益是有一个度的,并不是任务分解越细越好,而对于架构设计来说,如何把握这个粒度就非常关键了。
具体如何做任务分解,可以参考读 架构设计系列3:如何设计可扩展架构 中的拆分 这个章节。
2、提升性能的常用手段
接下来,我们来简单分析一下提升性能的几种常见手段。
2.1 服务化设计
1. What:什么是服务化?
服务化是指通过任务分解的方式,把一个复杂的业务系统,拆分成多个小而简单且需要互相配合的业务系统。
大型复杂系统服务化的必然结果,是业务中台化。另外,单体架构并不一定是坏的架构,这取决于应用的复杂度。例如,一个初创的公司,要在互联网上开展业务,由于业务规模不大,业务复杂性有限,研发数量也不多,这个时候,单体架构就是最合适的。
2. Why:为什么要服务化?
服务化的目的,是将可重用的服务灵活组合在一起,来快速响应多变的业务需求,以支撑业务快速试错。
如何判断一个系统需不需要服务化呢?通常我们主要需要考虑以下几个因素:
- 是否是大型复杂系统?
- 是否存在重复建设?
- 业务是否具备不确定性?
- 技术是否制约企业发展?
- 系统是否有性能瓶颈?
如果一个系统存在以上几个问题,那么通过服务化来对系统进行整体技术升级和业务重构,是一个较好的途径。
3. How:服务化怎么做?
组织架构调整到位
为了将系统服务化更好的落地,组织架构的调整,是非常关键的一步。
因为系统服务化以后,会延伸出来一些团队问题,例如团队的分工、协作等等。因此只有将组织架构调整到位,才能将服务化带来的好处最大化。
服务化的基础建设
服务化以后,其核心强调的是不同服务之间的通信,而这会衍生出一系列的复杂问题需要我们去解决,例如服务注册、服务发布、服务调用、负载均衡,监控等等,这需要一套完整的服务治理方案。
因此,服务化的必要条件就是要有一套服务化框架,这个服务化框架要能解决这些复杂问题,并且服务化框架的性能尤为重要。
目前主流的两种服务化框架:SpringCloud 和 Dubbo。
服务化的重要手段
- 无状态设计:无状态使服务更容易快速扩缩容。
- 拆分设计:化繁为简,降低难度,分而治之。
4. 小结
一句话总结:业务解耦,能力复用,高效交付。
2.2 异步设计
1. What:什么是异步?
异步是一种设计理念,是相对同步而言的。
同步指发出一个调用时,调用方得等待这个调用返回结果才能继续往后执行。
异步指发出一个调用时,调用者不会立刻得到结果,而是可继续执行后续操作,直到被调用者处理完成后,通过状态、通知或回调来通知调用者。
2. Why:为什么要异步?
通过异步可以降低时延,提升系统的整体性能,改善用户体验。
3. How:异步怎么实现?
1)IO 层面的异步
针对 IO 层面的异步调用,就是我们常说的 I/O 模型,有阻塞、非阻塞和同步、异步这几种类型。
在 Linux 操作系统内核中,内置了 5 种不同的 IO 交互模式,分别是阻塞 IO、非阻塞 IO、多路复用 IO、信号驱动 IO、异步 IO。针对网络 IO 模型而言,Linux 下,使用最多性能较好的是同步非阻塞模型。
异步调用的常用技术
- 异步通信:NIO,Netty
2)业务逻辑层面的异步
业务逻辑层面的异步流程,就是指让我们的应用程序在业务逻辑上可以异步的执行。
通常比较复杂的业务,都会有很多步骤流程,如果所有步骤都是同步的话,那么当这些步骤中有一步卡住,那么整个流程都会卡住,这样的流程显然性能不会很高。
为此,在业内,我们如果想要提高性能,提高并发,那么基本上都会采用异步流程的方式。
异步流程的常用技术
- 消息队列:异步解耦、流量削峰
- 异步编程:多线程,线程池
- 事件驱动:发布订阅模式(观察者模式)
- 作业驱动:定时任务,XXL-JOB
4. 小结
一句话总结总结:虽然异步的执行效率高,但是复杂性和编程难度也高,所以切勿滥用。
2.3 池化设计
1. What:什么是池化技术?
池化技术是一种常见的提高性能的技术,它将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,减少重复创建和销毁资源,方便统一管理和复用,从而提高系统性能。
2. Why:为什么需要池化技术?
通过池化技术来减少重复创建销毁所带来的系统开销,提高系统性能。
3. How:池化技术怎么实现?
线程池
- ForkJoinPool
- ThreadPoolExecutor
线程池的核心参数,需基于业务场景来进行设置,例如线程数可根据任务是IO密集型,还是CPU密集型来进行设置。
连接池
- 数据库连接池
- Redis连接池
- HttpClient连接池
4. 小结
一句话总结:统一管理和复用资源,提高性能和资源利用率。
2.4 缓存设计
1. What:什么是缓存?
缓存是一种提高资源访问速度的技术。其特性是:一次写入,无数次读取。
缓存的本质是用空间换时间。其牺牲了数据的实时性,以内存中的缓存数据,代替从目标服务器(如DB)读取最新的数据,可减轻服务器压力和减少网络延迟。
2. Why:为什么要使用缓存?
使用缓存的目的很明显就是为了提升系统性能(高性能、高并发)。
使用缓存的优缺点是什么?
优点
- 优化性能,缩短响应时间
- 降低压力,避免服务器过载
- 节省宽带,缓解网络瓶颈问题
缺点
- 消耗额外的空间
- 可能存在数据一致性问题
3. How:如何使用缓存?
1)怎么提高资源访问速度呢?
将资源存放在离用户较近或访问速度更快的系统中。
2)哪些位置可以使用缓存?
| 缓存分类 | 缓存维度 | 描述 |
|---|---|---|
| 客户端缓存 | 浏览器缓存 | 1、离用户最近的缓存点,借助用户的终端设备存储网络资源,性价比最高。 2、一般用于缓存图片、JS、CSS等,可通过消息头中的Expires和Cache-Control等属性来控制。 |
| 服务端缓存 | CDN缓存 | 1、存放HTML,CSS,JS等静态资源。 2、起分流作用,减轻源服务器的负载。 |
| 服务端缓存 | 反向代理缓存 | 1、动静分离,一般缓存静态资源,动态资源转发到应用服务器处理。 |
| 服务端缓存 | 本地缓存 | 1、内存缓存,访问速度快,适合缓存少量数据的场景。 2、硬盘缓存,数据缓存到文件,访问速度比通过网络获取数据更快,适合缓存大量数据的场景。 |
| 服务端缓存 | 分布式缓存 | 1、大型网站架构中必备的架构要素。 2、缓存热点数据,减轻数据库压力。 |
3)哪种类型的缓存引入成本更高? 为什么引入成本更高?
本地缓存 和 分布式缓存的引入成本会更高。因为这两种类型缓存的资源与业务相关,需要经过业务逻辑的计算,所以对缓存与原数据之间的数据一致性要求更高。
4)1个核心指标:缓存命中率
缓存命中率越高,性能越好 。其计算公式为:缓存命中率 = 命中的次数 / (命中的次数 + 未命中的次数)。
如何提高缓存命中率?常见的策略如下:
- 缓存时长:同等条件下,缓存时间越长,缓存命中率越高
- 缓存更新:数据变化时,直接更新缓存值,比移除缓存的命中率更高
- 缓存容量:缓存容量越大,缓存的数据越多,缓存命中率越高
- 缓存粒度:缓存粒度越小,数据变化越小,缓存命中率越高(降低大key的风险)
- 缓存预热:热点数据提前缓存,提高缓存命中率
一句话总结:如何提高缓存命中率?就是让数据更长时间的驻留在缓存中。
5)1个核心问题:缓存一致性
缓存一致性指缓存与源数据之间的一致性,要保障缓存一致性,事情将变得复杂起来。
如何实现缓存一致性呢?常用的缓存策略如下:
在Cache/DB 架构中,缓存策略就是如何从 Cache 和 DB 读取、写入数据。
1、过期缓存模式:Cache Expiry Pattern
- 特点:实现缓存一致性最简的方式,为缓存设置过期时间,达到最终一致性
- 缺点:需要容忍所设置过期时间的数据不一致
2、旁路缓存模式:Cache Aside Pattern
- 读取:Cache Hit,直接返回缓存数据,Cache Miss,从DB中加载数据到缓存,并返回
- 写入:先写DB,再将Cache中对应的数据删除
- 缺点:该模式可能会出现缓存和数据库双写不一致的情况,可以采用延迟双删模式最大限度降低这种不一致性
3、异步写入模式:Write Behind Pattern
- 读取:Cache Hit,直接返回缓存数据,Cache Miss,直接返回空
- 写入:先写DB,通过 DB 将写入的新数据投递到 MQ 中,再由异步进程消费 MQ 最终将数据写入 Cache 中
一句话总结:如何实现缓存一致性?就是让每个读操作,能够获得最新的写操作数据。
4. 小结
一句话总结:缓存是应对高并发的王者(缓存为王)。
2.5 数据存储设计
1. What:什么是数据存储?
数据存储通常是指数据以某种格式记录在计算机内部或外部存储介质上。
常见的存储介质有:磁带、磁盘等。数据存储方式因存储介质而不同。在磁带上数据仅按顺序文件方式存取;在磁盘上则可按使用要求采用顺序存取或直接存取方式。数据存储方式与数据文件组织密切相关,其关键在于建立记录的逻辑与物理顺序间对应关系,确定存储地址,以提高数据存取速度。
常见的数据存储管理系统有:数据库(MySQL)、搜索引擎(Elasticserach)、缓存系统(Redis)、消息队列(Kafka)等。这也是我们接下来探讨的重点。
2. Why:数据存储设计为什么重要?
在互联网时代,当系统并发量达到一定阶段时,数据存储往往会成为性能瓶颈。如果不在一开始就进行良好的设计,则后期的横向扩展,分库分表都会遇到困难。
为什么性能瓶颈往往是数据存储,而不是应用服务呢?
因为应用服务基本是无状态的,可以较为方便的进行水平扩展,因此应用服务的高性能会相对简单。但是对于数据存储的高性能,相对来说会复杂很多,因为数据是有状态的。
3. How:如何做数据存储设计?
常见的解决存储高性能的方案有如下几种,业界大多是围绕这些来构建,或者是做相关衍生和扩展。
1)读写分离
互联网系统往往都是读多写少,因此性能优化的第一步就是读写分离。
读写分离就是将读操作与写操作分离的一种优化手段,我们可以通过这一技术来解决数据存储的性能瓶颈问题。
目前业界流行的读写分离方案,通常是基于主从模式的架构,通过引入数据访问代理层,来实现访问动作的读写分离。具体有如下两种方式:
通过独立Proxy实现读写分离
引入数据访问代理的好处是源程序不需要做任何改动就可以实现读写分离,坏处是由于多了一层中间件做中转代理,性能上会有所下降,数据访问代理也容易成为性能瓶颈,并且还存在一定维护成本。典型产品有 MyCAT、阿里云-RDS数据库代理等。
通过内嵌SDK实现读写分离
还有另一种方式,是将数据访问代理层前置到应用侧,通过SDK方式与应用集成在一起,可避免独立一层所带来的性能损耗和维护成本高的问题。但这种方式对开发语言有一定要求,存在适用性问题。典型产品有 ShardingSphere 等。
2)数据分区
“分区”是指以物理方式将数据分割成独立的数据进行存储的过程。将数据分割成分区,可以对这些分区进行单独进行管理和访问。 分区可以改善可伸缩性、减少争用,以及优化性能。 另外,它还能提供一种按使用模式来分割数据的机制。
为何要将数据分区?
- 提高扩展性。纵向扩展单一数据库系统最终会达到物理硬件的限制。 如果跨多个分区来分割数据,则每个分区托管在独立的服务器上,使系统几乎能够无限横向扩展。
- 改善性能。在每个分区上的数据访问操作通过较小的数据卷进行。 在正确操作的情况下,分区可以提高系统的效率。
- 提供操作灵活性。使用分区可以从多方面优化操作、最大程度提高管理效率及降低成本。
- 提高可用性。跨多个服务器隔离数据可避免单点故障。 如果一个实例发生故障,只有该分区中的数据不可用。 其他分区上的操作可以继续进行。
如何设计分区?
数据分区的三个典型策略:
- 水平分区(即分片)。在此策略中,每个分区都是独立的数据存储,但所有分区具有相同的架构。 每个分区称为分片,保存数据的特定子集,例如特定的一组客户的所有订单。最重要的因素是分片键的选择。分片可以将负载分散到多台计算机,减少争用并改善性能。
- 垂直分区。在此策略中,每个分区在数据存储中保存项字段的子集。 这些字段已根据其使用模式进行分割。 例如,将经常访问的字段放在一个垂直分区,将较不经常访问的字段放在另一个垂直分区。垂直分区的最常见用途是降低与提取频繁访问的项相关的 I/O 和性能成本。
- 功能分区。在此策略中,数据已根据系统中每个界限上下文使用数据的方式进行聚合。 例如,电子商务系统可能在一个分区中存储发票数据,在另一个分区中存储产品库存数据。通过功能分区来改善隔离效果和数据访问性能。
3)分库分表
对于分库分表的概念大家应该并不会陌生,其拆开来讲就是分库和分表两个手段:
- 分表:指将一个表中的数据按照某种规则拆分到多张表中,降低表数据规模,提升查询效率。
- 分库:指将一个数据库中的数据按照某种规则拆分到多个数据库中,以降低单服务器的压力,提升读写性能(如:CPU、内存、磁盘、IO)。
分库分表的两种典型方案:
垂直拆分
- 垂直拆表:即大表拆小表,将一张表中不同”字段“分拆到多张表中,比如商品库将商品基本信息、商品库存、卖家信息等分拆到不同库表中。
- 垂直拆库:将一个系统中的不同业务领域拆分为多个业务库。比如:商品库、订单库、用户库等。
水平拆分
- 水平拆表:将数据按照某种维度拆分为多张表,但是由于多张表还是从属于一个库,其锁粒度降低,一定程度提升查询性能,但是仍然会有IO性能瓶颈。
- 水平拆库:将数据按照某种维度分拆到多个库中,降低单机单库的压力,提升读写性能。
常见的水平拆分手段
- range分库分表:通过范围法对分片键按照范围进行切分。比如:根据时间范围分库分表。
- hash分库分表:通过哈希法对分片键采用hash取模。比如:根据用户ID分库分表。
关于分库分表的更多信息见文章:开发设计实践:分库分表实现方案,此处不再赘述。
4)冷热分离
冷热分离是指将历史冷数据与当前热数据分开存储,冷库只存放那那些走到终态的数据,热库存放还需要去修改字段的数据,这样可以减轻当前热数据的存储量,可以提高性能。
如果判断数据到底是冷数据还是热数据?或者说,什么情况下可以使用冷热分离?
- 时间维度:用户能够接受新旧数据分开查询。例如,对于订单数据,我们可以将三个月之前的作为冷数据,三个月之内的数据作为热数据。
- 状态维度:数据走到终态后,只有读没有写的需求。例如,对于订单数据,我们可以将状态为已完结的订单作为冷数据,其他作为热数据。
4. 小结
一句话总结:通过拆分手段,分散读写压力,分散存储压力,从而提高性能。
四、最后
在追求系统高性能的同时,千万不要忽略了成本这个因素。因为,高性能往往意味着高成本。
因此在设计高性能系统时,要特别注意,成本最小化,收益最大化。
最后,作为一名技术人员,我们应该要有一个技术追求:学会用同样的资源做更多的工作。
相关文章:
架构设计系列4:如何设计高性能架构
在架构设计系列1:什么是架构设计中,我们讲了架构设计的主要目的,是为了解决软件系统复杂度带来的问题,今天我们来聊聊软件系统复杂度的来源之一高性能。 一、什么是高性能架构? 要搞清楚什么是高性能架构,…...
1392. 最长快乐前缀
链接: 1392. 最长快乐前缀 题解: class Solution { public:string longestPrefix(string s) {if (s.size() < 0) {return "";}int MOD 1e9 7;// 构建26的n次方,预处理std::vector<long> pow26(s.size());pow26[0] 1…...
【C++设计模式之备忘录模式:行为型】分析及示例
简介 备忘录模式(Memento Pattern)是一种行为型设计模式,它用于保存和恢复对象的状态。备忘录模式通过将对象的状态封装成一个备忘录(Memento),并将备忘录保存在一个管理者(Caretakerÿ…...
数据结构与算法(四):哈希表
参考引用 Hello 算法 Github:hello-algo 1. 哈希表 1.1 哈希表概述 哈希表(hash table),又称散列表,其通过建立键 key 与值 value 之间的映射,实现高效的元素查询 具体而言,向哈希表输入一个键…...
FFmpeg 命令:从入门到精通 | ffplay 播放控制选项
FFmpeg 命令:从入门到精通 | ffplay 播放控制选项 FFmpeg 命令:从入门到精通 | ffplay 播放控制选项选项表格图片 FFmpeg 命令:从入门到精通 | ffplay 播放控制选项 选项表格 项目说明Q,Esc退出播放F,鼠标左键双击全…...
代码随想录day59
647. 回文子串 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成&#…...
【小工具-生成合并文件】使用python实现2个excel文件根据主键合并生成csv文件
1 小工具说明 1.1 功能说明 一般来说,我们会先有一个老的文件,这个文件内容是定制好相关列的表格,作为每天的报告。 当下一天来的时候,需要根据新的报表文件和昨天的报表文件做一个合并,合并的时候就会出现有些事新增…...
【论文阅读】An Evaluation of Concurrency Control with One Thousand Cores
An Evaluation of Concurrency Control with One Thousand Cores Staring into the Abyss: An Evaluation of Concurrency Control with One Thousand Cores ABSTRACT 随着多核处理器的发展,一个芯片可能有几十乃至上百个core。在数百个线程并行运行的情况下&…...
网页版”高德地图“如何设置默认城市?
问题: 每次打开网页版高德地图时默认定位的都是“北京”,想设置起始点为目前本人所在城市,烦恼的是高德地图默认的初始位置是北京。 解决: 目前网页版高德地图暂不支持设置起始点,打开默认都是北京,只能将…...
小谈设计模式(8)—代理模式
小谈设计模式(8)—代理模式 专栏介绍专栏地址专栏介绍 代理模式代理模式角色分析抽象主题(Subject)真实主题(Real Subject)代理(Proxy) 应用场景远程代理虚拟代理安全代理智能引用代…...
queryWrapper的使用教程
大于、等于、小于 eq 等于 例:queryWrapper.eq("属性","lkm") ——> 属性 lkm ne 不等于 例:queryWrapper.ne("属性","lkm") ——> 属性<> lkm gt 大于 例:queryWrapper.gt("属性…...
数组模拟双链表
文章目录 QuestionIdeasCode Question 实现一个双链表,双链表初始为空,支持 5 种操作: 在最左侧插入一个数; 在最右侧插入一个数; 将第 k 个插入的数删除; 在第 k 个插入的数左侧插入一个数; …...
鸡群优化(CSO)算法(含MATLAB代码)
先做一个声明:文章是由我的个人公众号中的推送直接复制粘贴而来,因此对智能优化算法感兴趣的朋友,可关注我的个人公众号:启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法,经典的,或者是近几年…...
3. 安装lombok maven镜像设置
安装lombok & maven镜像设置 一、maven镜像设置 Maven:负责进行项目管理、依赖工具管理的 软件。 快捷解决方案: 1.方法一 直接配置系统默认的文件 各个人因为登录的用户名不同,所以目录名不同。 2.方法二 自定义本地仓库的位置 完成之后重新打…...
详谈Spring
作者:爱塔居 专栏:JavaEE 目录 一、Spring是什么? 1.1 Spring框架的一些核心特点: 二、IoC(控制反转)是什么? 2.1 实现手段 2.2 依赖注入(DI)的实现原理 2.3 优点 三、AO…...
PyTorch入门之【AlexNet】
参考文献:https://www.bilibili.com/video/BV1DP411C7Bw/?spm_id_from333.999.0.0&vd_source98d31d5c9db8c0021988f2c2c25a9620 AlexNet 是一个经典的卷积神经网络模型,用于图像分类任务。 目录 大纲dataloadermodeltraintest 大纲 各个文件的作用&…...
(六)正点原子STM32MP135移植——内核移植
目录 一、概述 二、编译官方代码 三、移植 四、编译 一、概述 前面已经移植好了TF-A、optee、u-boot,在u-boot能正常跑起来的情况下,现在来移植内核。 二、编译官方代码 进入kernel目录 2.1 解压源码、打补丁 /* 解压源码 */ tar xf linux-6.1.28.…...
自媒体工作内容管理助手
内容助手 访问地址:editor.yunwow.cn 背景介绍 最近在学习流量运营, 流量运营的第一站是内容创作, 我试过不少原创内容,都是跟生活相关的例如:录一段联琴的视频、录一段秋天的风景、写一段生活感悟、发一段小宠物的生…...
Echarts 教程一
Echarts 教程一 可视化大屏幕适配方案可视化大屏幕布局方案Echart 图表通用配置部分解决方案1. titile2. tooltip3. xAxis / yAxis 常用配置4. legend5. grid6. series7.color Echarts API 使用全局echarts对象echarts实例对象 可视化大屏幕适配方案 rem flexible.js 关于flex…...
【Kubernetes】Kubernetes 对象是什么?
什么是 Kubernetes 对象?常见的 Kubernetes 对象参考🔎感谢 💖 什么是 Kubernetes 对象? Kubernetes 对象是持久化的实体,用于描述整个集群的状态和配置。它们是在 etcd 等持久化存储中存储的,因此它们的状…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
