《分布式JAVA应用 基础与实践》 第七章 构建可伸缩的系统

时间:2022-10-13 19:21:37

通常将通过升级或增加单机机器的硬件来支撑访问量及数据量增长的方式称为垂直伸缩,将通过增加机器来支撑访问量和数据量的增长的方式称为水平伸缩。垂直伸缩实现难度较低,但机器的硬件是无法不断升级和增加,容易达到瓶颈;水平伸缩理论上没有瓶颈,但技术难度较高。两者各有一定的优点,因此在应用中通常可以混合采用。

7.1 垂直伸缩

垂直伸缩前,先要分析系统的瓶颈,针对性地根据瓶颈对硬件进行升级或增加。另外,需要从软件方面保证系统在垂直伸缩后服务能力的线性增长,以下介绍软件方面要作出的努力

7.1.1 支撑高访问量

       Web应用随着访问量的增长,通常瓶颈会出现在CPU或内存上,网络IO或磁盘IO出现瓶颈的几率较低,在此仅分析当增加CPU或内存时,应用如何做到线性增长。。

1.      增加CPU后

通常有以下三种情况会导致增加CPU后系统的服务能力无法线性增长。

锁竞争激烈

解决办法可参考性能调优中降低锁竞争的部分

用于支撑并发请求的线程数是固定的

可适当增加线程数

单线程任务

需要考虑按照CPU数对任务进行合理划分,以启动加多线程并行完成任务

2.       增加内存后

要做到增加内存后系统的服务增长,要求系统能够随着内存的增加,响应速度提升,主要有以下两种情况会造成增加内存后系统的服务能力无法线性增长。

Cache的集合大小是固定的

可根据可用的内存计算出一个比例来控制cache的数据的大小。

JVM堆内存是固定的

相应对堆内存增行调整即可。另外,操作系统位数对可设置的大小有限制,对于要使用超过2G内存的JVM堆而言,操作系统升级到64位是必要的。

7.1.2 支撑大数据

       主要的优化手段是分表。分表采用的办法有按主键ID、按时间等方式,具体的分表策略要根据业务而定。分表带来的好处是单张表的数据量减少,因此读写速度可以得到提升,其带来的问题是开发的复杂,通常须借助DAL来解决;另外,分布查询也比较难处理。

7.1.3 提升计算能力

       增加CPU后,通过并行计算提升计算勇力,如JDK7的(fork-join).

 

7.2 水平伸缩

为了做到增加机器后系统的服务能力能线性增长,在软件方面要做很多的努力,以下介绍一下软件方面要做的改动。

7.2.1 支撑高访问量

       首先要解决的,是系统能否水平伸缩的问题。对水平伸缩的系统而言,最佳的情况是应用是无状态的,但系统很难做到完全没有状态,业界通常采用一种称为SNA(Share NothingArchitecture)的体系来指导如何构建无状态的应用。SNA架构在实现时通常采用的方法是将有状态 的部分集中放入缓存或数据库中,通常数据库采用集中存储的方式,因此这里最需要关注的是放在缓存中的状态如何做到支持水平伸缩。

 

缓存状态的水平伸缩方法

对于Java应用而言,通常可采取的方法有如下三种。

1.      广播同步

通常基于Multicast实现。JAVA应用中用于实现广播同步的开源软件主要为JGroups,Jgroups除支持默认的Multicast方式广播外,也可借助TCP/IP实现更加可靠的广播,Jetty和Tomcat的Http Session的信息的同步就基于JGoups实现。

广播同步的方式,由于信息缓存和系统在同一个JVM中,性能较高,但广播同步会有一定的延时,要确保这个延时对于应用场景而言是可接受的。

如果要同步的数据多了后,单台机器会无法容纳,节点增多的情况下,同步消耗的时间会越来越长,最终导致所产生的延时会超过可接受的范围

2.      分布式缓存

分面式缓存是指由多台机器来构成一个巨大的缓存池,每台机器缓存一部分数据,其本身也是水平伸缩的。目前开源界中分布式缓存使用最广泛的是memcached,由Facebook开源,多家互联网公司均采用它来实现缓存。

Memcached客户端支持简单的对key hash,然后除以缓存机器的节点数取模的方式来寻找机器,也支持按一致性hash的方法来寻找,在实现时多采用ketama算法来实现。

每个key的数据是在memcached集群中只会保存一份,对于有些缓存高度敏感的应用而言,会产生一定的风险。Facebook采用的方法为构建多个memcached集群,将key进行冗余存储,这样当一个集群中的某台机器出现问题时,并不会影响这个key缓存的值。

 

除以上两种方式外,Terracotta也是一个可以考虑的选择。Terracotta是一个JVM级的集群框架,可实现多台机器的JVM堆对象的共享等;另外用户登录信息还可以直接改造为放入cookie中,那就不要广播同步或引入分布式缓存了,其他的一些不适合放入缓存的状态信息则可基于数据库来统一存储,这样仍然能保证应用是水平可伸缩的。

 

文件的水平伸缩方法

在系统中通常有上传文件这类场景,对于水平伸缩而言,就必须实现在集群中NodeA机器上传文件后,在NodeB也能看到,通常有以下三种办法可实现

1.      直连式存储(DAS: Direct-Attached Storage)

指各系统直接与一个集中的存储设备连接,比较简单,程序无需进行改造。但水平伸缩支持得不好,单一存储设备的容易是有限的,且随着需要访问存储设备的节点越来越多,性能明显下降,另一方面,存储设备通常比较贵,且要有主备两台机器来避免单点故障。

2.      网络存储(Fabric-Attached Storage)

网络存储主要有NAS(Network-Attached Storage)和SAN(Storage Area Network)两种方式。

NAS采用网络(TCP/IP、ATM等)技术,通过网络交换机连接存储系统和服务主机,采用标准的文件共享协议(NFS、CIFS等),从而实现异构平台下的文件共享,但当并发访问同一数据时,文件的读写速度会大幅度下降。对使用者而言,NAS提供的是文件系统,只须在机器上mount相应的文件系统即可操作,较为简单。

SAN采用光纤方式连接磁盘阵列和服务器主机,实现将多个磁盘阵列构成一个对外统一的存储区域,一方面可以提升存储空间的利用率,另一方面也有较好的伸缩性。对使用者而言,SAN提供的是块设备(BlockDevice),因此如希望多台机器共享同一Block,则要采用具有同时操作同一block功能的文件系统,例如IBM的TotalStorage SAN文件系统。相对而言,SAN硬件成本及使用成本都更高。

NAS和SAN各有住优缺点,目前已发展为采用NAS+SAN的方式来更好地满足网络存储的需求。

3.      分布式文件系统

分布式文件系统采用的方法由众多PC Server机器构成的巨大的存储池,每台机器只存储一部分数据,其本身通常可非常好地支持水平伸缩。

典型的分布式文件系统有Google的GFS及Yahoo的HDFS,HDFS遵循GFS实现。

GFS在存储时将文件分割成固定的块,块的大小默认情况下固定为64MB,对于大文件,性能优势明显。但对于小文件,意味着每次只能从一台服务器上读取,当存取大量小文件时,会对主服务器的造成一定的压力,因此GFS不适用于存储大量小文件的系统。

 

应用的水平伸缩方法

在系统建设初期,会采用将各种业务都放在同一个系统的方式,这会导致这个系统日渐庞大,所需的资源越来越多,在进行水平伸缩时要考虑系统里各种业务会造成的资源增加的现象,这种状况会导致水平伸缩很难进行。例如增加机器后就造成了多个数据库连接的增加,对于这样的情况,通常采用拆分应用的方式来解决。如eBay将其业务系统拆分为商品、用户、评价、交易等。

拆分后系统的功能较为单一,在进行水平伸缩时更容易判断其伸缩会带来哪些资源的增加,并且由于之前由众多功能共享的机器变为独享,对于提升系统的响应速度也会起到很好的作用。

 

水平伸缩方法后带来的数据库问题

系统水平伸缩后,对于数据库而言,通常带来的一个问题是数据库的连接池的增加,而由于大多数数据库对水平伸缩支持得不好,因此通常采用如下四种方法解决这个问题:

1.      缓存

通过页面静态化、页面片段缓存(通常基于ESI)、对读多写少的数据进行缓存等手段来减少数据查询

2.      分库

分库通常按照业务将一个数据库的数据拆分到多个数据库中,如eBay就按照业务拆分为商品、用户、评价、交易等数据库。分库主要面临两个问题:

已有系统需要作相应修改

对数据库的业务操作较复杂

3.      异步数据库访问

目前大部分数据库访问仍然采用同步方式,每进行一次数据库操作就要占用一个数据库连接,并且要等到数据库操作执行完毕才会将连接释放。这对于高并发的系统就很容易出现连接不够用,数据库资源竞争激烈的问题,异步数据库访问是解决这种现象的一种方法。

异步数据库访问要将传统的通过阻塞IO访问数据库的方式转变为采用非阻塞或异步IO的方式来访问。目前有不少数据库都有了异步访问的客户端,在JbossNetty的测试报告中,异步的数据库访问方式较之传统的数据库访问带来了极大的性能提升。

4.      DAL(Data Access Layer)

DAL是指系统直接提交给数据库的操作转变为能过DAL来进行提交,在这种情况下,可借助DAL来数据库的使用。采用DAL后,无论业务服务器如何水平伸缩,数据库连接都可在DAL上统一控制。但DAL增加了一个中间层,会导致性能有一定的下降,其稳定性也会受到影响。目前DAL开源的有Amoeba,DAL的另一个好处是可透明化分库、分表对于业务服务器带来的影响

 

7.2.2 支撑大数据量

数据量增长后带来的问题主要是读写性能的下降,可采用一些方法借助机器的增加来提升读写数据的性能,主要有读写分离和多master两种方式。

读写分离

       读写分离采用的方法为当写数据库时在一个数据库写入,读时则从多个其它的数据库中读取。多种数据库均提供了机制用于实现读写分离,如MySQL replication、Oracle standby等,mysql replication支持对称复制和非对称复制两种方式。

       对称复制是指从master库复制数据库到slave库,maser和slave的数据保持一致。非对称复制是指从master库复制部分数据到slave库,各slave的数据可能不同。非对称方式由于复制的较少,且slave的数据也较少,性能较主同,但操作较为复杂,和分库后带来的挑战类似。

       对于异构数据库之间的读写分离,如写入oracle,读取的为mysql,通常只能自行实现,一种方案为写入数据库的同时,写入一个消息中间件,由中间件通知相应的订阅者处理。

读写分离适用于读多写少,并允许一定延时的业务中,对于读写比例基本相等的业务而言,采用读写分离反而会带来大幅度复制,造成系统运行缓慢。

多master

为提升数据写的速度,通常可采用的方法是建立多个master,在建立多个master时最理想的状态是多个master没有关联,即各master的数据不相同。另外一种多master的状况则为每个master的数据保持一致,此时一个明显的挑战是数据一致性的问题,另外一个明显的挑战是自增ID的问题。

数据一致性通常采用复制、两阶段提交、三阶段提交或google paxos来解决;自增ID的问题通常要改为由程序来生成ID的方式来解决。

 

7.2.3 提升计算能力

垂直伸缩场景中采取的方法是将计算任务拆分,多线程并行计算后汇总结果。水平伸缩场景中则可演变为将计算任务拆分,然后分派给不同的机器进行处理,最后汇总。可通过MapReduce和MPI实现。