美文网首页
架构(3),高性能

架构(3),高性能

作者: 可乐爱上咖啡 | 来源:发表于2018-11-22 23:50 被阅读13次

    我们在谈到系统的时候,总会和性能挂钩,自然而然的会去想系统必须得性能好,设计系统的时候考虑最多的质量属性也是性能。高性能是每个程序员的追求,无论我们是做一个系统,还是写一行代码,都希望能够达到高性能的效果。

    高性能架构设计主要集中在两方面:

    (1 )尽量提升单服务器的性能,将单服务器的性能发挥到极致。

    (2 )如果单服务器无法支撑性能,设计服务器集群方案。

    我们该从哪些方面去考虑系统的性能呢?高性能从一个系统角度可以分为存储高性能和计算高性能。

    一、存储高性能

    我们先看一下存储高性能,从关系型数据库,NoSql数据库,缓存三块来提高存储的性能。关系型数据库可以通过读写分离和分库分表的方式提升存储性能;如果因为关系型数据库本身的限制,无法满足业务需求,可以选择适合的NoSql类型数据库;针对高并发的读操作时,可以考虑使用缓存的方案提升系统的性能。

    1.关系型数据库

    单台数据库服务器的性能优化就是硬件扩容和硬件升级,升级内存,升级硬盘,升级cpu,或者直接换小型机,大型机等。但这种方式的升级成本昂贵,而且性能的提升也容易达到瓶颈。随着互联网业务的发展,越来越多的数据,单个数据库服务器已经难以满足业务需要,如果继续使用关系型数据库,就必须考虑数据库集群的方式来提升性能。

    数据库集群的方案一般有两种:读写分离和分库分表。

    1)读写分离

    将数据库的读写操作分散到不同的节点上。

    数据库服务器搭建主从集群,一主一从或一主多从。

    数据库集群的主机负责读写操作,从机只负责读操作。

    主机通过数据库自带的复制技术,将数据同步到从机,每台数据库服务器都存储了所有的业务数据。

    业务服务器将写操作发给数据库主机,将读操作发送给数据库从机。

    主从和主备的区别:备机仅仅提供备份功能,不提供访问功能,从机是可以提供读数据的功能的。

    读写分离的方案,在实现上不复杂,但在实际使用过程中会存在复制延迟,MySql中这种延迟可能有1秒,如果数据量大的时候,甚至有可能会达到1分钟。根据业务的需要,考虑是否需要解决这种复制延迟。

    复制延迟的常见解决方法:

    写操作后的读操作指定发给数据库主服务器,但这种方案业务耦合性较大。

    读从机失败后,再次读取主机。也就是常说的“二次读取”,但这种方案会增加主机的读压力

    关键业务读写操作全部指向主机,非关键业务采用读写分离。

    2)分库分表

    读写分离的方案,主要是针对业务逻辑上,读压力远大于写压力的时候。但对于那些写压力也很大业务,使用读写分离的方法提升的性能十分有限。

    业务分库:按照业务模块将数据分散到不同的数据库服务器。例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上。

    分库虽然可以分散读写,但也带来新的问题:

    join操作问题,本来可以直接使用join实现的表连接,现在分在两个库,无法使用join,只能一张表一张表进行查询操作。

    事务问题,原本可以使用数据库的ACID来保证多表操作的事务,现在分在两个库中,事务的保证只能通过业务自己实现,或者使用第三方的分布式事务管理工具。

    分库会带来成本的上升,原来一台服务器,需要使用三台服务器,在业务的初始状况下,不用急着分库,单台数据库服务器的性能其实也没有想象的那么弱,一般来说,单台数据库服务器能够支撑10 万用户量量级的业务,初创业务从0 发展到10 万级用户,并不是想象得那么快。

    分表:单表数据拆分有两种方式: 垂直分表和水平分表。

    新的表即使在同一个数据库服务器中,也可能带来可观的性能提升。如果单表拆分为多表后,单台服务器依然无法满足性能要求,那就不得不再次进行业务分库的设计了。

    垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。带来的问题就是,原来只需要查询一次的,现在需要查询两次。

    水平分表适合表行数特别大的表,如果单表行数超过5000 万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。

    水平分表带来的复杂度:

    路由,查询某条数据时,具体属于哪个切分后的子表,需要增加路由算法进行计算。常见的路由算法:范围路由,hash路由,配置路由。

    join操作,别的表与该表进行join操作时,需要进行多次Join操作。

    count操作,数据分布在不同的表中,如果想知道总数,就要对多个表的count求和

    读写分离和分库分表的实现:

    i.代码集成:在应用中集成jar方式。开源软件淘宝的TDDL,提供了通用的数据访问层

    ii.中间件:独立的一套系统,应用访问中间件。MySQL 官方推荐MySQL Router,360 公司也开源了自己的数据库中间件Atlas。

    2.NoSql

    关系型数据库存在的问题:

    a.只能存储行记录,无法存储数据结构。

    b.表结构schema的扩展不方便。

    c.大数据量下的I/O会很高,只是针对某一列进行计算,也会读取整行数据。

    d.全文搜索比较弱,只能使用like 进行整表扫描匹配,性能非常低。

    常见的NoSQL 方案有如下4 类:

    • K-V 存储:解决关系数据库无法存储数据结构的问题,以Redis 为代表。

    • 文档数据库:解决关系数据库强schema 约束的问题,以MongoDB 为代表。 

    • 列式数据库: 解决关系数据库大数据场景下的I/O 问题,以HBase 为代表。

    • 全文搜索引擎:解决关系数据库的全文搜索性能问题,以Elasticsearch 为代表。

    针对不同的使用场景使用不同的存储系统方案,在关系数据库无法满足的时候,可以使用特定NoSql数据库来满足业务对性能的需求。

    3.缓存

    在某些复杂的业务场景下,单纯依靠存储系统的性能提升不够的:

        需要经过复杂运算后得出的数据,存储系统无能为力。

        读多写少的数据,存储系统有心无力。

    缓存的基本原理就是将可能重复使用的数据放到内存中,一次生成,多次使用, 避免每次使用都去访问存储系统。

    以Memcache 为例,单台Memcache 服务器简单的key-value查询能够达到5 万以上的TPS。

    缓存需要考虑的问题:

    缓存穿透:缓存没有发挥作用,业务系统需要再次去存储系统中查询数据。

        • 存储数据不存在,解决方法:如果查询存储系统的数据没有找到, 则直接设置一个默认值( 可以是空值,也可以是具体的值)并存到缓存中, 这样第二次读取缓存时就会获取默认值,而不会继续访问存储系统。

        • 缓存数据生成耗费大量时间或资源,例如分页计算,缓存的页面数据,一般只缓存前多少页。

    缓存雪崩:指当缓存失效(过期)后引起系统性能急剧下降的情况。

    对于一个高并发的业务系统来说,几百毫秒内可能会接到几百上千个请求。由于旧的缓存己经被清除,新的缓存还未生成,并且处理这些请求的线程都不知道另外有一个线程正在生成缓存,因此所有的请求都会去重新生成缓存,都会去访问存储系统,从而对存储系统造成巨大的性能压力。

    解决方案:(1)更新锁,对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新。(2)后台更新,由后台线程来更新缓存,而不是由业务线程来更新缓存。后台线程定时读取更新或者使用消息队列由业务线程通知。

    缓存热点:对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大。例如:明星微博宣告某一事件。

    解决方案就是复制多份缓存, 将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。

    二、计算高性能

    1.单服务器高性能——网络编程模型

    这里关于网络编程模型涉及到两个关键点:I/O 模型和进程模型

        I/0 模型:阻塞、非阻塞、同步、异步。

        进程模型:单进程、多进程、多线程。

    同步异步:是消息的通信机制,涉及到IO通知机制;所谓同步,就是发起调用后,被调用者处理消息,必须等处理完才直接返回结果,调用者主动等待结果;所谓异步,就是发起调用后,被调用者直接返回,但是并没有返回结果,等处理完消息后,通过状态、通知或者回调函数来通知调用者,调用者被动接收结果

    阻塞非阻塞:程序等待调用结果时的状态,涉及到CPU线程调度;所谓阻塞,就是调用结果返回之前,该执行线程会被挂起,不释放CPU执行权,线程不能做其它事情,只能等待,只有等到调用结果返回了,才能接着往下执行;所谓非阻塞,就是在没有获取调用结果时,不是一直等待,线程可以往下执行,如果是同步的,通过轮询的方式检查有没有调用结果返回,如果是异步的,会通知回调。

    请求立即返回就是非阻塞,不立即返回就是阻塞。

    同步阻塞,线程一直在等待结果返回;同步非阻塞,线程在轮询获取结果;异步非阻塞,线程继续往下执行,结果会由通知回调。

    PPC,prefork,TPC,prethread模式,都是请求进来后交给新的进程/线程处理。但创建进程或线程都是有代价的,高并发时还是有性能问题。

    Reactor模式

    I/O多路复用技术,只有当连接上有数据的时候进程才去处理。

    I/O 多路复用技术归纳起来有如下两个关键实现点:

    1.当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待 ,而无须再轮询所有连接。

    2.当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。

    Reactor的核心组成部分有两个:Reactor,负责监听和分配事件;处理资源池负责处理事件。

    Reactor示意图

    大致流程说明:

    (1)Reactor对象通过select监控连接事件,收到事件后通过dispatch进行分发。

    (2)如果是连接事件,则由Acceptor处理,Acceptor通过accept接收连接,并创建一个handler来处理连接后续的事件。

    (3)如果不是连接建立事件,则Reactor会调用连接对应的Handler(第2步创建的)来进行响应。

    (4)Handler会完成read->业务处理->send的完整业务流程。

    单Reactor单进程/线程模式:Reactor的监听处理和业务处理都由一个进程/或线程处理,优点没有进程间通信,没有线程竞争,缺点无法发挥CPU多核的性能,业务处理容易导致性能瓶颈。redis采用该模式。

    单Reactor多线程模式:Reactor承担所有事件监听和数据的读写操作由一个线程处理,业务处理由其他所有线程处理。多线程数据共享和访问比较复杂。

    多Reactor多进程/线程模式:mainReactor线程只负责监听连接事件,收到连接后分配给subReactor线程处理,由subReactor监听其他事件,读写和业务处理操作。nginx是多Reactor多进程,netty,memcache是多Reator多线程。

    Reactor是同步非阻塞,这里的同步指用户进程在执行read和send这类I/O操作的时候是同步的。

    Preactor模型,异步非阻塞模型,让异步I/O操作与计算重叠,把I/O操作由操作系统处理,应用进程无需进行I/O读写操作,但是在Linux下实现高并发网络编程时都是以Reactor模式为主。

    2.集群服务器高性能——负载均衡方案

    单服务器无论如何优化,无论采用多好的硬件,总会有一个性能天花板,高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的计算能力。

    常见的负载均衡系统包括 3 种:DNS 负载均衡、硬件负载均衡和软件负载均衡。

    DNS 负载均衡的本质是 DNS 解析同 一个 域名可以返回不同的 IP 地址。

    软件负载均衡通过负载均衡软件来实现负载均衡功能 , 常见的有 Nginx 和 LVS 。  

    Nginx是软件的 7 层负载均衡, LVS 是 Linux 内核的 4 层负载均衡。 4 层和 7 层的区别就在于协议和灵活性。

    Nginx 支持 HTTP 、 E-mail 协议。

     LVS 是4层负载均衡,和协议无关 ,几乎所有应用都可以做,例如,聊天、数据库等。

    一般的 Linux 服务器上装一个 Nginx 大概能到 5 万/每秒 : LVS 的性能是 十万级,

    负载均衡算法

    轮询:收到请求后,按照顺序轮流分配到服务器上 。

    加权轮询:根据服务器权重进行任务分配,这里的权重一般是根据硬件配置进行静态配 置的,采用动态的方式计算会更加契合业务,但复杂度也会更高。

    负载最低优先:将任务分配给当前负载最低的服务器,这里的负载根据不同的任务类型和业 务场景,可以用不同的指标来衡量。例如,以“连接数”来判断服务器的状态,以“ HTTP 请求数”来判断服务器状态等。

    性能最优类:优先将任务分配给处理速度最快的服务器。通过响应时间这个外部标准来衡量服务器状态,负载均衡系统需要收集和分析每个服务器每个任务的响应时间。减少这种统计上的消耗,可以采取采样的方式来统计。

    Hash 类:根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同 一台服务器上。

    相关文章

      网友评论

          本文标题:架构(3),高性能

          本文链接:https://www.haomeiwen.com/subject/pafpfqtx.html