人工智能和大数据分析是当代能创造大量价值的学科。人工智能和大数据分析都离不开大型分布式项目。大型项目里面的技术非常多,相关书籍瀚如烟海,不过原理、原则和核心思想都是相通的,学习这些知识对于应用人工智能和大数据分析,启发新思想有帮助。简单记录一下学习的一些重要思想和原则。
根据经济学原理,人类可利用的资源永远是稀缺的。因此用最低的成本去最大化办成一件事情,是非常重要的。奥卡姆剃刀原理反映了这一事实。
如果项目并没有很大的流量,那么简单的机器加上简单的服务即可,不需要上最新的解决方案。比如说当前有个小单位,日流水不大,那么服务框架很简单,一台主服务器+备份服务器,一台主机包干服务、存储全套任务。
如果业务项目很大,不得不上复杂的技术,此时不能认为就可以放开手脚。成本永远是非常重要的。因此,遇到大规模的需求,首先要想是否可以剥离部分不重要的业务以缩减规模,然后依次是分析需求,判断是计算密集还是IO密集,需求大量计算还是存储,针对性给出最小成本方案。
大型集群项目的一个著名属性叫CAP:一致性,可用性,局部容忍。一致性简单来说,就是用户提供的数据,无论何时何地都必须是一样的,用户需求的服务,要有一样的反馈;可用性表示,用户应该总能获得满意的服务;局部容忍,表示在出现特殊情况下依然能够提供服务。
这三者理论上不可兼得,但是实际中必须按场景来全面兼顾。在对外业务场景中,项目需要表现出一致性、可用性;内部处理时,则要照顾可用性和局部容忍。
数据分发到不同主机,为了保持一致性原则,可以使用数据校验方法。另外常用的还有Paxos算法。其基本原理是设立一个发起者,N个决策者,发起者提出一个全局唯一的决策问题,然后由决策者给出投票,按投票结果给出最终决策;这样的话,分布式系统可以就消息分发的值进行决议。该算法需要注意的问题有:发起者是唯一的,当其失效时,决策无法进行,必须进行相关处理,尤其是新发起者的重新设定;决策队列必须全局一一对应等。
分布式系统对数据进行修改很容易发生一致性问题。一般常用的可以盖上时间戳,依照时间先后进行处理。不过由于分布式系统各个主机的绝对时间是不可能一致的,因此简单的时间比较是不可取的。比较绝对的办法,有设定一个掌管全局时间的主机;良好设计的时间戳,先来后到;全局同步机制。
不能完全满足一致性和可用性的情况是相当多的,因此局部容忍是非常重要的。在大量交易量的场景下,要求告诉数据交换,因此数据不能及时dump到磁盘以持久化存储,为了提供满意的服务,可以暂时不存盘,将数据保留在内存或者Cache中,此时内存与磁盘的数据是不一致的。这种情况相当危险,一旦掉电宕机数据立马丢失,一般会留有后手。常见的思路有:
* 内存备份,比如内存数据交换很快,就在多台主机的内存上备份数据;
* 随机读与顺序写。无论是SSD还是机械硬盘,将大量的交换数据直接顺序写入设备都可以极大提升写入效率,这也导致读取可能是随机的;不过显然SSD和机械硬盘的随机读取时间是优于写入的。
* 如果数据量极大,则可以使用增量备份,相比镜像备份速度更快,有利于加速持久化存储速度,缺点是逻辑复杂。
* 使用冗余热备机制,譬如8个主机共同存储一套数据,只要存活6台主机就能完全恢复数据,损坏两台主丢失数据的概率是非常小的;这种技术在RAID5上面已经非常成熟,缺点是IO速度下降。
这些概念延伸到数据上,变为ACID原则:原子性,一致性,隔离性,持久性。其中原子性表示事务要么成功,要么失败,失败后必须保证数据库没有任何改动;隔离性指,两个事务操作是不能冲突的,类似于多线程的锁/同步机制,此时的操作会被串行化,顺序执行;持久性则表示操作成功之后,数据应该保证存储下来。日志系统其中的重要部分。
这些都非常考验存储部分的性能。
大型项目必须充分贯彻透明化原则,保证对全体设备的运行状况了如指掌。俗话说,编程要100%的功力,Debug则要300%的功力。套到大原则上,就是查找错误是非常困难的。监控系统是一个大的环节,一般硬件设备或者操作系统都会提供基本的监控功能。
透明化设计有助于优化系统。在对CPU负载、网络负载、磁盘负载等都要有足够的了解的基础上,可以清晰的知道如何设计负载,解决瓶颈。
分布式系统的设计和计算机原理有很多相似之处,分层是提升性能的一个重要手段。计算机存储分层为Cache-内存-外存,分布式系统则可以等价为缓存-高速外存-低速外存,采用不同的存储介质,降低成本。对于经常使用的热命中数据,将其放入缓存和高速外存中;不常使用的则看情况处理,像Amazon采取与用户签订协议,将数据放入离线磁带中,需要的时候再把数据放进来。由于数据的热度会改变,必须考虑数据层级迁移,这给管理带来巨大麻烦,因此存储层级不能太高。
在网站项目中,CDN扮演了一个重要角色。网络带宽资源始终是稀缺的,将经常访问的页面内容缓存下来可以解决用户访问速度缓慢的问题。CDN可以将服务器放置于世界各地,存储门户内容,然后分发给最近的用户,这样网络延迟降低到最低;当需要新的数据的再向总部申请数据。这样相当于给项目又增加了一级高速缓存。
除开存储分层,对业务分层也是重要的环节。考虑成本控制与收益以及二八原则,对VIP用户始终提供最佳的服务是必要的,这部分用户虽然较少但是创造了最大的收益,优先对其服务进行优化是必要的,成本比全面覆盖客户低得多。典型的例子就是消息分级,对不同的服务请求给予优先等级,然后放入消息队列中进行排序;一个明显的问题是,如果新来的消息总是高优先级,那么队列中优先级较低的消息可能总是得不到处理,因此队列中的消息排序方法,需要综合多个方面进行考虑。从商业角度看这个做法明显会招来大量客户不满,如何协调商户关系以及成本-服务质量矛盾就是另外一个问题。
高内聚低耦合是软件工程中的重要指导原则,分离是实现该原则的一个有效方法。对业务、系统进行合理的分离,有助于提升整体效率。典型应用有:业务分离、读写分离、分库分表、中间件的应用。
业务分离很好理解。为指定服务划分相应的处理服务器,专项专用,单独优化。当其中部分服务器宕机时,剩下的服务器可以顶上;当服务整体不可用时,保证其他服务还能正常运行,让故障的影响最小化。需要关注的问题有:消息引导与定向分发、路由规则、负载均衡等。
对于大型网站项目,随着业务升级,流量更多的是读取而不是写入。这相当于用户逛超市,看到的多拿到的货物少。此时遵从的原则是读写分离,将读数据服务器单独分离出来进行优化,保证用户的浏览顺畅无阻。此外搜索功能也算入读取部分。搜索和读取服务器既然独立于核心存储部分,那么就要关注以下问题:
- 读取如何更加快速。一方面是硬件升级,一方面是存储分层
- 搜索如何优化。关键技术有倒排、数据关联、pagerank等
- 搜索与读取的数据与核心存储保持一致。一般要将大量的数据存入磁盘等持久化存储设备中的时间是比较久的,因此搜索、读取服务器可以直接从核心存储的高速缓存、内存中分享数据,而不是直接同步磁盘等外部存储。
伴随着业务规模扩大带来的问题不仅是数据IO效率问题,还有存储容量问题。单机容量始终是有限的,将存储系统升级为分布式存储系统是必要的。小规模情况下,直接设计一台主服务器+从服务器即可,其中从服务器的作用为:数据备份;主机宕机时,顶替主机继续工作,无缝切换。中大规模场景下,就需要将当前活动的数据进行分离,散步到各个服务节点上。
一个典型应用是数据库的分库分表。首先我们可以将业务分离到不同服务器上执行,那么数据库中不同业务的库表也要分散到对应的主机;而单个业务可能也会膨胀到单机无法容纳,此时同一个业务的数据表可能也要分散到不同的主机上去。从架构上看,分库分表能够良好地解决大规模项目带来的数据库存储问题,然而具体技术上的实现就有很多障碍。需要关注的问题有:如何设计键值,保证存储和查询都能最优;如何快速找到目标所在的库表等。衍生出来的技术有:哈希算法;NoSql、分布式数据库;存储虚拟化等。
虚拟化可以在整体上充分利用硬件资源。
在以上描述中,我们看到如果分布式系统下的数据是分散在各个主机上,那么查询和写入都非常麻烦。如果所有的主机存储都能统一管理,甚至对任意一台主机、任意用户可见的就是一整块存储设备的话,那么整个架构设计都能得到极大地简化。虚拟化技术将分布式系统所有的计算资源、存储资源,通过一系列技术后整合,使得上层应用、用户面对的就好像是一个单机系统。虚拟化技术由来已久。最常见的两项应用,就是类似virtualbox的虚拟机软件以及路由器需要配置的vlan虚拟局域网。
单单看一项虚拟化系统,其性能肯定是不如原生的,单机不好用,分布式系统才有用。在分布式系统中,每台主机都是一个单独个体,如果分离工作中以单机为基本单位的话,那么很容易造成成本失控和负载不均衡。原因是:单独根据任务来配置主机硬件,成本较高;每台主机负责的业务任务,可能是变化;只负责某项具体业务的话,可能有相当部分硬件资源浪费了;如果我们要添加或者删除一台主机,那么原来的分离工作就被打破了,需要重新配置主机任务。使用虚拟化技术,终端看到的就是一整套计算资源和一整套存储设别,不需要关心这些资源在哪台主机上。需要关注的技术有:一致性哈希,用于抽象层来分配资源;分布式存储等。典型应用有阿里云服务,按收费给定指定的硬件设备;分布式存储Lustre,呈献给用户只有一个大容量盘。
负载均衡有利用硬件资源的充分利用。
早期的负载均衡技术,可以设立一个负载中心/路由服务器,通过路由规则来指定相应服务的去向。早期由于数据存储是分散的,不同主机的数据可能来不及更新,这可能导致同一个用户的先后两次服务是在存储不一致数据的两台主机上。对此的解决方法一般是:通过路由规则,使得两次服务在同一主机上,这容易导致负载不均衡;用先进的技术保证所有主机共享一致的数据。后者已经基本能够实现。
随着规模扩大,使用路由服务器不能满足日益增长的需求,直接使用路由服务器来进行任务分发是不可取。一般的做法是实现元数据-数据的架构,建立软负载中心,服务调用者向软负载中心申请资源,软负载中心根据实际负载情况,给出分配的资源地址,然后调用者自己根据分配内容去链接相应的硬件资源。这样软负载中心就是一个信使,其面对的数据不是业务本身而是服务元数据,其IO流量较小足以支撑大规模调度工作;本身存储的数据也较少,多个软负载中心共享数据的难度不大。
负载均衡的目的是保证可用性,除了从资源调度上考虑,更本质的还要关注如何最小化负载。除开缩减不必要的业务,还可以使用其他技术:数据压缩;增量数据传递而不是镜像数据传递。
计算机组成原理中,局部化原理是提升软件性能的重要手段。在分布式系统中也同样如此。
Google发表的mapreduce论文中,除了mapreduce本身的思想外,还给出了实现高性能分布式系统的许多重要方法。其中提到的一点是,鉴于成本控制,各个主机兼具计算和存储功能,在分配计算任务的时候,哪台主机有对应的数据,就把任务分配到那台主机上。
分布式项目中,主机可能分布在不同机房、大楼甚至异地,不同地方的主机的网络带宽较低、通信出错率较高,遵从局部化原理可以避免发生问题。
已经启用的大型项目,每小时创造的价值极高,停机会造成巨大损失。对大型项目进行升级或者数据迁移,如果能够不停机就进行的话,就可以避免损失。这称为平滑迁移。
对于软件升级工作,停机升级是最好的方法。如果不停机,那么最好保证在一开始设计软件的时候就考虑到扩展性和兼容性,升级的时候仅仅做一些增量工作就完了。如果新版本是不兼容旧版本的,那么可以后台升级,前台继续运行旧版本程序;过渡阶段,保证用户发来的消息包含版本信息,根据版本信息去决定调用哪个版本的程序;最后统一上架新版本。其中需要关注的技术是版本控制。
对于数据迁移工作,由于数据时刻在使用中,因此dump的数据和使用中的数据是无法保证一致性的,完全镜像复制是不可能的,此时的方法是使用增量备份,先镜像复制其中一个时间段的完整数据,然后记录该时间段之后增量内容,逐渐应用到dump数据中去。当然这要保证增量备份的速度是快于数据更新的速度的。
中间件可以解耦任务,隐藏底层细节,构建专业化服务框架。
随着业务升级,新生了大量复杂的任务流程。从本质上看,这些任务流程其实是由很多细粒度任务构成的。如果这些细粒度任务依赖性低,那么我们就可以像搭积木一样,搭配细粒度任务组成复杂的任务流程。解耦这些任务,是中间件的重要工作。
程序设计的一个原则是,隐藏底层细节,关注上层建筑。构建好中间件后,大量的高级任务的设计就会非常简单,无需关注底层工作。
中间件的任务可能是最繁重的,为了提升性能,使用了大量技术,需要关注的有:内存池、线程池、同步机制、异步IO等。