互联网架构的个人主观看法
架构是一个系统的整体骨架的简称。理论上可以说所有运行的系统都有自己的架构,不同的是数据处理方式、工具和目的。一个理想的架构需要表现出最大的扩展性、最好的安全性、最佳的运行性能、最简的维护工作、最快的升级特点、最合适的支撑团队、最低的运营成本和最优的发展目标。为了实现这些要求无数团队与个人在无私贡献丰厚的成果,但还远远不够,因为业务要求与市场不断在升级变化,与之要求的系统也需要不变的升级与变化。很多时候系统在随着变化而面临重构,更甚的会在不同的阶段重构一次或几次。但有些并非架构全责,比如:团队重组、语言变更、业务转向、硬件调整、成本管控等。
不可否认大公司很有钱可以拥有你不敢相像的团队,可以买你可能永远都接触不到的硬件或系统,去完成他们定义的高端架构标准,但他们在处理架构时经历了什么、成功到什么节点、能适应到什么节点、还有多少优化空间你能断言否?这不应该是架构的标杆,这只是有钱的标杆。我认为架构的初衷不应该有绑架的动机,它需要我们拿出自己擅长的能力不断去学别人所长补自己所短,权衡的思考现在的要求与情况、将来的要求与情况,再尽可能的去选择一套合适的方案,一步一步迭代与完善。
目前还没有一个绝对的权威的架构标准,不过业界都有同一个目标:把数据库、业务逻辑的处理量与并发量减少到可承受的范围内,并预留一定的扩展和伸缩空间。选择合适的方案是决策人能力的体现,很多人喜欢跟风选择去填补自己的眼界或尝试欲望,在使用前后没有充分的了解与分析,导致面临问题时手忙脚乱,甚至有些问题一直无法解决。跟风是顺应潮流是扩大生存空间,但也需要充分的准备学习才能更好的生存下去。
一个以数据库为核心的互联网系统中,架构的首要重点自然是数据库的规划;然后业务逻辑规划;最后对外服务规划。除非服务器长期的压力小到只需要要单台服务器就满足了,那架构就不需要专门去思考与研究,甚至都不需要专门考虑数据的原始性。如果服务器压力在上升架构的重要性就一定会摆在面前,而架构的重点就是合理安排各环节之间的通信法则。
数据库规划
数据库是整个系统的数据仓库,是提供数据保障的数据管理独立系统,但它在多节点的情况下处理方式有很大的变化。最主要的原因是数据库依赖索引来查询数据(而索引只能对单表,多表索引会影响拆数据的目的),数据拆分后索引也一同拆分(只有这样才能保证索引树更小,查询更快)。为尽可能少的对系统提供服务的影响需要在业务层增加一层数据库节点索引(或中间件)关联到不同的数据库节点上快速选择完成数据操作。
目前做法有
- 使用强大的中间件(比如mycat、ShardingSphere等),它们会自动处理分表,分片等工作,让你的架构不需要再考虑太多数据库的问题。但会受中间件的功能限制,某些更定制化的特殊需求可能需要在业务上进行复杂处理(比如熟悉的数据库不支持,更灵活的查询和复杂的查询不支持或性能不佳,不过中间件一般有团队在维护,只要市场有较大刚需算法上可行很快会增加对应的功能),当然也可以外加其它数据库系统来辅助完成。
- 对必用搜索字段做HASH业务索引分片到不同的节点表。当只有一个必用字段时问题不大,如果出现多个必用字段时会复杂的多,要么遍历主要必用字段节点群(业务上处理复杂性能稍差,如:分页需要额外计算拼装);要么给次要的必用搜索字段创建各自的节点群,这个节点群的数据列包含主要必用字段,和辅助搜索字段(需要额外占用部分空间,业务处理相对简单性能稍好,如分页只需要获取主分片ID集再查询主要必用字段节点群的数据再合并即为分页数据体),也可以包含所有数据(额外空间占用量大,直接返回分页数据体),但更新时都需要同时更新所有对应的节点群。这种做法更自由,业务适应能力强,对数据二次拆分或合并难度大,也可以借助其它非关系型数据库来辅助搜索数据。
- 使用商业收费数据库集群系统,就如同使用中间件一样,但需要在不同的量级下额外支付不同的费用。
- 按业务某些不怎么互通的区域拆分通过大量主从复制来分压,比如按地区拆分,按业务线拆分,这种做法会限制较多的查询功能,系统的支撑量有限也不能无限扩展。
- 依赖大量的主从复制,最原始的方式,简单粗暴,系统的支撑量非常有限并且也不能无限扩展,需要足够强的硬件支撑,当到极限时必需调整。
不同的做法适应不同的环境、背景、条件。相同的是集群服务器必需满足一些基本要求,比如:
各节点的数据可以互通且及时性高
如果数据互通性不好,及时性不高就会出现数据延时读脏,严重影响系统正常功能。高效的互通性与及时性需要保证各节点的压力一直在可承受范围内。比如主从复制分流,如果复制过于依赖某些写入量大的节点时会影响这些节点的写速度的效率发挥,让一些从节点做提供复制功能给其它节点,减少主节点的复制压力(当然这样会造成相对的复制延时,需要控制好复制的层次,视读写量级而定)- 各节点的数据处理量要相对均匀
如果各节点数据处理量极度不均匀压力就可能会失衡,容易造成部分节点压力过大而罢工。数据处理量要均匀就需要在分表分片时分散数据集中点,系统查询时分散到各只读节点上。在节点的数据量上一般需要选择一个唯一或索引层够高的字段来分片,比如:手机号取前缀或其它段、主键ID可以取模、取整、取尾数等,尽可能能让各节点的数据量相对均匀即可;对于系统查询量需要业务代码尽可能能雨露均沾的访问各节点,而不是集中在某些节点,一般可以使用权值计算、随机碰撞、访问顺序、时序,询压等。
3.支持数据锁最好支持一致性事务
数据锁可以保证数据的一致性,在并发环境中尤其重要,只有锁定数据才能保证各进程上下文中处理的数据不会被破坏。值得庆幸的是大部分数据库都提供了数据锁,但在集群中上锁可能会出现在不同的节点上,如果上锁的顺序混乱那么就可能造成死锁。
事务处理可以快速让一批数据的修改生效(提交)或失效(回滚),同时事务提供事务隔离与事务锁保证了数据的一致性,简化程序处理流程,避免程序回滚数据与数据加锁等操作。关系型数据大部分都支持标准事务,非关系型数据库基本没有或很简单的事务,但都提供了锁。在集群中同一个业务事务处理可能会出现在多个节点上,而在事务提交时可能会出现某些节点提交失败,而其它节点提交成功的情况,这会造成已经提交的数据无法回滚,而未提交成功的数据回滚的问题。多个节点事务无法保证事务的绝对一致性,有很多种特殊情况会导致事务只在部分节点上生效,比如:网络异常、数据库挂了、业务进程挂了、死锁等。
要想在多节点上保证事务的可靠性就需要额外增加一个独立一致性事务,以这个事务来决定各节点的事务成败。独立事务必需在其它各节点事务提交前提交并且记录各节点的事务处理数据,独立事务提交成功后,然后成功提交一个节点事务就删除一个独立事务对应的数据,当出现某个节点的事务无法正常提交时可再通过其它的守护进程取出独立事务内的数据写到异常节点中,保证事务一致性。如果在还没有来的及删除独立事务内的节点数据时就挂了则需要守护进程自行判断数据是否已经修改成功(插入或删除的数据通过唯一特征来判断,修改通过修改前唯一特征数据源增加限制条件)。这个独立事务可使用消息队列,先进先出,当某个节点事务提交失败时再去填补下;当然也可以使用数据库事务。准备一个提交一致性事务(记录各节点事务修改数据体,提交后就表示事务完成,然后逐个提交各节点事务,成功一个删除一个提交一致性事务对应的节点数据体或者全部提交完后统一删除),一个确认一致性事务(这个事务可以使用最后提交的节点事务来担任,这个节点的事务最先开启前加锁,最后提交解锁,如果提交中异常则交给其它守护进程处理)。另外守护进程需要堵塞其它业务进程事务处理,防止业务事务内处理待守护进程修改的数据,守护进程使用不当可能会影响系统的性能。守护进程可以使用定时队列服务、写到业务进程中或者两者均有,这完全取决于架构的选用。
理论上只要担任的一致性事务或锁能正常提供服务就可以保证各节点的数据的一致性。
各节点迭代、更新、升级、伸缩能够快速同步进行
业务发展会影响各阶段的人力与财力投入,也不可能一开始就使用非常大的集群来应对很小的业务,系统初期会预估1~5年的数据量,然后根据当前的财力来选择合适的集群,再作对应的分表分片处理。随着业务的变化很有可能需要再次升级或降级,如果集群升降级非常复杂并伴有不确认性那将会给系统的升降级来带不可估计的影响。一个好的架构需要具备快速的升降级处理条件,以应对业务的变化。而升降级中最困难的莫过于分表分片的二次拆分与合并,业务代码的调整。这也是为什么现在很多项目使用了数据库中间件的原因,中间件把这些操作封装好对外的就如同操作一个数据库,使得架构升降级时不需要调整业务代码,唯一的遗憾是中间件并不能像操作一个数据库那么的任性,因为他就是一个数据库节点路由器,操作不当会给系统带来隐患。当然如果在业务代码中来完成中间件的功能也是可以的,不过需要额外增加非常多的算法逻辑,好处是你的系统你作主,只要能力达到就以完成中间件的所有工作并且还可以更好的定制调整。
如果单从升降级处理上讲,分表与分片需要准备一个处理工具把这些数据进行复制转移,当数据复制转移完成后再切换到业务处理,如果切换失败则退回切换。中间需要存在一点切换停服时间,一来保证数据在切换中不会再变化,二来保证数据切换失败时退回前数据也不会变化。业务代码上的切换需要同数据切换同步,最好各业务节点里有两套代码,一套是原来的分表分片处理业务,一套是现在的分表分片处理业务,如果数据切换失败可以快速退回。节点群需要高可用允许少量服务器异常时依然可以正常提供服务
高可用是现在比较热门的话题,当用户体量足够多时系统异常机率会多起来,异常时间越长损失就越大,所以让系统在最短的时间内恢复正常止损是高可用的价值所在。高可用涉及到系统的出入口(负载均衡器)、业务服务器、数据服务器等各节点和环节。通常业务服务器会更容易点,因为业务代码相同且部署多个,部分异常时负载均衡器可以直接剔除,正常后再自动加入。系统的出入口与数据服务器一般是增加备用节点来替换,通过守护进程定时去核实主节点可用性,当主节点不可用时自动切换到备用节点上。系统的出入口可以通过修改域名解析IP,或修改机房网络等方式。数据服务器只需要修改连接地址或地址解析即可。但它们在切换之前最好保证异常的节点死透了或强制关闭,假死容易带来数据的混乱。- 合理查询不受限制
集群本来的目的是解决数据量的问题,但数据被切分后查询会变得更麻烦,如果一条查询涉及到多个节点则需要遍历这些节点,节点越多这条查询越慢,即使增加缓存也会因为第一次查询超时堵塞整个业务线。不同类型的数据库单表数据量不一样对查询的限制或要求也不一样,目前主要有关系与非关系型数据库,还有部分数据库在这两者之间(不过大部分可以定义为非关系型数据库)。关系型数据库可以使用关联查询减少数据的冗余量简化查询流程,会牺牲数据的容纳量与查询速度;非关系型数据库提升数据查询速度拆分查询流程,不能使用关系查询需要增加冗余数据来减少查询次数与复杂性。可以说数据拆分后会把数据非关系化,为了完成更多的查询要求就需要增加更多辅助冗余数据完成,当然这些冗余数据不一定要存放在相同的数据库中,可以是其它的数据库或缓存。
业务逻辑规划
业务逻辑是需要定制开发,基本没有太多的第二选择,即使使用了开源代码也需要二次开发。但在集群数据库系统中业务的处理需要格外小心,稍有不慎就会造成巨大的隐患甚至影响系统正常运行。
业务拆分
很多架构或决策人员愿意把直接操作数据的逻辑单独拆分出来组成微服务或低层服务提供给更外层的业务逻辑处理,让更专业的人员处理低层数据操作,其他人员处理外层业务逻辑操作,尽可能避免数据操作不当的灾难。这种做法好处比较明显,比如:系统更稳定可靠、外层处理人员不需要再考虑数据一致性等相关问题开发更快、独立服务提供更多应用场景、各层级更专一容易升级与维护等,缺点是:低层服务处理复杂、需要额外占用空间、增加系统整体负载与成本等。当业务很大时还是建议这种布局,因为开发人员的能力责任不一样,自然结果也会不一样,数据处理不当带来的灾难往往影响非常大,越是核心容易出错的地方越应该交给有能力的人去完成。拆分合理也需要综合评估,拆分不合理会把逻辑处理量偏向一边影响周期和扩展性。
开发语言
开发语言只是一个实际系统业务功能实现的工具,没有太大的贵贱之分,适应团队才是王道。合理的架构不只是节省成本,有的时候还决定了项目的成败。而很多人却把架构的失败归结到开发语言本身及框架和使用的各种服务工具;比如:像php、nodejs、python等弱类型语言安全性差、功能少、速度慢不能开发大型系统,而应该使用Java、go等高级别强类型语言,系统框架也应该使用流行强大的;数据库系统应该使用mongodb、postgresql、oracle、非关系数据库而不是使用mysql等等。但我认为架构的失败是决策人的全责与能力的体现,好的工具用的好才能发挥好,软件的世界里没有绝对好的东西,只有用的好的东西。强类型语言也有漏洞、多线程并发等特定高级功能并没有多少系统大量运用、运行速度也不一定绝对性的快;还有所有的数据库单表支撑都是有限的,数据库受硬件限制无法做到无限量,如果可以今天也不需要讨论分表分片搞集群。但有一点可以说的是强类型语言学习成本高难度稍大,自然能生成下来的开发人员能力与素质相对偏高;而弱类型语言学习成本低简单,很多低能力与低素质的人混杂在内拉低了整体的能力水平,导致弱类型语言成了弱鸡的代名词。
每个语言有自己的定位与特点,但目前弱类型语言都是由C或C++之类的更低级的强类型语言开发的出来的,为保证弱类型的特点会牺牲一些空间与性能来避免强类型语言的开发复杂性。但不代表性能就一定比高级别强类型语言差,一个系统运行的速度完全取决于实际CPU执行的指令时长和IO等待时长,只有最优的代码才能保证速度最优,可惜的是在业务代码中最优往往只是表象,系统中一般会使用框架及组件而真正使用到的功能可能还不能20%大部的功能是无用的损耗,无形中增加了系统的负载。在非常简单的功能对比弱类型语言基本不具备性能等优势,但面对庞大的功能时弱类型语言开发速度快,代码体量小,谁优谁劣还真不好说。当系统处理量足够大时,性能的体现才会非常明显,不过目前还没有哪个系统会用多种语言开发多套进行性能对比。目前所有的对比都是在一定的基础上对比,并不是成熟的大型系统上对比,语法与底层的差异使得对比本身不具备不公平性,每个语言都有自己的性能突出点和薄弱点,在性能突出点使用多的系统中自然性能更佳,相反性能更差。
至于业务代码的安全性与开发语言本身是没有直接关系的,没有哪个语言敢承诺开发出来的程序是绝对不会出现安全问题的。弱类型与强类型的语言在各自生存环境中有自己的的处理方式来避免安全问题,但难免会有疏忽,完全在于开发人员本身的意识与见解。
个人认为只要能力到了弱类型语言并不弱而强类型语言并没有强到哪里去,大部分复杂的功能与算法已经被写到各服务与工具、语言解析器和框架中,即使有更高级的算法要求也会是另外的算法团队来完成,而你们只需要去使用它(除非你能力足够强可以写出任何其它语言能做的事)。所有开发语言都有自己的优点与缺点,因此语言没有最好只有最合适。如果你评估确认某种语言及框架是团队最拿手的就不要再去冒然尝试团队的短板,除非你有万全的把握应对。
对外服务规划
对外服务有两种,一种是系统内部各服务节点之间的对外服务(不对公网开放),比如数据库、微服务等;一种是对公网和内网开放的服务,比如对外API、网站展示、数据输出等。不官哪种形式都需要保证服务的正常有序。
高可用性
如果服务出现异常应该如何保证对外服务还能正常呢?高可用性是所有系统都会面对的问题,软件与硬件存在许多的不确定性导致系统异常,在出现异常时如果去及时发现并处理即是高可用的主要目标。一般可以增加监控系统,发现系统异常后及时报警和自动处理,减少导演带来不可服务的损失。
安全
对外服务最怕的就是数据泄露、篡改、甚至造成系统不可服务。从理论上说,没有绝对的安全,但需要有对应的监控和预防机制尽可能止损。主要安全区域有:操作系统、组件服务、业务逻辑、业务组件框架、开发语言低层等,一般能够掌控的是业务逻辑安全,其它的安全问题基本上只能在配置、升级、补丁上来弥补。
业务逻辑安全。业务逻辑安全有很多,比如SQL注入、SHELL注入、跨站篡改、业务漏洞等。除了业务漏洞之外其它所有安全问题都是由业务逻辑不够严谨造成的。如果需要很好的避免漏洞就得需要知道漏洞的特点,并且还要有更好的洞察能力去避免。对于业务漏洞核心的思想是不要相信所有用户提供的数据,这些数据都需要经过验证或加工处理安全后才可以使用;所有会接收用户数据的进程尽可能不要给太高的操作权限,比如在linux系统下不要在root账号下启动这些服务进程。业务漏洞一般是需求考虑不周造成的,比如:做活动只能每人抽奖一次,但在其它业务中又可以赠送抽奖次数,导致抽奖数据不可控。
业务组件框架、开发语言低层安全。这部分安全问题很少,一旦出现影响会比较大,基本上只能升级对应的组件框架或开发语言,对于比较大的集群来说是一个不小的工作(当然如果集群管理工具强大另论),有的时候可能还需要修改比较多的业务代码来弥补。作为决策人应该需要考虑下这块的安全问题,尽早安排部署对就的工具,不同的开发语言工具不尽相同这里就不多说明,不过也可以自行开发简单的处理脚本进行远程批量操作。
组件服务安全。服务组件只要不对外网开放,就可以抵挡一大半安全问题,如果有对外开放的服务则需要注意相关配置,比如:端口号调整、使用加密协议通信、限制连接的地址或其它标识、使用专用账号启动服务、复杂的授权密码等。其次就是业务代码或管理工具漏洞也可能会导致组件服务被暴露出来,甚至可以任意控制,一般建议管理工具只在内网或专网使用,业务代码提供的操作账号必需限制权限,所有的授权密码或账号一定要尽可能复杂防止暴力破解。
操作系统安全。操作系统安全大部分是系统组件与系统辅助管理工具带来的,遇到了只能及时升级或打补丁。操作系统都会提供一起辅助管理工具和基本的IO操作管理进程,这些是操作系统不可缺少的。因为操作系统的复杂性,所以漏洞也难以避免。但也不是所有的漏洞是系统带来的,比如ssh如果配置使用不当会被利用甚至提权。一般建议集群节点只能通过内网或专网(***或其它固定IP)通道管理,减少其它外网访问的可能性,当然各节点依然需要增加二道防守,增加账号与密码强度与保密性,并且对外提供的连接账号不使用最高权限账号,只能通过这个账号进入后再提权。
监控管理
集群系统一定要有个监控管理系统,否则管理太多的服务器将是一个耗时易错的工作。监控系统主要负责服务可用与安全预警并及时给出报警与修复;管理系统主要负责集群升级、更新、调整、伸缩、安装等工作。不论是集群监控或管理都有很多成熟工具,按业务需求来选择,如果你有足够的能力或财力也可以自行研发,毕竟自己做的东西可以任意调整与定制。
实施
架构不是神秘黑盒子,很是一个有严格条理的各环节解决方案。当系统数据量足够大时各种情况都有可能发生,只有提前预知更多、考虑更多、准备更多才能保证系统更稳定,同样也不是每种构架方案都适合任何业务场景。
一般架构时需要画出架构图,同时给出各环节流程图以及问题与解决方案。没有哪个架构能一次性完美的解决所有当前问题和将来问题,业务在变化架构也需要不断的调整或升级,见招拆招是一条走不完的路,否则架构一次后面就没有技术团队啥事了。
选择
选择数据库。表层数据使用非关系型数据库是缓解系统压力的有效途径,一般建议一种以上,比如redis和es,在高速环境下比如秒杀,队列、分页缓存可以使用redis集群,在全文搜索上使用es集群。上层数据对事务要求不多,主要是在速度上要求更高,而非关系型数据库就是为速度而生的。落地底层数据库可以使用mysql、mongodb、postgresql、oracle等集群,也可以使用可持久化的非关系型数据库集群(当然非关系型数据在事务处理上会稍微复杂些)。如果业务有非常多复杂的关系网建议使用关系型数据库(比如:分销系统,电商平台系统),如果业务关系网比较简单建议使用mongodb或非关系型数据库(比如:秒杀系统)。当然如果团队能力OK只要能持久保存数据哪种数据库都可以。
开发语言。建议使用团队拿手的语言,最好比较流行的比如:java、php等因为被使用的越多问题修改的就越多也越完善。尝试新语言会付出很多的代价,除非你足够牛。
数据库集群操作。如果业务要求并不苛刻,还是建议使用开源的中间件,毕竟集群操作复杂、管理也复杂,什么都依靠团队来完成会严重影响开发周期,而且容易踩坑。
表层非关系型数据同步。落地底层数据库需要同步数据到表层非关系型数据库上,才能让表层数据库提供最新数据,一般对内存型的非关系型数据可直接写,比如写redis集群;不是内存型的非关系型数据库使用队列同步或三方工具,比如同步数据到es可以使用列队或阿里的canal同步工具等。
对外服务。对外服务相对比较固定,主要是负载均衡器、系统服务器、CDN,微服务等。负载均衡器建议使用云负载均衡器,它们已经帮你完成负载均衡及高可用等功能。也可以自己搭建,一般可以选用varnish(HTTP加速器)、nginx(反向代理器)等。系统服务器一般需要配套开发语言的,比如java需要使用tomcat,php或python使用nginx或apache等。CDN目前都在使用商业的,自己部署成本大,而且需要按地区部署点节省跨区域的网络拥堵。微服务与系统服务类似。