[转]高性能可伸缩系统构建的简要思想

时间:2021-10-18 18:46:25

为了支持高并发访问和海量数据的场景,在搭建应用时需要努力构建可伸缩的系统,这样在后期系统遇到瓶颈时可以简单地通过垂直伸缩或水平伸缩扩展系统。本文整理借鉴了林昊老师对构建可伸缩系统的文章。

一、垂直伸缩

垂直伸缩指通过增加或升级单台机器的硬件来获得对高并发访问和大数据量的更好支持。

1、支持高并发访问

可以增加CPU和内存,同时需要对系统进行一定调整,尽可能使得软件性能随硬件性能线性增长。

增加CPU时,需要进行如下调整:

首先需要减小锁的开销。当Java程序中线程比较多时,线程的上下文切换开销会增大,对锁的竞争也会变得更激烈,所以通常意义上线程并不是越多越好。当系统因为锁的竞争而导致CPU利用率较低时,可以采取以下策略减少锁带来的开销:

a、使用Java API中的并发类库

可以采用java.util.concurrent等包下面的并发类,通常它们已经经过了充分的优化,能有效地支持高并发环境下的操作,并发类中大量采用了非阻塞算法,有些利用了CAS实现无锁。这里有一个小提示:使用并发哈希表时应优先采用ConcurrentHashMap而不是Hashtable,前者通过分解锁的方法使得效率更高。

b、用CAS代替锁

和第一点中提到的一样,CAS可以减小锁的开销,但是CAS本身是基于轮询的操作,实际使用中反而可能增加开销,这一点需要实验来测试。

c、减小锁的粒度

尽可能缩小锁定的范围,可以从两个方面入手。第一是只锁定所需的对象,少用synchronized(this)。第二是尽可能缩小锁定的方法块,缩小临界区大小,避免将不必要的操作也归入临界区。

d、拆分锁

将普通的对象锁、互斥锁按照场景拆分为读写锁或像ConcurrentHashMap一样拆分为若干把锁。

e、利用写时复制

对于读操作次数远远大于写操作的场景,可以在读操作时不加锁,写操作时利用写时复制来完成,但是内存占用会相应上升。

其次,系统中的线程数最好不是固定的,而是按CPU数来计算,这样当CPU增加时,相应的系统会自动增加线程提高并发率。

最后,如果对系统的CPU使用率还不满意,应当考虑分解一些单线程任务,改为多线程并发执行,以提高效率。

增加内存时,需要进行如下调整:

首先类似于线程数按CPU数计算,将Cache大小按内存大小计算,扩展内存后,Cache可以自动增长大小。

其次,可以分配更大的JVM堆内存给虚拟机,能减少OOM发生的几率。

2、支持海量数据

主要是通过拆分数据库和拆分表来实现,可以提升读写速度,但是分表也会增加开发系统的难度,可能存在跨表甚至跨数据库的操作。

二、水平伸缩

1、支持高并发访问

主要通过分散用户访问来实现,可以部署多个缓存服务器接受请求,这个过程中需要注意的问题是服务器之间的同步,通常有以下几种策略:

a.广播

一台缓存服务器的数据更改时,通过广播的方式告诉其它服务器,其他服务器也进行相应的更改,这里会存在一定的延时。

b.分布式缓存

每台服务器只缓存一部分数据,通过Hash算法来实现请求的路由,Hash过程中必须对用户和节点进行至少两次Hash,以避免当集群中的机器增减后发生Hash失效的情况。

c.JVM集群堆共享

将若干台服务器的JVM实现堆共享,这种方式的问题是对堆的锁定,本来一个JVM的堆就存在同步,集群的堆共享会造成更激烈的锁竞争。

2、支持海量数据

采用分布式文件系统,例如Google的GFS、开源的HDFS、淘宝的TFS等支持海量文件的管理。随着NoSQL的逐渐普及,可以采用像10gen的MongoDB、淘宝的Tair等来支持分布式海量存储。

对于数据库,可以采用读写分离,MySQL、Oracle等数据库都支持读写分离的机制,这里需要处理好Master/Slave数据库同步的问题。MySQL支持对称复制和非对称复制,前者把Master的所有数据复制到Slave,后者相当于把Master的数据分割给若干个Slave。前者冗余度高但是容灾性比较好,后者冗余度低但一旦出现故障就没有备用数据库可用。

对于异构数据库,需要一个消息中间件转发写请求给被读的数据库。同时当采用多个数据库时存在分阶段提交和分布式事务的问题。

3、计算能力的增强

主要采用MapReduce算法将任务分布到集群中进行计算,可以基于开源的Hadoop进行系统构建。

原文连接:http://blog.hesey.net/2011/04/summary-of-construction-of-high-performance-scalable-system.html