在设计互联网服务的系统时,面向的是海量的互联网用户,尤其是对于拥有良好推广渠道、不用花费太长时间进行推广的互联网产品,可能在系统上线之初就会进入大量用户。因此需要预先估计系统的容量,作为产品推广、放量的依据;并且预先知道系统的瓶颈在哪、需要优先优化的方向、硬件需求取向、扩容的时机和规模。
典型的系统架构会有三层,分别是接入层、逻辑处理层、存储层。对于接入层的机器而言,一般是不负责任何逻辑处理,也不保存业务状态,因此对内存、外存没有太高要求,CPU 采用普通档次的即可,重要的是提供高带宽的网卡。对于负责业务逻辑处理的机器,CPU、内存资源更为重要。对于存储机器,需要尽可能快的外存,并且对机器的可靠性要求较高。
对大文件存储的应用来说,主要受限于外存的顺序读写性能,一般用 MBPS 考察;对于小型的数据存取应用来说,主要首先于外存的随机读写性能,一般用 IOPS 考察。7200转的硬盘,顺序读能力不超过150MB/s,顺序写能力不超过70MB/s;随机读写能力在 100 IOps 上下。因此,硬盘的性能对于顺序读写海量数据的应用来说是足够的,但是随机访问微小数据时,硬盘的机械特性就成了很严重的问题(需要先寻道,再读写,寻道时间为ms级)。以上面假想的硬盘指标计算,每天最多可以顺序读取约13TB的数据,写入约6.5TB的数据;或者执行800多万次随机IO。类似的,现在很火的 SSD 也可以如此计算。从 SSD 的单位容量成本看来,它不适合海量存储,从性能指标看来,和机械硬盘相比顺序读写的优势也不是太大,但是随机读写能力远远超过机械硬盘。
CPU 的性能指标是 IPS,最关键的指标是 CPU 的主频,决定了每秒最多能执行的指令的数目,这样就能知道 CPU 实际能承载的上限。在实际环境中,也可以通过观察在线系统的 CPU 使用率和当前系统使用量,估计出 CPU 的容量有多大。
内存有两方面的作用,一是提供业务处理所需要的存储空间,二是作为缓存。业务执行所需要的空间是临时性的,可以很快回收复用,只要分配的速度不是太快以至于这部分空间不够用即可。
系统中的缓存会长期占用内存空间,是使用内存的大户。计算缓存的大小,要知道被缓存数据的单位大小,以及所缓存的数据总数。在 Java 中,基本类型的大小是固定的,数据需要3个引用的额外开销,对象需要两个引用的额外开销,在组成高一级的数据结构(对象)时,JVM 会将以8字节为边界单位进行数据对齐操作。有关 Java 对象大小计算的文章可以参考《HOWTO: Determine the size of a Java Object or Class》。
还需要考虑业务执行时可以分配的最大线程数量。Linux 上的线程堆栈最大可以占用8MB内存空间,在 JVM 上可以通过启动参数配置线程堆栈的大小,只要不超过操作系统的限制即可。通过系统的总内存空间、缓存所需要的空间、应用运行所需要的临时空间,也就可以计算出线程数的限制。
网卡也是系统的限制之一,计算带宽可以通过以下公式:(请求或者回应平均大小 * 请求数) / 86400s / 8bit
请求或者回应的平均大小可以在实际环境中采集得到,也可以通过业务的数据情况计算得到上限和平均值。需要注意的是,需要支持多对列的网卡和相应的 Linux kernel 才能利用多CPU/多核,否则所有的网络 IO 开销都会集中在一颗 CPU 上,使得无法足够利用网卡的带宽。
从远程接口耗时计算系统的容量是比较简单的。变量是接口的耗时,可以在实际环境上采集数据得到。
计算请求数的公式:(1s / 请求平均耗时) * 线程数 * 86400s
需要注意的是,外部接口后面往往是另外一套复杂的系统,越复杂的东西越容器出错,而且自身系统到外部系统之间可能会经过可靠性较低的网络,数据传输过程中的失败率也会比较高,所以需要考虑在最糟糕的情况下如果处理,避免因为远程接口调用的失败率过高造成系统雪崩。
计算系统容量不是一个太容易的过程,需要开发人员对系统的软硬件都有较深入的了解,通过系统的物理限制得出程序所能提供吞吐量的上限。但是一旦掌握了计算的方法,就更容易做到对系统的能力心中有数,知道系统改进的方向,而不是盲人摸象般地开发或者重构。